Flavor Framework implementation
This patch introduces API and DB plugin for flavor framework. API adds Flavors and Service Profiles which are resources available only for admins to operate. This framework then should be leveraged by advanced services. Included tempest API tests in neutron tree Implements: blueprint neutron-flavor-framework Change-Id: I99ba0ce520ae3d8696eca5c994777c7d5ba3d4b1 Co-Authored-By: Doug Wiegley <dougw@a10networks.com> Co-Authored-By: Madhusudhan Kandadai <madhusudhan.kandadai@hp.com>
This commit is contained in:
parent
0d93458d1e
commit
e0eed14a1e
@ -163,5 +163,16 @@
|
|||||||
|
|
||||||
"get_service_provider": "rule:regular_user",
|
"get_service_provider": "rule:regular_user",
|
||||||
"get_lsn": "rule:admin_only",
|
"get_lsn": "rule:admin_only",
|
||||||
"create_lsn": "rule:admin_only"
|
"create_lsn": "rule:admin_only",
|
||||||
|
|
||||||
|
"create_flavor": "rule:admin_only",
|
||||||
|
"update_flavor": "rule:admin_only",
|
||||||
|
"delete_flavor": "rule:admin_only",
|
||||||
|
"get_flavors": "rule:regular_user",
|
||||||
|
"get_flavor": "rule:regular_user",
|
||||||
|
"create_service_profile": "rule:admin_only",
|
||||||
|
"update_service_profile": "rule:admin_only",
|
||||||
|
"delete_service_profile": "rule:admin_only",
|
||||||
|
"get_service_profiles": "rule:admin_only",
|
||||||
|
"get_service_profile": "rule:admin_only"
|
||||||
}
|
}
|
||||||
|
@ -414,6 +414,9 @@ class Controller(object):
|
|||||||
action,
|
action,
|
||||||
item[self._resource],
|
item[self._resource],
|
||||||
pluralized=self._collection)
|
pluralized=self._collection)
|
||||||
|
if 'tenant_id' not in item[self._resource]:
|
||||||
|
# no tenant_id - no quota check
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tenant_id = item[self._resource]['tenant_id']
|
tenant_id = item[self._resource]['tenant_id']
|
||||||
count = quota.QUOTAS.count(request.context, self._resource,
|
count = quota.QUOTAS.count(request.context, self._resource,
|
||||||
@ -571,8 +574,7 @@ class Controller(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _populate_tenant_id(context, res_dict, is_create):
|
def _populate_tenant_id(context, res_dict, attr_info, is_create):
|
||||||
|
|
||||||
if (('tenant_id' in res_dict and
|
if (('tenant_id' in res_dict and
|
||||||
res_dict['tenant_id'] != context.tenant_id and
|
res_dict['tenant_id'] != context.tenant_id and
|
||||||
not context.is_admin)):
|
not context.is_admin)):
|
||||||
@ -583,9 +585,9 @@ class Controller(object):
|
|||||||
if is_create and 'tenant_id' not in res_dict:
|
if is_create and 'tenant_id' not in res_dict:
|
||||||
if context.tenant_id:
|
if context.tenant_id:
|
||||||
res_dict['tenant_id'] = context.tenant_id
|
res_dict['tenant_id'] = context.tenant_id
|
||||||
else:
|
elif 'tenant_id' in attr_info:
|
||||||
msg = _("Running without keystone AuthN requires "
|
msg = _("Running without keystone AuthN requires "
|
||||||
" that tenant_id is specified")
|
"that tenant_id is specified")
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -627,7 +629,7 @@ class Controller(object):
|
|||||||
msg = _("Unable to find '%s' in request body") % resource
|
msg = _("Unable to find '%s' in request body") % resource
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
|
|
||||||
Controller._populate_tenant_id(context, res_dict, is_create)
|
Controller._populate_tenant_id(context, res_dict, attr_info, is_create)
|
||||||
Controller._verify_attributes(res_dict, attr_info)
|
Controller._verify_attributes(res_dict, attr_info)
|
||||||
|
|
||||||
if is_create: # POST
|
if is_create: # POST
|
||||||
|
356
neutron/db/flavors_db.py
Normal file
356
neutron/db/flavors_db.py
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
# 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)
|
@ -1,3 +1,3 @@
|
|||||||
30018084ec99
|
30018084ec99
|
||||||
52c5312f6baf
|
313373c0ffee
|
||||||
kilo
|
kilo
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
# Copyright 2014-2015 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Flavor framework
|
||||||
|
|
||||||
|
Revision ID: 313373c0ffee
|
||||||
|
Revises: 52c5312f6baf
|
||||||
|
|
||||||
|
Create Date: 2014-07-17 03:00:00.00
|
||||||
|
"""
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '313373c0ffee'
|
||||||
|
down_revision = '52c5312f6baf'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table(
|
||||||
|
'flavors',
|
||||||
|
sa.Column('id', sa.String(36)),
|
||||||
|
sa.Column('name', sa.String(255)),
|
||||||
|
sa.Column('description', sa.String(1024)),
|
||||||
|
sa.Column('enabled', sa.Boolean, nullable=False,
|
||||||
|
server_default=sa.sql.true()),
|
||||||
|
sa.Column('service_type', sa.String(36), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'serviceprofiles',
|
||||||
|
sa.Column('id', sa.String(36)),
|
||||||
|
sa.Column('description', sa.String(1024)),
|
||||||
|
sa.Column('driver', sa.String(1024), nullable=False),
|
||||||
|
sa.Column('enabled', sa.Boolean, nullable=False,
|
||||||
|
server_default=sa.sql.true()),
|
||||||
|
sa.Column('metainfo', sa.String(4096)),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'flavorserviceprofilebindings',
|
||||||
|
sa.Column('service_profile_id', sa.String(36), nullable=False),
|
||||||
|
sa.Column('flavor_id', sa.String(36), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['service_profile_id'],
|
||||||
|
['serviceprofiles.id']),
|
||||||
|
sa.ForeignKeyConstraint(['flavor_id'], ['flavors.id']),
|
||||||
|
sa.PrimaryKeyConstraint('service_profile_id', 'flavor_id')
|
||||||
|
)
|
@ -28,6 +28,7 @@ from neutron.db import dvr_mac_db # noqa
|
|||||||
from neutron.db import external_net_db # noqa
|
from neutron.db import external_net_db # noqa
|
||||||
from neutron.db import extradhcpopt_db # noqa
|
from neutron.db import extradhcpopt_db # noqa
|
||||||
from neutron.db import extraroute_db # noqa
|
from neutron.db import extraroute_db # noqa
|
||||||
|
from neutron.db import flavors_db # noqa
|
||||||
from neutron.db import l3_agentschedulers_db # noqa
|
from neutron.db import l3_agentschedulers_db # noqa
|
||||||
from neutron.db import l3_attrs_db # noqa
|
from neutron.db import l3_attrs_db # noqa
|
||||||
from neutron.db import l3_db # noqa
|
from neutron.db import l3_db # noqa
|
||||||
|
152
neutron/extensions/flavors.py
Normal file
152
neutron/extensions/flavors.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# 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.api import extensions
|
||||||
|
from neutron.api.v2 import attributes as attr
|
||||||
|
from neutron.api.v2 import base
|
||||||
|
from neutron.api.v2 import resource_helper
|
||||||
|
from neutron import manager
|
||||||
|
from neutron.plugins.common import constants
|
||||||
|
|
||||||
|
|
||||||
|
FLAVORS = 'flavors'
|
||||||
|
SERVICE_PROFILES = 'service_profiles'
|
||||||
|
FLAVORS_PREFIX = ""
|
||||||
|
|
||||||
|
RESOURCE_ATTRIBUTE_MAP = {
|
||||||
|
FLAVORS: {
|
||||||
|
'id': {'allow_post': False, 'allow_put': False,
|
||||||
|
'validate': {'type:uuid': None},
|
||||||
|
'is_visible': True,
|
||||||
|
'primary_key': True},
|
||||||
|
'name': {'allow_post': True, 'allow_put': True,
|
||||||
|
'validate': {'type:string': None},
|
||||||
|
'is_visible': True, 'default': ''},
|
||||||
|
'description': {'allow_post': True, 'allow_put': True,
|
||||||
|
'validate': {'type:string': None},
|
||||||
|
'is_visible': True, 'default': ''},
|
||||||
|
'service_type': {'allow_post': True, 'allow_put': False,
|
||||||
|
'validate': {'type:string': None},
|
||||||
|
'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': {'allow_post': True, 'allow_put': True,
|
||||||
|
'validate': {'type:uuid_list': None},
|
||||||
|
'is_visible': True, 'default': []},
|
||||||
|
'enabled': {'allow_post': True, 'allow_put': True,
|
||||||
|
'validate': {'type:boolean': None},
|
||||||
|
'default': True,
|
||||||
|
'is_visible': True},
|
||||||
|
},
|
||||||
|
SERVICE_PROFILES: {
|
||||||
|
'id': {'allow_post': False, 'allow_put': False,
|
||||||
|
'is_visible': True,
|
||||||
|
'primary_key': True},
|
||||||
|
'description': {'allow_post': True, 'allow_put': True,
|
||||||
|
'validate': {'type:string': None},
|
||||||
|
'is_visible': True},
|
||||||
|
# service_profile belong to one service type for now
|
||||||
|
#'service_types': {'allow_post': False, 'allow_put': False,
|
||||||
|
# 'is_visible': True},
|
||||||
|
'driver': {'allow_post': True, 'allow_put': False,
|
||||||
|
'validate': {'type:string': None},
|
||||||
|
'is_visible': True,
|
||||||
|
'default': attr.ATTR_NOT_SPECIFIED},
|
||||||
|
'metainfo': {'allow_post': True, 'allow_put': True,
|
||||||
|
'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},
|
||||||
|
'enabled': {'allow_post': True, 'allow_put': True,
|
||||||
|
'validate': {'type:boolean': None},
|
||||||
|
'is_visible': True, 'default': True},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SUB_RESOURCE_ATTRIBUTE_MAP = {
|
||||||
|
'service_profiles': {
|
||||||
|
'parent': {'collection_name': 'flavors',
|
||||||
|
'member_name': 'flavor'},
|
||||||
|
'parameters': {'id': {'allow_post': True, 'allow_put': False,
|
||||||
|
'validate': {'type:uuid': None},
|
||||||
|
'is_visible': True}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Flavors(extensions.ExtensionDescriptor):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_name(cls):
|
||||||
|
return "Neutron Service Flavors"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_alias(cls):
|
||||||
|
return "flavors"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_description(cls):
|
||||||
|
return "Service specification for advanced services"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_updated(cls):
|
||||||
|
return "2014-07-06T10:00:00-00:00"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_resources(cls):
|
||||||
|
"""Returns Ext Resources."""
|
||||||
|
plural_mappings = resource_helper.build_plural_mappings(
|
||||||
|
{}, RESOURCE_ATTRIBUTE_MAP)
|
||||||
|
attr.PLURALS.update(plural_mappings)
|
||||||
|
resources = resource_helper.build_resource_info(
|
||||||
|
plural_mappings,
|
||||||
|
RESOURCE_ATTRIBUTE_MAP,
|
||||||
|
constants.FLAVORS)
|
||||||
|
plugin = manager.NeutronManager.get_service_plugins()[
|
||||||
|
constants.FLAVORS]
|
||||||
|
for collection_name in SUB_RESOURCE_ATTRIBUTE_MAP:
|
||||||
|
# Special handling needed for sub-resources with 'y' ending
|
||||||
|
# (e.g. proxies -> proxy)
|
||||||
|
resource_name = collection_name[:-1]
|
||||||
|
parent = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get('parent')
|
||||||
|
params = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get(
|
||||||
|
'parameters')
|
||||||
|
|
||||||
|
controller = base.create_resource(collection_name, resource_name,
|
||||||
|
plugin, params,
|
||||||
|
allow_bulk=True,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
resource = extensions.ResourceExtension(
|
||||||
|
collection_name,
|
||||||
|
controller, parent,
|
||||||
|
path_prefix=FLAVORS_PREFIX,
|
||||||
|
attr_map=params)
|
||||||
|
resources.append(resource)
|
||||||
|
|
||||||
|
return resources
|
||||||
|
|
||||||
|
def update_attributes_map(self, attributes):
|
||||||
|
super(Flavors, self).update_attributes_map(
|
||||||
|
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
||||||
|
|
||||||
|
def get_extended_resources(self, version):
|
||||||
|
if version == "2.0":
|
||||||
|
return RESOURCE_ATTRIBUTE_MAP
|
||||||
|
else:
|
||||||
|
return {}
|
@ -23,6 +23,7 @@ from oslo_utils import importutils
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from neutron.common import utils
|
from neutron.common import utils
|
||||||
|
from neutron.db import flavors_db
|
||||||
from neutron.i18n import _LE, _LI
|
from neutron.i18n import _LE, _LI
|
||||||
from neutron.plugins.common import constants
|
from neutron.plugins.common import constants
|
||||||
|
|
||||||
@ -165,6 +166,11 @@ 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.
|
||||||
|
|
||||||
@ -204,6 +210,9 @@ 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")
|
||||||
|
@ -22,6 +22,7 @@ FIREWALL = "FIREWALL"
|
|||||||
VPN = "VPN"
|
VPN = "VPN"
|
||||||
METERING = "METERING"
|
METERING = "METERING"
|
||||||
L3_ROUTER_NAT = "L3_ROUTER_NAT"
|
L3_ROUTER_NAT = "L3_ROUTER_NAT"
|
||||||
|
FLAVORS = "FLAVORS"
|
||||||
|
|
||||||
# Maps extension alias to service type
|
# Maps extension alias to service type
|
||||||
EXT_TO_SERVICE_MAPPING = {
|
EXT_TO_SERVICE_MAPPING = {
|
||||||
@ -31,7 +32,8 @@ EXT_TO_SERVICE_MAPPING = {
|
|||||||
'fwaas': FIREWALL,
|
'fwaas': FIREWALL,
|
||||||
'vpnaas': VPN,
|
'vpnaas': VPN,
|
||||||
'metering': METERING,
|
'metering': METERING,
|
||||||
'router': L3_ROUTER_NAT
|
'router': L3_ROUTER_NAT,
|
||||||
|
'flavors': FLAVORS
|
||||||
}
|
}
|
||||||
|
|
||||||
# Service operation status constants
|
# Service operation status constants
|
||||||
|
@ -82,6 +82,8 @@ class BaseNetworkTest(neutron.tests.tempest.test.BaseTestCase):
|
|||||||
cls.ikepolicies = []
|
cls.ikepolicies = []
|
||||||
cls.floating_ips = []
|
cls.floating_ips = []
|
||||||
cls.metering_labels = []
|
cls.metering_labels = []
|
||||||
|
cls.service_profiles = []
|
||||||
|
cls.flavors = []
|
||||||
cls.metering_label_rules = []
|
cls.metering_label_rules = []
|
||||||
cls.fw_rules = []
|
cls.fw_rules = []
|
||||||
cls.fw_policies = []
|
cls.fw_policies = []
|
||||||
@ -146,6 +148,16 @@ class BaseNetworkTest(neutron.tests.tempest.test.BaseTestCase):
|
|||||||
cls._try_delete_resource(
|
cls._try_delete_resource(
|
||||||
cls.admin_client.delete_metering_label,
|
cls.admin_client.delete_metering_label,
|
||||||
metering_label['id'])
|
metering_label['id'])
|
||||||
|
# Clean up flavors
|
||||||
|
for flavor in cls.flavors:
|
||||||
|
cls._try_delete_resource(
|
||||||
|
cls.admin_client.delete_flavor,
|
||||||
|
flavor['id'])
|
||||||
|
# Clean up service profiles
|
||||||
|
for service_profile in cls.service_profiles:
|
||||||
|
cls._try_delete_resource(
|
||||||
|
cls.admin_client.delete_service_profile,
|
||||||
|
service_profile['id'])
|
||||||
# Clean up ports
|
# Clean up ports
|
||||||
for port in cls.ports:
|
for port in cls.ports:
|
||||||
cls._try_delete_resource(cls.client.delete_port,
|
cls._try_delete_resource(cls.client.delete_port,
|
||||||
@ -464,3 +476,22 @@ class BaseAdminNetworkTest(BaseNetworkTest):
|
|||||||
metering_label_rule = body['metering_label_rule']
|
metering_label_rule = body['metering_label_rule']
|
||||||
cls.metering_label_rules.append(metering_label_rule)
|
cls.metering_label_rules.append(metering_label_rule)
|
||||||
return metering_label_rule
|
return metering_label_rule
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_flavor(cls, name, description, service_type):
|
||||||
|
"""Wrapper utility that returns a test flavor."""
|
||||||
|
body = cls.admin_client.create_flavor(
|
||||||
|
description=description, service_type=service_type,
|
||||||
|
name=name)
|
||||||
|
flavor = body['flavor']
|
||||||
|
cls.flavors.append(flavor)
|
||||||
|
return flavor
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_service_profile(cls, description, metainfo, driver):
|
||||||
|
"""Wrapper utility that returns a test service profile."""
|
||||||
|
body = cls.admin_client.create_service_profile(
|
||||||
|
driver=driver, metainfo=metainfo, description=description)
|
||||||
|
service_profile = body['service_profile']
|
||||||
|
cls.service_profiles.append(service_profile)
|
||||||
|
return service_profile
|
||||||
|
154
neutron/tests/api/test_flavors_extensions.py
Normal file
154
neutron/tests/api/test_flavors_extensions.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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 neutron.tests.api import base
|
||||||
|
from neutron.tests.tempest import test
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlavorsJson(base.BaseAdminNetworkTest):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tests the following operations in the Neutron API using the REST client for
|
||||||
|
Neutron:
|
||||||
|
|
||||||
|
List, Show, Create, Update, Delete Flavors
|
||||||
|
List, Show, Create, Update, Delete service profiles
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(TestFlavorsJson, cls).resource_setup()
|
||||||
|
if not test.is_extension_enabled('flavors', 'network'):
|
||||||
|
msg = "flavors extension not enabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
service_type = "LOADBALANCER"
|
||||||
|
description_flavor = "flavor is created by tempest"
|
||||||
|
name_flavor = "Best flavor created by tempest"
|
||||||
|
cls.flavor = cls.create_flavor(name_flavor, description_flavor,
|
||||||
|
service_type)
|
||||||
|
description_sp = "service profile created by tempest"
|
||||||
|
# Future TODO(madhu_ak): Right now the dummy driver is loaded. Will
|
||||||
|
# make changes as soon I get to know the flavor supported drivers
|
||||||
|
driver = ""
|
||||||
|
metainfo = '{"data": "value"}'
|
||||||
|
cls.service_profile = cls.create_service_profile(
|
||||||
|
description=description_sp, metainfo=metainfo, driver=driver)
|
||||||
|
|
||||||
|
def _delete_service_profile(self, service_profile_id):
|
||||||
|
# Deletes a service profile and verifies if it is deleted or not
|
||||||
|
self.admin_client.delete_service_profile(service_profile_id)
|
||||||
|
# Asserting that service profile is not found in list after deletion
|
||||||
|
labels = self.admin_client.list_service_profiles(id=service_profile_id)
|
||||||
|
self.assertEqual(len(labels['service_profiles']), 0)
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50')
|
||||||
|
def test_create_update_delete_service_profile(self):
|
||||||
|
# Creates a service profile
|
||||||
|
description = "service_profile created by tempest"
|
||||||
|
driver = ""
|
||||||
|
metainfo = '{"data": "value"}'
|
||||||
|
body = self.admin_client.create_service_profile(
|
||||||
|
description=description, driver=driver, metainfo=metainfo)
|
||||||
|
service_profile = body['service_profile']
|
||||||
|
# Updates a service profile
|
||||||
|
self.admin_client.update_service_profile(service_profile['id'],
|
||||||
|
enabled=False)
|
||||||
|
self.assertTrue(service_profile['enabled'])
|
||||||
|
# Deletes a service profile
|
||||||
|
self.addCleanup(self._delete_service_profile,
|
||||||
|
service_profile['id'])
|
||||||
|
# Assert whether created service profiles are found in service profile
|
||||||
|
# lists or fail if created service profiles are not found in service
|
||||||
|
# profiles list
|
||||||
|
labels = (self.admin_client.list_service_profiles(
|
||||||
|
id=service_profile['id']))
|
||||||
|
self.assertEqual(len(labels['service_profiles']), 1)
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50')
|
||||||
|
def test_create_update_delete_flavor(self):
|
||||||
|
# Creates a flavor
|
||||||
|
description = "flavor created by tempest"
|
||||||
|
service = "LOADBALANCERS"
|
||||||
|
name = "Best flavor created by tempest"
|
||||||
|
body = self.admin_client.create_flavor(name=name, service_type=service,
|
||||||
|
description=description)
|
||||||
|
flavor = body['flavor']
|
||||||
|
# Updates a flavor
|
||||||
|
self.admin_client.update_flavor(flavor['id'], enabled=False)
|
||||||
|
self.assertTrue(flavor['enabled'])
|
||||||
|
# Deletes a flavor
|
||||||
|
self.addCleanup(self._delete_flavor, flavor['id'])
|
||||||
|
# Assert whether created flavors are found in flavor lists or fail
|
||||||
|
# if created flavors are not found in flavors list
|
||||||
|
labels = (self.admin_client.list_flavors(id=flavor['id']))
|
||||||
|
self.assertEqual(len(labels['flavors']), 1)
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968')
|
||||||
|
def test_show_service_profile(self):
|
||||||
|
# Verifies the details of a service profile
|
||||||
|
body = self.admin_client.show_service_profile(
|
||||||
|
self.service_profile['id'])
|
||||||
|
service_profile = body['service_profile']
|
||||||
|
self.assertEqual(self.service_profile['id'], service_profile['id'])
|
||||||
|
self.assertEqual(self.service_profile['description'],
|
||||||
|
service_profile['description'])
|
||||||
|
self.assertEqual(self.service_profile['metainfo'],
|
||||||
|
service_profile['metainfo'])
|
||||||
|
self.assertEqual(True, service_profile['enabled'])
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968')
|
||||||
|
def test_show_flavor(self):
|
||||||
|
# Verifies the details of a flavor
|
||||||
|
body = self.admin_client.show_flavor(self.flavor['id'])
|
||||||
|
flavor = body['flavor']
|
||||||
|
self.assertEqual(self.flavor['id'], flavor['id'])
|
||||||
|
self.assertEqual(self.flavor['description'], flavor['description'])
|
||||||
|
self.assertEqual(self.flavor['name'], flavor['name'])
|
||||||
|
self.assertEqual(True, flavor['enabled'])
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612')
|
||||||
|
def test_list_flavors(self):
|
||||||
|
# Verify flavor lists
|
||||||
|
body = self.admin_client.list_flavors(id=33)
|
||||||
|
flavors = body['flavors']
|
||||||
|
self.assertEqual(0, len(flavors))
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612')
|
||||||
|
def test_list_service_profiles(self):
|
||||||
|
# Verify service profiles lists
|
||||||
|
body = self.admin_client.list_service_profiles(id=33)
|
||||||
|
service_profiles = body['service_profiles']
|
||||||
|
self.assertEqual(0, len(service_profiles))
|
||||||
|
|
||||||
|
def _delete_flavor(self, flavor_id):
|
||||||
|
# Deletes a flavor and verifies if it is deleted or not
|
||||||
|
self.admin_client.delete_flavor(flavor_id)
|
||||||
|
# Asserting that the flavor is not found in list after deletion
|
||||||
|
labels = self.admin_client.list_flavors(id=flavor_id)
|
||||||
|
self.assertEqual(len(labels['flavors']), 0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlavorsIpV6TestJSON(TestFlavorsJson):
|
||||||
|
_ip_version = 6
|
@ -163,5 +163,16 @@
|
|||||||
|
|
||||||
"get_service_provider": "rule:regular_user",
|
"get_service_provider": "rule:regular_user",
|
||||||
"get_lsn": "rule:admin_only",
|
"get_lsn": "rule:admin_only",
|
||||||
"create_lsn": "rule:admin_only"
|
"create_lsn": "rule:admin_only",
|
||||||
|
|
||||||
|
"create_flavor": "rule:admin_only",
|
||||||
|
"update_flavor": "rule:admin_only",
|
||||||
|
"delete_flavor": "rule:admin_only",
|
||||||
|
"get_flavors": "rule:regular_user",
|
||||||
|
"get_flavor": "rule:regular_user",
|
||||||
|
"create_service_profile": "rule:admin_only",
|
||||||
|
"update_service_profile": "rule:admin_only",
|
||||||
|
"delete_service_profile": "rule:admin_only",
|
||||||
|
"get_service_profiles": "rule:admin_only",
|
||||||
|
"get_service_profile": "rule:admin_only"
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ class NetworkClientJSON(service_client.ServiceClient):
|
|||||||
# The following list represents resource names that do not require
|
# The following list represents resource names that do not require
|
||||||
# changing underscore to a hyphen
|
# changing underscore to a hyphen
|
||||||
hyphen_exceptions = ["health_monitors", "firewall_rules",
|
hyphen_exceptions = ["health_monitors", "firewall_rules",
|
||||||
"firewall_policies"]
|
"firewall_policies", "service_profiles"]
|
||||||
# the following map is used to construct proper URI
|
# the following map is used to construct proper URI
|
||||||
# for the given neutron resource
|
# for the given neutron resource
|
||||||
service_resource_prefix_map = {
|
service_resource_prefix_map = {
|
||||||
|
459
neutron/tests/unit/extensions/test_flavors.py
Normal file
459
neutron/tests/unit/extensions/test_flavors.py
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import fixtures
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from neutron import context
|
||||||
|
from neutron.db import api as dbapi
|
||||||
|
from neutron.db import flavors_db
|
||||||
|
from neutron.extensions import flavors
|
||||||
|
from neutron import manager
|
||||||
|
from neutron.plugins.common import constants
|
||||||
|
from neutron.tests import 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.extensions import base as extension
|
||||||
|
|
||||||
|
_uuid = uuidutils.generate_uuid
|
||||||
|
_get_path = test_base._get_path
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorExtensionTestCase(extension.ExtensionTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(FlavorExtensionTestCase, self).setUp()
|
||||||
|
self._setUpExtension(
|
||||||
|
'neutron.db.flavors_db.FlavorManager',
|
||||||
|
constants.FLAVORS, flavors.RESOURCE_ATTRIBUTE_MAP,
|
||||||
|
flavors.Flavors, '', supported_extension_aliases='flavors')
|
||||||
|
|
||||||
|
def test_create_flavor(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
data = {'flavor': {'name': 'GOLD',
|
||||||
|
'service_type': constants.LOADBALANCER,
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': True}}
|
||||||
|
|
||||||
|
expected = copy.deepcopy(data)
|
||||||
|
expected['flavor']['service_profiles'] = []
|
||||||
|
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.create_flavor.return_value = expected['flavor']
|
||||||
|
res = self.api.post(_get_path('flavors', fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
|
||||||
|
instance.create_flavor.assert_called_with(mock.ANY,
|
||||||
|
flavor=expected)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertIn('flavor', res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_update_flavor(self):
|
||||||
|
flavor_id = 'fake_id'
|
||||||
|
data = {'flavor': {'name': 'GOLD',
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'enabled': True}}
|
||||||
|
expected = copy.copy(data)
|
||||||
|
expected['flavor']['service_profiles'] = []
|
||||||
|
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.update_flavor.return_value = expected['flavor']
|
||||||
|
res = self.api.put(_get_path('flavors', id=flavor_id, fmt=self.fmt),
|
||||||
|
self.serialize(data),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
|
||||||
|
instance.update_flavor.assert_called_with(mock.ANY,
|
||||||
|
flavor_id,
|
||||||
|
flavor=expected)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertIn('flavor', res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_delete_flavor(self):
|
||||||
|
flavor_id = 'fake_id'
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
self.api.delete(_get_path('flavors', id=flavor_id, fmt=self.fmt),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
|
||||||
|
instance.delete_flavor.assert_called_with(mock.ANY,
|
||||||
|
flavor_id)
|
||||||
|
|
||||||
|
def test_show_flavor(self):
|
||||||
|
flavor_id = 'fake_id'
|
||||||
|
expected = {'flavor': {'id': flavor_id,
|
||||||
|
'name': 'GOLD',
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'enabled': True,
|
||||||
|
'service_profiles': ['profile-1']}}
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.get_flavor.return_value = expected['flavor']
|
||||||
|
res = self.api.get(_get_path('flavors', id=flavor_id, fmt=self.fmt))
|
||||||
|
instance.get_flavor.assert_called_with(mock.ANY,
|
||||||
|
flavor_id,
|
||||||
|
fields=mock.ANY)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_get_flavors(self):
|
||||||
|
data = {'flavors': [{'id': 'id1',
|
||||||
|
'name': 'GOLD',
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'enabled': True,
|
||||||
|
'service_profiles': ['profile-1']},
|
||||||
|
{'id': 'id2',
|
||||||
|
'name': 'GOLD',
|
||||||
|
'description': 'the best flavor',
|
||||||
|
'enabled': True,
|
||||||
|
'service_profiles': ['profile-2', 'profile-1']}]}
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.get_flavors.return_value = data['flavors']
|
||||||
|
res = self.api.get(_get_path('flavors', fmt=self.fmt))
|
||||||
|
instance.get_flavors.assert_called_with(mock.ANY,
|
||||||
|
fields=mock.ANY,
|
||||||
|
filters=mock.ANY)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertEqual(data, res)
|
||||||
|
|
||||||
|
def test_create_service_profile(self):
|
||||||
|
tenant_id = uuidutils.generate_uuid()
|
||||||
|
expected = {'service_profile': {'description': 'the best sp',
|
||||||
|
'driver': '',
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.create_service_profile.return_value = (
|
||||||
|
expected['service_profile'])
|
||||||
|
res = self.api.post(_get_path('service_profiles', fmt=self.fmt),
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
instance.create_service_profile.assert_called_with(
|
||||||
|
mock.ANY,
|
||||||
|
service_profile=expected)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertIn('service_profile', res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_update_service_profile(self):
|
||||||
|
sp_id = "fake_id"
|
||||||
|
expected = {'service_profile': {'description': 'the best sp',
|
||||||
|
'enabled': False,
|
||||||
|
'metainfo': '{"data1": "value3"}'}}
|
||||||
|
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.update_service_profile.return_value = (
|
||||||
|
expected['service_profile'])
|
||||||
|
res = self.api.put(_get_path('service_profiles',
|
||||||
|
id=sp_id, fmt=self.fmt),
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
|
||||||
|
instance.update_service_profile.assert_called_with(
|
||||||
|
mock.ANY,
|
||||||
|
sp_id,
|
||||||
|
service_profile=expected)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertIn('service_profile', res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_delete_service_profile(self):
|
||||||
|
sp_id = 'fake_id'
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
self.api.delete(_get_path('service_profiles', id=sp_id, fmt=self.fmt),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
instance.delete_service_profile.assert_called_with(mock.ANY,
|
||||||
|
sp_id)
|
||||||
|
|
||||||
|
def test_show_service_profile(self):
|
||||||
|
sp_id = 'fake_id'
|
||||||
|
expected = {'service_profile': {'id': 'id1',
|
||||||
|
'driver': 'entrypoint1',
|
||||||
|
'description': 'desc',
|
||||||
|
'metainfo': '{}',
|
||||||
|
'enabled': True}}
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.get_service_profile.return_value = (
|
||||||
|
expected['service_profile'])
|
||||||
|
res = self.api.get(_get_path('service_profiles',
|
||||||
|
id=sp_id, fmt=self.fmt))
|
||||||
|
instance.get_service_profile.assert_called_with(mock.ANY,
|
||||||
|
sp_id,
|
||||||
|
fields=mock.ANY)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_get_service_profiles(self):
|
||||||
|
expected = {'service_profiles': [{'id': 'id1',
|
||||||
|
'driver': 'entrypoint1',
|
||||||
|
'description': 'desc',
|
||||||
|
'metainfo': '{}',
|
||||||
|
'enabled': True},
|
||||||
|
{'id': 'id2',
|
||||||
|
'driver': 'entrypoint2',
|
||||||
|
'description': 'desc',
|
||||||
|
'metainfo': '{}',
|
||||||
|
'enabled': True}]}
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.get_service_profiles.return_value = (
|
||||||
|
expected['service_profiles'])
|
||||||
|
res = self.api.get(_get_path('service_profiles', fmt=self.fmt))
|
||||||
|
instance.get_service_profiles.assert_called_with(mock.ANY,
|
||||||
|
fields=mock.ANY,
|
||||||
|
filters=mock.ANY)
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_associate_service_profile_with_flavor(self):
|
||||||
|
expected = {'service_profile': {'id': _uuid()}}
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.create_flavor_service_profile.return_value = (
|
||||||
|
expected['service_profile'])
|
||||||
|
res = self.api.post('/flavors/fl_id/service_profiles',
|
||||||
|
self.serialize(expected),
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
instance.create_flavor_service_profile.assert_called_with(
|
||||||
|
mock.ANY, service_profile=expected, flavor_id='fl_id')
|
||||||
|
res = self.deserialize(res)
|
||||||
|
self.assertEqual(expected, res)
|
||||||
|
|
||||||
|
def test_disassociate_service_profile_with_flavor(self):
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
instance.delete_flavor_service_profile.return_value = None
|
||||||
|
self.api.delete('/flavors/fl_id/service_profiles/%s' % 'fake_spid',
|
||||||
|
content_type='application/%s' % self.fmt)
|
||||||
|
instance.delete_flavor_service_profile.assert_called_with(
|
||||||
|
mock.ANY,
|
||||||
|
'fake_spid',
|
||||||
|
flavor_id='fl_id')
|
||||||
|
|
||||||
|
|
||||||
|
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 FlavorManagerTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
|
||||||
|
base.PluginFixture):
|
||||||
|
def setUp(self):
|
||||||
|
super(FlavorManagerTestCase, self).setUp()
|
||||||
|
|
||||||
|
self.config_parse()
|
||||||
|
cfg.CONF.set_override(
|
||||||
|
'core_plugin',
|
||||||
|
'neutron.tests.unit.extensions.test_flavors.DummyCorePlugin')
|
||||||
|
cfg.CONF.set_override(
|
||||||
|
'service_plugins',
|
||||||
|
['neutron.tests.unit.extensions.test_flavors.DummyServicePlugin'])
|
||||||
|
|
||||||
|
self.useFixture(
|
||||||
|
fixtures.MonkeyPatch('neutron.manager.NeutronManager._instance'))
|
||||||
|
|
||||||
|
self.plugin = flavors_db.FlavorManager(
|
||||||
|
manager.NeutronManager().get_instance())
|
||||||
|
self.ctx = context.get_admin_context()
|
||||||
|
dbapi.get_engine()
|
||||||
|
|
||||||
|
def _create_flavor(self, description=None):
|
||||||
|
flavor = {'flavor': {'name': 'GOLD',
|
||||||
|
'service_type': constants.LOADBALANCER,
|
||||||
|
'description': description or 'the best flavor',
|
||||||
|
'enabled': True}}
|
||||||
|
return self.plugin.create_flavor(self.ctx, flavor), flavor
|
||||||
|
|
||||||
|
def test_create_flavor(self):
|
||||||
|
self._create_flavor()
|
||||||
|
res = self.ctx.session.query(flavors_db.Flavor).all()
|
||||||
|
self.assertEqual(1, len(res))
|
||||||
|
self.assertEqual('GOLD', res[0]['name'])
|
||||||
|
|
||||||
|
def test_update_flavor(self):
|
||||||
|
fl, flavor = self._create_flavor()
|
||||||
|
flavor = {'flavor': {'name': 'Silver',
|
||||||
|
'enabled': False}}
|
||||||
|
self.plugin.update_flavor(self.ctx, fl['id'], flavor)
|
||||||
|
res = (self.ctx.session.query(flavors_db.Flavor).
|
||||||
|
filter_by(id=fl['id']).one())
|
||||||
|
self.assertEqual('Silver', res['name'])
|
||||||
|
self.assertFalse(res['enabled'])
|
||||||
|
|
||||||
|
def test_delete_flavor(self):
|
||||||
|
fl, data = self._create_flavor()
|
||||||
|
self.plugin.delete_flavor(self.ctx, fl['id'])
|
||||||
|
res = (self.ctx.session.query(flavors_db.Flavor).all())
|
||||||
|
self.assertFalse(res)
|
||||||
|
|
||||||
|
def test_show_flavor(self):
|
||||||
|
fl, data = self._create_flavor()
|
||||||
|
show_fl = self.plugin.get_flavor(self.ctx, fl['id'])
|
||||||
|
self.assertEqual(fl, show_fl)
|
||||||
|
|
||||||
|
def test_get_flavors(self):
|
||||||
|
fl, flavor = self._create_flavor()
|
||||||
|
flavor['flavor']['name'] = 'SILVER'
|
||||||
|
self.plugin.create_flavor(self.ctx, flavor)
|
||||||
|
show_fl = self.plugin.get_flavors(self.ctx)
|
||||||
|
self.assertEqual(2, len(show_fl))
|
||||||
|
|
||||||
|
def _create_service_profile(self, description=None):
|
||||||
|
data = {'service_profile':
|
||||||
|
{'description': description or 'the best sp',
|
||||||
|
'driver':
|
||||||
|
('neutron.tests.unit.extensions.test_flavors.'
|
||||||
|
'DummyServiceDriver'),
|
||||||
|
'enabled': True,
|
||||||
|
'metainfo': '{"data": "value"}'}}
|
||||||
|
sp = self.plugin.unit_create_service_profile(self.ctx,
|
||||||
|
data)
|
||||||
|
return sp, data
|
||||||
|
|
||||||
|
def test_create_service_profile(self):
|
||||||
|
sp, data = self._create_service_profile()
|
||||||
|
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_update_service_profile(self):
|
||||||
|
sp, data = self._create_service_profile()
|
||||||
|
data['service_profile']['metainfo'] = '{"data": "value1"}'
|
||||||
|
sp = self.plugin.update_service_profile(self.ctx, sp['id'],
|
||||||
|
data)
|
||||||
|
res = (self.ctx.session.query(flavors_db.ServiceProfile).
|
||||||
|
filter_by(id=sp['id']).one())
|
||||||
|
self.assertEqual(data['service_profile']['metainfo'], res['metainfo'])
|
||||||
|
|
||||||
|
def test_delete_service_profile(self):
|
||||||
|
sp, data = self._create_service_profile()
|
||||||
|
self.plugin.delete_service_profile(self.ctx, sp['id'])
|
||||||
|
res = self.ctx.session.query(flavors_db.ServiceProfile).all()
|
||||||
|
self.assertFalse(res)
|
||||||
|
|
||||||
|
def test_show_service_profile(self):
|
||||||
|
sp, data = self._create_service_profile()
|
||||||
|
sp_show = self.plugin.get_service_profile(self.ctx, sp['id'])
|
||||||
|
self.assertEqual(sp, sp_show)
|
||||||
|
|
||||||
|
def test_get_service_profiles(self):
|
||||||
|
self._create_service_profile()
|
||||||
|
self._create_service_profile(description='another sp')
|
||||||
|
self.assertEqual(2, len(self.plugin.get_service_profiles(self.ctx)))
|
||||||
|
|
||||||
|
def test_associate_service_profile_with_flavor(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'])
|
||||||
|
binding = (
|
||||||
|
self.ctx.session.query(flavors_db.FlavorServiceProfileBinding).
|
||||||
|
first())
|
||||||
|
self.assertEqual(fl['id'], binding['flavor_id'])
|
||||||
|
self.assertEqual(sp['id'], binding['service_profile_id'])
|
||||||
|
|
||||||
|
res = self.plugin.get_flavor(self.ctx, fl['id'])
|
||||||
|
self.assertEqual(1, len(res['service_profiles']))
|
||||||
|
self.assertEqual(sp['id'], res['service_profiles'][0])
|
||||||
|
|
||||||
|
res = self.plugin.get_service_profile(self.ctx, sp['id'])
|
||||||
|
self.assertEqual(1, len(res['flavors']))
|
||||||
|
self.assertEqual(fl['id'], res['flavors'][0])
|
||||||
|
|
||||||
|
def test_autodelete_flavor_associations(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'])
|
||||||
|
self.plugin.delete_flavor(self.ctx, fl['id'])
|
||||||
|
binding = (
|
||||||
|
self.ctx.session.query(flavors_db.FlavorServiceProfileBinding).
|
||||||
|
first())
|
||||||
|
self.assertIsNone(binding)
|
||||||
|
|
||||||
|
def test_associate_service_profile_with_flavor_exists(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'])
|
||||||
|
self.assertRaises(flavors_db.FlavorServiceProfileBindingExists,
|
||||||
|
self.plugin.create_flavor_service_profile,
|
||||||
|
self.ctx,
|
||||||
|
{'service_profile': {'id': sp['id']}},
|
||||||
|
fl['id'])
|
||||||
|
|
||||||
|
def test_disassociate_service_profile_with_flavor(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'])
|
||||||
|
self.plugin.delete_flavor_service_profile(
|
||||||
|
self.ctx, sp['id'], fl['id'])
|
||||||
|
binding = (
|
||||||
|
self.ctx.session.query(flavors_db.FlavorServiceProfileBinding).
|
||||||
|
first())
|
||||||
|
self.assertIsNone(binding)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
flavors_db.FlavorServiceProfileBindingNotFound,
|
||||||
|
self.plugin.delete_flavor_service_profile,
|
||||||
|
self.ctx, sp['id'], fl['id'])
|
||||||
|
|
||||||
|
def test_delete_service_profile_in_use(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'])
|
||||||
|
self.assertRaises(
|
||||||
|
flavors_db.ServiceProfileInUse,
|
||||||
|
self.plugin.delete_service_profile,
|
||||||
|
self.ctx,
|
||||||
|
sp['id'])
|
@ -105,7 +105,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(3, len(svc_plugins))
|
self.assertEqual(4, 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())
|
||||||
|
Loading…
Reference in New Issue
Block a user