178 lines
5.5 KiB
Python
178 lines
5.5 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 OpenStack Foundation
|
|
#
|
|
# 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.
|
|
|
|
"""SQL backends for the various services.
|
|
|
|
Before using this module, call initialize(). This has to be done before
|
|
CONF() because it sets up configuration options.
|
|
|
|
"""
|
|
import contextlib
|
|
import functools
|
|
|
|
import sqlalchemy as sql
|
|
from sqlalchemy.ext import declarative
|
|
from sqlalchemy.orm.attributes import flag_modified, InstrumentedAttribute
|
|
from sqlalchemy import types as sql_types
|
|
|
|
from keystone import exception
|
|
from keystone.openstack.common.db import exception as db_exception
|
|
from keystone.openstack.common.db.sqlalchemy import models
|
|
from keystone.openstack.common.db.sqlalchemy import session
|
|
from keystone.openstack.common import jsonutils
|
|
|
|
|
|
ModelBase = declarative.declarative_base()
|
|
|
|
|
|
# For exporting to other modules
|
|
Column = sql.Column
|
|
Index = sql.Index
|
|
String = sql.String
|
|
ForeignKey = sql.ForeignKey
|
|
DateTime = sql.DateTime
|
|
IntegrityError = sql.exc.IntegrityError
|
|
OperationalError = sql.exc.OperationalError
|
|
NotFound = sql.orm.exc.NoResultFound
|
|
Boolean = sql.Boolean
|
|
Text = sql.Text
|
|
UniqueConstraint = sql.UniqueConstraint
|
|
relationship = sql.orm.relationship
|
|
joinedload = sql.orm.joinedload
|
|
# Suppress flake8's unused import warning for flag_modified:
|
|
flag_modified = flag_modified
|
|
|
|
|
|
def initialize():
|
|
"""Initialize the module."""
|
|
|
|
session.set_defaults(
|
|
sql_connection="sqlite:///keystone.db",
|
|
sqlite_db="keystone.db")
|
|
|
|
|
|
def initialize_decorator(init):
|
|
"""Ensure that the length of string field do not exceed the limit.
|
|
|
|
This decorator check the initialize arguments, to make sure the
|
|
length of string field do not exceed the length limit, or raise a
|
|
'StringLengthExceeded' exception.
|
|
|
|
Use decorator instead of inheritance, because the metaclass will
|
|
check the __tablename__, primary key columns, etc. at the class
|
|
definition.
|
|
|
|
"""
|
|
def initialize(self, *args, **kwargs):
|
|
cls = type(self)
|
|
for k, v in kwargs.items():
|
|
if hasattr(cls, k):
|
|
attr = getattr(cls, k)
|
|
if isinstance(attr, InstrumentedAttribute):
|
|
column = attr.property.columns[0]
|
|
if isinstance(column.type, String):
|
|
if not isinstance(v, unicode):
|
|
v = str(v)
|
|
if column.type.length and \
|
|
column.type.length < len(v):
|
|
raise exception.StringLengthExceeded(
|
|
string=v, type=k, length=column.type.length)
|
|
|
|
init(self, *args, **kwargs)
|
|
return initialize
|
|
|
|
ModelBase.__init__ = initialize_decorator(ModelBase.__init__)
|
|
|
|
|
|
# Special Fields
|
|
class JsonBlob(sql_types.TypeDecorator):
|
|
|
|
impl = sql.Text
|
|
|
|
def process_bind_param(self, value, dialect):
|
|
return jsonutils.dumps(value)
|
|
|
|
def process_result_value(self, value, dialect):
|
|
return jsonutils.loads(value)
|
|
|
|
|
|
class DictBase(models.ModelBase):
|
|
attributes = []
|
|
|
|
@classmethod
|
|
def from_dict(cls, d):
|
|
new_d = d.copy()
|
|
|
|
new_d['extra'] = dict((k, new_d.pop(k)) for k in d.iterkeys()
|
|
if k not in cls.attributes and k != 'extra')
|
|
|
|
return cls(**new_d)
|
|
|
|
def to_dict(self, include_extra_dict=False):
|
|
"""Returns the model's attributes as a dictionary.
|
|
|
|
If include_extra_dict is True, 'extra' attributes are literally
|
|
included in the resulting dictionary twice, for backwards-compatibility
|
|
with a broken implementation.
|
|
|
|
"""
|
|
d = self.extra.copy()
|
|
for attr in self.__class__.attributes:
|
|
d[attr] = getattr(self, attr)
|
|
|
|
if include_extra_dict:
|
|
d['extra'] = self.extra.copy()
|
|
|
|
return d
|
|
|
|
def __getitem__(self, key):
|
|
if key in self.extra:
|
|
return self.extra[key]
|
|
return getattr(self, key)
|
|
|
|
|
|
# Backends
|
|
class Base(object):
|
|
get_session = session.get_session
|
|
|
|
@contextlib.contextmanager
|
|
def transaction(self, expire_on_commit=False):
|
|
"""Return a SQLAlchemy session in a scoped transaction."""
|
|
session = self.get_session(expire_on_commit=expire_on_commit)
|
|
with session.begin():
|
|
yield session
|
|
|
|
|
|
def handle_conflicts(conflict_type='object'):
|
|
"""Converts select sqlalchemy exceptions into HTTP 409 Conflict."""
|
|
def decorator(method):
|
|
@functools.wraps(method)
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return method(*args, **kwargs)
|
|
except db_exception.DBDuplicateEntry as e:
|
|
raise exception.Conflict(type=conflict_type, details=str(e))
|
|
except db_exception.DBError as e:
|
|
# TODO(blk-u): inspecting inner_exception breaks encapsulation;
|
|
# oslo.db should provide exception we need.
|
|
if isinstance(e.inner_exception, IntegrityError):
|
|
raise exception.Conflict(type=conflict_type,
|
|
details=str(e))
|
|
raise
|
|
|
|
return wrapper
|
|
return decorator
|