Transitioned from file state to DB state

Managing states are now made with a DB.
Fixes on the file state manager.

Change-Id: I89bef295bfec965c06ce7df75fc6fb5cf87f6f57
This commit is contained in:
Stéphane Albert 2014-08-08 14:26:57 +02:00
parent 5765daf6c2
commit 6903c5c2fd
8 changed files with 302 additions and 12 deletions

38
cloudkitty/db/__init__.py Normal file
View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Stéphane Albert
#
from oslo.config import cfg
from oslo.db.sqlalchemy import session
_FACADE = None
def _create_facade_lazily():
global _FACADE
if _FACADE is None:
_FACADE = session.EngineFacade.from_config(cfg.CONF)
return _FACADE
def get_engine():
facade = _create_facade_lazily()
return facade.get_engine()
def get_session(**kwargs):
facade = _create_facade_lazily()
return facade.get_session(**kwargs)

70
cloudkitty/db/api.py Normal file
View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Stéphane Albert
#
import abc
from oslo.config import cfg
from oslo.db import api as db_api
import six
_BACKEND_MAPPING = {'sqlalchemy': 'cloudkitty.db.sqlalchemy.api'}
IMPL = db_api.DBAPI.from_config(cfg.CONF,
backend_mapping=_BACKEND_MAPPING,
lazy=True)
def get_instance():
"""Return a DB API instance."""
return IMPL
@six.add_metaclass(abc.ABCMeta)
class State(object):
"""Base class for state tracking."""
@abc.abstractmethod
def get_state(self, name):
"""Retrieve the current state.
:param name: Name of the state
:return float: State value
"""
@abc.abstractmethod
def set_state(self, name, state):
"""Store the state.
:param name: Name of the state
:param state: State value
"""
@abc.abstractmethod
def get_metadata(self, name):
"""Retrieve state metadata
:param name: Name of the state
:return str: Return a json dict with all metadata attached to this
state.
"""
@abc.abstractmethod
def set_metadata(self, name, metadata):
"""Store the state metadata.
:param name: Name of the state
:param metadata: Metadata value
"""

View File

View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Stéphane Albert
#
from oslo.config import cfg
from oslo.db.sqlalchemy import utils
import sqlalchemy
from cloudkitty import config # NOQA
from cloudkitty import db
from cloudkitty.db import api
from cloudkitty.db.sqlalchemy import models
from cloudkitty.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def get_backend():
return DBAPIManager
class State(api.State):
def get_state(self, name):
session = db.get_session()
try:
return bool(utils.model_query(
models.StateInfo,
session
).filter_by(
name=name,
).value('state'))
except sqlalchemy.orm.exc.NoResultFound:
return None
def set_state(self, name, state):
session = db.get_session()
with session.begin():
try:
q = utils.model_query(
models.StateInfo,
session
).filter_by(
name=name,
).with_lockmode('update')
db_state = q.one()
db_state.state = state
except sqlalchemy.orm.exc.NoResultFound:
db_state = models.StateInfo(name=name, state=state)
session.add(db_state)
return bool(db_state.state)
def get_metadata(self, name):
session = db.get_session()
return utils.model_query(
models.StateInfo,
session
).filter_by(
name=name,
).value('s_metadata')
def set_metadata(self, name, metadata):
session = db.get_session()
try:
db_state = utils.model_query(
models.StateInfo,
session
).filter_by(
name=name,
).with_lockmode('update').one()
db_state.s_metadata = metadata
except sqlalchemy.orm.exc.NoResultFound:
db_state = models.StateInfo(name=name, s_metadata=metadata)
session.add(db_state)
finally:
session.flush()
class DBAPIManager(object):
@staticmethod
def get_state():
return State()

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Stéphane Albert
#
from oslo.db.sqlalchemy import models
import sqlalchemy
from sqlalchemy.ext import declarative
Base = declarative.declarative_base()
class StateInfo(Base, models.ModelBase):
"""State
"""
__tablename__ = 'states'
name = sqlalchemy.Column(sqlalchemy.String(255),
primary_key=True)
state = sqlalchemy.Column(
sqlalchemy.Float(),
nullable=False)
s_metadata = sqlalchemy.Column(sqlalchemy.Text(),
nullable=True,
default='')
def __repr__(self):
return ('<StateInfo[{name}]: '
'state={state} metadata={metadata}>').format(
name=self.name,
state=self.state,
metadata=self.s_metadata)

View File

@ -49,10 +49,8 @@ class Orchestrator(object):
auth_url=CONF.auth.url)
s_backend = i_utils.import_class(CONF.state.backend)
self.sm = state.StateManager(s_backend,
CONF.state.basepath,
self.keystone.user_id,
'osrtf')
self.sm = state.DBStateManager(self.keystone.user_id,
'osrtf')
collector_args = {'user': CONF.auth.username,
'password': CONF.auth.password,

View File

@ -17,6 +17,8 @@
#
import json
from cloudkitty.db import api
class StateManager(object):
def __init__(self, state_backend, state_basepath, user_id, report_type,
@ -31,8 +33,13 @@ class StateManager(object):
self._ts = None
self._metadata = {}
# Load states
self._load()
def _gen_filename(self):
filename = '{}_{}.state'.format(self._type, self._uid)
# FIXME(sheeprine): Basepath can't be enforced at the moment
filename = '{}_{}.state'.format(self._type,
self._uid)
return filename
def _open(self, mode='rb'):
@ -43,9 +50,11 @@ class StateManager(object):
def _load(self):
try:
state_file = self._open()
state_data = json.loads(state_file.read())
self._ts = state_data['timestamp']
self._metadata = state_data['metadata']
raw_data = state_file.read()
if raw_data:
state_data = json.loads(raw_data)
self._ts = state_data['timestamp']
self._metadata = state_data['metadata']
state_file.close()
except IOError:
pass
@ -82,3 +91,35 @@ class StateManager(object):
if self._distributed:
self._load()
return self._metadata
class DBStateManager(object):
def __init__(self, user_id, report_type, distributed=False):
self._state_name = self._gen_name(report_type, user_id)
self._distributed = distributed
self._db = api.get_instance().get_state()
def _gen_name(self, state_type, uid):
name = '{}_{}'.format(state_type, uid)
return name
def get_state(self):
"""Get the state timestamp."""
return self._db.get_state(self._state_name)
def set_state(self, timestamp):
"""Set the current state's timestamp."""
self._db.set_state(self._state_name, timestamp)
def get_metadata(self):
"""Get metadata attached to the state."""
return json.loads(self._db.get_metadata(self._state_name))
def set_metadata(self, metadata):
"""Set metadata attached to the state."""
self._db.set_metadata(self._state_name,
json.dumps(metadata))

View File

@ -32,10 +32,8 @@ class BaseReportWriter(object):
self._write_orchestrator = write_orchestrator
self._write_backend = backend
self._uid = user_id
self._sm = state.StateManager(state_backend,
None,
self._uid,
self.report_type)
self._sm = state.DBStateManager(self._uid,
self.report_type)
self._report = None
self._period = 3600