introduce service profile model

Partially implements blueprint node-centric-chain-plugin

Change-Id: I2403e35d49a1e100f292f82082c7dad96d79fc29
This commit is contained in:
Ivar Lazzaro
2015-05-03 20:58:42 -07:00
parent 6cf6b03125
commit 2527cdd688
20 changed files with 855 additions and 63 deletions

View File

@@ -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"
}

View File

@@ -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

View File

@@ -1 +1 @@
fd98aa15958d
9744740aa75c

View File

@@ -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)

View File

@@ -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

View File

@@ -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.")

View File

@@ -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]

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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')

View File

@@ -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"
}

View File

@@ -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')

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)

View File

@@ -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']

View File

@@ -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()