Add base files for sqlalchemy implementation

These code is imported from Solum.

Implements: blueprint db-object-definitions
Change-Id: Idc5458bd6b758f3d4a62bf5f059ce4dd444a825a
This commit is contained in:
Motohiro OTSUKA 2014-12-01 10:23:04 +09:00
parent 5ab29b0557
commit e8f4831406
4 changed files with 299 additions and 0 deletions

View File

@ -0,0 +1,63 @@
# Copyright 2013 - 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.
"""Common functionality for the application object model
The object model must be initialized at service start via
magnum.objects.load()
and all objects should be retrieved via
magnum.objects.registry.<class>
in application code.
"""
from oslo.config import cfg
from oslo.db import api
from magnum.objects import registry as registry_mod
db_opts = [
cfg.StrOpt('schema_mode',
default='new',
help="The version of the schema that should be "
"running: 'old', 'transition', 'new'")
]
CONF = cfg.CONF
CONF.register_opts(db_opts, "database")
_BACKEND_MAPPING = {'sqlalchemy': 'magnum.objects.sqlalchemy'}
IMPL = api.DBAPI.from_config(CONF, backend_mapping=_BACKEND_MAPPING)
def transition_schema():
"""Is the new schema in write-only mode."""
return cfg.CONF.database.schema_mode == 'transition'
def new_schema():
"""Should objects be writing to the new schema."""
return cfg.CONF.database.schema_mode != 'old'
def load():
"""Ensure that the object model is initialized."""
global registry
registry.clear()
IMPL.load()
registry = registry_mod.Registry()

View File

@ -0,0 +1,51 @@
# Copyright 2013 - 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 sys
from oslo.config import cfg
from oslo.db.sqlalchemy import session
_FACADE = None
def get_facade():
global _FACADE
if not _FACADE:
_FACADE = session.EngineFacade.from_config(cfg.CONF)
return _FACADE
get_engine = lambda: get_facade().get_engine()
get_session = lambda: get_facade().get_session()
def get_backend():
"""The backend is this module itself."""
return sys.modules[__name__]
def cleanup():
global _FACADE
if _FACADE:
_FACADE._session_maker.close_all()
_FACADE.get_engine().dispose()
_FACADE = None
def load():
"""Activate the sqlalchemy backend."""
pass

View File

@ -0,0 +1,183 @@
# Copyright 2013 - 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.
"""
SQLAlchemy models for application data.
"""
import json
from oslo.config import cfg
from oslo.db import exception as db_exc
from oslo.db.sqlalchemy import models
import six
from six import moves
from sqlalchemy.ext import declarative
from sqlalchemy.orm import exc
from sqlalchemy import types
from magnum.common import exception
from magnum import objects
from magnum.objects import sqlalchemy as object_sqla
def table_args():
cfg.CONF.import_opt('connection', 'oslo.db.options',
group='database')
if cfg.CONF.database.connection is None:
# this is only within some object tests where
# the object classes are directly imported.
return None
engine_name = moves.urllib.parse.urlparse(
cfg.CONF.database.connection).scheme
if engine_name == 'mysql':
return {'mysql_engine': 'InnoDB',
'mysql_charset': "utf8"}
return None
def model_query(context, model, *args, **kwargs):
"""Query helper.
:param context: context to query under
:param session: if present, the session to use
"""
session = kwargs.get('session') or object_sqla.get_session()
query = session.query(model, *args)
return query
class MagnumBase(models.TimestampMixin, models.ModelBase):
metadata = None
@classmethod
def obj_name(cls):
return cls.__name__
def as_dict(self):
d = {}
for c in self.__table__.columns:
d[c.name] = self[c.name]
for k in self._extra_keys:
d[k] = self[k]
return d
@classmethod
def get_session(cls):
return object_sqla.get_session()
@classmethod
def get_by_id(cls, context, item_id):
try:
session = MagnumBase.get_session()
return session.query(cls).filter_by(id=item_id).one()
except exc.NoResultFound:
cls._raise_not_found(item_id)
@classmethod
def get_by_uuid(cls, context, item_uuid):
try:
session = MagnumBase.get_session()
return session.query(cls).filter_by(uuid=item_uuid).one()
except exc.NoResultFound:
cls._raise_not_found(item_uuid)
@classmethod
def _raise_duplicate_object(cls):
if hasattr(cls, '__resource__'):
raise exception.ResourceExists(name=cls.__resource__)
else:
raise exception.ObjectNotUnique(name=cls.__tablename__)
def _non_updatable_fields(self):
return set(('uuid', 'id'))
def _lazyhasattr(self, name):
return any(name in d for d in (self.__dict__,
self.__class__.__dict__))
def update(self, data):
for field in set(six.iterkeys(data)) - self._non_updatable_fields():
if self._lazyhasattr(field):
setattr(self, field, data[field])
def save(self, context):
if objects.transition_schema():
self.add_forward_schema_changes()
session = MagnumBase.get_session()
with session.begin():
session.merge(self)
def create(self, context):
session = MagnumBase.get_session()
try:
with session.begin():
session.add(self)
except (db_exc.DBDuplicateEntry):
self.__class__._raise_duplicate_object()
def destroy(self, context):
session = MagnumBase.get_session()
with session.begin():
session.query(self.__class__).filter_by(
id=self.id).delete()
@classmethod
def _raise_not_found(cls, item_id):
"""Raise a not found exception."""
if hasattr(cls, '__resource__'):
raise exception.ResourceNotFound(name=cls.__resource__, id=item_id)
else:
raise exception.ObjectNotFound(name=cls.__tablename__, id=item_id)
Base = declarative.declarative_base(cls=MagnumBase)
class JsonEncodedType(types.TypeDecorator):
"""Abstract base type serialized as json-encoded string in db."""
type = None
impl = types.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

View File

@ -6,6 +6,7 @@ pbr>=0.6,!=0.7,<1.0
Babel>=1.3
oslo.config>=1.4.0
oslo.db>=0.2.0 # Apache-2.0
oslo.messaging>=1.4.0
oslo.serialization>=1.0.0
oslo.utils>=1.0.0
@ -13,5 +14,6 @@ pecan>=0.8.0
python-keystoneclient>=0.11.1
python-zaqarclient>=0.0.3
six>=1.7.0
SQLAlchemy>=0.8.4,!=0.9.5,<=0.9.99
WSME>=0.6
docker-py>=0.5.1