DB models for cloudpulse

Implements: blueprint datamodel

Change-Id: Ib91bc89d25ccb133aa1c2e208c21dd9dc5949e89
This commit is contained in:
vinod pandarinathan
2015-06-06 08:48:24 -07:00
parent 9e4306098a
commit 450b6fb14f
5 changed files with 442 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
# Copyright 2012 Red Hat, Inc.
#
# 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.
import os
from oslo_config import cfg
PATH_OPTS = [
cfg.StrOpt('pybasedir',
default=os.path.abspath(os.path.join(os.path.dirname(__file__),
'../')),
help='Directory where cloudpulse python module is installed.'),
cfg.StrOpt('bindir',
default='$pybasedir/bin',
help='Directory where cloudpulse binaries are installed.'),
cfg.StrOpt('state_path',
default='$pybasedir',
help="Top-level directory for maintaining cloudpulse's state."),
]
CONF = cfg.CONF
CONF.register_opts(PATH_OPTS)
def basedir_def(*args):
"""Return an uninterpolated path relative to $pybasedir."""
return os.path.join('$pybasedir', *args)
def bindir_def(*args):
"""Return an uninterpolated path relative to $bindir."""
return os.path.join('$bindir', *args)
def state_path_def(*args):
"""Return an uninterpolated path relative to $state_path."""
return os.path.join('$state_path', *args)
def basedir_rel(*args):
"""Return a path relative to $pybasedir."""
return os.path.join(CONF.pybasedir, *args)
def bindir_rel(*args):
"""Return a path relative to $bindir."""
return os.path.join(CONF.bindir, *args)
def state_path_rel(*args):
"""Return a path relative to $state_path."""
return os.path.join(CONF.state_path, *args)

View File

View File

@@ -0,0 +1,223 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
"""SQLAlchemy storage backend."""
from cloudpulse.common import exception
from cloudpulse.common import utils
from cloudpulse.db import api
from cloudpulse.db.sqlalchemy import models
from cloudpulse.openstack.common._i18n import _
from cloudpulse.openstack.common import log
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_db.sqlalchemy import session as db_session
from oslo_db.sqlalchemy import utils as db_utils
from oslo_utils import timeutils
import sqlalchemy.orm.exc
CONF = cfg.CONF
LOG = log.getLogger(__name__)
_FACADE = None
def _create_facade_lazily():
global _FACADE
if _FACADE is None:
_FACADE = db_session.EngineFacade.from_config(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)
def get_backend():
"""The backend is this module itself."""
return Connection()
def model_query(model, *args, **kwargs):
"""Query helper for simpler session usage.
:param session: if present, the session to use
"""
session = kwargs.get('session') or get_session()
query = session.query(model, *args)
return query
def add_identity_filter(query, value):
"""Adds an identity filter to a query.
Filters results by ID, if supplied value is a valid integer.
Otherwise attempts to filter results by UUID.
:param query: Initial query to add filter to.
:param value: Value for filtering results by.
:return: Modified query.
"""
if utils.is_int_like(value):
return query.filter_by(id=value)
elif utils.is_uuid_like(value):
return query.filter_by(uuid=value)
else:
raise exception.InvalidIdentity(identity=value)
def _paginate_query(model, limit=None, marker=None, sort_key=None,
sort_dir=None, query=None):
if not query:
query = model_query(model)
sort_keys = ['id']
if sort_key and sort_key not in sort_keys:
sort_keys.insert(0, sort_key)
query = db_utils.paginate_query(query, model, limit, sort_keys,
marker=marker, sort_dir=sort_dir)
return query.all()
class Connection(api.Connection):
"""SqlAlchemy connection."""
def __init__(self):
pass
def _add_tenant_filters(self, context, query):
if context.project_id:
query = query.filter_by(project_id=context.project_id)
else:
query = query.filter_by(user_id=context.user_id)
return query
def _add_tests_filters(self, query, filters):
if filters is None:
filters = []
if 'name' in filters:
query = query.filter_by(name=filters['name'])
return query
def get_test_list(self, context, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
# query = model_query(models.cpulse)
query = model_query(models.cpulse)
return _paginate_query(models.cpulse, limit, marker,
sort_key, sort_dir, query)
def create_test(self, values):
# ensure defaults are present for new tests
if not values.get('uuid'):
values['uuid'] = utils.generate_uuid()
if not values.get('id'):
values['id'] = 777
cpulse = models.cpulse()
cpulse.update(values)
# TODO(VINOD)
try:
cpulse.save()
except db_exc.DBDuplicateEntry:
raise exception.TestAlreadyExists(uuid=values['uuid'])
return cpulse
def get_test_by_id(self, context, test_id):
query = model_query(models.cpulse)
query = self._add_tenant_filters(context, query)
query = query.filter_by(id=test_id)
try:
return query.one()
except sqlalchemy.orm.exc.NoResultFound:
raise exception.TestNotFound(test=test_id)
def get_test_by_name(self, context, test_name):
query = model_query(models.cpulse)
query = self._add_tenant_filters(context, query)
query = query.filter_by(name=test_name)
try:
return query.one()
except sqlalchemy.orm.exc.MultipleResultsFound:
raise exception.Conflict('Multiple tests exist with same name.'
' Please use the test uuid instead.')
except sqlalchemy.orm.exc.NoResultFound:
raise exception.TestNotFound(test=test_name)
def get_test_by_uuid(self, context, uuid):
query = model_query(models.cpulse)
# query = self._add_tenant_filters(context, query)
query = query.filter_by(uuid=uuid)
try:
return query.one()
except sqlalchemy.orm.exc.NoResultFound:
raise exception.TestNotFound(test=uuid)
def destroy_test(self, test_id):
session = get_session()
test_ref = None
with session.begin():
query = model_query(models.cpulse, session=session)
query = add_identity_filter(query, test_id)
try:
test_ref = query.one()
except sqlalchemy.orm.exc.NoResultFound:
raise exception.TestNotFound(test=test_id)
query.delete()
return test_ref
def update_test(self, test_id, values):
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing test.")
raise exception.InvalidParameterValue(err=msg)
return self._do_update_test(test_id, values)
def _do_update_test(self, test_id, values):
session = get_session()
with session.begin():
query = model_query(models.cpulse, session=session)
query = add_identity_filter(query, test_id)
try:
ref = query.with_lockmode('update').one()
except sqlalchemy.orm.exc.NoResultFound:
raise exception.TestNotFound(test=test_id)
if 'provision_state' in values:
values['provision_updated_at'] = timeutils.utcnow()
ref.update(values)
return ref
def create_test_lock(self, test_uuid, conductor_id):
pass
def steal_test_lock(self, test_uuid, old_conductor_id, new_conductor_id):
pass
def release_test_lock(self, test_uuid, conductor_id):
pass

View File

@@ -0,0 +1,138 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
"""
SQLAlchemy models for cloudpulse service
"""
import json
from oslo_config import cfg
from oslo_db import options as db_options
from oslo_db.sqlalchemy import models
import six.moves.urllib.parse as urlparse
from sqlalchemy import Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Integer
from sqlalchemy import schema
from sqlalchemy import String
from sqlalchemy import Text
from sqlalchemy.types import TypeDecorator, TEXT
from cloudpulse.common import paths
sql_opts = [
cfg.StrOpt('mysql_engine',
default='InnoDB',
help='MySQL engine to use.')
]
_DEFAULT_SQL_CONNECTION = ('sqlite:///' +
paths.state_path_def('cloudpulse.sqlite'))
cfg.CONF.register_opts(sql_opts, 'database')
db_options.set_defaults(cfg.CONF, _DEFAULT_SQL_CONNECTION, 'cloudpulse.sqlite')
def table_args():
engine_name = urlparse.urlparse(cfg.CONF.database.connection).scheme
if engine_name == 'mysql':
return {'mysql_engine': cfg.CONF.database.mysql_engine,
'mysql_charset': "utf8"}
return None
class JsonEncodedType(TypeDecorator):
"""Abstract base type serialized as json-encoded string in db."""
type = None
impl = TEXT
def process_bind_param(self, value, dialect):
if value is None:
# Save default value according to current type to keep the
# interface the consistent.
value = self.type()
elif not isinstance(value, self.type):
raise TypeError("%s supposes to store %s objects, but %s given"
% (self.__class__.__name__,
self.type.__name__,
type(value).__name__))
serialized_value = json.dumps(value)
return serialized_value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
class JSONEncodedDict(JsonEncodedType):
"""Represents dict serialized as json-encoded string in db."""
type = dict
class JSONEncodedList(JsonEncodedType):
"""Represents list serialized as json-encoded string in db."""
type = list
class CpulseBase(models.TimestampMixin,
models.ModelBase):
metadata = None
def as_dict(self):
d = {}
for c in self.__table__.columns:
d[c.name] = self[c.name]
return d
def save(self, session=None):
import cloudpulse.db.sqlalchemy.api as db_api
if session is None:
session = db_api.get_session()
super(CpulseBase, self).save(session)
Base = declarative_base(cls=CpulseBase)
class cpulse(Base):
"""Represents cloudpulse test"""
__tablename__ = 'cpulse'
__table_args__ = (
schema.UniqueConstraint('uuid', name='uniq_test0uuid'),
table_args()
)
id = Column(Integer, primary_key=True)
uuid = Column(String(64))
name = Column(String(255))
state = Column(String(255))
result = Column(Text)
class CpulseLock(Base):
"""Represents a cpulselock."""
__tablename__ = 'cpulselock'
__table_args__ = (
schema.UniqueConstraint('test_uuid', name='uniq_testlock0test_uuid'),
table_args()
)
id = Column(Integer, primary_key=True)
test_uuid = Column(String(36))
conductor_id = Column(String(64))

View File

@@ -4,3 +4,18 @@
pbr>=0.6,!=0.7,<1.0
Babel>=1.3
jsonpatch>=1.1
oslo.concurrency>=1.8.0,<1.9.0 # Apache-2.0
oslo.config>=1.9.3,<1.10.0 # Apache-2.0
oslo.context>=0.2.0,<0.3.0 # Apache-2.0
oslo.db>=1.7.0,<1.8.0 # Apache-2.0
oslo.messaging>=1.8.0,<1.9.0 # Apache-2.0
oslo.serialization>=1.4.0,<1.5.0 # Apache-2.0
oslo.utils>=1.4.0,<1.5.0 # Apache-2.0
oslo.versionedobjects>=0.1.1,<0.2.0
oslo.i18n>=1.5.0,<1.6.0 # Apache-2.0
six>=1.9.0
SQLAlchemy>=0.9.7,<=0.9.99
taskflow>=0.7.1,<0.8.0