527 lines
16 KiB
Python
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)
|