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:
parent
5ab29b0557
commit
e8f4831406
@ -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()
|
51
magnum/objects/sqlalchemy/__init__.py
Normal file
51
magnum/objects/sqlalchemy/__init__.py
Normal 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
|
183
magnum/objects/sqlalchemy/models.py
Normal file
183
magnum/objects/sqlalchemy/models.py
Normal 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
|
@ -6,6 +6,7 @@ pbr>=0.6,!=0.7,<1.0
|
|||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
|
|
||||||
oslo.config>=1.4.0
|
oslo.config>=1.4.0
|
||||||
|
oslo.db>=0.2.0 # Apache-2.0
|
||||||
oslo.messaging>=1.4.0
|
oslo.messaging>=1.4.0
|
||||||
oslo.serialization>=1.0.0
|
oslo.serialization>=1.0.0
|
||||||
oslo.utils>=1.0.0
|
oslo.utils>=1.0.0
|
||||||
@ -13,5 +14,6 @@ pecan>=0.8.0
|
|||||||
python-keystoneclient>=0.11.1
|
python-keystoneclient>=0.11.1
|
||||||
python-zaqarclient>=0.0.3
|
python-zaqarclient>=0.0.3
|
||||||
six>=1.7.0
|
six>=1.7.0
|
||||||
|
SQLAlchemy>=0.8.4,!=0.9.5,<=0.9.99
|
||||||
WSME>=0.6
|
WSME>=0.6
|
||||||
docker-py>=0.5.1
|
docker-py>=0.5.1
|
||||||
|
Loading…
Reference in New Issue
Block a user