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:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user