Fix Servicechain Instance update and spec update

1) Clearing Node ownership while taking down a deployed node in case of
service chain instance update

2) Propagating node Add/Remove nodes in spec update to the plumber and
node drivers

3) Update instance without causing node disruption unless spec is updated

Closes-Bug: 1470815

Change-Id: I91c39d9bbf6ed9e52283a7887851ed2f1a8a2da0
This commit is contained in:
Magesh GV
2015-07-02 14:14:54 +05:30
parent a2850d8c58
commit 4f591851a0
8 changed files with 216 additions and 30 deletions

View File

@@ -171,6 +171,21 @@ class NodeDriverBase(object):
"""
pass
@abc.abstractmethod
def notify_chain_parameters_updated(self, context):
"""Update a deployed Service Chain Node on GBP PRS updates
This method can be used to inform the node driver that some parameter
that affects the service chain is updated. The update may be
something like adding or removing an Allow Rule to the ruleset and
this has to be enforced in the Firewall Service VM, or it could simply
be a classifier update.
:param context: NodeDriverContext instance describing the service chain
and the specific node to be processed by this driver.
"""
pass
@abc.abstractproperty
def name(self):
pass
pass

View File

@@ -68,7 +68,7 @@ class ServiceTarget(model_base.BASEV2):
position = sa.Column(sa.Integer)
def set_node_ownership(context, driver_name):
def set_node_owner(context, driver_name):
session = context.session
with session.begin(subtransactions=True):
owner = NodeToDriverMapping(
@@ -89,6 +89,18 @@ def get_node_owner(context):
return query.all()
def unset_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'])
for owner in query.all():
session.delete(owner)
def set_service_target(context, policy_target_id, relationship):
session = context.session
with session.begin(subtransactions=True):

View File

@@ -67,7 +67,7 @@ class NodeDriverManager(stevedore.named.NamedExtensionManager):
for driver in self.ordered_drivers:
try:
driver.obj.validate_create(context)
model.set_node_ownership(context, driver.obj.name)
model.set_node_owner(context, driver.obj.name)
return driver.obj
except n_exc.NeutronException as e:
LOG.warn(e.message)
@@ -78,7 +78,10 @@ class NodeDriverManager(stevedore.named.NamedExtensionManager):
Given a NodeContext, this method returns the driver capable of
destroying the specific node.
"""
return self.get_owning_driver(context)
driver = self.get_owning_driver(context)
if driver:
model.unset_node_owner(context)
return driver
def schedule_update(self, context):
"""Schedule Node Driver for Node Update.
@@ -91,6 +94,15 @@ class NodeDriverManager(stevedore.named.NamedExtensionManager):
driver.validate_update(context)
return driver
def clear_node_owner(self, context):
"""Remove Node Driver ownership set for a Node
Given a NodeContext, this method removes the Node owner mapping in DB.
This method is used when we want to perform a disruptive chain update
by deleting and recreating the Node instances
"""
model.unset_node_owner(context)
def get_owning_driver(self, context):
owner = model.get_node_owner(context)
if owner:

View File

@@ -56,6 +56,10 @@ class NoopNodeDriver(driver_base.NodeDriverBase):
def update_policy_target_removed(self, context, policy_target):
pass
@log.log
def notify_chain_parameters_updated(self, context):
pass
@property
def name(self):
return self._name

View File

@@ -237,6 +237,10 @@ class HeatNodeDriver(driver_base.NodeDriverBase):
if context.current_profile['service_type'] == pconst.LOADBALANCER:
self.update(context)
@log.log
def notify_chain_parameters_updated(self, context):
self.update(context)
@property
def name(self):
return self._name
@@ -307,7 +311,7 @@ class HeatNodeDriver(driver_base.NodeDriverBase):
if stack.stack_status == 'DELETE_FAILED':
heatclient.delete(stack_id)
elif stack.stack_status not in ['UPDATE_IN_PROGRESS',
'PENDING_DELETE']:
'DELETE_IN_PROGRESS']:
return
except Exception:
LOG.exception(_("Retrieving the stack %(stack)s failed."),

View File

@@ -88,6 +88,7 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
"""
session = context.session
deployers = {}
updaters = {}
destroyers = {}
with session.begin(subtransactions=True):
original_instance = self.get_servicechain_instance(
@@ -102,10 +103,18 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
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)
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')
self._deploy_servicechain_nodes(context, deployers)
else:
self._update_servicechain_nodes(context, updaters)
return updated_instance
@log.log
@@ -205,10 +214,31 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
servicechain_spec, set_params=False)
self._validate_shared_update(context, original_sc_spec,
updated_sc_spec, 'servicechain_spec')
# REVISIT(Magesh): Handle this update in a proper way
if (original_sc_spec['nodes'] != updated_sc_spec['nodes'] and
original_sc_spec['instances']):
raise exc.InuseSpecNodeUpdateNotAllowed()
# Remove the deleted node instances and add any new ones added
# REVISIT(Magesh): Invoke a validate update in Node plumber
# and reject the update in case the plumber does not support node
# reordering in the spec. For now reordering is a NOOP
if original_sc_spec['nodes'] != updated_sc_spec['nodes']:
instances = original_sc_spec['instances']
instances = self.get_servicechain_instances(
context, filters={'id': instances})
for instance in instances:
removed_nodes = (set(original_sc_spec['nodes']) -
set(updated_sc_spec['nodes']))
added_nodes = (set(updated_sc_spec['nodes']) -
set(original_sc_spec['nodes']))
removed_nodes = self.get_servicechain_nodes(
context, filters={'id': removed_nodes})
added_nodes = self.get_servicechain_nodes(
context, filters={'id': added_nodes})
destroyers = self._get_scheduled_drivers(
context, instance, 'destroy', nodes=removed_nodes)
deployers = self._get_scheduled_drivers(
context, instance, 'deploy', nodes=added_nodes)
self._destroy_servicechain_nodes(context, destroyers)
self._deploy_servicechain_nodes(context, deployers)
return updated_sc_spec
@log.log
@@ -266,6 +296,26 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
LOG.error(_("Node Update on policy target 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 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):
if not instance['servicechain_specs']:
return []
@@ -282,11 +332,12 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
context, {'id': spec['instances']}))
return result
def _get_scheduled_drivers(self, context, instance, action):
nodes = self._get_instance_nodes(context, instance)
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:
for node in nodes or []:
node_context = ctx.get_node_driver_context(
self, context, instance, node)
driver = func(node_context)
@@ -306,6 +357,11 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
driver = deploy['driver']
driver.create(deploy['context'])
def _update_servicechain_nodes(self, context, updaters):
for update in updaters.values():
driver = update['driver']
driver.update(update['context'])
def _destroy_servicechain_nodes(self, context, destroyers):
# Actual node disruption
try:
@@ -318,9 +374,10 @@ class NodeCompositionPlugin(servicechain_db.ServiceChainDbPlugin,
driver['context'].current_node['id'])
except Exception as e:
LOG.exception(e)
finally:
self.driver_manager.clear_node_owner(destroy['context'])
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