node composition plugin implementation
Partially implements blueprint node-centric-chain-plugin Change-Id: I16d56ebb7bff0c621c68106039b4217cfac77755
This commit is contained in:
parent
657306d199
commit
7f90fa8c90
0
gbpservice/common/__init__.py
Normal file
0
gbpservice/common/__init__.py
Normal file
33
gbpservice/common/utils.py
Normal file
33
gbpservice/common/utils.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import importutils
|
||||||
|
from stevedore import driver
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugin(namespace, plugin):
|
||||||
|
try:
|
||||||
|
# Try to resolve plugin by name
|
||||||
|
mgr = driver.DriverManager(namespace, plugin)
|
||||||
|
plugin_class = mgr.driver
|
||||||
|
except RuntimeError as e1:
|
||||||
|
# fallback to class name
|
||||||
|
try:
|
||||||
|
plugin_class = importutils.import_class(plugin)
|
||||||
|
except ImportError as e2:
|
||||||
|
LOG.exception(_("Error loading plugin by name, %s"), e1)
|
||||||
|
LOG.exception(_("Error loading plugin by class, %s"), e2)
|
||||||
|
raise ImportError(_("Plugin not found."))
|
||||||
|
return plugin_class()
|
@ -1 +1 @@
|
|||||||
9744740aa75c
|
d08627f64e37
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""ncp_implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'd08627f64e37'
|
||||||
|
down_revision = '9744740aa75c'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table(
|
||||||
|
'ncp_node_to_driver_mapping',
|
||||||
|
sa.Column('servicechain_node_id', sa.String(length=36),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('driver_name', sa.String(length=36)),
|
||||||
|
sa.Column('servicechain_instance_id', sa.String(length=36)),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
'servicechain_node_id', 'servicechain_instance_id'),
|
||||||
|
sa.ForeignKeyConstraint(['servicechain_node_id'],
|
||||||
|
['sc_nodes.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['servicechain_instance_id'],
|
||||||
|
['sc_instances.id'], ondelete='CASCADE')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'ncp_service_targets',
|
||||||
|
sa.Column('servicechain_node_id', sa.String(length=36),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('servicechain_instance_id', sa.String(length=36)),
|
||||||
|
sa.Column('policy_target_id', sa.String(length=36)),
|
||||||
|
sa.Column('relationship', sa.String(length=25)),
|
||||||
|
sa.Column('position', sa.Integer),
|
||||||
|
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
'servicechain_node_id', 'servicechain_instance_id',
|
||||||
|
'policy_target_id'),
|
||||||
|
sa.ForeignKeyConstraint(['policy_target_id'],
|
||||||
|
['gp_policy_targets.id'], ondelete='CASCADE')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
@ -205,6 +205,8 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase,
|
|||||||
'config_param_names': spec.get('config_param_names'),
|
'config_param_names': spec.get('config_param_names'),
|
||||||
'shared': spec['shared']}
|
'shared': spec['shared']}
|
||||||
res['nodes'] = [sc_node['node_id'] for sc_node in spec['nodes']]
|
res['nodes'] = [sc_node['node_id'] for sc_node in spec['nodes']]
|
||||||
|
res['instances'] = [x['servicechain_instance_id'] for x in
|
||||||
|
spec['instances']]
|
||||||
return self._fields(res, fields)
|
return self._fields(res, fields)
|
||||||
|
|
||||||
def _make_sc_instance_dict(self, instance, fields=None):
|
def _make_sc_instance_dict(self, instance, fields=None):
|
||||||
@ -296,13 +298,16 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase,
|
|||||||
return self._get_collection_count(context, ServiceChainNode,
|
return self._get_collection_count(context, ServiceChainNode,
|
||||||
filters=filters)
|
filters=filters)
|
||||||
|
|
||||||
def _process_nodes_for_spec(self, context, spec_db, spec):
|
def _process_nodes_for_spec(self, context, spec_db, spec,
|
||||||
|
set_params=True):
|
||||||
if 'nodes' in spec:
|
if 'nodes' in spec:
|
||||||
self._set_nodes_for_spec(context, spec_db, spec['nodes'])
|
self._set_nodes_for_spec(context, spec_db, spec['nodes'],
|
||||||
|
set_params=set_params)
|
||||||
del spec['nodes']
|
del spec['nodes']
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
def _set_nodes_for_spec(self, context, spec_db, nodes_id_list):
|
def _set_nodes_for_spec(self, context, spec_db, nodes_id_list,
|
||||||
|
set_params=True):
|
||||||
|
|
||||||
if not nodes_id_list:
|
if not nodes_id_list:
|
||||||
spec_db.nodes = []
|
spec_db.nodes = []
|
||||||
@ -324,18 +329,21 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase,
|
|||||||
# it as clearing existing nodes.
|
# it as clearing existing nodes.
|
||||||
spec_db.nodes = []
|
spec_db.nodes = []
|
||||||
for node_id in nodes_id_list:
|
for node_id in nodes_id_list:
|
||||||
sc_node = self.get_servicechain_node(context, node_id)
|
if set_params:
|
||||||
node_dict = jsonutils.loads(sc_node['config'])
|
sc_node = self.get_servicechain_node(context, node_id)
|
||||||
config_params = (node_dict.get('parameters') or
|
node_dict = jsonutils.loads(sc_node['config'])
|
||||||
node_dict.get('Parameters'))
|
config_params = (node_dict.get('parameters') or
|
||||||
if config_params:
|
node_dict.get('Parameters'))
|
||||||
if not spec_db.config_param_names:
|
if config_params:
|
||||||
spec_db.config_param_names = str(config_params.keys())
|
if not spec_db.config_param_names:
|
||||||
else:
|
spec_db.config_param_names = str(
|
||||||
config_param_names = ast.literal_eval(
|
config_params.keys())
|
||||||
spec_db.config_param_names)
|
else:
|
||||||
config_param_names.extend(config_params.keys())
|
config_param_names = ast.literal_eval(
|
||||||
spec_db.config_param_names = str(config_param_names)
|
spec_db.config_param_names)
|
||||||
|
config_param_names.extend(config_params.keys())
|
||||||
|
spec_db.config_param_names = str(
|
||||||
|
config_param_names)
|
||||||
|
|
||||||
assoc = SpecNodeAssociation(servicechain_spec_id=spec_db.id,
|
assoc = SpecNodeAssociation(servicechain_spec_id=spec_db.id,
|
||||||
node_id=node_id)
|
node_id=node_id)
|
||||||
@ -371,7 +379,8 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase,
|
|||||||
instance_db.specs.append(assoc)
|
instance_db.specs.append(assoc)
|
||||||
|
|
||||||
@log.log
|
@log.log
|
||||||
def create_servicechain_spec(self, context, servicechain_spec):
|
def create_servicechain_spec(self, context, servicechain_spec,
|
||||||
|
set_params=True):
|
||||||
spec = servicechain_spec['servicechain_spec']
|
spec = servicechain_spec['servicechain_spec']
|
||||||
tenant_id = self._get_tenant_id_for_create(context, spec)
|
tenant_id = self._get_tenant_id_for_create(context, spec)
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
@ -380,18 +389,20 @@ class ServiceChainDbPlugin(schain.ServiceChainPluginBase,
|
|||||||
name=spec['name'],
|
name=spec['name'],
|
||||||
description=spec['description'],
|
description=spec['description'],
|
||||||
shared=spec['shared'])
|
shared=spec['shared'])
|
||||||
self._process_nodes_for_spec(context, spec_db, spec)
|
self._process_nodes_for_spec(context, spec_db, spec,
|
||||||
|
set_params=set_params)
|
||||||
context.session.add(spec_db)
|
context.session.add(spec_db)
|
||||||
return self._make_sc_spec_dict(spec_db)
|
return self._make_sc_spec_dict(spec_db)
|
||||||
|
|
||||||
@log.log
|
@log.log
|
||||||
def update_servicechain_spec(self, context, spec_id,
|
def update_servicechain_spec(self, context, spec_id,
|
||||||
servicechain_spec):
|
servicechain_spec, set_params=True):
|
||||||
spec = servicechain_spec['servicechain_spec']
|
spec = servicechain_spec['servicechain_spec']
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
spec_db = self._get_servicechain_spec(context,
|
spec_db = self._get_servicechain_spec(context,
|
||||||
spec_id)
|
spec_id)
|
||||||
spec = self._process_nodes_for_spec(context, spec_db, spec)
|
spec = self._process_nodes_for_spec(context, spec_db, spec,
|
||||||
|
set_params=set_params)
|
||||||
spec_db.update(spec)
|
spec_db.update(spec)
|
||||||
return self._make_sc_spec_dict(spec_db)
|
return self._make_sc_spec_dict(spec_db)
|
||||||
|
|
||||||
|
@ -19,7 +19,13 @@ service_chain_opts = [
|
|||||||
help=_("An ordered list of service chain node drivers "
|
help=_("An ordered list of service chain node drivers "
|
||||||
"entrypoints to be loaded from the "
|
"entrypoints to be loaded from the "
|
||||||
"gbpservice.neutron.servicechain.ncp_drivers "
|
"gbpservice.neutron.servicechain.ncp_drivers "
|
||||||
"namespace."))
|
"namespace.")),
|
||||||
|
cfg.StrOpt('node_plumber',
|
||||||
|
default='dummy_plumber',
|
||||||
|
help=_("The plumber used by the Node Composition Plugin "
|
||||||
|
"for service plumbing. Entrypoint loaded from the "
|
||||||
|
"gbpservice.neutron.servicechain.ncp_plumbers "
|
||||||
|
"namespace."))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from neutron import manager
|
|||||||
from neutron.plugins.common import constants as pconst
|
from neutron.plugins.common import constants as pconst
|
||||||
|
|
||||||
from gbpservice.neutron.extensions import group_policy
|
from gbpservice.neutron.extensions import group_policy
|
||||||
|
from gbpservice.neutron.services.servicechain.plugins.ncp import model
|
||||||
|
|
||||||
|
|
||||||
def get_gbp_plugin():
|
def get_gbp_plugin():
|
||||||
@ -26,13 +27,19 @@ def get_node_driver_context(sc_plugin, context, sc_instance,
|
|||||||
management_group=None, service_targets=None):
|
management_group=None, service_targets=None):
|
||||||
specs = sc_plugin.get_servicechain_specs(
|
specs = sc_plugin.get_servicechain_specs(
|
||||||
context, filters={'id': sc_instance['servicechain_specs']})
|
context, filters={'id': sc_instance['servicechain_specs']})
|
||||||
provider = _ptg_or_ep(context, sc_instance['provider_ptg_id'])
|
position = _calculate_node_position(specs, current_node['id'])
|
||||||
consumer = _ptg_or_ep(context, sc_instance['consumer_ptg_id'])
|
provider = _get_ptg_or_ep(context, sc_instance['provider_ptg_id'])
|
||||||
|
consumer = _get_ptg_or_ep(context, sc_instance['consumer_ptg_id'])
|
||||||
current_profile = sc_plugin.get_service_profile(
|
current_profile = sc_plugin.get_service_profile(
|
||||||
context, current_node['service_profile_id'])
|
context, current_node['service_profile_id'])
|
||||||
original_profile = sc_plugin.get_service_profile(
|
original_profile = sc_plugin.get_service_profile(
|
||||||
context,
|
context,
|
||||||
original_node['service_profile_id']) if original_node else None
|
original_node['service_profile_id']) if original_node else None
|
||||||
|
if not service_targets:
|
||||||
|
service_targets = model.get_service_targets(
|
||||||
|
context.session, servicechain_instance_id=sc_instance['id'],
|
||||||
|
position=position, servicechain_node_id=current_node['id'])
|
||||||
|
|
||||||
return NodeDriverContext(sc_plugin=sc_plugin,
|
return NodeDriverContext(sc_plugin=sc_plugin,
|
||||||
context=context,
|
context=context,
|
||||||
service_chain_instance=sc_instance,
|
service_chain_instance=sc_instance,
|
||||||
@ -44,10 +51,11 @@ def get_node_driver_context(sc_plugin, context, sc_instance,
|
|||||||
management_group=management_group,
|
management_group=management_group,
|
||||||
original_service_chain_node=original_node,
|
original_service_chain_node=original_node,
|
||||||
original_service_profile=original_profile,
|
original_service_profile=original_profile,
|
||||||
service_targets=service_targets)
|
service_targets=service_targets,
|
||||||
|
position=position)
|
||||||
|
|
||||||
|
|
||||||
def _ptg_or_ep(context, group_id):
|
def _get_ptg_or_ep(context, group_id):
|
||||||
group = None
|
group = None
|
||||||
if group_id:
|
if group_id:
|
||||||
try:
|
try:
|
||||||
@ -59,11 +67,20 @@ def _ptg_or_ep(context, group_id):
|
|||||||
return group
|
return group
|
||||||
|
|
||||||
|
|
||||||
|
def _calculate_node_position(specs, node_id):
|
||||||
|
for spec in specs:
|
||||||
|
pos = 0
|
||||||
|
for node in spec['nodes']:
|
||||||
|
pos += 1
|
||||||
|
if node_id == node:
|
||||||
|
return pos
|
||||||
|
|
||||||
|
|
||||||
class NodeDriverContext(object):
|
class NodeDriverContext(object):
|
||||||
""" Context passed down to NCC Node Drivers."""
|
"""Context passed down to NCP Node Drivers."""
|
||||||
|
|
||||||
def __init__(self, sc_plugin, context, service_chain_instance,
|
def __init__(self, sc_plugin, context, service_chain_instance,
|
||||||
service_chain_specs, current_service_chain_node,
|
service_chain_specs, current_service_chain_node, position,
|
||||||
current_service_profile, provider_group, consumer_group=None,
|
current_service_profile, provider_group, consumer_group=None,
|
||||||
management_group=None, original_service_chain_node=None,
|
management_group=None, original_service_chain_node=None,
|
||||||
original_service_profile=None, service_targets=None):
|
original_service_profile=None, service_targets=None):
|
||||||
@ -85,6 +102,7 @@ class NodeDriverContext(object):
|
|||||||
self._core_plugin = manager.NeutronManager.get_plugin()
|
self._core_plugin = manager.NeutronManager.get_plugin()
|
||||||
self._l3_plugin = manager.NeutronManager.get_service_plugins().get(
|
self._l3_plugin = manager.NeutronManager.get_service_plugins().get(
|
||||||
pconst.L3_ROUTER_NAT)
|
pconst.L3_ROUTER_NAT)
|
||||||
|
self._position = position
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gbp_plugin(self):
|
def gbp_plugin(self):
|
||||||
@ -136,6 +154,10 @@ class NodeDriverContext(object):
|
|||||||
def current_profile(self):
|
def current_profile(self):
|
||||||
return self._current_service_profile
|
return self._current_service_profile
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_position(self):
|
||||||
|
return self._position
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def original_node(self):
|
def original_node(self):
|
||||||
return self._original_service_chain_node
|
return self._original_service_chain_node
|
||||||
@ -152,18 +174,6 @@ class NodeDriverContext(object):
|
|||||||
self.current_node['id'] in x['nodes']]
|
self.current_node['id'] in x['nodes']]
|
||||||
return self._relevant_specs
|
return self._relevant_specs
|
||||||
|
|
||||||
@property
|
|
||||||
def service_targets(self):
|
|
||||||
""" Returns the service targets assigned for this service if any.
|
|
||||||
The result looks like the following:
|
|
||||||
{
|
|
||||||
"provider": [pt_uuids],
|
|
||||||
"consumer": [pt_uuids],
|
|
||||||
"management": [pt_uuids],
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
return self._service_targets
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def provider(self):
|
def provider(self):
|
||||||
return self._provider_group
|
return self._provider_group
|
||||||
@ -175,3 +185,19 @@ class NodeDriverContext(object):
|
|||||||
@property
|
@property
|
||||||
def management(self):
|
def management(self):
|
||||||
return self._management_group
|
return self._management_group
|
||||||
|
|
||||||
|
def get_service_targets(self, update=False):
|
||||||
|
""" Returns the service targets assigned for this service if any.
|
||||||
|
The result looks like the following:
|
||||||
|
{
|
||||||
|
"provider": [pt_uuids],
|
||||||
|
"consumer": [pt_uuids],
|
||||||
|
"management": [pt_uuids],
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if update:
|
||||||
|
self._service_targets = model.get_service_targets(
|
||||||
|
self.session, servicechain_instance_id=self.instance['id'],
|
||||||
|
position=self.current_position,
|
||||||
|
servicechain_node_id=self.current_node['id'])
|
||||||
|
return self._service_targets
|
||||||
|
@ -28,12 +28,13 @@ class NodeDriverBase(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def initialize(self):
|
def initialize(self, name):
|
||||||
"""Perform driver initialization.
|
"""Perform driver initialization.
|
||||||
|
|
||||||
Called after all drivers have been loaded and the database has
|
Called after all drivers have been loaded and the database has
|
||||||
been initialized. No abstract methods defined below will be
|
been initialized. No abstract methods defined below will be
|
||||||
called prior to this method being called.
|
called prior to this method being called. Name is a unique attribute
|
||||||
|
that identifies the driver.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -143,3 +144,7 @@ class NodeDriverBase(object):
|
|||||||
and the specific node to be processed by this driver.
|
and the specific node to be processed by this driver.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def name(self):
|
||||||
|
pass
|
@ -0,0 +1,48 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Exceptions used by NodeCompositionPlugin and drivers."""
|
||||||
|
|
||||||
|
from neutron.common import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class NodeDriverError(exceptions.NeutronException):
|
||||||
|
"""Node driver call failed."""
|
||||||
|
message = _("%(method)s failed.")
|
||||||
|
|
||||||
|
|
||||||
|
class NodeCompositionPluginException(exceptions.NeutronException):
|
||||||
|
"""Base for node driver exceptions returned to user."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NodeCompositionPluginBadRequest(exceptions.BadRequest,
|
||||||
|
NodeCompositionPluginException):
|
||||||
|
"""Base for node driver bad request exceptions returned to user."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OneSpecPerInstanceAllowed(NodeCompositionPluginBadRequest):
|
||||||
|
message = _("The Node Composition Plugin only supports one Servicechain"
|
||||||
|
"Spec per Servicechain Instance.")
|
||||||
|
|
||||||
|
|
||||||
|
class NoDriverAvailableForAction(NodeCompositionPluginBadRequest):
|
||||||
|
message = _("The Node Composition Plugin can't find any Node Driver "
|
||||||
|
"available for executing %(action)s on node %(node_id)s. "
|
||||||
|
"This may be caused by a Servicechain Node misconfiguration "
|
||||||
|
"or an unsupported Service Profile.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceProfileInUseByAnInstance(NodeCompositionPluginBadRequest):
|
||||||
|
message = _("Cannot update Service Profile %(profile_id)s because it's "
|
||||||
|
"used by servicechain instance %(instance_id)s.")
|
121
gbpservice/neutron/services/servicechain/plugins/ncp/model.py
Normal file
121
gbpservice/neutron/services/servicechain/plugins/ncp/model.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from neutron.db import model_base
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from gbpservice.neutron.db.grouppolicy import group_policy_db as gp_db
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
PROVIDER = 'provider'
|
||||||
|
CONSUMER = 'consumer'
|
||||||
|
MANAGEMENT = 'management'
|
||||||
|
RELATIONSHIPS = [PROVIDER, CONSUMER, MANAGEMENT]
|
||||||
|
|
||||||
|
|
||||||
|
class NodeToDriverMapping(model_base.BASEV2):
|
||||||
|
"""Node to Driver mapping DB.
|
||||||
|
|
||||||
|
This table keeps track of the driver owning a specific SC Node based on
|
||||||
|
the SC instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = 'ncp_node_to_driver_mapping'
|
||||||
|
servicechain_node_id = sa.Column(sa.String(36),
|
||||||
|
sa.ForeignKey('sc_nodes.id',
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
nullable=False, primary_key=True)
|
||||||
|
# Based on the extension name
|
||||||
|
driver_name = sa.Column(sa.String(36), nullable=False)
|
||||||
|
servicechain_instance_id = sa.Column(sa.String(36),
|
||||||
|
sa.ForeignKey('sc_instances.id',
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceTarget(model_base.BASEV2):
|
||||||
|
"""Service related policy targets.
|
||||||
|
|
||||||
|
Internal information regarding the policy targets owned by services.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = 'ncp_service_targets'
|
||||||
|
policy_target_id = sa.Column(sa.String(36),
|
||||||
|
sa.ForeignKey(gp_db.PolicyTarget.id,
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
nullable=False, primary_key=True)
|
||||||
|
# Not a FK to avoid constraint error on SCI delete
|
||||||
|
# keeping the DB entry is useful to identify uncleaned PTs
|
||||||
|
servicechain_instance_id = sa.Column(sa.String(36),
|
||||||
|
nullable=False, primary_key=True)
|
||||||
|
# Not a FK to avoid constraint error on SCN delete.
|
||||||
|
# keeping the DB entry is useful to identify uncleaned PTs
|
||||||
|
servicechain_node_id = sa.Column(sa.String(36),
|
||||||
|
nullable=False, primary_key=True)
|
||||||
|
# Defines on which "side" of the chain the PT is placed. typically
|
||||||
|
# its values can be "provider", "consumer" or "management"
|
||||||
|
relationship = sa.Column(sa.String(25), nullable=False)
|
||||||
|
position = sa.Column(sa.Integer)
|
||||||
|
|
||||||
|
|
||||||
|
def set_node_ownership(context, driver_name):
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
owner = NodeToDriverMapping(
|
||||||
|
servicechain_instance_id=context.instance['id'],
|
||||||
|
servicechain_node_id=context.current_node['id'],
|
||||||
|
driver_name=driver_name)
|
||||||
|
session.add(owner)
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_owner(context):
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
query = session.query(NodeToDriverMapping)
|
||||||
|
query = query.filter_by(
|
||||||
|
servicechain_instance_id=context.instance['id'])
|
||||||
|
query = query.filter_by(
|
||||||
|
servicechain_node_id=context.current_node['id'])
|
||||||
|
return query.all()
|
||||||
|
|
||||||
|
|
||||||
|
def set_service_target(context, policy_target_id, relationship):
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
owner = ServiceTarget(
|
||||||
|
policy_target_id=policy_target_id,
|
||||||
|
servicechain_instance_id=context.instance['id'],
|
||||||
|
servicechain_node_id=context.current_node['id'],
|
||||||
|
position=context.current_position,
|
||||||
|
relationship=relationship)
|
||||||
|
session.add(owner)
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_targets(session, policy_target_id=None, relationship=None,
|
||||||
|
servicechain_instance_id=None, position=None,
|
||||||
|
servicechain_node_id=None):
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
query = session.query(ServiceTarget)
|
||||||
|
if servicechain_instance_id:
|
||||||
|
query = query.filter_by(
|
||||||
|
servicechain_instance_id=servicechain_instance_id)
|
||||||
|
if servicechain_node_id:
|
||||||
|
query = query.filter_by(
|
||||||
|
servicechain_node_id=servicechain_node_id)
|
||||||
|
if policy_target_id:
|
||||||
|
query = query.filter_by(policy_target_id=policy_target_id)
|
||||||
|
if position:
|
||||||
|
query = query.filter_by(position=position)
|
||||||
|
if relationship:
|
||||||
|
query = query.filter_by(relationship=relationship)
|
||||||
|
return query.all()
|
@ -10,11 +10,13 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from neutron.common import exceptions as n_exc
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import stevedore
|
import stevedore
|
||||||
|
|
||||||
from gbpservice.neutron.services.servicechain.plugins.ncp import config # noqa
|
from gbpservice.neutron.services.servicechain.plugins.ncp import config # noqa
|
||||||
|
from gbpservice.neutron.services.servicechain.plugins.ncp import model
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -52,6 +54,45 @@ class NodeDriverManager(stevedore.named.NamedExtensionManager):
|
|||||||
for driver in self.ordered_drivers:
|
for driver in self.ordered_drivers:
|
||||||
LOG.info(_("Initializing service chain node drivers '%s'"),
|
LOG.info(_("Initializing service chain node drivers '%s'"),
|
||||||
driver.name)
|
driver.name)
|
||||||
driver.obj.initialize()
|
driver.obj.initialize(driver.name)
|
||||||
self.native_bulk_support &= getattr(driver.obj,
|
self.native_bulk_support &= getattr(driver.obj,
|
||||||
'native_bulk_support', True)
|
'native_bulk_support', True)
|
||||||
|
|
||||||
|
def schedule_deploy(self, context):
|
||||||
|
"""Schedule Node Driver for Node creation.
|
||||||
|
|
||||||
|
Given a NodeContext, this method returns the driver capable of creating
|
||||||
|
the specific node.
|
||||||
|
"""
|
||||||
|
for driver in self.ordered_drivers:
|
||||||
|
try:
|
||||||
|
driver.obj.validate_create(context)
|
||||||
|
model.set_node_ownership(context, driver.obj.name)
|
||||||
|
return driver.obj
|
||||||
|
except n_exc.NeutronException as e:
|
||||||
|
LOG.warn(e.message)
|
||||||
|
|
||||||
|
def schedule_destroy(self, context):
|
||||||
|
"""Schedule Node Driver for Node disruption.
|
||||||
|
|
||||||
|
Given a NodeContext, this method returns the driver capable of
|
||||||
|
destroying the specific node.
|
||||||
|
"""
|
||||||
|
return self._get_owning_driver(context)
|
||||||
|
|
||||||
|
def schedule_update(self, context):
|
||||||
|
"""Schedule Node Driver for Node Update.
|
||||||
|
|
||||||
|
Given a NodeContext, this method returns the driver capable of updating
|
||||||
|
the specific node.
|
||||||
|
"""
|
||||||
|
driver = self._get_owning_driver(context)
|
||||||
|
if driver:
|
||||||
|
driver.validate_update(context)
|
||||||
|
return driver
|
||||||
|
|
||||||
|
def _get_owning_driver(self, context):
|
||||||
|
owner = model.get_node_owner(context)
|
||||||
|
if owner:
|
||||||
|
driver = self.drivers.get(owner[0].driver_name)
|
||||||
|
return driver.obj if driver else None
|
||||||
|
@ -20,8 +20,9 @@ class NoopNodeDriver(driver_base.NodeDriverBase):
|
|||||||
initialized = False
|
initialized = False
|
||||||
|
|
||||||
@log.log
|
@log.log
|
||||||
def initialize(self):
|
def initialize(self, name):
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
|
self._name = name
|
||||||
|
|
||||||
@log.log
|
@log.log
|
||||||
def get_plumbing_info(self, context):
|
def get_plumbing_info(self, context):
|
||||||
@ -46,3 +47,7 @@ class NoopNodeDriver(driver_base.NodeDriverBase):
|
|||||||
@log.log
|
@log.log
|
||||||
def update(self, context):
|
def update(self, context):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from neutron.common import log
|
||||||
|
|
||||||
|
from gbpservice.neutron.services.servicechain.plugins.ncp import plumber_base
|
||||||
|
|
||||||
|
|
||||||
|
class NoopPlumber(plumber_base.NodePlumberBase):
|
||||||
|
|
||||||
|
initialized = False
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def initialize(self):
|
||||||
|
self.initialized = True
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def plug_services(self, context, deployment):
|
||||||
|
self._sort_deployment(deployment)
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def unplug_services(self, context, deployment):
|
||||||
|
self._sort_deployment(deployment)
|
@ -10,16 +10,28 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from neutron.common import log
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
|
|
||||||
|
from gbpservice.common import utils
|
||||||
from gbpservice.neutron.db import servicechain_db
|
from gbpservice.neutron.db import servicechain_db
|
||||||
|
from gbpservice.neutron.services.servicechain.plugins.ncp import (
|
||||||
|
context as ctx)
|
||||||
|
from gbpservice.neutron.services.servicechain.plugins.ncp import (
|
||||||
|
exceptions as exc)
|
||||||
from gbpservice.neutron.services.servicechain.plugins.ncp import (
|
from gbpservice.neutron.services.servicechain.plugins.ncp import (
|
||||||
node_driver_manager as manager)
|
node_driver_manager as manager)
|
||||||
|
from gbpservice.neutron.services.servicechain.plugins import sharing
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PLUMBER_NAMESPACE = 'gbpservice.neutron.servicechain.ncp_plumbers'
|
||||||
|
|
||||||
class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin):
|
|
||||||
|
class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
|
||||||
|
sharing.SharingMixin):
|
||||||
|
|
||||||
"""Implementation of the Service Chain Plugin.
|
"""Implementation of the Service Chain Plugin.
|
||||||
|
|
||||||
@ -30,3 +42,265 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin):
|
|||||||
self.driver_manager = manager.NodeDriverManager()
|
self.driver_manager = manager.NodeDriverManager()
|
||||||
super(NodeCompositionPlugin, self).__init__()
|
super(NodeCompositionPlugin, self).__init__()
|
||||||
self.driver_manager.initialize()
|
self.driver_manager.initialize()
|
||||||
|
self.plumber = utils.load_plugin(
|
||||||
|
PLUMBER_NAMESPACE, cfg.CONF.node_composition_plugin.node_plumber)
|
||||||
|
self.plumber.initialize()
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def create_servicechain_instance(self, context, servicechain_instance):
|
||||||
|
"""Instance created.
|
||||||
|
|
||||||
|
When a Servicechain Instance is created, all its nodes need to be
|
||||||
|
instantiated.
|
||||||
|
"""
|
||||||
|
session = context.session
|
||||||
|
deployers = {}
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
instance = super(NodeCompositionPlugin,
|
||||||
|
self).create_servicechain_instance(
|
||||||
|
context, servicechain_instance)
|
||||||
|
if len(instance['servicechain_specs']) > 1:
|
||||||
|
raise exc.OneSpecPerInstanceAllowed()
|
||||||
|
deployers = self._get_scheduled_drivers(context, instance,
|
||||||
|
'deploy')
|
||||||
|
|
||||||
|
# Actual node deploy
|
||||||
|
try:
|
||||||
|
self._deploy_servicechain_nodes(context, deployers)
|
||||||
|
except Exception:
|
||||||
|
# Some node could not be deployed
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.error(_("Node deployment failed, "
|
||||||
|
"deleting servicechain_instance %s"),
|
||||||
|
instance['id'])
|
||||||
|
self.delete_servicechain_instance(context, instance['id'])
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def update_servicechain_instance(self, context, servicechain_instance_id,
|
||||||
|
servicechain_instance):
|
||||||
|
"""Instance updated.
|
||||||
|
|
||||||
|
When a Servicechain Instance is updated and the spec changed, all the
|
||||||
|
nodes of the previous spec should be destroyed and the newer ones
|
||||||
|
created.
|
||||||
|
"""
|
||||||
|
session = context.session
|
||||||
|
deployers = {}
|
||||||
|
destroyers = {}
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
original_instance = self.get_servicechain_instance(
|
||||||
|
context, servicechain_instance_id)
|
||||||
|
updated_instance = super(
|
||||||
|
NodeCompositionPlugin, self).update_servicechain_instance(
|
||||||
|
context, servicechain_instance_id, servicechain_instance)
|
||||||
|
|
||||||
|
if (original_instance['servicechain_specs'] !=
|
||||||
|
updated_instance['servicechain_specs']):
|
||||||
|
if len(updated_instance['servicechain_specs']) > 1:
|
||||||
|
raise exc.OneSpecPerInstanceAllowed()
|
||||||
|
destroyers = self._get_scheduled_drivers(
|
||||||
|
context, original_instance, 'destroy')
|
||||||
|
deployers = self._get_scheduled_drivers(
|
||||||
|
context, updated_instance, 'deploy')
|
||||||
|
self._destroy_servicechain_nodes(context, destroyers)
|
||||||
|
self._deploy_servicechain_nodes(context, deployers)
|
||||||
|
return updated_instance
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def delete_servicechain_instance(self, context, servicechain_instance_id):
|
||||||
|
"""Instance deleted.
|
||||||
|
|
||||||
|
When a Servicechain Instance is deleted, all its nodes need to be
|
||||||
|
destroyed.
|
||||||
|
"""
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
instance = self.get_servicechain_instance(context,
|
||||||
|
servicechain_instance_id)
|
||||||
|
destroyers = self._get_scheduled_drivers(context, instance,
|
||||||
|
'destroy')
|
||||||
|
self._destroy_servicechain_nodes(context, destroyers)
|
||||||
|
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
super(NodeCompositionPlugin, self).delete_servicechain_instance(
|
||||||
|
context, servicechain_instance_id)
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def create_servicechain_node(self, context, servicechain_node):
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
result = super(NodeCompositionPlugin,
|
||||||
|
self).create_servicechain_node(context,
|
||||||
|
servicechain_node)
|
||||||
|
self._validate_shared_create(context, result, 'servicechain_node')
|
||||||
|
return result
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def update_servicechain_node(self, context, servicechain_node_id,
|
||||||
|
servicechain_node):
|
||||||
|
"""Node Update.
|
||||||
|
|
||||||
|
When a Servicechain Node is updated, all the corresponding instances
|
||||||
|
need to be updated as well. This usually results in a node
|
||||||
|
reconfiguration.
|
||||||
|
"""
|
||||||
|
session = context.session
|
||||||
|
updaters = {}
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
original_sc_node = self.get_servicechain_node(
|
||||||
|
context, servicechain_node_id)
|
||||||
|
updated_sc_node = super(NodeCompositionPlugin,
|
||||||
|
self).update_servicechain_node(
|
||||||
|
context, servicechain_node_id,
|
||||||
|
servicechain_node)
|
||||||
|
self._validate_shared_update(context, original_sc_node,
|
||||||
|
updated_sc_node, 'servicechain_node')
|
||||||
|
instances = self._get_node_instances(context, updated_sc_node)
|
||||||
|
for instance in instances:
|
||||||
|
node_context = ctx.get_node_driver_context(
|
||||||
|
self, context, instance, updated_sc_node, original_sc_node)
|
||||||
|
# TODO(ivar): Validate that the node driver understands the
|
||||||
|
# update.
|
||||||
|
driver = self.driver_manager.schedule_update(node_context)
|
||||||
|
if not driver:
|
||||||
|
raise exc.NoDriverAvailableForAction(
|
||||||
|
action='update', node_id=original_sc_node['id'])
|
||||||
|
updaters[instance['id']] = {}
|
||||||
|
updaters[instance['id']]['context'] = node_context
|
||||||
|
updaters[instance['id']]['driver'] = driver
|
||||||
|
updaters[instance['id']]['plumbing_info'] = (
|
||||||
|
driver.get_plumbing_info(node_context))
|
||||||
|
# Update the nodes
|
||||||
|
for update in updaters.values():
|
||||||
|
try:
|
||||||
|
update['driver'].update(update['context'])
|
||||||
|
except exc.NodeDriverError as ex:
|
||||||
|
LOG.error(_("Node Update failed, %s"),
|
||||||
|
ex.message)
|
||||||
|
|
||||||
|
return updated_sc_node
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def create_servicechain_spec(self, context, servicechain_spec):
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
result = super(
|
||||||
|
NodeCompositionPlugin, self).create_servicechain_spec(
|
||||||
|
context, servicechain_spec, set_params=False)
|
||||||
|
self._validate_shared_create(context, result, 'servicechain_spec')
|
||||||
|
return result
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def update_servicechain_spec(self, context, servicechain_spec_id,
|
||||||
|
servicechain_spec):
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
original_sc_spec = self.get_servicechain_spec(
|
||||||
|
context, servicechain_spec_id)
|
||||||
|
updated_sc_spec = super(NodeCompositionPlugin,
|
||||||
|
self).update_servicechain_spec(
|
||||||
|
context, servicechain_spec_id,
|
||||||
|
servicechain_spec, set_params=False)
|
||||||
|
self._validate_shared_update(context, original_sc_spec,
|
||||||
|
updated_sc_spec, 'servicechain_spec')
|
||||||
|
return updated_sc_spec
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def create_service_profile(self, context, service_profile):
|
||||||
|
session = context.session
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
result = super(
|
||||||
|
NodeCompositionPlugin, self).create_service_profile(
|
||||||
|
context, service_profile)
|
||||||
|
self._validate_shared_create(context, result, 'service_profile')
|
||||||
|
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(NodeCompositionPlugin,
|
||||||
|
self).update_service_profile(
|
||||||
|
context, service_profile_id,
|
||||||
|
service_profile)
|
||||||
|
self._validate_profile_update(context, original_profile,
|
||||||
|
updated_profile)
|
||||||
|
return updated_profile
|
||||||
|
|
||||||
|
def _get_instance_nodes(self, context, instance):
|
||||||
|
if not instance['servicechain_specs']:
|
||||||
|
return []
|
||||||
|
specs = self.get_servicechain_spec(
|
||||||
|
context, instance['servicechain_specs'][0])
|
||||||
|
return self.get_servicechain_nodes(context, {'id': specs['nodes']})
|
||||||
|
|
||||||
|
def _get_node_instances(self, context, node):
|
||||||
|
specs = self.get_servicechain_specs(
|
||||||
|
context, {'id': node['servicechain_specs']})
|
||||||
|
result = []
|
||||||
|
for spec in specs:
|
||||||
|
result.extend(self.get_servicechain_instances(
|
||||||
|
context, {'id': spec['instances']}))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _get_scheduled_drivers(self, context, instance, action):
|
||||||
|
nodes = self._get_instance_nodes(context, instance)
|
||||||
|
result = {}
|
||||||
|
func = getattr(self.driver_manager, 'schedule_' + action)
|
||||||
|
for node in nodes:
|
||||||
|
node_context = ctx.get_node_driver_context(
|
||||||
|
self, context, instance, node)
|
||||||
|
driver = func(node_context)
|
||||||
|
if not driver:
|
||||||
|
raise exc.NoDriverAvailableForAction(action=action,
|
||||||
|
node_id=node['id'])
|
||||||
|
result[node['id']] = {}
|
||||||
|
result[node['id']]['driver'] = driver
|
||||||
|
result[node['id']]['context'] = node_context
|
||||||
|
result[node['id']]['plumbing_info'] = driver.get_plumbing_info(
|
||||||
|
node_context)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _deploy_servicechain_nodes(self, context, deployers):
|
||||||
|
self.plumber.plug_services(context, deployers.values())
|
||||||
|
for deploy in deployers.values():
|
||||||
|
driver = deploy['driver']
|
||||||
|
driver.create(deploy['context'])
|
||||||
|
|
||||||
|
def _destroy_servicechain_nodes(self, context, destroyers):
|
||||||
|
# Actual node disruption
|
||||||
|
try:
|
||||||
|
for destroy in destroyers.values():
|
||||||
|
driver = destroy['driver']
|
||||||
|
try:
|
||||||
|
driver.delete(destroy['context'])
|
||||||
|
except exc.NodeDriverError:
|
||||||
|
LOG.error(_("Node destroy failed, for node %s "),
|
||||||
|
driver['context'].current_node['id'])
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
finally:
|
||||||
|
self.plumber.unplug_services(context, destroyers.values())
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _validate_profile_update(self, context, original, updated):
|
||||||
|
# Raise if the profile is in use by any instance
|
||||||
|
# Ugly one shot query to verify whether the profile is in use
|
||||||
|
db = servicechain_db
|
||||||
|
query = context.session.query(db.ServiceChainInstance)
|
||||||
|
query = query.join(db.InstanceSpecAssociation)
|
||||||
|
query = query.join(db.ServiceChainSpec)
|
||||||
|
query = query.join(db.SpecNodeAssociation)
|
||||||
|
query = query.join(db.ServiceChainNode)
|
||||||
|
instance = query.filter(
|
||||||
|
db.ServiceChainNode.service_profile_id == original['id']).first()
|
||||||
|
if instance:
|
||||||
|
raise exc.ServiceProfileInUseByAnInstance(
|
||||||
|
profile_id=original['id'], instance_id=instance.id)
|
||||||
|
self._validate_shared_update(context, original, updated,
|
||||||
|
'service_profile')
|
@ -0,0 +1,78 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class NodePlumberBase(object):
|
||||||
|
"""Node Plumber base Class
|
||||||
|
|
||||||
|
The node plumber is an entity which takes care of plumbing nodes in a
|
||||||
|
chain. By node plumbing is intended the creation/disruption of the
|
||||||
|
appropriate Neutron and GBP constructs (typically Ports and Policy Targets)
|
||||||
|
based on the specific Node needs, taking into account the whole service
|
||||||
|
chain in the process. Ideally, this module will ensure that the traffic
|
||||||
|
flows as expected according to the user intent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def initialize(self):
|
||||||
|
"""Perform plumber initialization.
|
||||||
|
|
||||||
|
No abstract methods defined below will be called prior to this method
|
||||||
|
being called.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def plug_services(self, context, deployment):
|
||||||
|
"""Plug services
|
||||||
|
|
||||||
|
Given a deployment, this method is expected to create all the needed
|
||||||
|
policy targets / neutron ports placed where needed based on the whole
|
||||||
|
chain configuration.
|
||||||
|
The expectation is that this method ultimately creates ServiceTarget
|
||||||
|
object that will be retrieved by the node drivers at the right time.
|
||||||
|
|
||||||
|
A deployment is a list composed as follows:
|
||||||
|
[{'context': node_context,
|
||||||
|
'driver': deploying_driver,
|
||||||
|
'plumbing_info': node_plumbing_needs},
|
||||||
|
...]
|
||||||
|
No assumptions should be made on the order of the nodes as received in
|
||||||
|
the deployment, but it can be retrieved by calling
|
||||||
|
node_context.current_position
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def unplug_services(self, context, deployment):
|
||||||
|
"""Plug services
|
||||||
|
|
||||||
|
Given a deployment, this method is expected to destroy all the
|
||||||
|
policy targets / neutron ports previously created for this chain
|
||||||
|
configuration.
|
||||||
|
The expectation is that this method ultimately removes all the
|
||||||
|
ServiceTarget related to this particular chain.
|
||||||
|
|
||||||
|
A deployment is a list composed as follows:
|
||||||
|
[{'context': node_context,
|
||||||
|
'driver': deploying_driver,
|
||||||
|
'plumbing_info': node_plumbing_needs},
|
||||||
|
...]
|
||||||
|
No assumptions should be made on the order of the nodes as received in
|
||||||
|
the deployment, but it can be retrieved by calling
|
||||||
|
node_context.current_position
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _sort_deployment(self, deployment):
|
||||||
|
deployment.sort(key=lambda x: x['context'].current_position)
|
@ -140,8 +140,10 @@ class ServiceChainDbTestCase(ServiceChainDBTestBase,
|
|||||||
if not sc_plugin:
|
if not sc_plugin:
|
||||||
sc_plugin = DB_GP_PLUGIN_KLASS
|
sc_plugin = DB_GP_PLUGIN_KLASS
|
||||||
if not service_plugins:
|
if not service_plugins:
|
||||||
service_plugins = {'gp_plugin_name': gp_plugin or GP_PLUGIN_KLASS,
|
service_plugins = {
|
||||||
'sc_plugin_name': sc_plugin}
|
'l3_plugin_name': 'router',
|
||||||
|
'gp_plugin_name': gp_plugin or GP_PLUGIN_KLASS,
|
||||||
|
'sc_plugin_name': sc_plugin}
|
||||||
|
|
||||||
super(ServiceChainDbTestCase, self).setUp(
|
super(ServiceChainDbTestCase, self).setUp(
|
||||||
plugin=core_plugin, ext_mgr=ext_mgr,
|
plugin=core_plugin, ext_mgr=ext_mgr,
|
||||||
|
@ -11,13 +11,23 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from neutron.common import config # noqa
|
||||||
|
from neutron.common import exceptions as n_exc
|
||||||
from neutron import context as n_context
|
from neutron import context as n_context
|
||||||
from neutron.db import api as db_api
|
from neutron.db import api as db_api
|
||||||
from neutron.db import model_base
|
from neutron.db import model_base
|
||||||
|
from neutron import manager
|
||||||
|
from neutron.plugins.common import constants as pconst
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from gbpservice.neutron.db.grouppolicy import group_policy_mapping_db # noqa
|
||||||
|
from gbpservice.neutron.services.grouppolicy import config as gpconfig # noqa
|
||||||
from gbpservice.neutron.services.servicechain.plugins.ncp import (
|
from gbpservice.neutron.services.servicechain.plugins.ncp import (
|
||||||
context as ncp_context)
|
context as ncp_context)
|
||||||
|
from gbpservice.neutron.services.servicechain.plugins.ncp import (
|
||||||
|
exceptions as exc)
|
||||||
import gbpservice.neutron.services.servicechain.plugins.ncp.config # noqa
|
import gbpservice.neutron.services.servicechain.plugins.ncp.config # noqa
|
||||||
from gbpservice.neutron.services.servicechain.plugins.ncp.node_drivers import (
|
from gbpservice.neutron.services.servicechain.plugins.ncp.node_drivers import (
|
||||||
dummy_driver as dummy_driver)
|
dummy_driver as dummy_driver)
|
||||||
@ -40,21 +50,72 @@ class NodeCompositionPluginTestCase(
|
|||||||
def setUp(self, core_plugin=None, gp_plugin=None, node_drivers=None):
|
def setUp(self, core_plugin=None, gp_plugin=None, node_drivers=None):
|
||||||
if node_drivers:
|
if node_drivers:
|
||||||
cfg.CONF.set_override('node_drivers', node_drivers,
|
cfg.CONF.set_override('node_drivers', node_drivers,
|
||||||
group='node_composition_chain')
|
group='node_composition_plugin')
|
||||||
|
config.cfg.CONF.set_override('policy_drivers',
|
||||||
|
['implicit_policy', 'resource_mapping'],
|
||||||
|
group='group_policy')
|
||||||
super(NodeCompositionPluginTestCase, self).setUp(
|
super(NodeCompositionPluginTestCase, self).setUp(
|
||||||
core_plugin=core_plugin or CORE_PLUGIN,
|
core_plugin=core_plugin or CORE_PLUGIN,
|
||||||
gp_plugin=gp_plugin or GP_PLUGIN_KLASS,
|
gp_plugin=gp_plugin or GP_PLUGIN_KLASS,
|
||||||
sc_plugin=SC_PLUGIN_KLASS)
|
sc_plugin=SC_PLUGIN_KLASS)
|
||||||
engine = db_api.get_engine()
|
engine = db_api.get_engine()
|
||||||
model_base.BASEV2.metadata.create_all(engine)
|
model_base.BASEV2.metadata.create_all(engine)
|
||||||
|
self.driver = self.sc_plugin.driver_manager.ordered_drivers[0].obj
|
||||||
|
|
||||||
def test_node_shared(self):
|
@property
|
||||||
pass
|
def sc_plugin(self):
|
||||||
|
plugins = manager.NeutronManager.get_service_plugins()
|
||||||
|
servicechain_plugin = plugins.get(pconst.SERVICECHAIN)
|
||||||
|
return servicechain_plugin
|
||||||
|
|
||||||
def test_profile_shared(self):
|
def _create_redirect_rule(self, spec_id):
|
||||||
pass
|
action = self.create_policy_action(action_type='REDIRECT',
|
||||||
|
action_value=spec_id)
|
||||||
|
classifier = self.create_policy_classifier(
|
||||||
|
port_range=80, protocol='tcp', direction='bi')
|
||||||
|
rule = self.create_policy_rule(
|
||||||
|
policy_actions=[action['policy_action']['id']],
|
||||||
|
policy_classifier_id=classifier['policy_classifier']['id'])
|
||||||
|
return rule
|
||||||
|
|
||||||
def test_spec_shared(self):
|
def _create_redirect_prs(self, spec_id):
|
||||||
|
rule = self._create_redirect_rule(spec_id)['policy_rule']
|
||||||
|
prs = self.create_policy_rule_set(policy_rules=[rule['id']])
|
||||||
|
return prs
|
||||||
|
|
||||||
|
def _create_simple_service_chain(self, number_of_nodes=1):
|
||||||
|
prof = self.create_service_profile(
|
||||||
|
service_type='LOADBALANCER')['service_profile']
|
||||||
|
|
||||||
|
node_ids = []
|
||||||
|
for x in xrange(number_of_nodes):
|
||||||
|
node_ids.append(self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'],
|
||||||
|
expected_res_status=201)['servicechain_node']['id'])
|
||||||
|
|
||||||
|
return self._create_chain_with_nodes(node_ids)
|
||||||
|
|
||||||
|
def _create_chain_with_nodes(self, node_ids=None):
|
||||||
|
node_ids = node_ids or []
|
||||||
|
spec = self.create_servicechain_spec(
|
||||||
|
nodes=node_ids,
|
||||||
|
expected_res_status=201)['servicechain_spec']
|
||||||
|
prs = self._create_redirect_prs(spec['id'])['policy_rule_set']
|
||||||
|
provider = self.create_policy_target_group(
|
||||||
|
provided_policy_rule_sets={prs['id']: ''})['policy_target_group']
|
||||||
|
consumer = self.create_policy_target_group(
|
||||||
|
consumed_policy_rule_sets={prs['id']: ''})['policy_target_group']
|
||||||
|
return provider, consumer, prs
|
||||||
|
|
||||||
|
def _add_node_driver(self, name):
|
||||||
|
inst = dummy_driver.NoopNodeDriver()
|
||||||
|
inst.initialize(name)
|
||||||
|
ext = mock.Mock()
|
||||||
|
ext.obj = inst
|
||||||
|
self.sc_plugin.driver_manager.ordered_drivers.append(ext)
|
||||||
|
self.sc_plugin.driver_manager.drivers[name] = ext
|
||||||
|
|
||||||
|
def test_spec_ordering_list_servicechain_instances(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_context_attributes(self):
|
def test_context_attributes(self):
|
||||||
@ -86,16 +147,15 @@ class NodeCompositionPluginTestCase(
|
|||||||
self.assertIsNotNone(ctx.session)
|
self.assertIsNotNone(ctx.session)
|
||||||
self.assertIsNotNone(ctx.admin_context)
|
self.assertIsNotNone(ctx.admin_context)
|
||||||
self.assertIsNotNone(ctx.admin_session)
|
self.assertIsNotNone(ctx.admin_session)
|
||||||
self.assertEqual(ctx.instance, instance)
|
|
||||||
self.assertEqual(ctx.provider, provider)
|
|
||||||
self.assertEqual(ctx.consumer, consumer)
|
|
||||||
self.assertEqual(ctx.management, management)
|
|
||||||
self.assertEqual(ctx.management, management)
|
|
||||||
self.assertEqual(ctx.relevant_specs, [spec])
|
|
||||||
del ctx.current_profile['nodes']
|
del ctx.current_profile['nodes']
|
||||||
self.assertEqual(ctx.current_profile, profile)
|
self.assertEqual(ctx.current_profile, profile)
|
||||||
|
self.assertEqual(instance['id'], ctx.instance['id'])
|
||||||
|
self.assertEqual(provider['id'], ctx.provider['id'])
|
||||||
|
self.assertEqual(consumer['id'], ctx.consumer['id'])
|
||||||
|
self.assertEqual(management['id'], ctx.management['id'])
|
||||||
|
self.assertEqual([spec['id']], [x['id'] for x in ctx.relevant_specs])
|
||||||
self.assertIsNone(ctx.original_node)
|
self.assertIsNone(ctx.original_node)
|
||||||
self.assertIsNone(ctx.service_targets)
|
self.assertEqual(0, len(ctx.get_service_targets()))
|
||||||
|
|
||||||
def test_context_relevant_specs(self):
|
def test_context_relevant_specs(self):
|
||||||
plugin_context = n_context.get_admin_context()
|
plugin_context = n_context.get_admin_context()
|
||||||
@ -104,24 +164,15 @@ class NodeCompositionPluginTestCase(
|
|||||||
spec_used = self.create_servicechain_spec(
|
spec_used = self.create_servicechain_spec(
|
||||||
nodes=[node_used['id']])['servicechain_spec']
|
nodes=[node_used['id']])['servicechain_spec']
|
||||||
|
|
||||||
node_unused = self._create_profiled_servicechain_node(
|
|
||||||
service_type="TYPE", config='{}')['servicechain_node']
|
|
||||||
spec_unused = self.create_servicechain_spec(
|
|
||||||
nodes=[node_unused['id']])['servicechain_spec']
|
|
||||||
|
|
||||||
provider = self.create_policy_target_group()['policy_target_group']
|
provider = self.create_policy_target_group()['policy_target_group']
|
||||||
instance = self.create_servicechain_instance(
|
instance = self.create_servicechain_instance(
|
||||||
provider_ptg_id=provider['id'],
|
provider_ptg_id=provider['id'],
|
||||||
servicechain_specs=[spec_used['id'],
|
servicechain_specs=[spec_used['id']])['servicechain_instance']
|
||||||
spec_unused['id']])['servicechain_instance']
|
|
||||||
self.assertEqual(len(instance['servicechain_specs']), 2)
|
|
||||||
|
|
||||||
ctx = ncp_context.get_node_driver_context(
|
ctx = ncp_context.get_node_driver_context(
|
||||||
self.plugin, plugin_context, instance, node_used)
|
self.plugin, plugin_context, instance, node_used)
|
||||||
self.assertEqual(ctx.relevant_specs, [spec_used])
|
self.assertEqual([spec_used['id']],
|
||||||
|
[x['id'] for x in ctx.relevant_specs])
|
||||||
|
|
||||||
class TestNcpNodeDriverManager(NodeCompositionPluginTestCase):
|
|
||||||
|
|
||||||
def test_manager_initialized(self):
|
def test_manager_initialized(self):
|
||||||
mgr = self.plugin.driver_manager
|
mgr = self.plugin.driver_manager
|
||||||
@ -129,3 +180,187 @@ class TestNcpNodeDriverManager(NodeCompositionPluginTestCase):
|
|||||||
dummy_driver.NoopNodeDriver)
|
dummy_driver.NoopNodeDriver)
|
||||||
for driver in mgr.ordered_drivers:
|
for driver in mgr.ordered_drivers:
|
||||||
self.assertTrue(driver.obj.initialized)
|
self.assertTrue(driver.obj.initialized)
|
||||||
|
|
||||||
|
def test_spec_parameters(self):
|
||||||
|
"""Test that config_param_names is empty when using NCP.
|
||||||
|
In NCP the config attribute of a node may be something different than
|
||||||
|
a HEAT template, therefore config_param_names is not used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
params_node_1 = ['p1', 'p2', 'p3']
|
||||||
|
params_node_2 = ['p4', 'p5', 'p6']
|
||||||
|
|
||||||
|
def params_dict(params):
|
||||||
|
return jsonutils.dumps({'Parameters':
|
||||||
|
dict((x, {}) for x in params)})
|
||||||
|
|
||||||
|
prof = self.create_service_profile(
|
||||||
|
service_type='LOADBALANCER', shared=True,
|
||||||
|
tenant_id='admin')['service_profile']
|
||||||
|
|
||||||
|
# Create 2 nodes with different parameters
|
||||||
|
node1 = self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'], shared=True,
|
||||||
|
config=params_dict(params_node_1),
|
||||||
|
expected_res_status=201)['servicechain_node']
|
||||||
|
node2 = self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'], shared=True,
|
||||||
|
config=params_dict(params_node_2),
|
||||||
|
expected_res_status=201)['servicechain_node']
|
||||||
|
|
||||||
|
# Create SC spec with the nodes assigned
|
||||||
|
spec = self.create_servicechain_spec(
|
||||||
|
nodes=[node1['id'], node2['id']], shared=True,
|
||||||
|
expected_res_status=201)['servicechain_spec']
|
||||||
|
|
||||||
|
# Verify param names is empty
|
||||||
|
self.assertIsNone(spec['config_param_names'])
|
||||||
|
|
||||||
|
# Update the spec removing one node
|
||||||
|
self.update_servicechain_spec(spec['id'], nodes=[node1['id']],
|
||||||
|
expected_res_status=200)
|
||||||
|
|
||||||
|
spec = self.show_servicechain_spec(spec['id'])['servicechain_spec']
|
||||||
|
# Verify param names is empty
|
||||||
|
self.assertIsNone(spec['config_param_names'])
|
||||||
|
|
||||||
|
def test_create_service_chain(self):
|
||||||
|
deploy = self.driver.create = mock.Mock()
|
||||||
|
destroy = self.driver.delete = mock.Mock()
|
||||||
|
|
||||||
|
self._create_simple_service_chain(1)
|
||||||
|
self.assertEqual(1, deploy.call_count)
|
||||||
|
self.assertEqual(0, destroy.call_count)
|
||||||
|
|
||||||
|
deploy.reset_mock()
|
||||||
|
|
||||||
|
provider, _, _ = self._create_simple_service_chain(3)
|
||||||
|
self.assertEqual(3, deploy.call_count)
|
||||||
|
self.assertEqual(0, destroy.call_count)
|
||||||
|
|
||||||
|
self.update_policy_target_group(provider['id'],
|
||||||
|
provided_policy_rule_sets={})
|
||||||
|
self.assertEqual(3, deploy.call_count)
|
||||||
|
self.assertEqual(3, destroy.call_count)
|
||||||
|
|
||||||
|
def test_create_service_chain_fails(self):
|
||||||
|
deploy = self.driver.create = mock.Mock()
|
||||||
|
destroy = self.driver.delete = mock.Mock()
|
||||||
|
|
||||||
|
deploy.side_effect = Exception
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._create_simple_service_chain(3)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(1, deploy.call_count)
|
||||||
|
self.assertEqual(3, destroy.call_count)
|
||||||
|
|
||||||
|
def test_update_node_fails(self):
|
||||||
|
validate_update = self.driver.validate_update = mock.Mock()
|
||||||
|
validate_update.side_effect = exc.NodeCompositionPluginBadRequest(
|
||||||
|
resource='node', msg='reason')
|
||||||
|
|
||||||
|
prof = self.create_service_profile(
|
||||||
|
service_type='LOADBALANCER')['service_profile']
|
||||||
|
|
||||||
|
node_id = self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'],
|
||||||
|
expected_res_status=201)['servicechain_node']['id']
|
||||||
|
|
||||||
|
spec = self.create_servicechain_spec(
|
||||||
|
nodes=[node_id],
|
||||||
|
expected_res_status=201)['servicechain_spec']
|
||||||
|
prs = self._create_redirect_prs(spec['id'])['policy_rule_set']
|
||||||
|
self.create_policy_target_group(
|
||||||
|
provided_policy_rule_sets={prs['id']: ''})
|
||||||
|
self.create_policy_target_group(
|
||||||
|
consumed_policy_rule_sets={prs['id']: ''})
|
||||||
|
|
||||||
|
res = self.update_servicechain_node(node_id,
|
||||||
|
description='somethingelse',
|
||||||
|
expected_res_status=400)
|
||||||
|
self.assertEqual('NodeCompositionPluginBadRequest',
|
||||||
|
res['NeutronError']['type'])
|
||||||
|
|
||||||
|
def test_update_instantiated_profile_fails(self):
|
||||||
|
prof = self.create_service_profile(
|
||||||
|
service_type='LOADBALANCER')['service_profile']
|
||||||
|
|
||||||
|
node_id = self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'],
|
||||||
|
expected_res_status=201)['servicechain_node']['id']
|
||||||
|
|
||||||
|
spec = self.create_servicechain_spec(
|
||||||
|
nodes=[node_id],
|
||||||
|
expected_res_status=201)['servicechain_spec']
|
||||||
|
prs = self._create_redirect_prs(spec['id'])['policy_rule_set']
|
||||||
|
self.create_policy_target_group(
|
||||||
|
provided_policy_rule_sets={prs['id']: ''})
|
||||||
|
self.create_policy_target_group(
|
||||||
|
consumed_policy_rule_sets={prs['id']: ''})
|
||||||
|
|
||||||
|
res = self.update_service_profile(prof['id'],
|
||||||
|
vendor='somethingelse',
|
||||||
|
expected_res_status=400)
|
||||||
|
self.assertEqual('ServiceProfileInUseByAnInstance',
|
||||||
|
res['NeutronError']['type'])
|
||||||
|
|
||||||
|
def test_second_driver_scheduled_if_first_fails(self):
|
||||||
|
self._add_node_driver('test')
|
||||||
|
drivers = [x.obj for x in
|
||||||
|
self.sc_plugin.driver_manager.ordered_drivers]
|
||||||
|
create_1 = drivers[0].validate_create = mock.Mock()
|
||||||
|
create_1.side_effect = n_exc.NeutronException()
|
||||||
|
|
||||||
|
# This happens without error
|
||||||
|
profile = self.create_service_profile(
|
||||||
|
service_type="TYPE")['service_profile']
|
||||||
|
node = self.create_servicechain_node(
|
||||||
|
service_profile_id=profile['id'], config='{}')['servicechain_node']
|
||||||
|
spec = self.create_servicechain_spec(
|
||||||
|
nodes=[node['id']])['servicechain_spec']
|
||||||
|
provider = self.create_policy_target_group()['policy_target_group']
|
||||||
|
consumer = self.create_policy_target_group()['policy_target_group']
|
||||||
|
self.create_servicechain_instance(
|
||||||
|
provider_ptg_id=provider['id'], consumer_ptg_id=consumer['id'],
|
||||||
|
servicechain_specs=[spec['id']], expected_res_status=201)
|
||||||
|
|
||||||
|
def test_chain_fails_if_no_drivers_available(self):
|
||||||
|
self._add_node_driver('test')
|
||||||
|
drivers = [x.obj for x in
|
||||||
|
self.sc_plugin.driver_manager.ordered_drivers]
|
||||||
|
create_1 = drivers[0].validate_create = mock.Mock()
|
||||||
|
create_1.side_effect = n_exc.NeutronException()
|
||||||
|
create_2 = drivers[1].validate_create = mock.Mock()
|
||||||
|
create_2.side_effect = n_exc.NeutronException()
|
||||||
|
|
||||||
|
profile = self.create_service_profile(
|
||||||
|
service_type="TYPE")['service_profile']
|
||||||
|
node = self.create_servicechain_node(
|
||||||
|
service_profile_id=profile['id'], config='{}')['servicechain_node']
|
||||||
|
spec = self.create_servicechain_spec(
|
||||||
|
nodes=[node['id']])['servicechain_spec']
|
||||||
|
provider = self.create_policy_target_group()['policy_target_group']
|
||||||
|
consumer = self.create_policy_target_group()['policy_target_group']
|
||||||
|
self.create_servicechain_instance(
|
||||||
|
provider_ptg_id=provider['id'], consumer_ptg_id=consumer['id'],
|
||||||
|
servicechain_specs=[spec['id']], expected_res_status=400)
|
||||||
|
|
||||||
|
def test_multiple_nodes_update(self):
|
||||||
|
update = self.driver.update = mock.Mock()
|
||||||
|
prof = self.create_service_profile(
|
||||||
|
service_type='LOADBALANCER')['service_profile']
|
||||||
|
node = self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'], config='{}')['servicechain_node']
|
||||||
|
|
||||||
|
self._create_chain_with_nodes([node['id']])
|
||||||
|
self.update_servicechain_node(node['id'], name='somethingelse')
|
||||||
|
self.assertEqual(1, update.call_count)
|
||||||
|
|
||||||
|
update.reset_mock()
|
||||||
|
self._create_chain_with_nodes([node['id']])
|
||||||
|
self._create_chain_with_nodes([node['id']])
|
||||||
|
self.update_servicechain_node(node['id'], name='somethingelse')
|
||||||
|
self.assertEqual(3, update.call_count)
|
@ -11,8 +11,12 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import collections
|
||||||
|
|
||||||
from neutron import context as n_ctx
|
from neutron import context as n_ctx
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
from gbpservice.neutron.services.servicechain.plugins.msc import context
|
from gbpservice.neutron.services.servicechain.plugins.msc import context
|
||||||
from gbpservice.neutron.tests.unit.db.grouppolicy import (
|
from gbpservice.neutron.tests.unit.db.grouppolicy import (
|
||||||
@ -195,3 +199,47 @@ class TestGroupPolicyPluginGroupResources(
|
|||||||
self.assertIsNone(ctx.original_profile)
|
self.assertIsNone(ctx.original_profile)
|
||||||
self.assertEqual(ctx.current['id'], current['id'])
|
self.assertEqual(ctx.current['id'], current['id'])
|
||||||
self.assertIsNone(ctx.current_profile)
|
self.assertIsNone(ctx.current_profile)
|
||||||
|
|
||||||
|
def test_spec_parameters(self):
|
||||||
|
params_node_1 = ['p1', 'p2', 'p3']
|
||||||
|
params_node_2 = ['p4', 'p5', 'p6']
|
||||||
|
|
||||||
|
def params_dict(params):
|
||||||
|
return jsonutils.dumps({'Parameters':
|
||||||
|
dict((x, {}) for x in params)})
|
||||||
|
|
||||||
|
prof = self.create_service_profile(
|
||||||
|
service_type='LOADBALANCER', shared=True,
|
||||||
|
tenant_id='admin')['service_profile']
|
||||||
|
|
||||||
|
# Create 2 nodes with different parameters
|
||||||
|
node1 = self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'], shared=True,
|
||||||
|
config=params_dict(params_node_1),
|
||||||
|
expected_res_status=201)['servicechain_node']
|
||||||
|
node2 = self.create_servicechain_node(
|
||||||
|
service_profile_id=prof['id'], shared=True,
|
||||||
|
config=params_dict(params_node_2),
|
||||||
|
expected_res_status=201)['servicechain_node']
|
||||||
|
|
||||||
|
# Create SC spec with the nodes assigned
|
||||||
|
spec = self.create_servicechain_spec(
|
||||||
|
nodes=[node1['id'], node2['id']], shared=True,
|
||||||
|
expected_res_status=201)['servicechain_spec']
|
||||||
|
|
||||||
|
# Verify param names correspondence
|
||||||
|
self.assertEqual(
|
||||||
|
collections.Counter(params_node_1 + params_node_2),
|
||||||
|
collections.Counter(ast.literal_eval(spec['config_param_names'])))
|
||||||
|
|
||||||
|
# REVISIT(ivar): update verification fails because of bug/1460186
|
||||||
|
|
||||||
|
# Update the spec removing one node
|
||||||
|
#self.update_servicechain_spec(spec['id'], nodes=[node1['id']],
|
||||||
|
# expected_res_status=200)
|
||||||
|
|
||||||
|
#spec = self.show_servicechain_spec(spec['id'])['servicechain_spec']
|
||||||
|
# Verify param names correspondence
|
||||||
|
#self.assertEqual(
|
||||||
|
# collections.Counter(params_node_1),
|
||||||
|
# collections.Counter(ast.literal_eval(spec['config_param_names'])))
|
||||||
|
@ -59,6 +59,8 @@ gbpservice.neutron.servicechain.servicechain_drivers =
|
|||||||
oneconvergence_servicechain_driver = gbpservice.neutron.services.servicechain.plugins.msc.drivers.oneconvergence_servicechain_driver:OneconvergenceServiceChainDriver
|
oneconvergence_servicechain_driver = gbpservice.neutron.services.servicechain.plugins.msc.drivers.oneconvergence_servicechain_driver:OneconvergenceServiceChainDriver
|
||||||
gbpservice.neutron.servicechain.ncp_drivers =
|
gbpservice.neutron.servicechain.ncp_drivers =
|
||||||
node_dummy = gbpservice.neutron.services.servicechain.plugins.ncp.node_drivers.dummy_driver:NoopNodeDriver
|
node_dummy = gbpservice.neutron.services.servicechain.plugins.ncp.node_drivers.dummy_driver:NoopNodeDriver
|
||||||
|
gbpservice.neutron.servicechain.ncp_plumbers =
|
||||||
|
dummy_plumber = gbpservice.neutron.services.servicechain.plugins.ncp.node_plumbers.dummy_plumber:NoopPlumber
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
|
Loading…
Reference in New Issue
Block a user