cfda6bc4fb
The cfg API is now available via the oslo-config library, so switch to it and remove the copied-and-pasted version. Add the 2013.1b3 tarball to tools/pip-requires - this will be changed to 'oslo-config>=2013.1' when oslo-config is published to pypi. This will happen in time for grizzly final. Remove the 'deps = pep8' from tox.ini as it means all the other deps get installed with easy_install which can't install oslo-config from the URL. Retain dummy cfg.py file until keystoneclient middleware has been updated (I18c450174277c8e2d15ed93879da6cd92074c27a). Change-Id: I4815aeb8a9341a31a250e920157f15ee15cfc5bc
329 lines
14 KiB
Python
329 lines
14 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
# Copyright 2013 OpenStack LLC.
|
|
# 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.
|
|
#
|
|
# @author: Salvatore Orlando, VMware
|
|
#
|
|
|
|
from oslo.config import cfg
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import orm
|
|
from sqlalchemy.orm import exc as orm_exc
|
|
from sqlalchemy.sql import expression as expr
|
|
|
|
from quantum.common import exceptions as q_exc
|
|
from quantum import context
|
|
from quantum.db import api as db
|
|
from quantum.db import model_base
|
|
from quantum.db import models_v2
|
|
from quantum.openstack.common import log as logging
|
|
from quantum import policy
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
DEFAULT_SVCTYPE_NAME = 'default'
|
|
|
|
default_servicetype_opts = [
|
|
cfg.StrOpt('description',
|
|
default='',
|
|
help=_('Textual description for the default service type')),
|
|
cfg.MultiStrOpt('service_definition',
|
|
help=_('Defines a provider for an advanced service '
|
|
'using the format: <service>:<plugin>[:<driver>]'))
|
|
]
|
|
|
|
cfg.CONF.register_opts(default_servicetype_opts, 'DEFAULT_SERVICETYPE')
|
|
|
|
|
|
def parse_service_definition_opt():
|
|
""" parse service definition opts and returns result """
|
|
results = []
|
|
svc_def_opt = cfg.CONF.DEFAULT_SERVICETYPE.service_definition
|
|
try:
|
|
for svc_def_str in svc_def_opt:
|
|
split = svc_def_str.split(':')
|
|
svc_def = {'service_class': split[0],
|
|
'plugin': split[1]}
|
|
try:
|
|
svc_def['driver'] = split[2]
|
|
except IndexError:
|
|
# Never mind, driver is optional
|
|
LOG.debug(_("Default service type - no driver for service "
|
|
"%(service_class)s and plugin %(plugin)s"),
|
|
svc_def)
|
|
results.append(svc_def)
|
|
return results
|
|
except (TypeError, IndexError):
|
|
raise q_exc.InvalidConfigurationOption(opt_name='service_definition',
|
|
opt_value=svc_def_opt)
|
|
|
|
|
|
class NoDefaultServiceDefinition(q_exc.QuantumException):
|
|
message = _("No default service definition in configuration file. "
|
|
"Please add service definitions using the service_definition "
|
|
"variable in the [DEFAULT_SERVICETYPE] section")
|
|
|
|
|
|
class ServiceTypeNotFound(q_exc.NotFound):
|
|
message = _("Service type %(service_type_id)s could not be found ")
|
|
|
|
|
|
class ServiceTypeInUse(q_exc.InUse):
|
|
message = _("There are still active instances of service type "
|
|
"'%(service_type_id)s'. Therefore it cannot be removed.")
|
|
|
|
|
|
class ServiceDefinition(model_base.BASEV2, models_v2.HasId):
|
|
service_class = sa.Column(sa.String(255), primary_key=True)
|
|
plugin = sa.Column(sa.String(255))
|
|
driver = sa.Column(sa.String(255))
|
|
service_type_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('servicetypes.id',
|
|
ondelete='CASCADE'),
|
|
primary_key=True)
|
|
|
|
|
|
class ServiceType(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
|
""" Service Type Object Model """
|
|
name = sa.Column(sa.String(255))
|
|
description = sa.Column(sa.String(255))
|
|
default = sa.Column(sa.Boolean(), nullable=False, default=False)
|
|
service_definitions = orm.relationship(ServiceDefinition,
|
|
backref='servicetypes',
|
|
lazy='joined',
|
|
cascade='all')
|
|
# Keep track of number of instances for this service type
|
|
num_instances = sa.Column(sa.Integer(), default=0)
|
|
|
|
def as_dict(self):
|
|
""" Convert a row into a dict """
|
|
ret_dict = {}
|
|
for c in self.__table__.columns:
|
|
ret_dict[c.name] = getattr(self, c.name)
|
|
return ret_dict
|
|
|
|
|
|
class ServiceTypeManager(object):
|
|
""" Manage service type objects in Quantum database """
|
|
|
|
_instance = None
|
|
|
|
@classmethod
|
|
def get_instance(cls):
|
|
if cls._instance is None:
|
|
cls._instance = cls()
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
self._initialize_db()
|
|
ctx = context.get_admin_context()
|
|
# Init default service type from configuration file
|
|
svc_defs = cfg.CONF.DEFAULT_SERVICETYPE.service_definition
|
|
if not svc_defs:
|
|
raise NoDefaultServiceDefinition()
|
|
def_service_type = {'name': DEFAULT_SVCTYPE_NAME,
|
|
'description':
|
|
cfg.CONF.DEFAULT_SERVICETYPE.description,
|
|
'service_definitions':
|
|
parse_service_definition_opt(),
|
|
'default': True}
|
|
# Create or update record in database
|
|
def_svc_type_db = self._get_default_service_type(ctx)
|
|
if not def_svc_type_db:
|
|
def_svc_type_db = self._create_service_type(ctx, def_service_type)
|
|
else:
|
|
self._update_service_type(ctx,
|
|
def_svc_type_db['id'],
|
|
def_service_type,
|
|
svc_type_db=def_svc_type_db)
|
|
LOG.debug(_("Default service type record updated in Quantum database. "
|
|
"identifier is '%s'"), def_svc_type_db['id'])
|
|
|
|
def _initialize_db(self):
|
|
db.configure_db()
|
|
# Register models for service type management
|
|
# Note this might have been already done if configure_db also
|
|
# created the engine
|
|
db.register_models(models_v2.model_base.BASEV2)
|
|
|
|
def _create_service_type(self, context, service_type):
|
|
svc_defs = service_type.pop('service_definitions')
|
|
with context.session.begin(subtransactions=True):
|
|
svc_type_db = ServiceType(**service_type)
|
|
# and now insert provided service type definitions
|
|
for svc_def in svc_defs:
|
|
svc_type_db.service_definitions.append(
|
|
ServiceDefinition(**svc_def))
|
|
# sqlalchemy save-update on relationship is on by
|
|
# default, the following will save both the service
|
|
# type and its service definitions
|
|
context.session.add(svc_type_db)
|
|
return svc_type_db
|
|
|
|
def _update_service_type(self, context, id, service_type,
|
|
svc_type_db=None):
|
|
with context.session.begin(subtransactions=True):
|
|
if not svc_type_db:
|
|
svc_type_db = self._get_service_type(context, id)
|
|
try:
|
|
svc_defs_map = dict([(svc_def['service'], svc_def)
|
|
for svc_def in
|
|
service_type.pop('service_definitions')])
|
|
except KeyError:
|
|
# No service defs in request
|
|
svc_defs_map = {}
|
|
svc_type_db.update(service_type)
|
|
for svc_def_db in svc_type_db.service_definitions:
|
|
try:
|
|
svc_def_db.update(svc_defs_map.pop(
|
|
svc_def_db['service_class']))
|
|
except KeyError:
|
|
# too bad, the service def was not there
|
|
# then we should delete it.
|
|
context.session.delete(svc_def_db)
|
|
# Add remaining service definitions
|
|
for svc_def in svc_defs_map:
|
|
context.session.add(ServiceDefinition(**svc_def))
|
|
return svc_type_db
|
|
|
|
def _check_service_type_view_auth(self, context, service_type):
|
|
# FIXME(salvatore-orlando): This should be achieved via policy
|
|
# engine without need for explicit checks in manager code.
|
|
# Also, the policy in this way does not make a lot of sense
|
|
return policy.check(context,
|
|
"extension:service_type:view_extended",
|
|
service_type)
|
|
|
|
def _get_service_type(self, context, svc_type_id):
|
|
try:
|
|
query = context.session.query(ServiceType)
|
|
return query.filter(ServiceType.id == svc_type_id).one()
|
|
# filter is on primary key, do not catch MultipleResultsFound
|
|
except orm_exc.NoResultFound:
|
|
raise ServiceTypeNotFound(service_type_id=svc_type_id)
|
|
|
|
def _get_default_service_type(self, context):
|
|
try:
|
|
query = context.session.query(ServiceType)
|
|
return query.filter(ServiceType.default == expr.true()).one()
|
|
except orm_exc.NoResultFound:
|
|
return
|
|
except orm_exc.MultipleResultsFound:
|
|
# This should never happen. If it does, take the first instance
|
|
query2 = context.session.query(ServiceType)
|
|
results = query2.filter(ServiceType.default == expr.true()).all()
|
|
LOG.warning(_("Multiple default service type instances found."
|
|
"Will use instance '%s'"), results[0]['id'])
|
|
return results[0]
|
|
|
|
def _make_svc_type_dict(self, context, svc_type, fields=None):
|
|
|
|
def _make_svc_def_dict(svc_def_db):
|
|
svc_def = {'service_class': svc_def_db['service_class']}
|
|
if self._check_service_type_view_auth(context,
|
|
svc_type.as_dict()):
|
|
svc_def.update({'plugin': svc_def_db['plugin'],
|
|
'driver': svc_def_db['driver']})
|
|
return svc_def
|
|
|
|
res = {'id': svc_type['id'],
|
|
'name': svc_type['name'],
|
|
'default': svc_type['default'],
|
|
'service_definitions':
|
|
[_make_svc_def_dict(svc_def) for svc_def
|
|
in svc_type['service_definitions']]}
|
|
if self._check_service_type_view_auth(context,
|
|
svc_type.as_dict()):
|
|
res['num_instances'] = svc_type['num_instances']
|
|
# Field selection
|
|
if fields:
|
|
return dict(((k, v) for k, v in res.iteritems()
|
|
if k in fields))
|
|
return res
|
|
|
|
def get_service_type(self, context, id, fields=None):
|
|
""" Retrieve a service type record """
|
|
return self._make_svc_type_dict(context,
|
|
self._get_service_type(context, id),
|
|
fields)
|
|
|
|
def get_service_types(self, context, fields=None, filters=None):
|
|
""" Retrieve a possibly filtered list of service types """
|
|
query = context.session.query(ServiceType)
|
|
if filters:
|
|
for key, value in filters.iteritems():
|
|
column = getattr(ServiceType, key, None)
|
|
if column:
|
|
query = query.filter(column.in_(value))
|
|
return [self._make_svc_type_dict(context, svc_type, fields)
|
|
for svc_type in query.all()]
|
|
|
|
def create_service_type(self, context, service_type):
|
|
""" Create a new service type """
|
|
svc_type_data = service_type['service_type']
|
|
svc_type_db = self._create_service_type(context, svc_type_data)
|
|
LOG.debug(_("Created service type object:%s"), svc_type_db['id'])
|
|
return self._make_svc_type_dict(context, svc_type_db)
|
|
|
|
def update_service_type(self, context, id, service_type):
|
|
""" Update a service type """
|
|
svc_type_data = service_type['service_type']
|
|
svc_type_db = self._update_service_type(context, id,
|
|
svc_type_data)
|
|
return self._make_svc_type_dict(context, svc_type_db)
|
|
|
|
def delete_service_type(self, context, id):
|
|
""" Delete a service type """
|
|
# Verify that the service type is not in use.
|
|
svc_type_db = self._get_service_type(context, id)
|
|
if svc_type_db['num_instances'] > 0:
|
|
raise ServiceTypeInUse(service_type_id=svc_type_db['id'])
|
|
with context.session.begin(subtransactions=True):
|
|
context.session.delete(svc_type_db)
|
|
|
|
def increase_service_type_refcount(self, context, id):
|
|
""" Increase references count for a service type object
|
|
|
|
This method should be invoked by plugins using the service
|
|
type concept everytime an instance of an object associated
|
|
with a given service type is created.
|
|
"""
|
|
#TODO(salvatore-orlando): Devise a better solution than this
|
|
#refcount mechanisms. Perhaps adding hooks into models which
|
|
#use service types in order to enforce ref. integrity and cascade
|
|
with context.session.begin(subtransactions=True):
|
|
svc_type_db = self._get_service_type(context, id)
|
|
svc_type_db['num_instances'] = svc_type_db['num_instances'] + 1
|
|
return svc_type_db['num_instances']
|
|
|
|
def decrease_service_type_refcount(self, context, id):
|
|
""" Decrease references count for a service type object
|
|
|
|
This method should be invoked by plugins using the service
|
|
type concept everytime an instance of an object associated
|
|
with a given service type is removed
|
|
"""
|
|
#TODO(salvatore-orlando): Devise a better solution than this
|
|
#refcount mechanisms. Perhaps adding hooks into models which
|
|
#use service types in order to enforce ref. integrity and cascade
|
|
with context.session.begin(subtransactions=True):
|
|
svc_type_db = self._get_service_type(context, id)
|
|
if svc_type_db['num_instances'] == 0:
|
|
LOG.warning(_("Number of instances for service type "
|
|
"'%s' is already 0."), svc_type_db['name'])
|
|
return
|
|
svc_type_db['num_instances'] = svc_type_db['num_instances'] - 1
|
|
return svc_type_db['num_instances']
|