DB models for cloudpulse
Implements: blueprint datamodel Change-Id: Ib91bc89d25ccb133aa1c2e208c21dd9dc5949e89
This commit is contained in:
66
cloudpulse/common/paths.py
Normal file
66
cloudpulse/common/paths.py
Normal 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)
|
0
cloudpulse/db/sqlalchemy/__init__.py
Normal file
0
cloudpulse/db/sqlalchemy/__init__.py
Normal file
223
cloudpulse/db/sqlalchemy/api.py
Normal file
223
cloudpulse/db/sqlalchemy/api.py
Normal 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
|
138
cloudpulse/db/sqlalchemy/models.py
Normal file
138
cloudpulse/db/sqlalchemy/models.py
Normal 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))
|
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user