600 lines
27 KiB
Python
600 lines
27 KiB
Python
# 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.quota import resource_registry
|
|
from neutron_lib.plugins import constants as pconst
|
|
from oslo_config import cfg
|
|
from oslo_log import helpers as log
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
|
|
from gbpservice.common import utils
|
|
from gbpservice.neutron.db import api as db_api
|
|
from gbpservice.neutron.db import servicechain_db
|
|
from gbpservice.neutron.services.grouppolicy.common import constants as gp_cts
|
|
from gbpservice.neutron.services.grouppolicy.common import utils as gutils
|
|
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 (
|
|
node_driver_manager as manager)
|
|
from gbpservice.neutron.services.servicechain.plugins import sharing
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
PLUMBER_NAMESPACE = 'gbpservice.neutron.servicechain.ncp_plumbers'
|
|
cfg.CONF.import_opt('policy_drivers',
|
|
'gbpservice.neutron.services.grouppolicy.config',
|
|
group='group_policy')
|
|
STATUS = 'status'
|
|
STATUS_DETAILS = 'status_details'
|
|
STATUS_SET = set([STATUS, STATUS_DETAILS])
|
|
|
|
|
|
class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
|
|
sharing.SharingMixin):
|
|
|
|
"""Implementation of the Service Chain Plugin.
|
|
|
|
"""
|
|
supported_extension_aliases = ["servicechain"]
|
|
path_prefix = gp_cts.GBP_PREFIXES[pconst.SERVICECHAIN]
|
|
|
|
@resource_registry.tracked_resources(
|
|
servicechain_node=servicechain_db.ServiceChainNode,
|
|
servicechain_spec=servicechain_db.ServiceChainSpec,
|
|
servicechain_instance=servicechain_db.ServiceChainInstance,
|
|
service_profile=servicechain_db.ServiceProfile)
|
|
def __init__(self):
|
|
self.driver_manager = manager.NodeDriverManager()
|
|
super(NodeCompositionPlugin, self).__init__()
|
|
self.driver_manager.initialize()
|
|
plumber_klass = cfg.CONF.node_composition_plugin.node_plumber
|
|
self.plumber = utils.load_plugin(
|
|
PLUMBER_NAMESPACE, plumber_klass)
|
|
self.plumber.initialize()
|
|
LOG.info("Initialized node plumber '%s'", plumber_klass)
|
|
|
|
@log.log_method_call
|
|
def create_servicechain_instance(self, context, servicechain_instance):
|
|
"""Instance created.
|
|
|
|
When a Servicechain Instance is created, all its nodes need to be
|
|
instantiated.
|
|
"""
|
|
instance = self._process_commit_phase(context)
|
|
if instance:
|
|
return instance
|
|
|
|
deployers = {}
|
|
# REVISIT: Consider adding ensure_tenant() call here
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
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')
|
|
if not gutils.is_precommit_policy_driver_configured():
|
|
# 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
|
|
|
|
def _process_commit_phase(self, context):
|
|
if hasattr(context, 'commit_phase'):
|
|
if not gutils.is_precommit_policy_driver_configured() and (
|
|
context.commit_phase == gp_cts.PRE_COMMIT):
|
|
# The following is a bit of a hack to no-op
|
|
# the call from the postcommit policy driver
|
|
# during the pre-commit phase.
|
|
return True
|
|
if gutils.is_precommit_policy_driver_configured() and (
|
|
context.commit_phase == gp_cts.POST_COMMIT):
|
|
instance = self.get_servicechain_instance(
|
|
context, context.servicechain_instance['id'])
|
|
self._call_deploy_sc_node(context, instance)
|
|
return instance
|
|
|
|
def _call_deploy_sc_node(self, context, instance):
|
|
# Actual node deploy
|
|
try:
|
|
deployers = self._get_scheduled_drivers(
|
|
context, instance, 'deploy')
|
|
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, "
|
|
"servicechain_instance %s is in ERROR state",
|
|
instance['id'])
|
|
|
|
@log.log_method_call
|
|
def get_servicechain_instance(self, context,
|
|
servicechain_instance_id, fields=None):
|
|
"""Instance retrieved.
|
|
|
|
While get Servicechain Instance details, get all nodes details.
|
|
"""
|
|
return self._get_resource(context, 'servicechain_instance',
|
|
servicechain_instance_id, fields)
|
|
|
|
@log.log_method_call
|
|
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.
|
|
"""
|
|
instance = self._process_commit_phase(context)
|
|
if instance:
|
|
return instance
|
|
|
|
deployers = {}
|
|
updaters = {}
|
|
destroyers = {}
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
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')
|
|
else: # Could be classifier update
|
|
updaters = self._get_scheduled_drivers(
|
|
context, original_instance, 'update')
|
|
|
|
if (original_instance['servicechain_specs'] !=
|
|
updated_instance['servicechain_specs']):
|
|
self._destroy_servicechain_nodes(context, destroyers)
|
|
deployers = self._get_scheduled_drivers(
|
|
context, updated_instance, 'deploy')
|
|
context.deployers = deployers
|
|
context.servicechain_instance = updated_instance
|
|
if not gutils.is_precommit_policy_driver_configured():
|
|
self._deploy_servicechain_nodes(context, deployers)
|
|
else:
|
|
self._update_servicechain_nodes(context, updaters)
|
|
return updated_instance
|
|
|
|
@log.log_method_call
|
|
def delete_servicechain_instance(self, context, servicechain_instance_id):
|
|
"""Instance deleted.
|
|
|
|
When a Servicechain Instance is deleted, all its nodes need to be
|
|
destroyed.
|
|
"""
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
instance = self.get_servicechain_instance(context,
|
|
servicechain_instance_id)
|
|
destroyers = self._get_scheduled_drivers(context, instance,
|
|
'destroy')
|
|
self._destroy_servicechain_nodes(context, destroyers)
|
|
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
super(NodeCompositionPlugin, self).delete_servicechain_instance(
|
|
context, servicechain_instance_id)
|
|
|
|
@log.log_method_call
|
|
def create_servicechain_node(self, context, servicechain_node):
|
|
# REVISIT: Consider adding ensure_tenant() call here
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
result = super(NodeCompositionPlugin,
|
|
self).create_servicechain_node(context,
|
|
servicechain_node)
|
|
self._validate_shared_create(context, result, 'servicechain_node')
|
|
return result
|
|
|
|
@log.log_method_call
|
|
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.
|
|
"""
|
|
updaters = {}
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
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 list(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_method_call
|
|
def get_servicechain_node(
|
|
self, context, servicechain_node_id, fields=None):
|
|
return self._get_resource(context, 'servicechain_node',
|
|
servicechain_node_id, fields)
|
|
|
|
@log.log_method_call
|
|
def create_servicechain_spec(self, context, servicechain_spec):
|
|
# REVISIT: Consider adding ensure_tenant() call here
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
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_method_call
|
|
def update_servicechain_spec(self, context, servicechain_spec_id,
|
|
servicechain_spec):
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
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')
|
|
# The reference plumber does not support modifying or reordering of
|
|
# nodes in a service chain spec. Disallow update for now
|
|
if (original_sc_spec['nodes'] != updated_sc_spec['nodes'] and
|
|
original_sc_spec['instances']):
|
|
raise exc.InuseSpecNodeUpdateNotAllowed()
|
|
|
|
return updated_sc_spec
|
|
|
|
@log.log_method_call
|
|
def get_servicechain_spec(self, context,
|
|
servicechain_spec_id, fields=None):
|
|
return self._get_resource(context, 'servicechain_spec',
|
|
servicechain_spec_id, fields)
|
|
|
|
@log.log_method_call
|
|
def create_service_profile(self, context, service_profile):
|
|
# REVISIT: Consider adding ensure_tenant() call here
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
result = super(
|
|
NodeCompositionPlugin, self).create_service_profile(
|
|
context, service_profile)
|
|
self._validate_shared_create(context, result, 'service_profile')
|
|
return result
|
|
|
|
@log.log_method_call
|
|
def update_service_profile(self, context, service_profile_id,
|
|
service_profile):
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
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
|
|
|
|
@log.log_method_call
|
|
def get_service_profile(self, context, service_profile_id, fields=None):
|
|
return self._get_resource(context, 'service_profile',
|
|
service_profile_id, fields)
|
|
|
|
def update_chains_pt_added(self, context, policy_target, instance_id):
|
|
""" Auto scaling function.
|
|
|
|
Notify the correct set of node drivers that a new policy target has
|
|
been added to a relevant PTG.
|
|
"""
|
|
self._update_chains_pt_modified(context, policy_target, instance_id,
|
|
'added')
|
|
|
|
def update_chains_pt_removed(self, context, policy_target, instance_id):
|
|
""" Auto scaling function.
|
|
|
|
Notify the correct set of node drivers that a new policy target has
|
|
been removed from a relevant PTG.
|
|
"""
|
|
self._update_chains_pt_modified(context, policy_target, instance_id,
|
|
'removed')
|
|
|
|
def update_chains_consumer_added(self, context, policy_target_group,
|
|
instance_id):
|
|
""" Auto scaling function.
|
|
|
|
Override this method to react to policy target group addition as
|
|
a consumer of a chain.
|
|
"""
|
|
self._update_chains_consumer_modified(context, policy_target_group,
|
|
instance_id, 'added')
|
|
|
|
def policy_target_group_updated(self, context, old_policy_target_group,
|
|
current_policy_target_group,
|
|
instance_id):
|
|
""" Utility function.
|
|
Override this method to react to policy target group update
|
|
"""
|
|
self._policy_target_group_updated(context,
|
|
old_policy_target_group,
|
|
current_policy_target_group,
|
|
instance_id)
|
|
|
|
def update_chains_consumer_removed(self, context, policy_target_group,
|
|
instance_id):
|
|
""" Auto scaling function.
|
|
|
|
Override this method to react to policy target group removed as a
|
|
consumer of a chain
|
|
"""
|
|
self._update_chains_consumer_modified(context, policy_target_group,
|
|
instance_id, 'removed')
|
|
|
|
def _policy_target_group_updated(self, context, old_policy_target_group,
|
|
current_policy_target_group,
|
|
instance_id):
|
|
updaters = self._get_scheduled_drivers(
|
|
context,
|
|
self.get_servicechain_instance(context, instance_id),
|
|
'update')
|
|
for update in list(updaters.values()):
|
|
try:
|
|
update['driver'].policy_target_group_updated(
|
|
update['context'],
|
|
old_policy_target_group,
|
|
current_policy_target_group)
|
|
except exc.NodeDriverError as ex:
|
|
LOG.error("Node Update on policy target group modification"
|
|
" failed, %s", ex.message)
|
|
|
|
def _update_chains_pt_modified(self, context, policy_target, instance_id,
|
|
action):
|
|
updaters = self._get_scheduled_drivers(
|
|
context, self.get_servicechain_instance(context, instance_id),
|
|
'update')
|
|
for update in list(updaters.values()):
|
|
try:
|
|
getattr(update['driver'],
|
|
'update_policy_target_' + action)(
|
|
update['context'], policy_target)
|
|
except exc.NodeDriverError as ex:
|
|
LOG.error("Node Update on policy target modification "
|
|
"failed, %s", ex.message)
|
|
|
|
def _update_chains_consumer_modified(self, context, policy_target_group,
|
|
instance_id, action):
|
|
updaters = self._get_scheduled_drivers(
|
|
context, self.get_servicechain_instance(context, instance_id),
|
|
'update')
|
|
for update in list(updaters.values()):
|
|
try:
|
|
getattr(update['driver'],
|
|
'update_node_consumer_ptg_' + action)(
|
|
update['context'], policy_target_group)
|
|
except exc.NodeDriverError as ex:
|
|
LOG.error(
|
|
"Node Update on policy target group modification "
|
|
"failed, %s", ex.message)
|
|
|
|
def notify_chain_parameters_updated(self, context,
|
|
servicechain_instance_id):
|
|
"""Hook for GBP drivers to inform about any updates that affect the SCI
|
|
|
|
Notify the correct set of node drivers that some parameter that affects
|
|
the service chain is updated. The updates could be something like
|
|
adding or removing an Allow Rule to the ruleset and may have to be
|
|
enforced in the Firewall Service VM, or it could simply be a
|
|
classifier update.
|
|
"""
|
|
sci = self.get_servicechain_instance(context, servicechain_instance_id)
|
|
updaters = self._get_scheduled_drivers(context, sci, 'update')
|
|
for update in list(updaters.values()):
|
|
try:
|
|
getattr(update['driver'],
|
|
'notify_chain_parameters_updated')(update['context'])
|
|
except exc.NodeDriverError as ex:
|
|
LOG.error("Node Update on GBP parameter update "
|
|
"failed, %s", ex.message)
|
|
|
|
def _get_instance_nodes(self, context, instance):
|
|
context = utils.admin_context(context)
|
|
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):
|
|
context = utils.admin_context(context)
|
|
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=None):
|
|
if nodes is None:
|
|
nodes = self._get_instance_nodes(context, instance)
|
|
result = {}
|
|
func = getattr(self.driver_manager, 'schedule_' + action)
|
|
for node in nodes or []:
|
|
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 _get_resource(self, context, resource_name, resource_id, fields=None):
|
|
deployers = {}
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
resource = getattr(super(NodeCompositionPlugin,
|
|
self), 'get_' + resource_name)(context, resource_id)
|
|
if resource_name == 'servicechain_instance':
|
|
if len(resource['servicechain_specs']) > 1:
|
|
raise exc.OneSpecPerInstanceAllowed()
|
|
try:
|
|
deployers = self._get_scheduled_drivers(context, resource,
|
|
'get')
|
|
except Exception:
|
|
LOG.warning("Failed to get node driver")
|
|
|
|
# Invoke drivers only if status attributes are requested
|
|
if not fields or STATUS_SET.intersection(set(fields)):
|
|
_resource = self._get_resource_status(context, resource_name,
|
|
deployers)
|
|
if _resource:
|
|
updated_status = _resource['status']
|
|
updated_status_details = _resource['status_details']
|
|
if resource['status'] != updated_status or (
|
|
resource['status_details'] != updated_status_details):
|
|
new_status = {resource_name:
|
|
{'status': updated_status,
|
|
'status_details': updated_status_details}}
|
|
session = context.session
|
|
with session.begin(subtransactions=True):
|
|
getattr(super(NodeCompositionPlugin, self),
|
|
'update_' + resource_name)(
|
|
context, resource['id'], new_status)
|
|
resource['status'] = updated_status
|
|
resource['status_details'] = updated_status_details
|
|
return db_api.resource_fields(resource, fields)
|
|
|
|
def _get_resource_status(self, context, resource_name, deployers=None):
|
|
"""
|
|
Invoke node drivers only for servicechain_instance.
|
|
Node driver should implement get_status api to return status
|
|
and status_details of servicechain_instance
|
|
"""
|
|
if resource_name == 'servicechain_instance':
|
|
nodes_status = []
|
|
result = {'status': 'BUILD',
|
|
'status_details': 'node deployment in progress'}
|
|
if deployers:
|
|
try:
|
|
for deploy in list(deployers.values()):
|
|
driver = deploy['driver']
|
|
nodes_status.append(driver.get_status(
|
|
deploy['context']))
|
|
node_status = [node['status'] for node in nodes_status]
|
|
if 'ERROR' in node_status:
|
|
result['status'] = 'ERROR'
|
|
result['status_details'] = 'node deployment failed'
|
|
elif node_status.count('ACTIVE') == len(
|
|
list(deployers.values())):
|
|
result['status'] = 'ACTIVE'
|
|
result['status_details'] = 'node deployment completed'
|
|
except Exception as exc:
|
|
LOG.error("Failed to get servicechain instance status "
|
|
"from node driver, Error: %(exc)s", {'exc': exc})
|
|
return
|
|
return result
|
|
result = {'status': 'ACTIVE', 'status_details': ''}
|
|
return result
|
|
|
|
def _deploy_servicechain_nodes(self, context, deployers):
|
|
self.plumber.plug_services(context, list(deployers.values()))
|
|
for deploy in list(deployers.values()):
|
|
driver = deploy['driver']
|
|
driver.create(deploy['context'])
|
|
|
|
def _update_servicechain_nodes(self, context, updaters):
|
|
for update in list(updaters.values()):
|
|
driver = update['driver']
|
|
driver.update(update['context'])
|
|
|
|
def _destroy_servicechain_nodes(self, context, destroyers):
|
|
# Actual node disruption
|
|
try:
|
|
for destroy in list(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:
|
|
if db_api.is_retriable(e):
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.debug(
|
|
"Node driver '%(name)s' failed in"
|
|
" %(method)s, operation will be retried",
|
|
{'name': driver._name, 'method': 'delete'}
|
|
)
|
|
LOG.exception(e)
|
|
finally:
|
|
self.driver_manager.clear_node_owner(destroy['context'])
|
|
finally:
|
|
self.plumber.unplug_services(context, list(destroyers.values()))
|
|
|
|
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')
|