From 2527cdd6882e3db790bb44a9ece9244c2a0c14f2 Mon Sep 17 00:00:00 2001 From: Ivar Lazzaro Date: Sun, 3 May 2015 20:58:42 -0700 Subject: [PATCH] introduce service profile model Partially implements blueprint node-centric-chain-plugin Change-Id: I2403e35d49a1e100f292f82082c7dad96d79fc29 --- etc/policy.json | 8 +- .../versions/9744740aa75c_service_profile.py | 56 ++++++ .../alembic_migrations/versions/HEAD | 2 +- gbpservice/neutron/db/servicechain_db.py | 115 +++++++++++- gbpservice/neutron/extensions/servicechain.py | 80 +++++++- .../services/grouppolicy/common/exceptions.py | 2 +- .../services/servicechain/common/constants.py | 23 +++ .../servicechain/plugins/msc/context.py | 36 ++++ .../plugins/msc/driver_manager.py | 24 +++ .../plugins/msc/drivers/dummy_driver.py | 24 +++ .../plugins/msc/drivers/simplechain_driver.py | 34 +++- .../servicechain/plugins/msc/plugin.py | 69 +++++++ .../services/servicechain/plugins/sharing.py | 11 +- gbpservice/neutron/tests/etc/test-policy.json | 8 +- gbpservice/neutron/tests/unit/common.py | 21 +++ .../db/grouppolicy/test_servicechain_db.py | 177 +++++++++++++++--- .../grouppolicy/test_resource_mapping.py | 32 +++- .../servicechain/test_servicechain_plugin.py | 158 +++++++++++++++- .../servicechain/test_simple_chain_driver.py | 27 +-- .../tests/unit/test_extension_servicechain.py | 11 +- 20 files changed, 855 insertions(+), 63 deletions(-) create mode 100644 gbpservice/neutron/db/migration/alembic_migrations/versions/9744740aa75c_service_profile.py create mode 100644 gbpservice/neutron/services/servicechain/common/constants.py diff --git a/etc/policy.json b/etc/policy.json index 9f76dbcfe..d83520dac 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -18,6 +18,7 @@ "shared_nsp": "field:network_service_policies:shared=True", "shared_scn": "field:servicechain_nodes:shared=True", "shared_scs": "field:servicechain_specs:shared=True", + "shared_sp": "field:service_profiles:shared=True", "create_policy_target_group": "", "create_policy_target_group:shared": "rule:admin_only", @@ -86,5 +87,10 @@ "create_servicechain_instance": "", "get_servicechain_instance": "rule:admin_or_owner", - "update_servicechain_instance:shared": "rule:admin_only" + "update_servicechain_instance:shared": "rule:admin_only", + + "create_service_profile": "", + "create_service_profile:shared": "rule:admin_only", + "get_service_profile": "rule:admin_or_owner or rule:shared_sp", + "update_service_profile:shared": "rule:admin_only" } diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/9744740aa75c_service_profile.py b/gbpservice/neutron/db/migration/alembic_migrations/versions/9744740aa75c_service_profile.py new file mode 100644 index 000000000..37daea814 --- /dev/null +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/9744740aa75c_service_profile.py @@ -0,0 +1,56 @@ +# 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. +# + +"""service_profile +""" + +# revision identifiers, used by Alembic. +revision = '9744740aa75c' +down_revision = 'fd98aa15958d' + + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table( + 'service_profiles', + sa.Column('id', sa.String(36), nullable=False), + sa.Column('tenant_id', sa.String(length=255), nullable=True), + sa.Column('name', sa.String(length=50), nullable=True), + sa.Column('vendor', sa.String(length=50), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.Column('shared', sa.Boolean), + sa.Column('insertion_mode', sa.String(length=50), nullable=True), + sa.Column('service_type', sa.String(length=50), nullable=True), + sa.Column('service_flavor', sa.String(length=1024), nullable=True), + sa.PrimaryKeyConstraint('id'), + ) + + op.add_column( + 'sc_nodes', + sa.Column('service_profile_id', sa.String(36), nullable=True) + ) + + op.create_foreign_key('sc_nodes_ibfk_profile', source='sc_nodes', + referent='service_profiles', + local_cols=['service_profile_id'], + remote_cols=['id']) + + op.alter_column("sc_nodes", "service_type", + existing_type=sa.String(length=50), nullable=True) + + +def downgrade(): + pass \ No newline at end of file diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD index 522dbffd0..633a2879d 100644 --- a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -fd98aa15958d +9744740aa75c \ No newline at end of file diff --git a/gbpservice/neutron/db/servicechain_db.py b/gbpservice/neutron/db/servicechain_db.py index c73a7393b..8245489d8 100644 --- a/gbpservice/neutron/db/servicechain_db.py +++ b/gbpservice/neutron/db/servicechain_db.py @@ -63,12 +63,15 @@ class ServiceChainNode(model_base.BASEV2, models_v2.HasId, __tablename__ = 'sc_nodes' name = sa.Column(sa.String(50)) description = sa.Column(sa.String(255)) - service_type = sa.Column(sa.String(50)) config = sa.Column(sa.String(4096)) specs = orm.relationship(SpecNodeAssociation, backref="nodes", cascade='all, delete, delete-orphan') shared = sa.Column(sa.Boolean) + service_type = sa.Column(sa.String(50), nullable=True) + service_profile_id = sa.Column( + sa.String(36), sa.ForeignKey('service_profiles.id'), + nullable=True) class ServiceChainInstance(model_base.BASEV2, models_v2.HasId, @@ -115,6 +118,23 @@ class ServiceChainSpec(model_base.BASEV2, models_v2.HasId, shared = sa.Column(sa.Boolean) +class ServiceProfile(model_base.BASEV2, models_v2.HasId, + models_v2.HasTenant): + """ Service Profile + """ + __tablename__ = 'service_profiles' + name = sa.Column(sa.String(50)) + description = sa.Column(sa.String(255)) + vendor = sa.Column(sa.String(50)) + shared = sa.Column(sa.Boolean) + # Not using ENUM for less painful upgrades. Validation will happen at the + # API level + insertion_mode = sa.Column(sa.String(50)) + service_type = sa.Column(sa.String(50)) + service_flavor = sa.Column(sa.String(1024)) + nodes = orm.relationship(ServiceChainNode, backref="service_profile") + + class ServiceChainDbPlugin(schain.ServiceChainPluginBase, common_db_mixin.CommonDbMixin): """ServiceChain plugin interface implementation using SQLAlchemy models.""" @@ -157,11 +177,19 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase, raise schain.ServiceChainInstanceNotFound( sc_instance_id=instance_id) + def _get_service_profile(self, context, profile_id): + try: + return self._get_by_id(context, ServiceProfile, profile_id) + except exc.NoResultFound: + raise schain.ServiceProfileNotFound( + profile_id=profile_id) + def _make_sc_node_dict(self, sc_node, fields=None): res = {'id': sc_node['id'], 'tenant_id': sc_node['tenant_id'], 'name': sc_node['name'], 'description': sc_node['description'], + 'service_profile_id': sc_node['service_profile_id'], 'service_type': sc_node['service_type'], 'config': sc_node['config'], 'shared': sc_node['shared']} @@ -192,6 +220,19 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase, for sc_spec in instance['specs']] return self._fields(res, fields) + def _make_service_profile_dict(self, profile, fields=None): + res = {'id': profile['id'], + 'tenant_id': profile['tenant_id'], + 'name': profile['name'], + 'description': profile['description'], + 'shared': profile['shared'], + 'service_type': profile['service_type'], + 'service_flavor': profile['service_flavor'], + 'vendor': profile['vendor'], + 'insertion_mode': profile['insertion_mode']} + res['nodes'] = [node['id'] for node in profile['nodes']] + return self._fields(res, fields) + @staticmethod def validate_service_type(service_type): if service_type not in schain.sc_supported_type: @@ -202,13 +243,12 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase, node = servicechain_node['servicechain_node'] tenant_id = self._get_tenant_id_for_create(context, node) with context.session.begin(subtransactions=True): - node_db = ServiceChainNode(id=uuidutils.generate_uuid(), - tenant_id=tenant_id, - name=node['name'], - description=node['description'], - service_type=node['service_type'], - config=node['config'], - shared=node['shared']) + node_db = ServiceChainNode( + id=uuidutils.generate_uuid(), tenant_id=tenant_id, + name=node['name'], description=node['description'], + service_profile_id=node['service_profile_id'], + service_type=node['service_type'], + config=node['config'], shared=node['shared']) context.session.add(node_db) return self._make_sc_node_dict(node_db) @@ -447,6 +487,61 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase, page_reverse=page_reverse) @log.log - def get_servicechain_instances_count(self, context, filters=None): - return self._get_collection_count(context, ServiceChainInstance, + def get_service_profile_count(self, context, filters=None): + return self._get_collection_count(context, ServiceProfile, filters=filters) + + @log.log + def create_service_profile(self, context, service_profile): + profile = service_profile['service_profile'] + tenant_id = self._get_tenant_id_for_create(context, profile) + with context.session.begin(subtransactions=True): + profile_db = ServiceProfile( + id=uuidutils.generate_uuid(), tenant_id=tenant_id, + name=profile['name'], description=profile['description'], + service_type=profile['service_type'], + insertion_mode=profile['insertion_mode'], + vendor=profile['vendor'], + service_flavor=profile['service_flavor'], + shared=profile['shared']) + context.session.add(profile_db) + return self._make_service_profile_dict(profile_db) + + @log.log + def update_service_profile(self, context, service_profile_id, + service_profile): + profile = service_profile['service_profile'] + with context.session.begin(subtransactions=True): + profile_db = self._get_service_profile(context, + service_profile_id) + profile_db.update(profile) + return self._make_service_profile_dict(profile_db) + + @log.log + def delete_service_profile(self, context, service_profile_id): + with context.session.begin(subtransactions=True): + profile_db = self._get_service_profile(context, + service_profile_id) + if profile_db.nodes: + raise schain.ServiceProfileInUse( + profile_id=service_profile_id) + context.session.delete(profile_db) + + @log.log + def get_service_profile(self, context, service_profile_id, fields=None): + profile_db = self._get_service_profile( + context, service_profile_id) + return self._make_service_profile_dict(profile_db, fields) + + @log.log + def get_service_profiles(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + marker_obj = self._get_marker_obj(context, 'service_profile', + limit, marker) + return self._get_collection(context, ServiceProfile, + self._make_service_profile_dict, + filters=filters, fields=fields, + sorts=sorts, limit=limit, + marker_obj=marker_obj, + page_reverse=page_reverse) diff --git a/gbpservice/neutron/extensions/servicechain.py b/gbpservice/neutron/extensions/servicechain.py index d322789a5..f914183c7 100644 --- a/gbpservice/neutron/extensions/servicechain.py +++ b/gbpservice/neutron/extensions/servicechain.py @@ -23,6 +23,7 @@ from oslo_log import log as logging import six import gbpservice.neutron.extensions +from gbpservice.neutron.services.servicechain.common import constants as scc # The code below is a monkey patch of key Neutron's modules. This is needed for @@ -37,6 +38,15 @@ LOG = logging.getLogger(__name__) # Service Chain Exceptions +class ServiceProfileNotFound(nexc.NotFound): + message = _("ServiceProfile %(profile_id)s could not be found") + + +class ServiceProfileInUse(nexc.NotFound): + message = _("Unable to complete operation, ServiceProfile " + "%(profile_id)s is in use") + + class ServiceChainNodeNotFound(nexc.NotFound): message = _("ServiceChainNode %(sc_node_id)s could not be found") @@ -94,6 +104,7 @@ attr.validators['type:string_list'] = _validate_str_list SERVICECHAIN_NODES = 'servicechain_nodes' SERVICECHAIN_SPECS = 'servicechain_specs' SERVICECHAIN_INSTANCES = 'servicechain_instances' +SERVICE_PROFILES = 'service_profiles' RESOURCE_ATTRIBUTE_MAP = { SERVICECHAIN_NODES: { @@ -110,8 +121,11 @@ RESOURCE_ATTRIBUTE_MAP = { 'validate': {'type:string': None}, 'required_by_policy': True, 'is_visible': True}, 'service_type': {'allow_post': True, 'allow_put': False, - 'validate': {'type:string': None}, - 'required': True, 'is_visible': True}, + 'validate': {'type:string_or_none': None}, + 'is_visible': True, 'default': None}, + 'service_profile_id': {'allow_post': True, 'allow_put': True, + 'validate': {'type:uuid_or_none': None}, + 'is_visible': True, 'default': None}, 'config': {'allow_post': True, 'allow_put': False, 'validate': {'type:string': None}, 'required': True, 'is_visible': True}, @@ -180,6 +194,37 @@ RESOURCE_ATTRIBUTE_MAP = { 'validate': {'type:string': None}, 'default': "", 'is_visible': True}, }, + SERVICE_PROFILES: { + '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}, + 'default': '', 'is_visible': True}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, 'is_visible': True}, + attr.SHARED: {'allow_post': True, 'allow_put': True, + 'default': False, 'convert_to': attr.convert_to_boolean, + 'is_visible': True, 'required_by_policy': True, + 'enforce_policy': True}, + 'vendor': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'insertion_mode': {'allow_post': True, 'allow_put': True, + 'validate': {'type:values': + scc.VALID_INSERTION_MODES}, + 'is_visible': True, 'default': None}, + 'service_type': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'required': True}, + 'service_flavor': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string_or_none': None}, + 'is_visible': True, 'default': None}, + }, } @@ -321,3 +366,34 @@ class ServiceChainPluginBase(service_base.ServicePluginBase): @log.log def delete_servicechain_instance(self, context, servicechain_instance_id): pass + + @abc.abstractmethod + @log.log + def get_service_profile_count(self, context, filters=None): + pass + + @abc.abstractmethod + @log.log + def create_service_profile(self, context, service_profile): + pass + + @abc.abstractmethod + @log.log + def update_service_profile(self, context, service_profile_id, + service_profile): + pass + + @abc.abstractmethod + @log.log + def delete_service_profile(self, context, service_profile_id): + pass + + @abc.abstractmethod + @log.log + def get_service_profile(self, context, service_profile_id, fields=None): + pass + + @abc.abstractmethod + @log.log + def get_service_profiles(self, context, filters=None, fields=None): + pass diff --git a/gbpservice/neutron/services/grouppolicy/common/exceptions.py b/gbpservice/neutron/services/grouppolicy/common/exceptions.py index 42b21234b..40dac9b65 100644 --- a/gbpservice/neutron/services/grouppolicy/common/exceptions.py +++ b/gbpservice/neutron/services/grouppolicy/common/exceptions.py @@ -125,7 +125,7 @@ class NonSharedNetworkOnSharedL2PolicyNotSupported(GroupPolicyBadRequest): class InvalidSharedAttributeUpdate(GroupPolicyBadRequest): - message = _("Invalid shared attribute update. Shared resource %(id)s is" + message = _("Invalid shared attribute update. Shared resource %(id)s is " "referenced by %(rid)s, which is either shared or owned by a " "different tenant.") diff --git a/gbpservice/neutron/services/servicechain/common/constants.py b/gbpservice/neutron/services/servicechain/common/constants.py new file mode 100644 index 000000000..2792f237e --- /dev/null +++ b/gbpservice/neutron/services/servicechain/common/constants.py @@ -0,0 +1,23 @@ +# 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. + +INSERTION_MODE_L2 = 'l2' +INSERTION_MODE_L3 = 'l3' +INSERTION_MODE_BITW = 'bitw' +INSERTION_MODE_TAP = 'tap' +INSERTION_MODE_NONE = None + +VALID_INSERTION_MODES = [INSERTION_MODE_L2, + INSERTION_MODE_L3, + INSERTION_MODE_BITW, + INSERTION_MODE_TAP, + INSERTION_MODE_NONE] diff --git a/gbpservice/neutron/services/servicechain/plugins/msc/context.py b/gbpservice/neutron/services/servicechain/plugins/msc/context.py index 213fd2404..0e494fbb0 100644 --- a/gbpservice/neutron/services/servicechain/plugins/msc/context.py +++ b/gbpservice/neutron/services/servicechain/plugins/msc/context.py @@ -24,7 +24,18 @@ class ServiceChainNodeContext(ServiceChainContext): original_sc_node=None): super(ServiceChainNodeContext, self).__init__(plugin, plugin_context) self._sc_node = sc_node + self._profile = None + if self._sc_node['service_profile_id']: + self._profile = self._plugin.get_service_profile( + self._plugin_context, self._sc_node['service_profile_id']) + self._original_sc_node = original_sc_node + self._original_profile = None + if (self._original_sc_node and + self._original_sc_node['service_profile_id']): + self._original_profile = self._plugin.get_service_profile( + self._plugin_context, + self._original_sc_node['service_profile_id']) @property def current(self): @@ -34,6 +45,14 @@ class ServiceChainNodeContext(ServiceChainContext): def original(self): return self._original_sc_node + @property + def current_profile(self): + return self._profile + + @property + def original_profile(self): + return self._original_profile + class ServiceChainSpecContext(ServiceChainContext): @@ -68,3 +87,20 @@ class ServiceChainInstanceContext(ServiceChainContext): @property def original(self): return self._original_sc_instance + + +class ServiceProfileContext(ServiceChainContext): + + def __init__(self, plugin, plugin_context, profile, + original_profile=None): + super(ServiceProfileContext, self).__init__(plugin, plugin_context) + self._profile = profile + self._original_profile = original_profile + + @property + def current(self): + return self._profile + + @property + def original(self): + return self._original_profile diff --git a/gbpservice/neutron/services/servicechain/plugins/msc/driver_manager.py b/gbpservice/neutron/services/servicechain/plugins/msc/driver_manager.py index 86ea780c8..28f87cf69 100644 --- a/gbpservice/neutron/services/servicechain/plugins/msc/driver_manager.py +++ b/gbpservice/neutron/services/servicechain/plugins/msc/driver_manager.py @@ -156,3 +156,27 @@ class DriverManager(stevedore.named.NamedExtensionManager): def delete_servicechain_instance_postcommit(self, context): self._call_on_drivers("delete_servicechain_instance_postcommit", context) + + def create_service_profile_precommit(self, context): + self._call_on_drivers("create_service_profile_precommit", + context) + + def create_service_profile_postcommit(self, context): + self._call_on_drivers("create_service_profile_postcommit", + context) + + def update_service_profile_precommit(self, context): + self._call_on_drivers("update_service_profile_precommit", + context) + + def update_service_profile_postcommit(self, context): + self._call_on_drivers("update_service_profile_postcommit", + context) + + def delete_service_profile_precommit(self, context): + self._call_on_drivers("delete_service_profile_precommit", + context) + + def delete_service_profile_postcommit(self, context): + self._call_on_drivers("delete_service_profile_postcommit", + context) diff --git a/gbpservice/neutron/services/servicechain/plugins/msc/drivers/dummy_driver.py b/gbpservice/neutron/services/servicechain/plugins/msc/drivers/dummy_driver.py index 7424258b4..614419a39 100644 --- a/gbpservice/neutron/services/servicechain/plugins/msc/drivers/dummy_driver.py +++ b/gbpservice/neutron/services/servicechain/plugins/msc/drivers/dummy_driver.py @@ -90,3 +90,27 @@ class NoopDriver(object): @log.log def delete_servicechain_instance_postcommit(self, context): pass + + @log.log + def create_service_profile_precommit(self, context): + pass + + @log.log + def create_service_profile_postcommit(self, context): + pass + + @log.log + def update_service_profile_precommit(self, context): + pass + + @log.log + def update_service_profile_postcommit(self, context): + pass + + @log.log + def delete_service_profile_precommit(self, context): + pass + + @log.log + def delete_service_profile_postcommit(self, context): + pass \ No newline at end of file diff --git a/gbpservice/neutron/services/servicechain/plugins/msc/drivers/simplechain_driver.py b/gbpservice/neutron/services/servicechain/plugins/msc/drivers/simplechain_driver.py index e06ef4ab6..998e0afda 100644 --- a/gbpservice/neutron/services/servicechain/plugins/msc/drivers/simplechain_driver.py +++ b/gbpservice/neutron/services/servicechain/plugins/msc/drivers/simplechain_driver.py @@ -14,7 +14,6 @@ import ast import time from heatclient import client as heat_client - from heatclient import exc as heat_exc from neutron.common import log from neutron.db import model_base @@ -75,8 +74,12 @@ class SimpleChainDriver(object): @log.log def create_servicechain_node_precommit(self, context): - if context.current['service_type'] not in sc_supported_type: - raise exc.InvalidServiceTypeForReferenceDriver() + if context.current['service_profile_id'] is None: + if context.current['service_type'] not in sc_supported_type: + raise exc.InvalidServiceTypeForReferenceDriver() + elif context.current['service_type']: + LOG.warn(_('Both service_profile_id and service_type are' + 'specified, service_type will be ignored.')) @log.log def create_servicechain_node_postcommit(self, context): @@ -168,6 +171,31 @@ class SimpleChainDriver(object): self._delete_servicechain_instance_stacks(context._plugin_context, context.current['id']) + @log.log + def create_service_profile_precommit(self, context): + if context.current['service_type'] not in sc_supported_type: + raise exc.InvalidServiceTypeForReferenceDriver() + + @log.log + def create_service_profile_postcommit(self, context): + pass + + @log.log + def update_service_profile_precommit(self, context): + pass + + @log.log + def update_service_profile_postcommit(self, context): + pass + + @log.log + def delete_service_profile_precommit(self, context): + pass + + @log.log + def delete_service_profile_postcommit(self, context): + pass + def _get_ptg(self, context, ptg_id): return self._get_resource(self._grouppolicy_plugin, context._plugin_context, diff --git a/gbpservice/neutron/services/servicechain/plugins/msc/plugin.py b/gbpservice/neutron/services/servicechain/plugins/msc/plugin.py index 49796df36..08f2f9a62 100644 --- a/gbpservice/neutron/services/servicechain/plugins/msc/plugin.py +++ b/gbpservice/neutron/services/servicechain/plugins/msc/plugin.py @@ -240,3 +240,72 @@ class ServiceChainPlugin(servicechain_db.ServiceChainDbPlugin, LOG.exception(_("delete_servicechain_instance_postcommit failed " "for servicechain_instance %s"), servicechain_instance_id) + + @log.log + def create_service_profile(self, context, service_profile): + session = context.session + with session.begin(subtransactions=True): + result = super(ServiceChainPlugin, + self).create_service_profile( + context, service_profile) + self._validate_shared_create(context, result, 'service_profile') + sc_context = servicechain_context.ServiceProfileContext( + self, context, result) + self.driver_manager.create_service_profile_precommit( + sc_context) + + try: + self.driver_manager.create_service_profile_postcommit( + sc_context) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error(_( + "driver_manager.create_service_profile_postcommit " + "failed, deleting service_profile %s"), + result['id']) + self.delete_service_profile(context, result['id']) + + return result + + @log.log + def update_service_profile(self, context, service_profile_id, + service_profile): + session = context.session + with session.begin(subtransactions=True): + original_profile = self.get_service_profile( + context, service_profile_id) + updated_profile = super(ServiceChainPlugin, + self).update_service_profile( + context, service_profile_id, service_profile) + self._validate_shared_update(context, original_profile, + updated_profile, 'service_profile') + sc_context = servicechain_context.ServiceProfileContext( + self, context, updated_profile, + original_profile=original_profile) + self.driver_manager.update_service_profile_precommit( + sc_context) + + self.driver_manager.update_service_profile_postcommit( + sc_context) + return updated_profile + + @log.log + def delete_service_profile(self, context, service_profile_id): + session = context.session + with session.begin(subtransactions=True): + profile = self.get_service_profile( + context, service_profile_id) + sc_context = servicechain_context.ServiceProfileContext( + self, context, profile) + self.driver_manager.delete_service_profile_precommit( + sc_context) + super(ServiceChainPlugin, self).delete_service_profile( + context, service_profile_id) + + try: + self.driver_manager.delete_service_profile_postcommit( + sc_context) + except Exception: + LOG.exception(_("delete_service_profile_postcommit failed " + "for service_profile %s"), + service_profile_id) diff --git a/gbpservice/neutron/services/servicechain/plugins/sharing.py b/gbpservice/neutron/services/servicechain/plugins/sharing.py index ed12167e8..673efd1e2 100644 --- a/gbpservice/neutron/services/servicechain/plugins/sharing.py +++ b/gbpservice/neutron/services/servicechain/plugins/sharing.py @@ -30,8 +30,11 @@ class SharingMixin(object): usage_graph = {'servicechain_spec': {'nodes': 'servicechain_node'}, - 'servicechain_node': {}, - 'servicechain_instance': {}} + 'servicechain_node': {'service_profile_id': + 'service_profile'}, + 'servicechain_instance': {}, + 'service_profile': {}, + } _plurals = None @property @@ -72,3 +75,7 @@ class SharingMixin(object): gbp_plugin.GroupPolicyPlugin._check_shared_or_different_tenant( context, obj, self.gbp_plugin.get_policy_actions, 'action_value', [obj['id']]) + + def _validate_service_profile_unshare(self, context, obj): + gbp_plugin.GroupPolicyPlugin._check_shared_or_different_tenant( + context, obj, self.get_servicechain_nodes, 'service_profile_id') diff --git a/gbpservice/neutron/tests/etc/test-policy.json b/gbpservice/neutron/tests/etc/test-policy.json index e161f236d..b14f56b2c 100644 --- a/gbpservice/neutron/tests/etc/test-policy.json +++ b/gbpservice/neutron/tests/etc/test-policy.json @@ -19,6 +19,7 @@ "shared_nsp": "field:network_service_policies:shared=True", "shared_scn": "field:servicechain_nodes:shared=True", "shared_scs": "field:servicechain_specs:shared=True", + "shared_sp": "field:service_profiles:shared=True", "create_policy_target_group": "", "get_policy_target_group": "rule:admin_or_owner or rule:shared_ptg", @@ -65,5 +66,10 @@ "create_servicechain_instance": "", "get_servicechain_instance": "rule:admin_or_owner", - "update_servicechain_instance:shared": "rule:admin_only" + "update_servicechain_instance:shared": "rule:admin_only", + + "create_service_profile": "", + "create_service_profile:shared": "rule:admin_only", + "get_service_profile": "rule:admin_or_owner or rule:shared_sp", + "update_service_profile:shared": "rule:admin_only" } diff --git a/gbpservice/neutron/tests/unit/common.py b/gbpservice/neutron/tests/unit/common.py index 65dc25b2b..1d5713d1c 100644 --- a/gbpservice/neutron/tests/unit/common.py +++ b/gbpservice/neutron/tests/unit/common.py @@ -293,6 +293,27 @@ def get_update_nat_pool_attrs(): return {'name': 'new_name'} +@gbp_attributes +def get_create_service_profile_default_attrs(): + return {'name': '', 'description': ''} + + +@gbp_attributes +def get_create_service_profile_attrs(): + return { + 'name': 'serviceprofile1', + 'service_type': 'FIREWALL', + 'description': 'test service profile', + } + + +@gbp_attributes +def get_update_service_profile_attrs(): + return { + 'name': 'new_name', + } + + def get_resource_plural(resource): if resource.endswith('y'): resource_plural = resource.replace('y', 'ies') diff --git a/gbpservice/neutron/tests/unit/db/grouppolicy/test_servicechain_db.py b/gbpservice/neutron/tests/unit/db/grouppolicy/test_servicechain_db.py index 8368725e2..19eda8dcc 100644 --- a/gbpservice/neutron/tests/unit/db/grouppolicy/test_servicechain_db.py +++ b/gbpservice/neutron/tests/unit/db/grouppolicy/test_servicechain_db.py @@ -15,6 +15,8 @@ import webob.exc from neutron.api import extensions from neutron import context +from neutron.db import api as db_api +from neutron.db import model_base from neutron.openstack.common import uuidutils from neutron.plugins.common import constants from neutron.tests.unit.api import test_extensions @@ -23,6 +25,7 @@ from oslo_utils import importutils from gbpservice.neutron.db import servicechain_db as svcchain_db from gbpservice.neutron.extensions import servicechain as service_chain +from gbpservice.neutron.services.servicechain.common import constants as sccon JSON_FORMAT = 'json' @@ -55,12 +58,24 @@ class ServiceChainDBTestBase(object): self.assertEqual(sorted([i['id'] for i in res[resource_plural]]), sorted([i[resource]['id'] for i in items])) - def _get_test_servicechain_node_attrs(self, name='scn1', - description='test scn', - service_type=constants.FIREWALL, - config="{}", shared=False): + def _get_test_service_profile_attrs( + self, name='sp1', description='test sp', + service_type=constants.LOADBALANCER, vendor='', + insertion_mode=sccon.INSERTION_MODE_L3, service_flavor=''): attrs = {'name': name, 'description': description, 'service_type': service_type, + 'vendor': vendor, 'insertion_mode': insertion_mode, + 'service_flavor': service_flavor, + 'tenant_id': self._tenant_id} + + return attrs + + def _get_test_servicechain_node_attrs(self, name='scn1', + description='test scn', + service_profile_id=None, + config="{}", shared=False): + attrs = {'name': name, 'description': description, + 'service_profile_id': service_profile_id, 'config': config, 'tenant_id': self._tenant_id, 'shared': shared} @@ -96,13 +111,38 @@ class ServiceChainDBTestBase(object): return attrs - def create_servicechain_node(self, service_type=constants.FIREWALL, + def create_service_profile(self, service_type=constants.FIREWALL, + insertion_mode=sccon.INSERTION_MODE_L3, + vendor='', service_flavor='', + expected_res_status=None, **kwargs): + defaults = {'name': 'sp1', 'description': 'test sp'} + defaults.update(kwargs) + data = {'service_profile': {'service_type': service_type, + 'service_flavor': service_flavor, + 'tenant_id': self._tenant_id, + 'insertion_mode': insertion_mode, + 'vendor': vendor}} + data['service_profile'].update(defaults) + + scn_req = self.new_create_request('service_profiles', data, self.fmt) + scn_res = scn_req.get_response(self.ext_api) + + if expected_res_status: + self.assertEqual(scn_res.status_int, expected_res_status) + elif scn_res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError(code=scn_res.status_int) + + scn = self.deserialize(self.fmt, scn_res) + + return scn + + def create_servicechain_node(self, service_profile_id=None, config="{}", expected_res_status=None, **kwargs): defaults = {'name': 'scn1', 'description': 'test scn', 'shared': False} defaults.update(kwargs) - data = {'servicechain_node': {'service_type': service_type, + data = {'servicechain_node': {'service_profile_id': service_profile_id, 'tenant_id': self._tenant_id, 'config': config}} data['servicechain_node'].update(defaults) @@ -142,7 +182,9 @@ class ServiceChainDBTestBase(object): def test_create_servicechain_specs_same_node(self): template1 = '{"key1":"value1"}' - scn = self.create_servicechain_node(config=template1) + sp = self.create_service_profile()['service_profile'] + scn = self.create_servicechain_node( + config=template1, service_profile_id=sp['id']) scn_id = scn['servicechain_node']['id'] spec1 = {"servicechain_spec": {'name': 'scs1', 'tenant_id': self._tenant_id, @@ -198,6 +240,16 @@ class ServiceChainDBTestBase(object): return sci + def _create_profiled_servicechain_node( + self, service_type=constants.LOADBALANCER, shared_profile=False, + profile_tenant_id=None, **kwargs): + prof = self.create_service_profile( + service_type=service_type, + shared=shared_profile, + tenant_id=profile_tenant_id or self._tenant_id)['service_profile'] + return self.create_servicechain_node( + service_profile_id=prof['id'], **kwargs) + class ServiceChainDBTestPlugin(svcchain_db.ServiceChainDbPlugin): @@ -230,6 +282,8 @@ class ServiceChainDbTestCase(ServiceChainDBTestBase, if not ext_mgr: ext_mgr = extensions.PluginAwareExtensionManager.get_instance() self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr) + engine = db_api.get_engine() + model_base.BASEV2.metadata.create_all(engine) class TestServiceChainResources(ServiceChainDbTestCase): @@ -245,11 +299,14 @@ class TestServiceChainResources(ServiceChainDbTestCase): self.assertEqual(v, res[resource][k]) def test_create_and_show_servicechain_node(self): + profile = self.create_service_profile() attrs = self._get_test_servicechain_node_attrs( - service_type=constants.LOADBALANCER) + service_profile_id=profile['service_profile']['id'], + config="config1") scn = self.create_servicechain_node( - service_type=constants.LOADBALANCER) + service_profile_id=profile['service_profile']['id'], + config="config1") for k, v in attrs.iteritems(): self.assertEqual(v, scn['servicechain_node'][k]) @@ -259,19 +316,26 @@ class TestServiceChainResources(ServiceChainDbTestCase): attrs) def test_list_servicechain_nodes(self): - scns = [self.create_servicechain_node(name='scn1', description='scn'), - self.create_servicechain_node(name='scn2', description='scn'), - self.create_servicechain_node(name='scn3', description='scn')] + scns = [ + self._create_profiled_servicechain_node(name='scn1', + description='scn'), + self._create_profiled_servicechain_node(name='scn2', + description='scn'), + self._create_profiled_servicechain_node(name='scn3', + description='scn')] self._test_list_resources('servicechain_node', scns, query_params='description=scn') def test_update_servicechain_node(self): name = 'new_servicechain_node' description = 'new desc' - attrs = self._get_test_servicechain_node_attrs(name=name, - description=description) + profile = self.create_service_profile() + attrs = self._get_test_servicechain_node_attrs( + name=name, description=description, + service_profile_id=profile['service_profile']['id']) - scn = self.create_servicechain_node() + scn = self.create_servicechain_node( + service_profile_id=profile['service_profile']['id']) data = {'servicechain_node': {'name': name, 'description': description}} @@ -289,7 +353,7 @@ class TestServiceChainResources(ServiceChainDbTestCase): def test_delete_servicechain_node(self): ctx = context.get_admin_context() - scn = self.create_servicechain_node() + scn = self._create_profiled_servicechain_node() scn_id = scn['servicechain_node']['id'] scs = self.create_servicechain_spec(nodes=[scn_id]) @@ -313,7 +377,7 @@ class TestServiceChainResources(ServiceChainDbTestCase): def test_create_and_show_servicechain_spec(self): name = "scs1" - scn = self.create_servicechain_node() + scn = self._create_profiled_servicechain_node() scn_id = scn['servicechain_node']['id'] attrs = self._get_test_servicechain_spec_attrs(name, nodes=[scn_id]) @@ -329,9 +393,9 @@ class TestServiceChainResources(ServiceChainDbTestCase): def test_create_spec_multiple_nodes(self): name = "scs1" - scn1 = self.create_servicechain_node() + scn1 = self._create_profiled_servicechain_node() scn1_id = scn1['servicechain_node']['id'] - scn2 = self.create_servicechain_node() + scn2 = self._create_profiled_servicechain_node() scn2_id = scn2['servicechain_node']['id'] attrs = self._get_test_servicechain_spec_attrs( name, nodes=[scn1_id, scn2_id]) @@ -348,8 +412,10 @@ class TestServiceChainResources(ServiceChainDbTestCase): query_params='description=scs') def test_node_ordering_list_servicechain_specs(self): - scn1_id = self.create_servicechain_node()['servicechain_node']['id'] - scn2_id = self.create_servicechain_node()['servicechain_node']['id'] + scn1_id = self._create_profiled_servicechain_node()[ + 'servicechain_node']['id'] + scn2_id = self._create_profiled_servicechain_node()[ + 'servicechain_node']['id'] nodes_list = [scn1_id, scn2_id] scs = self.create_servicechain_spec(name='scs1', nodes=nodes_list) @@ -376,7 +442,8 @@ class TestServiceChainResources(ServiceChainDbTestCase): def test_update_servicechain_spec(self): name = "new_servicechain_spec1" description = 'new desc' - scn_id = self.create_servicechain_node()['servicechain_node']['id'] + scn_id = self._create_profiled_servicechain_node()[ + 'servicechain_node']['id'] attrs = self._get_test_servicechain_spec_attrs(name=name, description=description, nodes=[scn_id]) @@ -558,3 +625,69 @@ class TestServiceChainResources(ServiceChainDbTestCase): self.assertRaises(service_chain.ServiceChainInstanceNotFound, self.plugin.get_servicechain_instance, ctx, sci_id) + + def test_create_and_show_service_profile(self): + attrs = self._get_test_service_profile_attrs( + service_type=constants.FIREWALL, vendor="vendor1") + + scn = self.create_service_profile( + service_type=constants.FIREWALL, vendor="vendor1") + + for k, v in attrs.iteritems(): + self.assertEqual(scn['service_profile'][k], v) + + self._test_show_resource('service_profile', + scn['service_profile']['id'], attrs) + + def test_list_service_profile(self): + scns = [self.create_service_profile(name='sp1', description='sp'), + self.create_service_profile(name='sp2', description='sp'), + self.create_service_profile(name='sp3', description='sp')] + self._test_list_resources('service_profile', scns, + query_params='description=sp') + + def test_update_service_profile(self): + name = 'new_service_profile' + description = 'new desc' + attrs = self._get_test_service_profile_attrs( + name=name, description=description, + service_type=constants.FIREWALL) + + scn = self.create_service_profile() + + data = {'service_profile': {'name': name, + 'description': description}} + req = self.new_update_request('service_profiles', data, + scn['service_profile']['id']) + res = self.deserialize(self.fmt, req.get_response(self.ext_api)) + + for k, v in attrs.iteritems(): + self.assertEqual(res['service_profile'][k], v) + + self._test_show_resource('service_profile', + scn['service_profile']['id'], attrs) + + def test_delete_service_profile(self): + ctx = context.get_admin_context() + + sp = self.create_service_profile() + sp_id = sp['service_profile']['id'] + + scn = self.create_servicechain_node(service_profile_id=sp_id) + scn_id = scn['servicechain_node']['id'] + + # Deleting Service Chain Node in use by a Spec should fail + self.assertRaises(service_chain.ServiceProfileInUse, + self.plugin.delete_service_profile, ctx, sp_id) + + req = self.new_delete_request('servicechain_nodes', scn_id) + res = req.get_response(self.ext_api) + self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code) + + # After deleting the Service Chain Spec, node delete should succeed + req = self.new_delete_request('service_profiles', sp_id) + res = req.get_response(self.ext_api) + self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code) + self.assertRaises(service_chain.ServiceProfileNotFound, + self.plugin.get_service_profile, + ctx, sp_id) diff --git a/gbpservice/neutron/tests/unit/services/grouppolicy/test_resource_mapping.py b/gbpservice/neutron/tests/unit/services/grouppolicy/test_resource_mapping.py index 658c70316..aa4cd1620 100644 --- a/gbpservice/neutron/tests/unit/services/grouppolicy/test_resource_mapping.py +++ b/gbpservice/neutron/tests/unit/services/grouppolicy/test_resource_mapping.py @@ -42,6 +42,7 @@ from gbpservice.neutron.tests.unit.services.grouppolicy import ( test_grouppolicy_plugin as test_plugin) +SERVICE_PROFILES = 'servicechain/service_profiles' SERVICECHAIN_NODES = 'servicechain/servicechain_nodes' SERVICECHAIN_SPECS = 'servicechain/servicechain_specs' SERVICECHAIN_INSTANCES = 'servicechain/servicechain_instances' @@ -1479,8 +1480,17 @@ class TestPolicyRuleSet(ResourceMappingTestCase): self._verify_prs_rules(policy_rule_set_id) + def _create_service_profile(self, node_type='LOADBALANCER'): + data = {'service_profile': {'service_type': node_type, + 'tenant_id': self._tenant_id}} + scn_req = self.new_create_request(SERVICE_PROFILES, data, self.fmt) + node = self.deserialize(self.fmt, scn_req.get_response(self.ext_api)) + scn_id = node['service_profile']['id'] + return scn_id + def _create_servicechain_node(self, node_type="LOADBALANCER"): - data = {'servicechain_node': {'service_type': node_type, + profile_id = self._create_service_profile(node_type) + data = {'servicechain_node': {'service_profile_id': profile_id, 'tenant_id': self._tenant_id, 'config': "{}"}} scn_req = self.new_create_request(SERVICECHAIN_NODES, data, self.fmt) @@ -1623,12 +1633,19 @@ class TestPolicyRuleSet(ResourceMappingTestCase): self._assert_proper_chain_instance(sc_instance, provider_ptg_id, consumer_ptg_id, [scs_id]) - data = {'servicechain_node': {'service_type': "FIREWALL", + data = {'service_profile': {'service_type': "FIREWALL", + 'tenant_id': self._tenant_id}} + service_profile = self.new_create_request(SERVICE_PROFILES, + data, self.fmt) + service_profile = self.deserialize( + self.fmt, service_profile.get_response(self.ext_api)) + sp_id = service_profile['service_profile']['id'] + data = {'servicechain_node': {'service_profile_id': sp_id, 'tenant_id': self._tenant_id, 'config': "{}"}} scn_req = self.new_create_request(SERVICECHAIN_NODES, data, self.fmt) new_node = self.deserialize( - self.fmt, scn_req.get_response(self.ext_api)) + self.fmt, scn_req.get_response(self.ext_api)) new_scn_id = new_node['servicechain_node']['id'] data = {'servicechain_spec': {'tenant_id': self._tenant_id, 'nodes': [new_scn_id]}} @@ -1856,7 +1873,14 @@ class TestPolicyRuleSet(ResourceMappingTestCase): child_prs_id = child_prs['policy_rule_set']['id'] self._verify_prs_rules(child_prs_id) - data = {'servicechain_node': {'service_type': "FIREWALL", + data = {'service_profile': {'service_type': "FIREWALL", + 'tenant_id': self._tenant_id}} + service_profile = self.new_create_request(SERVICE_PROFILES, + data, self.fmt) + service_profile = self.deserialize( + self.fmt, service_profile.get_response(self.ext_api)) + sp_id = service_profile['service_profile']['id'] + data = {'servicechain_node': {'service_profile_id': sp_id, 'tenant_id': self._tenant_id, 'config': "{}"}} parent_scn_req = self.new_create_request(SERVICECHAIN_NODES, diff --git a/gbpservice/neutron/tests/unit/services/servicechain/test_servicechain_plugin.py b/gbpservice/neutron/tests/unit/services/servicechain/test_servicechain_plugin.py index 875dea133..f921ff580 100644 --- a/gbpservice/neutron/tests/unit/services/servicechain/test_servicechain_plugin.py +++ b/gbpservice/neutron/tests/unit/services/servicechain/test_servicechain_plugin.py @@ -11,8 +11,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from neutron import context as n_ctx from oslo_config import cfg +from gbpservice.neutron.services.servicechain.plugins.msc import context from gbpservice.neutron.tests.unit.db.grouppolicy import ( test_servicechain_db as test_servicechain_db) @@ -37,4 +39,158 @@ class ServiceChainPluginTestCase(test_servicechain_db.ServiceChainDbTestCase): class TestGroupPolicyPluginGroupResources( ServiceChainPluginTestCase, test_servicechain_db.TestServiceChainResources): - pass + + def test_spec_shared(self): + # Shared spec can only point shared nodes + node = self._create_profiled_servicechain_node( + 'LOADBALANCER', shared=True, shared_profile=True, + profile_tenant_id='admin', tenant_id='admin')['servicechain_node'] + self.create_servicechain_spec(nodes=[node['id']], shared=True, + expected_res_status=201) + self.create_servicechain_spec(nodes=[node['id']], shared=False, + tenant_id='admin', + expected_res_status=201) + + node = self._create_profiled_servicechain_node( + 'LOADBALANCER', shared=False, profile_tenant_id='nonadmin', + tenant_id='nonadmin')['servicechain_node'] + self.create_servicechain_spec(nodes=[node['id']], shared=True, + expected_res_status=400) + self.create_servicechain_spec(nodes=[node['id']], shared=True, + tenant_id='nonadmin', + expected_res_status=400) + self.create_servicechain_spec(nodes=[node['id']], shared=False, + tenant_id='nonadmin', + expected_res_status=201) + + def test_node_shared(self): + # Shared node can only point shared profile + prof = self.create_service_profile( + service_type='LOADBALANCER', shared=True, + tenant_id='admin')['service_profile'] + to_update = self.create_servicechain_node( + service_profile_id=prof['id'], shared=True, + expected_res_status=201)['servicechain_node'] + self.create_servicechain_node( + service_profile_id=prof['id'], shared=False, tenant_id='admin', + expected_res_status=201) + + prof = self.create_service_profile( + service_type='LOADBALANCER', shared=False, + tenant_id='admin')['service_profile'] + self.create_servicechain_node( + service_profile_id=prof['id'], shared=True, + expected_res_status=400) + self.create_servicechain_node( + service_profile_id=prof['id'], shared=True, + tenant_id='admin', expected_res_status=400) + self.create_servicechain_node( + service_profile_id=prof['id'], shared=False, + tenant_id='admin', expected_res_status=201) + + self.create_servicechain_spec(nodes=[to_update['id']], shared=True, + tenant_id='nonadmin', + expected_res_status=201) + + data = {'servicechain_node': {'shared': False}} + req = self.new_update_request('servicechain_nodes', data, + to_update['id']) + res = req.get_response(self.ext_api) + self.assertEqual(400, res.status_int) + res = self.deserialize(self.fmt, res) + self.assertEqual('InvalidSharedAttributeUpdate', + res['NeutronError']['type']) + + def test_profile_shared(self): + prof = self.create_service_profile( + service_type='LOADBALANCER', shared=True, + tenant_id='admin')['service_profile'] + self.create_servicechain_node( + service_profile_id=prof['id'], shared=True, + expected_res_status=201) + + data = {'service_profile': {'shared': False}} + req = self.new_update_request('service_profiles', data, + prof['id']) + res = req.get_response(self.ext_api) + self.assertEqual(400, res.status_int) + res = self.deserialize(self.fmt, res) + self.assertEqual('InvalidSharedAttributeUpdate', + res['NeutronError']['type']) + + prof = self.create_service_profile( + service_type='LOADBALANCER', shared=False)['service_profile'] + self.create_servicechain_node( + service_profile_id=prof['id'], shared=False, + expected_res_status=201) + + data = {'service_profile': {'shared': True}} + req = self.new_update_request('service_profiles', data, + prof['id']) + res = req.get_response(self.ext_api) + self.assertEqual(200, res.status_int) + res = self.deserialize(self.fmt, res) + self.assertTrue(res['service_profile']['shared']) + + def test_node_context_profile(self): + + # Current node with profile + plugin_context = n_ctx.get_admin_context() + plugin_context.is_admin = plugin_context.is_advsvc = False + plugin_context.tenant_id = 'test-tenant' + + prof = self.create_service_profile( + service_type='LOADBALANCER')['service_profile'] + current = self.create_servicechain_node( + service_profile_id=prof['id'], + expected_res_status=201)['servicechain_node'] + ctx = context.ServiceChainNodeContext(self.plugin, plugin_context, + current) + + self.assertIsNone(ctx.original) + self.assertIsNone(ctx.original_profile) + self.assertEqual(ctx.current['id'], current['id']) + self.assertEqual(ctx.current_profile['id'], prof['id']) + + # Original node with profile + + prof2 = self.create_service_profile( + service_type='LOADBALANCER')['service_profile'] + original = self.create_servicechain_node( + service_profile_id=prof2['id'], + expected_res_status=201)['servicechain_node'] + ctx = context.ServiceChainNodeContext(self.plugin, plugin_context, + current, original) + + self.assertEqual(ctx.original['id'], original['id']) + self.assertEqual(ctx.original_profile['id'], prof2['id']) + self.assertEqual(ctx.current['id'], current['id']) + self.assertEqual(ctx.current_profile['id'], prof['id']) + + def test_node_context_no_profile(self): + + plugin_context = n_ctx.get_admin_context() + plugin_context.is_admin = plugin_context.is_advsvc = False + plugin_context.tenant_id = 'test_tenant' + + current = self.create_servicechain_node( + service_type='TEST', + expected_res_status=201)['servicechain_node'] + ctx = context.ServiceChainNodeContext(self.plugin, plugin_context, + current) + + self.assertIsNone(ctx.original) + self.assertIsNone(ctx.original_profile) + self.assertEqual(ctx.current['id'], current['id']) + self.assertIsNone(ctx.current_profile) + + original = self.create_servicechain_node( + service_type='TEST', + expected_res_status=201)['servicechain_node'] + ctx = context.ServiceChainNodeContext(self.plugin, plugin_context, + current, original) + + self.assertEqual(ctx.original['id'], original['id']) + self.assertIsNone(ctx.original_profile) + self.assertEqual(ctx.current['id'], current['id']) + self.assertIsNone(ctx.current_profile) diff --git a/gbpservice/neutron/tests/unit/services/servicechain/test_simple_chain_driver.py b/gbpservice/neutron/tests/unit/services/servicechain/test_simple_chain_driver.py index d1f84d1d6..e4eaec7bb 100644 --- a/gbpservice/neutron/tests/unit/services/servicechain/test_simple_chain_driver.py +++ b/gbpservice/neutron/tests/unit/services/servicechain/test_simple_chain_driver.py @@ -69,27 +69,32 @@ class SimpleChainDriverTestCase( class TestServiceChainInstance(SimpleChainDriverTestCase): def test_invalid_service_type_rejected(self): - res = self.create_servicechain_node( - service_type="test", config='{}', + res = self.create_service_profile( + service_type="test", expected_res_status=webob.exc.HTTPBadRequest.code) self.assertEqual('InvalidServiceTypeForReferenceDriver', res['NeutronError']['type']) def test_chain_node_create_success(self): - res = self.create_servicechain_node( + res = self._create_profiled_servicechain_node( service_type=constants.FIREWALL, config='{}', expected_res_status=webob.exc.HTTPCreated.code) - self.assertEqual(constants.FIREWALL, - res['servicechain_node']['service_type']) + self.assertEqual('{}', res['servicechain_node']['config']) + + def test_chain_node_create_success_service_type(self): + res = self.create_servicechain_node( + service_type=constants.FIREWALL, config='{}', + expected_res_status=webob.exc.HTTPCreated.code) + self.assertEqual('{}', res['servicechain_node']['config']) def test_chain_spec_update(self): template1 = '{"key1":"value1"}' - scn = self.create_servicechain_node(config=template1) + scn = self._create_profiled_servicechain_node(config=template1) scn1_name = scn['servicechain_node']['name'] scn_id = scn['servicechain_node']['id'] name = "scs1" template2 = '{"key2":"value2"}' - scn2 = self.create_servicechain_node(config=template2) + scn2 = self._create_profiled_servicechain_node(config=template2) scn2_id = scn2['servicechain_node']['id'] scn2_name = scn2['servicechain_node']['name'] scs = self.create_servicechain_spec(name=name, nodes=[scn_id]) @@ -159,7 +164,7 @@ class TestServiceChainInstance(SimpleChainDriverTestCase): def test_chain_instance_create(self): name = "scs1" - scn = self.create_servicechain_node() + scn = self._create_profiled_servicechain_node() scn_id = scn['servicechain_node']['id'] scs = self.create_servicechain_spec(name=name, nodes=[scn_id]) sc_spec_id = scs['servicechain_spec']['id'] @@ -183,7 +188,7 @@ class TestServiceChainInstance(SimpleChainDriverTestCase): def test_chain_instance_delete(self): name = "scs1" - scn = self.create_servicechain_node() + scn = self._create_profiled_servicechain_node() scn_id = scn['servicechain_node']['id'] scs = self.create_servicechain_spec(name=name, nodes=[scn_id]) sc_spec_id = scs['servicechain_spec']['id'] @@ -207,7 +212,7 @@ class TestServiceChainInstance(SimpleChainDriverTestCase): def test_wait_stack_delete_for_instance_delete(self): name = "scs1" - scn = self.create_servicechain_node() + scn = self._create_profiled_servicechain_node() scn_id = scn['servicechain_node']['id'] scs = self.create_servicechain_spec(name=name, nodes=[scn_id]) sc_spec_id = scs['servicechain_spec']['id'] @@ -266,7 +271,7 @@ class TestServiceChainInstance(SimpleChainDriverTestCase): def test_stack_not_found_ignored(self): name = "scs1" - scn = self.create_servicechain_node() + scn = self._create_profiled_servicechain_node() scn_id = scn['servicechain_node']['id'] scs = self.create_servicechain_spec(name=name, nodes=[scn_id]) sc_spec_id = scs['servicechain_spec']['id'] diff --git a/gbpservice/neutron/tests/unit/test_extension_servicechain.py b/gbpservice/neutron/tests/unit/test_extension_servicechain.py index 90ff74948..28e6265c0 100644 --- a/gbpservice/neutron/tests/unit/test_extension_servicechain.py +++ b/gbpservice/neutron/tests/unit/test_extension_servicechain.py @@ -31,6 +31,7 @@ SERVICECHAIN_URI = 'servicechain' SERVICECHAIN_NODES_URI = SERVICECHAIN_URI + '/' + 'servicechain_nodes' SERVICECHAIN_SPECS_URI = SERVICECHAIN_URI + '/' + 'servicechain_specs' SERVICECHAIN_INSTANCES_URI = SERVICECHAIN_URI + '/' + 'servicechain_instances' +SERVICE_PROFILE_URI = SERVICECHAIN_URI + '/' + 'service_profiles' class ServiceChainExtensionTestCase(test_extensions_base.ExtensionTestCase): @@ -73,11 +74,12 @@ class ServiceChainExtensionTestCase(test_extensions_base.ExtensionTestCase): def _get_create_servicechain_node_attrs(self): return { 'name': 'servicechain1', - 'service_type': 'FIREWALL', + 'service_profile_id': _uuid(), 'tenant_id': _uuid(), 'description': 'test servicechain node', 'config': 'test_config', - 'shared': True + 'shared': True, + 'service_type': None, } def _get_update_servicechain_node_attrs(self): @@ -89,9 +91,10 @@ class ServiceChainExtensionTestCase(test_extensions_base.ExtensionTestCase): servicechain_node_id = _uuid() data = { 'servicechain_node': { - 'service_type': 'FIREWALL', + 'service_profile_id': _uuid(), 'tenant_id': _uuid(), - 'config': 'test_config' + 'config': 'test_config', + 'service_type': None, } } default_attrs = self._get_create_servicechain_node_default_attrs()