trove/trove/datastore/models.py

527 lines
16 KiB
Python

# Copyright 2013 OpenStack Foundation
# Copyright 2013 Rackspace Hosting
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# 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.
#
from trove.common import cfg
from trove.common import exception
from trove.common import utils
from trove.db import get_db_api
from trove.db import models as dbmodels
from trove.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
db_api = get_db_api()
def persisted_models():
return {
'datastore': DBDatastore,
'capabilities': DBCapabilities,
'datastore_version': DBDatastoreVersion,
'capability_overrides': DBCapabilityOverrides,
}
class DBDatastore(dbmodels.DatabaseModelBase):
_data_fields = ['id', 'name', 'default_version_id']
class DBCapabilities(dbmodels.DatabaseModelBase):
_data_fields = ['id', 'name', 'description', 'enabled']
class DBCapabilityOverrides(dbmodels.DatabaseModelBase):
_data_fields = ['id', 'capability_id', 'datastore_version_id', 'enabled']
class DBDatastoreVersion(dbmodels.DatabaseModelBase):
_data_fields = ['id', 'datastore_id', 'name', 'manager', 'image_id',
'packages', 'active']
class Capabilities(object):
def __init__(self, datastore_version_id=None):
self.capabilities = []
self.datastore_version_id = datastore_version_id
def __contains__(self, item):
return item in [capability.name for capability in self.capabilities]
def __len__(self):
return len(self.capabilities)
def __iter__(self):
for item in self.capabilities:
yield item
def __repr__(self):
return '<%s: %s>' % (type(self), self.capabilities)
def add(self, capability, enabled):
"""
Add a capability override to a datastore version.
"""
if self.datastore_version_id is not None:
DBCapabilityOverrides.create(
capability_id=capability.id,
datastore_version_id=self.datastore_version_id,
enabled=enabled)
self._load()
def _load(self):
"""
Bulk load and override default capabilities with configured
datastore version specific settings.
"""
capability_defaults = [Capability(c)
for c in DBCapabilities.find_all()]
capability_overrides = []
if self.datastore_version_id is not None:
# This should always happen but if there is any future case where
# we don't have a datastore version id number it won't stop
# defaults from rendering.
capability_overrides = [
CapabilityOverride(ce)
for ce in DBCapabilityOverrides.find_all(
datastore_version_id=self.datastore_version_id)
]
def override(cap):
# This logic is necessary to apply datastore version specific
# capability overrides when they are present in the database.
for capability_override in capability_overrides:
if cap.id == capability_override.capability_id:
# we have a mapped entity that indicates this datastore
# version has an override so we honor that.
return capability_override
# There were no overrides for this capability so we just hand it
# right back.
return cap
self.capabilities = map(override, capability_defaults)
LOG.debug('Capabilities for datastore %(ds_id)s: %(capabilities)s' %
{'ds_id': self.datastore_version_id,
'capabilities': self.capabilities})
@classmethod
def load(cls, datastore_version_id=None):
"""
Generates a Capabilities object by looking up all capabilities from
defaults and overrides and provides the one structure that should be
used as the interface to controlling capabilities per datastore.
:returns Capabilities:
"""
self = cls(datastore_version_id)
self._load()
return self
class BaseCapability(object):
def __init__(self, db_info):
self.db_info = db_info
def __repr__(self):
return ('<%(my_class)s: name: %(name)s, enabled: %(enabled)s>' %
{'my_class': type(self), 'name': self.name,
'enabled': self.enabled})
@property
def id(self):
"""
The capability's id
:returns str:
"""
return self.db_info.id
@property
def enabled(self):
"""
Is the capability/feature enabled?
:returns bool:
"""
return self.db_info.enabled
def enable(self):
"""
Enable the capability.
"""
self.db_info.enabled = True
self.db_info.save()
def disable(self):
"""
Disable the capability
"""
self.db_info.enabled = False
self.db_info.save()
def delete(self):
"""
Delete the capability from the database.
"""
self.db_info.delete()
class CapabilityOverride(BaseCapability):
"""
A capability override is simply an setting that applies to a
specific datastore version that overrides the default setting in the
base capability's entry for Trove.
"""
def __init__(self, db_info):
super(CapabilityOverride, self).__init__(db_info)
# This *may* be better solved with a join in the SQLAlchemy model but
# I was unable to get our query object to work properly for this.
parent_capability = Capability.load(db_info.capability_id)
if parent_capability:
self.parent_name = parent_capability.name
self.parent_description = parent_capability.description
else:
raise exception.CapabilityNotFound(
_("Somehow we got a datastore version capability without a "
"parent, that shouldn't happen. %s") % db_info.capability_id)
@property
def name(self):
"""
The name of the capability.
:returns str:
"""
return self.parent_name
@property
def description(self):
"""
The description of the capability.
:returns str:
"""
return self.parent_description
@property
def capability_id(self):
"""
Because capability overrides is an association table there are times
where having the capability id is necessary.
:returns str:
"""
return self.db_info.capability_id
@classmethod
def load(cls, capability_id):
"""
Generates a CapabilityOverride object from the capability_override id.
:returns CapabilityOverride:
"""
try:
return cls(DBCapabilityOverrides.find_by(
capability_id=capability_id))
except exception.ModelNotFoundError:
raise exception.CapabilityNotFound(
_("Capability Override not found for "
"capability %s") % capability_id)
@classmethod
def create(cls, capability, datastore_version_id, enabled):
"""
Create a new CapabilityOverride.
:param capability: The capability to be overridden for
this DS Version
:param datastore_version_id: The datastore version to apply the
override to.
:param enabled: Set enabled to True or False
:returns CapabilityOverride:
"""
return CapabilityOverride(
DBCapabilityOverrides.create(
capability_id=capability.id,
datastore_version_id=datastore_version_id,
enabled=enabled)
)
class Capability(BaseCapability):
@property
def name(self):
"""
The Capability name
:returns str:
"""
return self.db_info.name
@property
def description(self):
"""
The Capability description
:returns str:
"""
return self.db_info.description
@classmethod
def load(cls, capability_id_or_name):
"""
Generates a Capability object by looking up the capability first by
ID then by name.
:returns Capability:
"""
try:
return cls(DBCapabilities.find_by(id=capability_id_or_name))
except exception.ModelNotFoundError:
try:
return cls(DBCapabilities.find_by(name=capability_id_or_name))
except exception.ModelNotFoundError:
raise exception.CapabilityNotFound(
capability=capability_id_or_name)
@classmethod
def create(cls, name, description, enabled=False):
"""
Creates a new capability.
:returns Capability:
"""
return Capability(DBCapabilities.create(
name=name, description=description, enabled=enabled))
class Datastore(object):
def __init__(self, db_info):
self.db_info = db_info
@classmethod
def load(cls, id_or_name):
try:
return cls(DBDatastore.find_by(id=id_or_name))
except exception.ModelNotFoundError:
try:
return cls(DBDatastore.find_by(name=id_or_name))
except exception.ModelNotFoundError:
raise exception.DatastoreNotFound(datastore=id_or_name)
@property
def id(self):
return self.db_info.id
@property
def name(self):
return self.db_info.name
@property
def default_version_id(self):
return self.db_info.default_version_id
def delete(self):
self.db_info.delete()
class Datastores(object):
def __init__(self, db_info):
self.db_info = db_info
@classmethod
def load(cls, only_active=True):
datastores = DBDatastore.find_all()
if only_active:
datastores = datastores.join(DBDatastoreVersion).filter(
DBDatastoreVersion.active == 1)
return cls(datastores)
def __iter__(self):
for item in self.db_info:
yield item
class DatastoreVersion(object):
def __init__(self, db_info):
self._capabilities = None
self.db_info = db_info
self._datastore_name = None
@classmethod
def load(cls, datastore, id_or_name):
try:
return cls(DBDatastoreVersion.find_by(datastore_id=datastore.id,
id=id_or_name))
except exception.ModelNotFoundError:
versions = DBDatastoreVersion.find_all(datastore_id=datastore.id,
name=id_or_name)
if versions.count() == 0:
raise exception.DatastoreVersionNotFound(version=id_or_name)
if versions.count() > 1:
raise exception.NoUniqueMatch(name=id_or_name)
return cls(versions.first())
@classmethod
def load_by_uuid(cls, uuid):
try:
return cls(DBDatastoreVersion.find_by(id=uuid))
except exception.ModelNotFoundError:
raise exception.DatastoreVersionNotFound(version=uuid)
@property
def id(self):
return self.db_info.id
@property
def datastore_id(self):
return self.db_info.datastore_id
@property
def datastore_name(self):
if self._datastore_name is None:
self._datastore_name = Datastore.load(self.datastore_id).name
return self._datastore_name
# TODO(tim.simpson): This would be less confusing if it was called
# "version" and datastore_name was called "name".
@property
def name(self):
return self.db_info.name
@property
def image_id(self):
return self.db_info.image_id
@property
def packages(self):
return self.db_info.packages
@property
def active(self):
return (True if self.db_info.active else False)
@property
def manager(self):
return self.db_info.manager
@property
def capabilities(self):
if self._capabilities is None:
self._capabilities = Capabilities.load(self.db_info.id)
return self._capabilities
class DatastoreVersions(object):
def __init__(self, db_info):
self.db_info = db_info
@classmethod
def load(cls, id_or_name, only_active=True):
datastore = Datastore.load(id_or_name)
if only_active:
versions = DBDatastoreVersion.find_all(datastore_id=datastore.id,
active=True)
else:
versions = DBDatastoreVersion.find_all(datastore_id=datastore.id)
return cls(versions)
@classmethod
def load_all(cls, only_active=True):
if only_active:
return cls(DBDatastoreVersion.find_all(active=True))
return cls(DBDatastoreVersion.find_all())
def __iter__(self):
for item in self.db_info:
yield item
def get_datastore_version(type=None, version=None, return_inactive=False):
datastore = type or CONF.default_datastore
if not datastore:
raise exception.DatastoreDefaultDatastoreNotFound()
datastore = Datastore.load(datastore)
version = version or datastore.default_version_id
if not version:
raise exception.DatastoreDefaultVersionNotFound(
datastore=datastore.name)
datastore_version = DatastoreVersion.load(datastore, version)
if datastore_version.datastore_id != datastore.id:
raise exception.DatastoreNoVersion(datastore=datastore.name,
version=datastore_version.name)
if not datastore_version.active and not return_inactive:
raise exception.DatastoreVersionInactive(
version=datastore_version.name)
return (datastore, datastore_version)
def update_datastore(name, default_version):
db_api.configure_db(CONF)
try:
datastore = DBDatastore.find_by(name=name)
except exception.ModelNotFoundError:
# Create a new one
datastore = DBDatastore()
datastore.id = utils.generate_uuid()
datastore.name = name
if default_version:
version = DatastoreVersion.load(datastore, default_version)
if not version.active:
raise exception.DatastoreVersionInactive(version=version.name)
datastore.default_version_id = version.id
db_api.save(datastore)
def update_datastore_version(datastore, name, manager, image_id, packages,
active):
db_api.configure_db(CONF)
datastore = Datastore.load(datastore)
try:
version = DBDatastoreVersion.find_by(datastore_id=datastore.id,
name=name)
except exception.ModelNotFoundError:
# Create a new one
version = DBDatastoreVersion()
version.id = utils.generate_uuid()
version.name = name
version.datastore_id = datastore.id
version.manager = manager
version.image_id = image_id
version.packages = packages
version.active = active
db_api.save(version)