# 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 oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import importutils from oslo_utils import uuidutils import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.orm import exc as sa_exc from neutron.common import exceptions as qexception from neutron.db import common_db_mixin from neutron.db import model_base from neutron.db import models_v2 from neutron.plugins.common import constants LOG = logging.getLogger(__name__) # Flavor Exceptions class FlavorNotFound(qexception.NotFound): message = _("Flavor %(flavor_id)s could not be found") class FlavorInUse(qexception.InUse): message = _("Flavor %(flavor_id)s is used by some service instance") class ServiceProfileNotFound(qexception.NotFound): message = _("Service Profile %(sp_id)s could not be found") class ServiceProfileInUse(qexception.InUse): message = _("Service Profile %(sp_id)s is used by some service instance") class FlavorServiceProfileBindingExists(qexception.Conflict): message = _("Service Profile %(sp_id)s is already associated " "with flavor %(fl_id)s") class FlavorServiceProfileBindingNotFound(qexception.NotFound): message = _("Service Profile %(sp_id)s is not associated " "with flavor %(fl_id)s") class DummyCorePlugin(object): pass class DummyServicePlugin(object): def driver_loaded(self, driver, service_profile): pass def get_plugin_type(self): return constants.DUMMY def get_plugin_description(self): return "Dummy service plugin, aware of flavors" class DummyServiceDriver(object): @staticmethod def get_service_type(): return constants.DUMMY def __init__(self, plugin): pass class Flavor(model_base.BASEV2, models_v2.HasId): name = sa.Column(sa.String(255)) description = sa.Column(sa.String(1024)) enabled = sa.Column(sa.Boolean, nullable=False, default=True, server_default=sa.sql.true()) # Make it True for multi-type flavors service_type = sa.Column(sa.String(36), nullable=True) service_profiles = orm.relationship("FlavorServiceProfileBinding", cascade="all, delete-orphan") class ServiceProfile(model_base.BASEV2, models_v2.HasId): description = sa.Column(sa.String(1024)) driver = sa.Column(sa.String(1024), nullable=False) enabled = sa.Column(sa.Boolean, nullable=False, default=True, server_default=sa.sql.true()) metainfo = sa.Column(sa.String(4096)) flavors = orm.relationship("FlavorServiceProfileBinding") class FlavorServiceProfileBinding(model_base.BASEV2): flavor_id = sa.Column(sa.String(36), sa.ForeignKey("flavors.id", ondelete="CASCADE"), nullable=False, primary_key=True) flavor = orm.relationship(Flavor) service_profile_id = sa.Column(sa.String(36), sa.ForeignKey("serviceprofiles.id", ondelete="CASCADE"), nullable=False, primary_key=True) service_profile = orm.relationship(ServiceProfile) class FlavorManager(common_db_mixin.CommonDbMixin): """Class to support flavors and service profiles.""" supported_extension_aliases = ["flavors"] def __init__(self, manager=None): # manager = None is UT usage where FlavorManager is loaded as # a core plugin self.manager = manager def get_plugin_name(self): return constants.FLAVORS def get_plugin_type(self): return constants.FLAVORS def get_plugin_description(self): return "Neutron Flavors and Service Profiles manager plugin" def _get_flavor(self, context, flavor_id): try: return self._get_by_id(context, Flavor, flavor_id) except sa_exc.NoResultFound: raise FlavorNotFound(flavor_id=flavor_id) def _get_service_profile(self, context, sp_id): try: return self._get_by_id(context, ServiceProfile, sp_id) except sa_exc.NoResultFound: raise ServiceProfileNotFound(sp_id=sp_id) def _make_flavor_dict(self, flavor_db, fields=None): res = {'id': flavor_db['id'], 'name': flavor_db['name'], 'description': flavor_db['description'], 'enabled': flavor_db['enabled'], 'service_profiles': []} if flavor_db.service_profiles: res['service_profiles'] = [sp['service_profile_id'] for sp in flavor_db.service_profiles] return self._fields(res, fields) def _make_service_profile_dict(self, sp_db, fields=None): res = {'id': sp_db['id'], 'description': sp_db['description'], 'driver': sp_db['driver'], 'enabled': sp_db['enabled'], 'metainfo': sp_db['metainfo']} if sp_db.flavors: res['flavors'] = [fl['flavor_id'] for fl in sp_db.flavors] return self._fields(res, fields) def _ensure_flavor_not_in_use(self, context, flavor_id): """Checks that flavor is not associated with service instance.""" # Future TODO(enikanorov): check that there is no binding to # instances. Shall address in future upon getting the right # flavor supported driver pass def _ensure_service_profile_not_in_use(self, context, sp_id): # Future TODO(enikanorov): check that there is no binding to instances # and no binding to flavors. Shall be addressed in future fl = (context.session.query(FlavorServiceProfileBinding). filter_by(service_profile_id=sp_id).first()) if fl: raise ServiceProfileInUse(sp_id=sp_id) def create_flavor(self, context, flavor): fl = flavor['flavor'] with context.session.begin(subtransactions=True): fl_db = Flavor(id=uuidutils.generate_uuid(), name=fl['name'], description=fl['description'], enabled=fl['enabled']) context.session.add(fl_db) return self._make_flavor_dict(fl_db) def update_flavor(self, context, flavor_id, flavor): fl = flavor['flavor'] with context.session.begin(subtransactions=True): self._ensure_flavor_not_in_use(context, flavor_id) fl_db = self._get_flavor(context, flavor_id) fl_db.update(fl) return self._make_flavor_dict(fl_db) def get_flavor(self, context, flavor_id, fields=None): fl = self._get_flavor(context, flavor_id) return self._make_flavor_dict(fl, fields) def delete_flavor(self, context, flavor_id): with context.session.begin(subtransactions=True): self._ensure_flavor_not_in_use(context, flavor_id) fl_db = self._get_flavor(context, flavor_id) context.session.delete(fl_db) def get_flavors(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): return self._get_collection(context, Flavor, self._make_flavor_dict, filters=filters, fields=fields, sorts=sorts, limit=limit, marker_obj=marker, page_reverse=page_reverse) def create_flavor_service_profile(self, context, service_profile, flavor_id): sp = service_profile['service_profile'] with context.session.begin(subtransactions=True): bind_qry = context.session.query(FlavorServiceProfileBinding) binding = bind_qry.filter_by(service_profile_id=sp['id'], flavor_id=flavor_id).first() if binding: raise FlavorServiceProfileBindingExists( sp_id=sp['id'], fl_id=flavor_id) binding = FlavorServiceProfileBinding( service_profile_id=sp['id'], flavor_id=flavor_id) context.session.add(binding) fl_db = self._get_flavor(context, flavor_id) sps = [x['service_profile_id'] for x in fl_db.service_profiles] return sps def delete_flavor_service_profile(self, context, service_profile_id, flavor_id): with context.session.begin(subtransactions=True): binding = (context.session.query(FlavorServiceProfileBinding). filter_by(service_profile_id=service_profile_id, flavor_id=flavor_id).first()) if not binding: raise FlavorServiceProfileBindingNotFound( sp_id=service_profile_id, fl_id=flavor_id) context.session.delete(binding) def get_flavor_service_profile(self, context, service_profile_id, flavor_id, fields=None): with context.session.begin(subtransactions=True): binding = (context.session.query(FlavorServiceProfileBinding). filter_by(service_profile_id=service_profile_id, flavor_id=flavor_id).first()) if not binding: raise FlavorServiceProfileBindingNotFound( sp_id=service_profile_id, fl_id=flavor_id) res = {'service_profile_id': service_profile_id, 'flavor_id': flavor_id} return self._fields(res, fields) def _load_dummy_driver(self, driver): driver = DummyServiceDriver driver_klass = driver return driver_klass def _load_driver(self, profile): driver_klass = importutils.import_class(profile.driver) return driver_klass def create_service_profile(self, context, service_profile): sp = service_profile['service_profile'] with context.session.begin(subtransactions=True): driver_klass = self._load_dummy_driver(sp['driver']) # 'get_service_type' must be a static method so it cant be changed svc_type = DummyServiceDriver.get_service_type() sp_db = ServiceProfile(id=uuidutils.generate_uuid(), description=sp['description'], driver=svc_type, enabled=sp['enabled'], metainfo=jsonutils.dumps(sp['metainfo'])) context.session.add(sp_db) try: # driver_klass = self._load_dummy_driver(sp_db) # Future TODO(madhu_ak): commented for now to load dummy driver # until there is flavor supported driver # plugin = self.manager.get_service_plugins()[svc_type] # plugin.driver_loaded(driver_klass(plugin), sp_db) # svc_type = DummyServiceDriver.get_service_type() # plugin = self.manager.get_service_plugins()[svc_type] # plugin = FlavorManager(manager.NeutronManager().get_instance()) # plugin = DummyServicePlugin.get_plugin_type(svc_type) plugin = DummyServicePlugin() plugin.driver_loaded(driver_klass(svc_type), sp_db) except Exception: # Future TODO(enikanorov): raise proper exception self.delete_service_profile(context, sp_db['id']) raise return self._make_service_profile_dict(sp_db) def unit_create_service_profile(self, context, service_profile): # Note: Triggered by unit tests pointing to dummy driver sp = service_profile['service_profile'] with context.session.begin(subtransactions=True): sp_db = ServiceProfile(id=uuidutils.generate_uuid(), description=sp['description'], driver=sp['driver'], enabled=sp['enabled'], metainfo=sp['metainfo']) context.session.add(sp_db) try: driver_klass = self._load_driver(sp_db) # require get_service_type be a static method svc_type = driver_klass.get_service_type() plugin = self.manager.get_service_plugins()[svc_type] plugin.driver_loaded(driver_klass(plugin), sp_db) except Exception: # Future TODO(enikanorov): raise proper exception self.delete_service_profile(context, sp_db['id']) raise return self._make_service_profile_dict(sp_db) def update_service_profile(self, context, service_profile_id, service_profile): sp = service_profile['service_profile'] with context.session.begin(subtransactions=True): self._ensure_service_profile_not_in_use(context, service_profile_id) sp_db = self._get_service_profile(context, service_profile_id) sp_db.update(sp) return self._make_service_profile_dict(sp_db) def get_service_profile(self, context, sp_id, fields=None): sp_db = self._get_service_profile(context, sp_id) return self._make_service_profile_dict(sp_db, fields) def delete_service_profile(self, context, sp_id): with context.session.begin(subtransactions=True): self._ensure_service_profile_not_in_use(context, sp_id) sp_db = self._get_service_profile(context, sp_id) context.session.delete(sp_db) def get_service_profiles(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): return self._get_collection(context, ServiceProfile, self._make_service_profile_dict, filters=filters, fields=fields, sorts=sorts, limit=limit, marker_obj=marker, page_reverse=page_reverse)