Fix Neutron flavor framework
Make flavor service profile store actual driver instead of hardcoded dummy driver. Ensure service type on flavor persisted. Raise ServiceProfileDriverNotFound if non-empty driver is not part of ServiceTypeManager providers. Raise ServiceProfileEmpty if profile has neither a driver nor any metainfo. Raise InvalidFlavorServiceType if invalid service type passed. Show flavors associated with a profile, not just profiles associated with a flavor, to ease diagnosis when ServiceProfileInUse raised. Create method to extract provider given a flavor for use with neutron-lbaas plus tests. Ensure various boolean forms accepted for enabled flag. To enable in DevStack, add to local.conf: enable_plugin neutron https://git.openstack.org/openstack/neutron enable_service q-flavors Add associated unit tests. Fix tempest api test that used invalid LOADBALANCERS service type. Change-Id: I5c22ab655a8e2a2e586c10eae9de9b72db49755f Implements: blueprint neutron-flavor-framework
This commit is contained in:
parent
a93b889ae5
commit
6bc53cc7f8
8
devstack/lib/flavors
Normal file
8
devstack/lib/flavors
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Neutron flavors plugin
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
FLAVORS_PLUGIN=neutron.services.flavors.flavors_plugin.FlavorsPlugin
|
||||||
|
|
||||||
|
function configure_flavors {
|
||||||
|
_neutron_service_plugin_class_add $FLAVORS_PLUGIN
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
LIBDIR=$DEST/neutron/devstack/lib
|
LIBDIR=$DEST/neutron/devstack/lib
|
||||||
|
|
||||||
|
source $LIBDIR/flavors
|
||||||
source $LIBDIR/l2_agent
|
source $LIBDIR/l2_agent
|
||||||
source $LIBDIR/l2_agent_sriovnicswitch
|
source $LIBDIR/l2_agent_sriovnicswitch
|
||||||
source $LIBDIR/ml2
|
source $LIBDIR/ml2
|
||||||
@ -8,6 +9,9 @@ source $LIBDIR/qos
|
|||||||
if [[ "$1" == "stack" ]]; then
|
if [[ "$1" == "stack" ]]; then
|
||||||
case "$2" in
|
case "$2" in
|
||||||
install)
|
install)
|
||||||
|
if is_service_enabled q-flavors; then
|
||||||
|
configure_flavors
|
||||||
|
fi
|
||||||
if is_service_enabled q-qos; then
|
if is_service_enabled q-qos; then
|
||||||
configure_qos
|
configure_qos
|
||||||
fi
|
fi
|
||||||
|
@ -199,5 +199,9 @@
|
|||||||
"update_rbac_policy": "rule:admin_or_owner",
|
"update_rbac_policy": "rule:admin_or_owner",
|
||||||
"update_rbac_policy:target_tenant": "rule:restrict_wildcard and rule:admin_or_owner",
|
"update_rbac_policy:target_tenant": "rule:restrict_wildcard and rule:admin_or_owner",
|
||||||
"get_rbac_policy": "rule:admin_or_owner",
|
"get_rbac_policy": "rule:admin_or_owner",
|
||||||
"delete_rbac_policy": "rule:admin_or_owner"
|
"delete_rbac_policy": "rule:admin_or_owner",
|
||||||
|
|
||||||
|
"create_flavor_service_profile": "rule:admin_only",
|
||||||
|
"delete_flavor_service_profile": "rule:admin_only",
|
||||||
|
"get_flavor_service_profile": "rule:regular_user"
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ UNLIMITED = None
|
|||||||
NAME_MAX_LEN = 255
|
NAME_MAX_LEN = 255
|
||||||
TENANT_ID_MAX_LEN = 255
|
TENANT_ID_MAX_LEN = 255
|
||||||
DESCRIPTION_MAX_LEN = 255
|
DESCRIPTION_MAX_LEN = 255
|
||||||
|
LONG_DESCRIPTION_MAX_LEN = 1024
|
||||||
DEVICE_ID_MAX_LEN = 255
|
DEVICE_ID_MAX_LEN = 255
|
||||||
DEVICE_OWNER_MAX_LEN = 255
|
DEVICE_OWNER_MAX_LEN = 255
|
||||||
|
|
||||||
|
@ -13,76 +13,20 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
from oslo_utils import importutils
|
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
from sqlalchemy.orm import exc as sa_exc
|
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 common_db_mixin
|
||||||
from neutron.db import model_base
|
from neutron.db import model_base
|
||||||
from neutron.db import models_v2
|
from neutron.db import models_v2
|
||||||
from neutron.plugins.common import constants
|
from neutron.db import servicetype_db as sdb
|
||||||
|
from neutron.extensions import flavors as ext_flavors
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
class Flavor(model_base.BASEV2, models_v2.HasId):
|
||||||
name = sa.Column(sa.String(255))
|
name = sa.Column(sa.String(255))
|
||||||
description = sa.Column(sa.String(1024))
|
description = sa.Column(sa.String(1024))
|
||||||
@ -116,36 +60,21 @@ class FlavorServiceProfileBinding(model_base.BASEV2):
|
|||||||
service_profile = orm.relationship(ServiceProfile)
|
service_profile = orm.relationship(ServiceProfile)
|
||||||
|
|
||||||
|
|
||||||
class FlavorManager(common_db_mixin.CommonDbMixin):
|
class FlavorsDbMixin(common_db_mixin.CommonDbMixin):
|
||||||
|
|
||||||
"""Class to support flavors and service profiles."""
|
"""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):
|
def _get_flavor(self, context, flavor_id):
|
||||||
try:
|
try:
|
||||||
return self._get_by_id(context, Flavor, flavor_id)
|
return self._get_by_id(context, Flavor, flavor_id)
|
||||||
except sa_exc.NoResultFound:
|
except sa_exc.NoResultFound:
|
||||||
raise FlavorNotFound(flavor_id=flavor_id)
|
raise ext_flavors.FlavorNotFound(flavor_id=flavor_id)
|
||||||
|
|
||||||
def _get_service_profile(self, context, sp_id):
|
def _get_service_profile(self, context, sp_id):
|
||||||
try:
|
try:
|
||||||
return self._get_by_id(context, ServiceProfile, sp_id)
|
return self._get_by_id(context, ServiceProfile, sp_id)
|
||||||
except sa_exc.NoResultFound:
|
except sa_exc.NoResultFound:
|
||||||
raise ServiceProfileNotFound(sp_id=sp_id)
|
raise ext_flavors.ServiceProfileNotFound(sp_id=sp_id)
|
||||||
|
|
||||||
def _make_flavor_dict(self, flavor_db, fields=None):
|
def _make_flavor_dict(self, flavor_db, fields=None):
|
||||||
res = {'id': flavor_db['id'],
|
res = {'id': flavor_db['id'],
|
||||||
@ -178,12 +107,21 @@ class FlavorManager(common_db_mixin.CommonDbMixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _ensure_service_profile_not_in_use(self, context, sp_id):
|
def _ensure_service_profile_not_in_use(self, context, sp_id):
|
||||||
# Future TODO(enikanorov): check that there is no binding to instances
|
"""Ensures no current bindings to flavors exist."""
|
||||||
# and no binding to flavors. Shall be addressed in future
|
|
||||||
fl = (context.session.query(FlavorServiceProfileBinding).
|
fl = (context.session.query(FlavorServiceProfileBinding).
|
||||||
filter_by(service_profile_id=sp_id).first())
|
filter_by(service_profile_id=sp_id).first())
|
||||||
if fl:
|
if fl:
|
||||||
raise ServiceProfileInUse(sp_id=sp_id)
|
raise ext_flavors.ServiceProfileInUse(sp_id=sp_id)
|
||||||
|
|
||||||
|
def _validate_driver(self, context, driver):
|
||||||
|
"""Confirms a non-empty driver is a valid provider."""
|
||||||
|
service_type_manager = sdb.ServiceTypeManager.get_instance()
|
||||||
|
providers = service_type_manager.get_service_providers(
|
||||||
|
context,
|
||||||
|
filters={'driver': driver})
|
||||||
|
|
||||||
|
if not providers:
|
||||||
|
raise ext_flavors.ServiceProfileDriverNotFound(driver=driver)
|
||||||
|
|
||||||
def create_flavor(self, context, flavor):
|
def create_flavor(self, context, flavor):
|
||||||
fl = flavor['flavor']
|
fl = flavor['flavor']
|
||||||
@ -202,7 +140,6 @@ class FlavorManager(common_db_mixin.CommonDbMixin):
|
|||||||
self._ensure_flavor_not_in_use(context, flavor_id)
|
self._ensure_flavor_not_in_use(context, flavor_id)
|
||||||
fl_db = self._get_flavor(context, flavor_id)
|
fl_db = self._get_flavor(context, flavor_id)
|
||||||
fl_db.update(fl)
|
fl_db.update(fl)
|
||||||
|
|
||||||
return self._make_flavor_dict(fl_db)
|
return self._make_flavor_dict(fl_db)
|
||||||
|
|
||||||
def get_flavor(self, context, flavor_id, fields=None):
|
def get_flavor(self, context, flavor_id, fields=None):
|
||||||
@ -231,15 +168,14 @@ class FlavorManager(common_db_mixin.CommonDbMixin):
|
|||||||
binding = bind_qry.filter_by(service_profile_id=sp['id'],
|
binding = bind_qry.filter_by(service_profile_id=sp['id'],
|
||||||
flavor_id=flavor_id).first()
|
flavor_id=flavor_id).first()
|
||||||
if binding:
|
if binding:
|
||||||
raise FlavorServiceProfileBindingExists(
|
raise ext_flavors.FlavorServiceProfileBindingExists(
|
||||||
sp_id=sp['id'], fl_id=flavor_id)
|
sp_id=sp['id'], fl_id=flavor_id)
|
||||||
binding = FlavorServiceProfileBinding(
|
binding = FlavorServiceProfileBinding(
|
||||||
service_profile_id=sp['id'],
|
service_profile_id=sp['id'],
|
||||||
flavor_id=flavor_id)
|
flavor_id=flavor_id)
|
||||||
context.session.add(binding)
|
context.session.add(binding)
|
||||||
fl_db = self._get_flavor(context, flavor_id)
|
fl_db = self._get_flavor(context, flavor_id)
|
||||||
sps = [x['service_profile_id'] for x in fl_db.service_profiles]
|
return self._make_flavor_dict(fl_db)
|
||||||
return sps
|
|
||||||
|
|
||||||
def delete_flavor_service_profile(self, context,
|
def delete_flavor_service_profile(self, context,
|
||||||
service_profile_id, flavor_id):
|
service_profile_id, flavor_id):
|
||||||
@ -248,7 +184,7 @@ class FlavorManager(common_db_mixin.CommonDbMixin):
|
|||||||
filter_by(service_profile_id=service_profile_id,
|
filter_by(service_profile_id=service_profile_id,
|
||||||
flavor_id=flavor_id).first())
|
flavor_id=flavor_id).first())
|
||||||
if not binding:
|
if not binding:
|
||||||
raise FlavorServiceProfileBindingNotFound(
|
raise ext_flavors.FlavorServiceProfileBindingNotFound(
|
||||||
sp_id=service_profile_id, fl_id=flavor_id)
|
sp_id=service_profile_id, fl_id=flavor_id)
|
||||||
context.session.delete(binding)
|
context.session.delete(binding)
|
||||||
|
|
||||||
@ -259,55 +195,21 @@ class FlavorManager(common_db_mixin.CommonDbMixin):
|
|||||||
filter_by(service_profile_id=service_profile_id,
|
filter_by(service_profile_id=service_profile_id,
|
||||||
flavor_id=flavor_id).first())
|
flavor_id=flavor_id).first())
|
||||||
if not binding:
|
if not binding:
|
||||||
raise FlavorServiceProfileBindingNotFound(
|
raise ext_flavors.FlavorServiceProfileBindingNotFound(
|
||||||
sp_id=service_profile_id, fl_id=flavor_id)
|
sp_id=service_profile_id, fl_id=flavor_id)
|
||||||
res = {'service_profile_id': service_profile_id,
|
res = {'service_profile_id': service_profile_id,
|
||||||
'flavor_id': flavor_id}
|
'flavor_id': flavor_id}
|
||||||
return self._fields(res, fields)
|
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):
|
def create_service_profile(self, context, service_profile):
|
||||||
sp = service_profile['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 can't be changed
|
|
||||||
svc_type = DummyServiceDriver.get_service_type()
|
|
||||||
|
|
||||||
sp_db = ServiceProfile(id=uuidutils.generate_uuid(),
|
if sp['driver']:
|
||||||
description=sp['description'],
|
self._validate_driver(context, sp['driver'])
|
||||||
driver=svc_type,
|
else:
|
||||||
enabled=sp['enabled'],
|
if not sp['metainfo']:
|
||||||
metainfo=jsonutils.dumps(sp['metainfo']))
|
raise ext_flavors.ServiceProfileEmpty()
|
||||||
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):
|
with context.session.begin(subtransactions=True):
|
||||||
sp_db = ServiceProfile(id=uuidutils.generate_uuid(),
|
sp_db = ServiceProfile(id=uuidutils.generate_uuid(),
|
||||||
description=sp['description'],
|
description=sp['description'],
|
||||||
@ -315,21 +217,16 @@ class FlavorManager(common_db_mixin.CommonDbMixin):
|
|||||||
enabled=sp['enabled'],
|
enabled=sp['enabled'],
|
||||||
metainfo=sp['metainfo'])
|
metainfo=sp['metainfo'])
|
||||||
context.session.add(sp_db)
|
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)
|
return self._make_service_profile_dict(sp_db)
|
||||||
|
|
||||||
def update_service_profile(self, context,
|
def update_service_profile(self, context,
|
||||||
service_profile_id, service_profile):
|
service_profile_id, service_profile):
|
||||||
sp = service_profile['service_profile']
|
sp = service_profile['service_profile']
|
||||||
|
|
||||||
|
if sp.get('driver'):
|
||||||
|
self._validate_driver(context, sp['driver'])
|
||||||
|
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
self._ensure_service_profile_not_in_use(context,
|
self._ensure_service_profile_not_in_use(context,
|
||||||
service_profile_id)
|
service_profile_id)
|
||||||
@ -356,3 +253,41 @@ class FlavorManager(common_db_mixin.CommonDbMixin):
|
|||||||
sorts=sorts, limit=limit,
|
sorts=sorts, limit=limit,
|
||||||
marker_obj=marker,
|
marker_obj=marker,
|
||||||
page_reverse=page_reverse)
|
page_reverse=page_reverse)
|
||||||
|
|
||||||
|
def get_flavor_next_provider(self, context, flavor_id,
|
||||||
|
filters=None, fields=None,
|
||||||
|
sorts=None, limit=None,
|
||||||
|
marker=None, page_reverse=False):
|
||||||
|
"""From flavor, choose service profile and find provider for driver."""
|
||||||
|
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
bind_qry = context.session.query(FlavorServiceProfileBinding)
|
||||||
|
binding = bind_qry.filter_by(flavor_id=flavor_id).first()
|
||||||
|
if not binding:
|
||||||
|
raise ext_flavors.FlavorServiceProfileBindingNotFound(
|
||||||
|
sp_id='', fl_id=flavor_id)
|
||||||
|
|
||||||
|
# Get the service profile from the first binding
|
||||||
|
# TODO(jwarendt) Should become a scheduling framework instead
|
||||||
|
sp_db = self._get_service_profile(context,
|
||||||
|
binding['service_profile_id'])
|
||||||
|
|
||||||
|
if not sp_db.enabled:
|
||||||
|
raise ext_flavors.ServiceProfileDisabled()
|
||||||
|
|
||||||
|
LOG.debug("Found driver %s.", sp_db.driver)
|
||||||
|
|
||||||
|
service_type_manager = sdb.ServiceTypeManager.get_instance()
|
||||||
|
providers = service_type_manager.get_service_providers(
|
||||||
|
context,
|
||||||
|
filters={'driver': sp_db.driver})
|
||||||
|
|
||||||
|
if not providers:
|
||||||
|
raise ext_flavors.ServiceProfileDriverNotFound(driver=sp_db.driver)
|
||||||
|
|
||||||
|
LOG.debug("Found providers %s.", providers)
|
||||||
|
|
||||||
|
res = {'driver': sp_db.driver,
|
||||||
|
'provider': providers[0].get('name')}
|
||||||
|
|
||||||
|
return [self._fields(res, fields)]
|
||||||
|
@ -16,10 +16,67 @@ from neutron.api import extensions
|
|||||||
from neutron.api.v2 import attributes as attr
|
from neutron.api.v2 import attributes as attr
|
||||||
from neutron.api.v2 import base
|
from neutron.api.v2 import base
|
||||||
from neutron.api.v2 import resource_helper
|
from neutron.api.v2 import resource_helper
|
||||||
|
from neutron.common import exceptions as nexception
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
from neutron.plugins.common import constants
|
from neutron.plugins.common import constants
|
||||||
|
|
||||||
|
|
||||||
|
# Flavor Exceptions
|
||||||
|
class FlavorNotFound(nexception.NotFound):
|
||||||
|
message = _("Flavor %(flavor_id)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorInUse(nexception.InUse):
|
||||||
|
message = _("Flavor %(flavor_id)s is used by some service instance.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceProfileNotFound(nexception.NotFound):
|
||||||
|
message = _("Service Profile %(sp_id)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceProfileInUse(nexception.InUse):
|
||||||
|
message = _("Service Profile %(sp_id)s is used by some service instance.")
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorServiceProfileBindingExists(nexception.Conflict):
|
||||||
|
message = _("Service Profile %(sp_id)s is already associated "
|
||||||
|
"with flavor %(fl_id)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorServiceProfileBindingNotFound(nexception.NotFound):
|
||||||
|
message = _("Service Profile %(sp_id)s is not associated "
|
||||||
|
"with flavor %(fl_id)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceProfileDriverNotFound(nexception.NotFound):
|
||||||
|
message = _("Service Profile driver %(driver)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceProfileEmpty(nexception.InvalidInput):
|
||||||
|
message = _("Service Profile needs either a driver or metainfo.")
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorDisabled(nexception.ServiceUnavailable):
|
||||||
|
message = _("Flavor is not enabled.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceProfileDisabled(nexception.ServiceUnavailable):
|
||||||
|
message = _("Service Profile is not enabled.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidFlavorServiceType(nexception.InvalidInput):
|
||||||
|
message = _("Invalid service type %(service_type)s.")
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_flavor_service_type(validate_type, valid_values=None):
|
||||||
|
"""Ensure requested flavor service type plugin is loaded."""
|
||||||
|
plugins = manager.NeutronManager.get_service_plugins()
|
||||||
|
if validate_type not in plugins:
|
||||||
|
raise InvalidFlavorServiceType(service_type=validate_type)
|
||||||
|
|
||||||
|
attr.validators['type:validate_flavor_service_type'] = (
|
||||||
|
_validate_flavor_service_type)
|
||||||
|
|
||||||
FLAVORS = 'flavors'
|
FLAVORS = 'flavors'
|
||||||
SERVICE_PROFILES = 'service_profiles'
|
SERVICE_PROFILES = 'service_profiles'
|
||||||
FLAVORS_PREFIX = ""
|
FLAVORS_PREFIX = ""
|
||||||
@ -31,13 +88,15 @@ RESOURCE_ATTRIBUTE_MAP = {
|
|||||||
'is_visible': True,
|
'is_visible': True,
|
||||||
'primary_key': True},
|
'primary_key': True},
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
'name': {'allow_post': True, 'allow_put': True,
|
||||||
'validate': {'type:string': None},
|
'validate': {'type:string': attr.NAME_MAX_LEN},
|
||||||
'is_visible': True, 'default': ''},
|
'is_visible': True, 'default': ''},
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
'description': {'allow_post': True, 'allow_put': True,
|
||||||
'validate': {'type:string': None},
|
'validate': {'type:string_or_none':
|
||||||
|
attr.LONG_DESCRIPTION_MAX_LEN},
|
||||||
'is_visible': True, 'default': ''},
|
'is_visible': True, 'default': ''},
|
||||||
'service_type': {'allow_post': True, 'allow_put': False,
|
'service_type': {'allow_post': True, 'allow_put': False,
|
||||||
'validate': {'type:string': None},
|
'validate':
|
||||||
|
{'type:validate_flavor_service_type': None},
|
||||||
'is_visible': True},
|
'is_visible': True},
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||||
'required_by_policy': True,
|
'required_by_policy': True,
|
||||||
@ -47,38 +106,57 @@ RESOURCE_ATTRIBUTE_MAP = {
|
|||||||
'validate': {'type:uuid_list': None},
|
'validate': {'type:uuid_list': None},
|
||||||
'is_visible': True, 'default': []},
|
'is_visible': True, 'default': []},
|
||||||
'enabled': {'allow_post': True, 'allow_put': True,
|
'enabled': {'allow_post': True, 'allow_put': True,
|
||||||
'validate': {'type:boolean': None},
|
'convert_to': attr.convert_to_boolean_if_not_none,
|
||||||
'default': True,
|
'default': True,
|
||||||
'is_visible': True},
|
'is_visible': True},
|
||||||
},
|
},
|
||||||
SERVICE_PROFILES: {
|
SERVICE_PROFILES: {
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
'id': {'allow_post': False, 'allow_put': False,
|
||||||
|
'validate': {'type:uuid': None},
|
||||||
'is_visible': True,
|
'is_visible': True,
|
||||||
'primary_key': True},
|
'primary_key': True},
|
||||||
'description': {'allow_post': True, 'allow_put': True,
|
'description': {'allow_post': True, 'allow_put': True,
|
||||||
'validate': {'type:string': None},
|
'validate': {'type:string_or_none':
|
||||||
'is_visible': True},
|
attr.LONG_DESCRIPTION_MAX_LEN},
|
||||||
# service_profile belong to one service type for now
|
'is_visible': True, 'default': ''},
|
||||||
#'service_types': {'allow_post': False, 'allow_put': False,
|
'driver': {'allow_post': True, 'allow_put': True,
|
||||||
# 'is_visible': True},
|
'validate': {'type:string':
|
||||||
'driver': {'allow_post': True, 'allow_put': False,
|
attr.LONG_DESCRIPTION_MAX_LEN},
|
||||||
'validate': {'type:string': None},
|
|
||||||
'is_visible': True,
|
'is_visible': True,
|
||||||
'default': attr.ATTR_NOT_SPECIFIED},
|
'default': ''},
|
||||||
'metainfo': {'allow_post': True, 'allow_put': True,
|
'metainfo': {'allow_post': True, 'allow_put': True,
|
||||||
'is_visible': True},
|
'is_visible': True,
|
||||||
|
'default': ''},
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||||
'required_by_policy': True,
|
'required_by_policy': True,
|
||||||
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
|
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
|
||||||
'is_visible': True},
|
'is_visible': True},
|
||||||
'enabled': {'allow_post': True, 'allow_put': True,
|
'enabled': {'allow_post': True, 'allow_put': True,
|
||||||
'validate': {'type:boolean': None},
|
'convert_to': attr.convert_to_boolean_if_not_none,
|
||||||
'is_visible': True, 'default': True},
|
'is_visible': True, 'default': True},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SUB_RESOURCE_ATTRIBUTE_MAP = {
|
SUB_RESOURCE_ATTRIBUTE_MAP = {
|
||||||
|
'next_providers': {
|
||||||
|
'parent': {'collection_name': 'flavors',
|
||||||
|
'member_name': 'flavor'},
|
||||||
|
'parameters': {'provider': {'allow_post': False,
|
||||||
|
'allow_put': False,
|
||||||
|
'is_visible': True},
|
||||||
|
'driver': {'allow_post': False,
|
||||||
|
'allow_put': False,
|
||||||
|
'is_visible': True},
|
||||||
|
'metainfo': {'allow_post': False,
|
||||||
|
'allow_put': False,
|
||||||
|
'is_visible': True},
|
||||||
|
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||||
|
'required_by_policy': True,
|
||||||
|
'validate': {'type:string':
|
||||||
|
attr.TENANT_ID_MAX_LEN},
|
||||||
|
'is_visible': True}}
|
||||||
|
},
|
||||||
'service_profiles': {
|
'service_profiles': {
|
||||||
'parent': {'collection_name': 'flavors',
|
'parent': {'collection_name': 'flavors',
|
||||||
'member_name': 'flavor'},
|
'member_name': 'flavor'},
|
||||||
@ -106,11 +184,11 @@ class Flavors(extensions.ExtensionDescriptor):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_description(cls):
|
def get_description(cls):
|
||||||
return "Service specification for advanced services"
|
return "Flavor specification for Neutron advanced services"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_updated(cls):
|
def get_updated(cls):
|
||||||
return "2014-07-06T10:00:00-00:00"
|
return "2015-09-17T10:00:00-00:00"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_resources(cls):
|
def get_resources(cls):
|
||||||
|
@ -22,7 +22,6 @@ from oslo_service import periodic_task
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from neutron.common import utils
|
from neutron.common import utils
|
||||||
from neutron.db import flavors_db
|
|
||||||
from neutron.i18n import _LI
|
from neutron.i18n import _LI
|
||||||
from neutron.plugins.common import constants
|
from neutron.plugins.common import constants
|
||||||
|
|
||||||
@ -162,11 +161,6 @@ class NeutronManager(object):
|
|||||||
LOG.info(_LI("Service %s is supported by the core plugin"),
|
LOG.info(_LI("Service %s is supported by the core plugin"),
|
||||||
service_type)
|
service_type)
|
||||||
|
|
||||||
def _load_flavors_manager(self):
|
|
||||||
# pass manager instance to resolve cyclical import dependency
|
|
||||||
self.service_plugins[constants.FLAVORS] = (
|
|
||||||
flavors_db.FlavorManager(self))
|
|
||||||
|
|
||||||
def _load_service_plugins(self):
|
def _load_service_plugins(self):
|
||||||
"""Loads service plugins.
|
"""Loads service plugins.
|
||||||
|
|
||||||
@ -206,9 +200,6 @@ class NeutronManager(object):
|
|||||||
"Description: %(desc)s",
|
"Description: %(desc)s",
|
||||||
{"type": plugin_inst.get_plugin_type(),
|
{"type": plugin_inst.get_plugin_type(),
|
||||||
"desc": plugin_inst.get_plugin_description()})
|
"desc": plugin_inst.get_plugin_description()})
|
||||||
# do it after the loading from conf to avoid conflict with
|
|
||||||
# configuration provided by unit tests.
|
|
||||||
self._load_flavors_manager()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@utils.synchronized("manager")
|
@utils.synchronized("manager")
|
||||||
|
0
neutron/services/flavors/__init__.py
Normal file
0
neutron/services/flavors/__init__.py
Normal file
31
neutron/services/flavors/flavors_plugin.py
Normal file
31
neutron/services/flavors/flavors_plugin.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Copyright (c) 2015, 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 neutron.db import flavors_db
|
||||||
|
from neutron.plugins.common import constants
|
||||||
|
from neutron.services import service_base
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorsPlugin(service_base.ServicePluginBase,
|
||||||
|
flavors_db.FlavorsDbMixin):
|
||||||
|
"""Implements Neutron Flavors Service plugin."""
|
||||||
|
|
||||||
|
supported_extension_aliases = ['flavors']
|
||||||
|
|
||||||
|
def get_plugin_type(self):
|
||||||
|
return constants.FLAVORS
|
||||||
|
|
||||||
|
def get_plugin_description(self):
|
||||||
|
return "Neutron Flavors and Service Profiles manager plugin"
|
@ -13,6 +13,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from tempest_lib import exceptions as lib_exc
|
||||||
|
|
||||||
from neutron.tests.api import base
|
from neutron.tests.api import base
|
||||||
from neutron.tests.tempest import test
|
from neutron.tests.tempest import test
|
||||||
@ -37,14 +38,25 @@ class TestFlavorsJson(base.BaseAdminNetworkTest):
|
|||||||
if not test.is_extension_enabled('flavors', 'network'):
|
if not test.is_extension_enabled('flavors', 'network'):
|
||||||
msg = "flavors extension not enabled."
|
msg = "flavors extension not enabled."
|
||||||
raise cls.skipException(msg)
|
raise cls.skipException(msg)
|
||||||
service_type = "LOADBALANCER"
|
|
||||||
|
# Use flavors service type as know this is loaded
|
||||||
|
service_type = "FLAVORS"
|
||||||
description_flavor = "flavor is created by tempest"
|
description_flavor = "flavor is created by tempest"
|
||||||
name_flavor = "Best flavor created by tempest"
|
name_flavor = "Best flavor created by tempest"
|
||||||
cls.flavor = cls.create_flavor(name_flavor, description_flavor,
|
|
||||||
service_type)
|
# The check above will pass if api_extensions=all, which does
|
||||||
|
# not mean flavors extension itself is present.
|
||||||
|
try:
|
||||||
|
cls.flavor = cls.create_flavor(name_flavor, description_flavor,
|
||||||
|
service_type)
|
||||||
|
except lib_exc.NotFound:
|
||||||
|
msg = "flavors plugin not enabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
|
||||||
description_sp = "service profile created by tempest"
|
description_sp = "service profile created by tempest"
|
||||||
# Future TODO(madhu_ak): Right now the dummy driver is loaded. Will
|
# Drivers are supported as is an empty driver field. Use an
|
||||||
# make changes as soon I get to know the flavor supported drivers
|
# empty field for now since otherwise driver is validated against the
|
||||||
|
# servicetype configuration which may differ in test scenarios.
|
||||||
driver = ""
|
driver = ""
|
||||||
metainfo = '{"data": "value"}'
|
metainfo = '{"data": "value"}'
|
||||||
cls.service_profile = cls.create_service_profile(
|
cls.service_profile = cls.create_service_profile(
|
||||||
@ -86,7 +98,7 @@ class TestFlavorsJson(base.BaseAdminNetworkTest):
|
|||||||
def test_create_update_delete_flavor(self):
|
def test_create_update_delete_flavor(self):
|
||||||
# Creates a flavor
|
# Creates a flavor
|
||||||
description = "flavor created by tempest"
|
description = "flavor created by tempest"
|
||||||
service = "LOADBALANCERS"
|
service = "FLAVORS"
|
||||||
name = "Best flavor created by tempest"
|
name = "Best flavor created by tempest"
|
||||||
body = self.admin_client.create_flavor(name=name, service_type=service,
|
body = self.admin_client.create_flavor(name=name, service_type=service,
|
||||||
description=description)
|
description=description)
|
||||||
|
@ -199,5 +199,9 @@
|
|||||||
"update_rbac_policy": "rule:admin_or_owner",
|
"update_rbac_policy": "rule:admin_or_owner",
|
||||||
"update_rbac_policy:target_tenant": "rule:restrict_wildcard and rule:admin_or_owner",
|
"update_rbac_policy:target_tenant": "rule:restrict_wildcard and rule:admin_or_owner",
|
||||||
"get_rbac_policy": "rule:admin_or_owner",
|
"get_rbac_policy": "rule:admin_or_owner",
|
||||||
"delete_rbac_policy": "rule:admin_or_owner"
|
"delete_rbac_policy": "rule:admin_or_owner",
|
||||||
|
|
||||||
|
"create_flavor_service_profile": "rule:admin_only",
|
||||||
|
"delete_flavor_service_profile": "rule:admin_only",
|
||||||
|
"get_flavor_service_profile": "rule:regular_user"
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,17 @@ import mock
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
from webob import exc
|
||||||
|
|
||||||
|
from neutron.api.v2 import attributes as attr
|
||||||
from neutron import context
|
from neutron import context
|
||||||
from neutron.db import api as dbapi
|
from neutron.db import api as dbapi
|
||||||
from neutron.db import flavors_db
|
from neutron.db import flavors_db
|
||||||
|
from neutron.db import servicetype_db
|
||||||
from neutron.extensions import flavors
|
from neutron.extensions import flavors
|
||||||
from neutron import manager
|
|
||||||
from neutron.plugins.common import constants
|
from neutron.plugins.common import constants
|
||||||
|
from neutron.services.flavors import flavors_plugin
|
||||||
|
from neutron.services import provider_configuration as provconf
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
from neutron.tests.unit.api.v2 import test_base
|
from neutron.tests.unit.api.v2 import test_base
|
||||||
from neutron.tests.unit.db import test_db_base_plugin_v2
|
from neutron.tests.unit.db import test_db_base_plugin_v2
|
||||||
@ -34,20 +38,27 @@ from neutron.tests.unit.extensions import base as extension
|
|||||||
_uuid = uuidutils.generate_uuid
|
_uuid = uuidutils.generate_uuid
|
||||||
_get_path = test_base._get_path
|
_get_path = test_base._get_path
|
||||||
|
|
||||||
|
_driver = ('neutron.tests.unit.extensions.test_flavors.'
|
||||||
|
'DummyServiceDriver')
|
||||||
|
_provider = 'dummy'
|
||||||
|
_long_name = 'x' * (attr.NAME_MAX_LEN + 1)
|
||||||
|
_long_description = 'x' * (attr.LONG_DESCRIPTION_MAX_LEN + 1)
|
||||||
|
|
||||||
|
|
||||||
class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(FlavorExtensionTestCase, self).setUp()
|
super(FlavorExtensionTestCase, self).setUp()
|
||||||
self._setUpExtension(
|
self._setUpExtension(
|
||||||
'neutron.db.flavors_db.FlavorManager',
|
'neutron.services.flavors.flavors_plugin.FlavorsPlugin',
|
||||||
constants.FLAVORS, flavors.RESOURCE_ATTRIBUTE_MAP,
|
constants.FLAVORS, flavors.RESOURCE_ATTRIBUTE_MAP,
|
||||||
flavors.Flavors, '', supported_extension_aliases='flavors')
|
flavors.Flavors, '', supported_extension_aliases='flavors')
|
||||||
|
|
||||||
def test_create_flavor(self):
|
def test_create_flavor(self):
|
||||||
tenant_id = uuidutils.generate_uuid()
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
# Use service_type FLAVORS since plugin must be loaded to validate
|
||||||
data = {'flavor': {'name': 'GOLD',
|
data = {'flavor': {'name': 'GOLD',
|
||||||
'service_type': constants.LOADBALANCER,
|
'service_type': constants.FLAVORS,
|
||||||
'description': 'the best flavor',
|
'description': 'the best flavor',
|
||||||
'tenant_id': tenant_id,
|
'tenant_id': tenant_id,
|
||||||
'enabled': True}}
|
'enabled': True}}
|
||||||
@ -67,6 +78,54 @@ class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
|||||||
self.assertIn('flavor', res)
|
self.assertIn('flavor', res)
|
||||||
self.assertEqual(expected, res)
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_create_flavor_invalid_service_type(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
data = {'flavor': {'name': 'GOLD',
|
||||||
|
'service_type': 'BROKEN',
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': True}}
|
||||||
|
self.api.post(_get_path('flavors', fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_create_flavor_too_long_name(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
data = {'flavor': {'name': _long_name,
|
||||||
|
'service_type': constants.FLAVORS,
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': True}}
|
||||||
|
self.api.post(_get_path('flavors', fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_create_flavor_too_long_description(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
data = {'flavor': {'name': _long_name,
|
||||||
|
'service_type': constants.FLAVORS,
|
||||||
|
'description': _long_description,
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': True}}
|
||||||
|
self.api.post(_get_path('flavors', fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_create_flavor_invalid_enabled(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
data = {'flavor': {'name': _long_name,
|
||||||
|
'service_type': constants.FLAVORS,
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': 'BROKEN'}}
|
||||||
|
self.api.post(_get_path('flavors', fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
def test_update_flavor(self):
|
def test_update_flavor(self):
|
||||||
flavor_id = 'fake_id'
|
flavor_id = 'fake_id'
|
||||||
data = {'flavor': {'name': 'GOLD',
|
data = {'flavor': {'name': 'GOLD',
|
||||||
@ -88,6 +147,36 @@ class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
|||||||
self.assertIn('flavor', res)
|
self.assertIn('flavor', res)
|
||||||
self.assertEqual(expected, res)
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_update_flavor_too_long_name(self):
|
||||||
|
flavor_id = 'fake_id'
|
||||||
|
data = {'flavor': {'name': _long_name,
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'enabled': True}}
|
||||||
|
self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_update_flavor_too_long_description(self):
|
||||||
|
flavor_id = 'fake_id'
|
||||||
|
data = {'flavor': {'name': 'GOLD',
|
||||||
|
'description': _long_description,
|
||||||
|
'enabled': True}}
|
||||||
|
self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_update_flavor_invalid_enabled(self):
|
||||||
|
flavor_id = 'fake_id'
|
||||||
|
data = {'flavor': {'name': 'GOLD',
|
||||||
|
'description': _long_description,
|
||||||
|
'enabled': 'BROKEN'}}
|
||||||
|
self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
def test_delete_flavor(self):
|
def test_delete_flavor(self):
|
||||||
flavor_id = 'fake_id'
|
flavor_id = 'fake_id'
|
||||||
instance = self.plugin.return_value
|
instance = self.plugin.return_value
|
||||||
@ -154,6 +243,42 @@ class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
|||||||
self.assertIn('service_profile', res)
|
self.assertIn('service_profile', res)
|
||||||
self.assertEqual(expected, res)
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_create_service_profile_too_long_description(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
expected = {'service_profile': {'description': _long_description,
|
||||||
|
'driver': '',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
self.api.post(_get_path('service_profiles', fmt=self.fmt),
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_create_service_profile_too_long_driver(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
expected = {'service_profile': {'description': 'the best sp',
|
||||||
|
'driver': _long_description,
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
self.api.post(_get_path('service_profiles', fmt=self.fmt),
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_create_service_profile_invalid_enabled(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
expected = {'service_profile': {'description': 'the best sp',
|
||||||
|
'driver': '',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': 'BROKEN',
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
self.api.post(_get_path('service_profiles', fmt=self.fmt),
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
def test_update_service_profile(self):
|
def test_update_service_profile(self):
|
||||||
sp_id = "fake_id"
|
sp_id = "fake_id"
|
||||||
expected = {'service_profile': {'description': 'the best sp',
|
expected = {'service_profile': {'description': 'the best sp',
|
||||||
@ -176,6 +301,28 @@ class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
|||||||
self.assertIn('service_profile', res)
|
self.assertIn('service_profile', res)
|
||||||
self.assertEqual(expected, res)
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_update_service_profile_too_long_description(self):
|
||||||
|
sp_id = "fake_id"
|
||||||
|
expected = {'service_profile': {'description': 'the best sp',
|
||||||
|
'enabled': 'BROKEN',
|
||||||
|
'metainfo': '{"data1": "value3"}'}}
|
||||||
|
self.api.put(_get_path('service_profiles',
|
||||||
|
id=sp_id, fmt=self.fmt),
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
def test_update_service_profile_invalid_enabled(self):
|
||||||
|
sp_id = "fake_id"
|
||||||
|
expected = {'service_profile': {'description': 'the best sp',
|
||||||
|
'enabled': 'BROKEN',
|
||||||
|
'metainfo': '{"data1": "value3"}'}}
|
||||||
|
self.api.put(_get_path('service_profiles',
|
||||||
|
id=sp_id, fmt=self.fmt),
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
def test_delete_service_profile(self):
|
def test_delete_service_profile(self):
|
||||||
sp_id = 'fake_id'
|
sp_id = 'fake_id'
|
||||||
instance = self.plugin.return_value
|
instance = self.plugin.return_value
|
||||||
@ -187,7 +334,7 @@ class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
|||||||
def test_show_service_profile(self):
|
def test_show_service_profile(self):
|
||||||
sp_id = 'fake_id'
|
sp_id = 'fake_id'
|
||||||
expected = {'service_profile': {'id': 'id1',
|
expected = {'service_profile': {'id': 'id1',
|
||||||
'driver': 'entrypoint1',
|
'driver': _driver,
|
||||||
'description': 'desc',
|
'description': 'desc',
|
||||||
'metainfo': '{}',
|
'metainfo': '{}',
|
||||||
'enabled': True}}
|
'enabled': True}}
|
||||||
@ -204,12 +351,12 @@ class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
|||||||
|
|
||||||
def test_get_service_profiles(self):
|
def test_get_service_profiles(self):
|
||||||
expected = {'service_profiles': [{'id': 'id1',
|
expected = {'service_profiles': [{'id': 'id1',
|
||||||
'driver': 'entrypoint1',
|
'driver': _driver,
|
||||||
'description': 'desc',
|
'description': 'desc',
|
||||||
'metainfo': '{}',
|
'metainfo': '{}',
|
||||||
'enabled': True},
|
'enabled': True},
|
||||||
{'id': 'id2',
|
{'id': 'id2',
|
||||||
'driver': 'entrypoint2',
|
'driver': _driver,
|
||||||
'description': 'desc',
|
'description': 'desc',
|
||||||
'metainfo': '{}',
|
'metainfo': '{}',
|
||||||
'enabled': True}]}
|
'enabled': True}]}
|
||||||
@ -248,6 +395,15 @@ class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
|||||||
'fake_spid',
|
'fake_spid',
|
||||||
flavor_id='fl_id')
|
flavor_id='fl_id')
|
||||||
|
|
||||||
|
def test_update_association_error(self):
|
||||||
|
"""Confirm that update is not permitted with user error."""
|
||||||
|
new_id = uuidutils.generate_uuid()
|
||||||
|
data = {'service_profile': {'id': new_id}}
|
||||||
|
self.api.put('/flavors/fl_id/service_profiles/%s' % 'fake_spid',
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt,
|
||||||
|
status=exc.HTTPBadRequest.code)
|
||||||
|
|
||||||
|
|
||||||
class DummyCorePlugin(object):
|
class DummyCorePlugin(object):
|
||||||
pass
|
pass
|
||||||
@ -275,10 +431,10 @@ class DummyServiceDriver(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
class FlavorPluginTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
||||||
base.PluginFixture):
|
base.PluginFixture):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(FlavorManagerTestCase, self).setUp()
|
super(FlavorPluginTestCase, self).setUp()
|
||||||
|
|
||||||
self.config_parse()
|
self.config_parse()
|
||||||
cfg.CONF.set_override(
|
cfg.CONF.set_override(
|
||||||
@ -291,14 +447,24 @@ class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
|||||||
self.useFixture(
|
self.useFixture(
|
||||||
fixtures.MonkeyPatch('neutron.manager.NeutronManager._instance'))
|
fixtures.MonkeyPatch('neutron.manager.NeutronManager._instance'))
|
||||||
|
|
||||||
self.plugin = flavors_db.FlavorManager(
|
self.plugin = flavors_plugin.FlavorsPlugin()
|
||||||
manager.NeutronManager().get_instance())
|
|
||||||
self.ctx = context.get_admin_context()
|
self.ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
providers = [DummyServiceDriver.get_service_type() +
|
||||||
|
":" + _provider + ":" + _driver]
|
||||||
|
self.service_manager = servicetype_db.ServiceTypeManager.get_instance()
|
||||||
|
self.service_providers = mock.patch.object(
|
||||||
|
provconf.NeutronModule, 'service_providers').start()
|
||||||
|
self.service_providers.return_value = providers
|
||||||
|
for provider in providers:
|
||||||
|
self.service_manager.add_provider_configuration(
|
||||||
|
provider.split(':')[0], provconf.ProviderConfiguration())
|
||||||
|
|
||||||
dbapi.get_engine()
|
dbapi.get_engine()
|
||||||
|
|
||||||
def _create_flavor(self, description=None):
|
def _create_flavor(self, description=None):
|
||||||
flavor = {'flavor': {'name': 'GOLD',
|
flavor = {'flavor': {'name': 'GOLD',
|
||||||
'service_type': constants.LOADBALANCER,
|
'service_type': constants.DUMMY,
|
||||||
'description': description or 'the best flavor',
|
'description': description or 'the best flavor',
|
||||||
'enabled': True}}
|
'enabled': True}}
|
||||||
return self.plugin.create_flavor(self.ctx, flavor), flavor
|
return self.plugin.create_flavor(self.ctx, flavor), flavor
|
||||||
@ -308,7 +474,7 @@ class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
|||||||
res = self.ctx.session.query(flavors_db.Flavor).all()
|
res = self.ctx.session.query(flavors_db.Flavor).all()
|
||||||
self.assertEqual(1, len(res))
|
self.assertEqual(1, len(res))
|
||||||
self.assertEqual('GOLD', res[0]['name'])
|
self.assertEqual('GOLD', res[0]['name'])
|
||||||
self.assertEqual(constants.LOADBALANCER, res[0]['service_type'])
|
self.assertEqual(constants.DUMMY, res[0]['service_type'])
|
||||||
|
|
||||||
def test_update_flavor(self):
|
def test_update_flavor(self):
|
||||||
fl, flavor = self._create_flavor()
|
fl, flavor = self._create_flavor()
|
||||||
@ -341,13 +507,11 @@ class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
|||||||
def _create_service_profile(self, description=None):
|
def _create_service_profile(self, description=None):
|
||||||
data = {'service_profile':
|
data = {'service_profile':
|
||||||
{'description': description or 'the best sp',
|
{'description': description or 'the best sp',
|
||||||
'driver':
|
'driver': _driver,
|
||||||
('neutron.tests.unit.extensions.test_flavors.'
|
|
||||||
'DummyServiceDriver'),
|
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'metainfo': '{"data": "value"}'}}
|
'metainfo': '{"data": "value"}'}}
|
||||||
sp = self.plugin.unit_create_service_profile(self.ctx,
|
sp = self.plugin.create_service_profile(self.ctx,
|
||||||
data)
|
data)
|
||||||
return sp, data
|
return sp, data
|
||||||
|
|
||||||
def test_create_service_profile(self):
|
def test_create_service_profile(self):
|
||||||
@ -357,6 +521,41 @@ class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
|||||||
self.assertEqual(data['service_profile']['driver'], res['driver'])
|
self.assertEqual(data['service_profile']['driver'], res['driver'])
|
||||||
self.assertEqual(data['service_profile']['metainfo'], res['metainfo'])
|
self.assertEqual(data['service_profile']['metainfo'], res['metainfo'])
|
||||||
|
|
||||||
|
def test_create_service_profile_empty_driver(self):
|
||||||
|
data = {'service_profile':
|
||||||
|
{'description': 'the best sp',
|
||||||
|
'driver': '',
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
sp = self.plugin.create_service_profile(self.ctx,
|
||||||
|
data)
|
||||||
|
res = (self.ctx.session.query(flavors_db.ServiceProfile).
|
||||||
|
filter_by(id=sp['id']).one())
|
||||||
|
self.assertEqual(data['service_profile']['driver'], res['driver'])
|
||||||
|
self.assertEqual(data['service_profile']['metainfo'], res['metainfo'])
|
||||||
|
|
||||||
|
def test_create_service_profile_invalid_driver(self):
|
||||||
|
data = {'service_profile':
|
||||||
|
{'description': 'the best sp',
|
||||||
|
'driver': "Broken",
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
self.assertRaises(flavors.ServiceProfileDriverNotFound,
|
||||||
|
self.plugin.create_service_profile,
|
||||||
|
self.ctx,
|
||||||
|
data)
|
||||||
|
|
||||||
|
def test_create_service_profile_invalid_empty(self):
|
||||||
|
data = {'service_profile':
|
||||||
|
{'description': '',
|
||||||
|
'driver': '',
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': ''}}
|
||||||
|
self.assertRaises(flavors.ServiceProfileEmpty,
|
||||||
|
self.plugin.create_service_profile,
|
||||||
|
self.ctx,
|
||||||
|
data)
|
||||||
|
|
||||||
def test_update_service_profile(self):
|
def test_update_service_profile(self):
|
||||||
sp, data = self._create_service_profile()
|
sp, data = self._create_service_profile()
|
||||||
data['service_profile']['metainfo'] = '{"data": "value1"}'
|
data['service_profile']['metainfo'] = '{"data": "value1"}'
|
||||||
@ -423,7 +622,7 @@ class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
|||||||
self.ctx,
|
self.ctx,
|
||||||
{'service_profile': {'id': sp['id']}},
|
{'service_profile': {'id': sp['id']}},
|
||||||
fl['id'])
|
fl['id'])
|
||||||
self.assertRaises(flavors_db.FlavorServiceProfileBindingExists,
|
self.assertRaises(flavors.FlavorServiceProfileBindingExists,
|
||||||
self.plugin.create_flavor_service_profile,
|
self.plugin.create_flavor_service_profile,
|
||||||
self.ctx,
|
self.ctx,
|
||||||
{'service_profile': {'id': sp['id']}},
|
{'service_profile': {'id': sp['id']}},
|
||||||
@ -444,7 +643,7 @@ class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
|||||||
self.assertIsNone(binding)
|
self.assertIsNone(binding)
|
||||||
|
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
flavors_db.FlavorServiceProfileBindingNotFound,
|
flavors.FlavorServiceProfileBindingNotFound,
|
||||||
self.plugin.delete_flavor_service_profile,
|
self.plugin.delete_flavor_service_profile,
|
||||||
self.ctx, sp['id'], fl['id'])
|
self.ctx, sp['id'], fl['id'])
|
||||||
|
|
||||||
@ -456,7 +655,65 @@ class FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
|||||||
{'service_profile': {'id': sp['id']}},
|
{'service_profile': {'id': sp['id']}},
|
||||||
fl['id'])
|
fl['id'])
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
flavors_db.ServiceProfileInUse,
|
flavors.ServiceProfileInUse,
|
||||||
self.plugin.delete_service_profile,
|
self.plugin.delete_service_profile,
|
||||||
self.ctx,
|
self.ctx,
|
||||||
sp['id'])
|
sp['id'])
|
||||||
|
|
||||||
|
def test_get_flavor_next_provider_no_binding(self):
|
||||||
|
fl, data = self._create_flavor()
|
||||||
|
self.assertRaises(
|
||||||
|
flavors.FlavorServiceProfileBindingNotFound,
|
||||||
|
self.plugin.get_flavor_next_provider,
|
||||||
|
self.ctx,
|
||||||
|
fl['id'])
|
||||||
|
|
||||||
|
def test_get_flavor_next_provider_disabled(self):
|
||||||
|
data = {'service_profile':
|
||||||
|
{'description': 'the best sp',
|
||||||
|
'driver': _driver,
|
||||||
|
'enabled': False,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
sp = self.plugin.create_service_profile(self.ctx,
|
||||||
|
data)
|
||||||
|
fl, data = self._create_flavor()
|
||||||
|
self.plugin.create_flavor_service_profile(
|
||||||
|
self.ctx,
|
||||||
|
{'service_profile': {'id': sp['id']}},
|
||||||
|
fl['id'])
|
||||||
|
self.assertRaises(
|
||||||
|
flavors.ServiceProfileDisabled,
|
||||||
|
self.plugin.get_flavor_next_provider,
|
||||||
|
self.ctx,
|
||||||
|
fl['id'])
|
||||||
|
|
||||||
|
def test_get_flavor_next_provider_no_driver(self):
|
||||||
|
data = {'service_profile':
|
||||||
|
{'description': 'the best sp',
|
||||||
|
'driver': '',
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
sp = self.plugin.create_service_profile(self.ctx,
|
||||||
|
data)
|
||||||
|
fl, data = self._create_flavor()
|
||||||
|
self.plugin.create_flavor_service_profile(
|
||||||
|
self.ctx,
|
||||||
|
{'service_profile': {'id': sp['id']}},
|
||||||
|
fl['id'])
|
||||||
|
self.assertRaises(
|
||||||
|
flavors.ServiceProfileDriverNotFound,
|
||||||
|
self.plugin.get_flavor_next_provider,
|
||||||
|
self.ctx,
|
||||||
|
fl['id'])
|
||||||
|
|
||||||
|
def test_get_flavor_next_provider(self):
|
||||||
|
sp, data = self._create_service_profile()
|
||||||
|
fl, data = self._create_flavor()
|
||||||
|
self.plugin.create_flavor_service_profile(
|
||||||
|
self.ctx,
|
||||||
|
{'service_profile': {'id': sp['id']}},
|
||||||
|
fl['id'])
|
||||||
|
providers = self.plugin.get_flavor_next_provider(
|
||||||
|
self.ctx,
|
||||||
|
fl['id'])
|
||||||
|
self.assertEqual(_provider, providers[0].get('provider', None))
|
||||||
|
@ -106,7 +106,7 @@ class NeutronManagerTestCase(base.BaseTestCase):
|
|||||||
"MultiServiceCorePlugin")
|
"MultiServiceCorePlugin")
|
||||||
mgr = manager.NeutronManager.get_instance()
|
mgr = manager.NeutronManager.get_instance()
|
||||||
svc_plugins = mgr.get_service_plugins()
|
svc_plugins = mgr.get_service_plugins()
|
||||||
self.assertEqual(4, len(svc_plugins))
|
self.assertEqual(3, len(svc_plugins))
|
||||||
self.assertIn(constants.CORE, svc_plugins.keys())
|
self.assertIn(constants.CORE, svc_plugins.keys())
|
||||||
self.assertIn(constants.LOADBALANCER, svc_plugins.keys())
|
self.assertIn(constants.LOADBALANCER, svc_plugins.keys())
|
||||||
self.assertIn(constants.DUMMY, svc_plugins.keys())
|
self.assertIn(constants.DUMMY, svc_plugins.keys())
|
||||||
|
@ -115,6 +115,7 @@ neutron.service_plugins =
|
|||||||
neutron.services.loadbalancer.plugin.LoadBalancerPlugin = neutron_lbaas.services.loadbalancer.plugin:LoadBalancerPlugin
|
neutron.services.loadbalancer.plugin.LoadBalancerPlugin = neutron_lbaas.services.loadbalancer.plugin:LoadBalancerPlugin
|
||||||
neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin
|
neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin
|
||||||
qos = neutron.services.qos.qos_plugin:QoSPlugin
|
qos = neutron.services.qos.qos_plugin:QoSPlugin
|
||||||
|
flavors = neutron.services.flavors.flavors_plugin:FlavorsPlugin
|
||||||
neutron.qos.notification_drivers =
|
neutron.qos.notification_drivers =
|
||||||
message_queue = neutron.services.qos.notification_drivers.message_queue:RpcQosServiceNotificationDriver
|
message_queue = neutron.services.qos.notification_drivers.message_queue:RpcQosServiceNotificationDriver
|
||||||
neutron.ml2.type_drivers =
|
neutron.ml2.type_drivers =
|
||||||
|
Loading…
Reference in New Issue
Block a user