group-based-policy/gbpservice/neutron/services/grouppolicy/drivers/chain_mapping.py

1032 lines
49 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 keystoneclient import exceptions as k_exceptions
from keystoneclient.v2_0 import client as k_client
from neutron_lib.db import model_base
from oslo_config import cfg
from oslo_log import helpers as log
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import excutils
import sqlalchemy as sa
from gbpservice.common import utils
from gbpservice.network.neutronv2 import local_api
from gbpservice.neutron.db.grouppolicy import group_policy_mapping_db as gpdb
from gbpservice.neutron.db import servicechain_db # noqa
from gbpservice.neutron.services.grouppolicy import (
group_policy_driver_api as api)
from gbpservice.neutron.services.grouppolicy.common import constants as gconst
from gbpservice.neutron.services.grouppolicy.common import exceptions as exc
from gbpservice.neutron.services.grouppolicy.common import utils as gutils
from gbpservice.neutron.services.grouppolicy.drivers import nsp_manager
from gbpservice.neutron.services.grouppolicy import sc_notifications
LOG = logging.getLogger(__name__)
SCI_CONSUMER_NOT_AVAILABLE = 'N/A'
chain_mapping_opts = [
cfg.StrOpt('chain_owner_user',
help=_("Chain owner username. If set, will be used in "
"place of the Neutron service admin for retrieving "
"tenant owner information through Keystone."),
default=''),
cfg.StrOpt('chain_owner_password',
help=_("Chain owner password."), default='',
secret=True),
cfg.StrOpt('chain_owner_tenant_name',
help=_("Name of the Tenant that will own the service chain "
"instances for this driver. Leave empty for provider "
"owned chains."), default=''),
]
cfg.CONF.register_opts(chain_mapping_opts, "chain_mapping")
cfg.CONF.import_opt('policy_drivers',
'gbpservice.neutron.services.grouppolicy.config',
group='group_policy')
class PtgServiceChainInstanceMapping(model_base.BASEV2, model_base.HasProject):
"""Policy Target Group to ServiceChainInstance mapping DB."""
__tablename__ = 'gpm_ptgs_servicechain_mapping'
provider_ptg_id = sa.Column(sa.String(36),
sa.ForeignKey('gp_policy_target_groups.id',
ondelete='CASCADE'),
nullable=False)
# Consumer PTG could be an External Policy
consumer_ptg_id = 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 ChainMappingDriver(api.PolicyDriver, local_api.LocalAPI,
nsp_manager.NetworkServicePolicyMappingMixin,
sc_notifications.ServiceChainNotificationsMixin):
"""Resource Mapping driver for Group Policy plugin.
This driver implements service chain semantics by mapping group
policy resources to various service chain constructs.
"""
@log.log_method_call
def initialize(self):
self._cached_agent_notifier = None
self.chain_owner = ChainMappingDriver.chain_tenant_id(reraise=True)
@staticmethod
def chain_tenant_id(reraise=False):
try:
keystone = ChainMappingDriver.chain_tenant_keystone_client()
except cfg.NoSuchOptError:
return None
if keystone:
tenant = cfg.CONF.chain_mapping.chain_owner_tenant_name
try:
# Can it be retrieved directly, without a further keystone
# call?
tenant = keystone.tenants.find(name=tenant)
return tenant.id
except k_exceptions.NotFound:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error('No tenant with name %s exists.', tenant)
except k_exceptions.NoUniqueMatch:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error('Multiple tenants matches found for %s',
tenant)
@staticmethod
def chain_tenant_keystone_client():
chain_user = cfg.CONF.chain_mapping.chain_owner_user
user, pwd, tenant, auth_url = utils.get_keystone_creds()
user = (chain_user or user)
pwd = (cfg.CONF.chain_mapping.chain_owner_password or
(pwd if not chain_user else ''))
# Tenant must be configured in the resource_mapping section, provider
# owner will be used otherwise.
tenant = cfg.CONF.chain_mapping.chain_owner_tenant_name
if tenant:
return k_client.Client(username=user, password=pwd,
auth_url=auth_url)
@log.log_method_call
def precommit_wrapper_for_chain_mapping(self, caller_method, context):
# This wrapper is called from both precommit and postcommit for
# deciding ordering for execution of precommit and postcommit
if 'precommit' in caller_method:
context._plugin_context.commit_phase = gconst.PRE_COMMIT
# In case of aim_mapping driver (or a similar precommit driver),
# postcommit functionality will be executed as a part of precommit
if gutils.is_precommit_policy_driver_configured():
if 'delete_policy_target_group' in caller_method:
context.ptg_chain_map = self._get_ptg_servicechain_mapping(
context._plugin_context.session,
context.current['id'])
self._cleanup_redirect_action(context)
else:
method = getattr(self, '_' + caller_method.replace(
'precommit', 'postcommit'))
method(context)
else:
context._plugin_context.commit_phase = gconst.POST_COMMIT
# If driver is not aim_mapping (or a similar precommit driver),
# then postcommit functionality will be executed by the call
# from gbp plugin
if not gutils.is_precommit_policy_driver_configured():
method = getattr(self, '_' + caller_method)
method(context)
elif 'create_policy_target_group' in caller_method or (
'create_external_policy' in caller_method):
if hasattr(context, 'provider_context') and (
hasattr(context, 'servicechain_attrs')):
context.provider_context.commit_phase = (
context._plugin_context.commit_phase)
context.provider_context.servicechain_instance = (
context._plugin_context.servicechain_instance)
super(ChainMappingDriver,
self)._create_servicechain_instance(
context.provider_context,
context.servicechain_attrs)
@log.log_method_call
def _create_policy_target_postcommit(self, context):
if not context._plugin._is_service_target(context._plugin_context,
context.current['id']):
mappings = self._get_ptg_servicechain_mapping(
context._plugin_context.session,
provider_ptg_id=context.current['policy_target_group_id'])
for mapping in mappings:
chain_context = self._get_chain_admin_context(
context._plugin_context,
instance_id=mapping.servicechain_instance_id)
self._notify_sc_plugin_pt_added(
chain_context, context.current,
mapping.servicechain_instance_id)
@log.log_method_call
def create_policy_target_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'create_policy_target_precommit', context)
@log.log_method_call
def create_policy_target_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'create_policy_target_postcommit', context)
@log.log_method_call
def delete_policy_target_precommit(self, context):
context._is_service_target = context._plugin._is_service_target(
context._plugin_context, context.current['id'])
@log.log_method_call
def delete_policy_target_postcommit(self, context):
if not context._is_service_target:
mappings = self._get_ptg_servicechain_mapping(
context._plugin_context.session,
provider_ptg_id=context.current['policy_target_group_id'])
for mapping in mappings:
chain_context = self._get_chain_admin_context(
context._plugin_context,
instance_id=mapping.servicechain_instance_id)
self._notify_sc_plugin_pt_removed(
chain_context, context.current,
mapping.servicechain_instance_id)
@log.log_method_call
def _create_policy_target_group_postcommit(self, context):
if (context.current['provided_policy_rule_sets'] and
self._is_group_chainable(context, context.current)):
self._handle_redirect_action(
context, context.current['provided_policy_rule_sets'],
providing_ptg=context.current)
self._handle_prs_added(context)
self._handle_provider_updated(context)
@log.log_method_call
def create_policy_target_group_precommit(self, context):
self._validate_ptg_prss(context, context.current)
self.precommit_wrapper_for_chain_mapping(
'create_policy_target_group_precommit', context)
@log.log_method_call
def create_policy_target_group_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'create_policy_target_group_postcommit', context)
@log.log_method_call
def _update_policy_target_group_postcommit(self, context):
# Update service chain instance when any ruleset is changed
orig = context.original
curr = context.current
new_provided_policy_rule_sets = list(
set(curr['provided_policy_rule_sets']) - set(
orig['provided_policy_rule_sets']))
# Only the ones set in context in precommit operation will be deleted
self._cleanup_redirect_action(context)
# If the spec is changed, then update the chain with new spec
# If redirect is newly added, create the chain
if (self._is_redirect_in_policy_rule_sets(
context, new_provided_policy_rule_sets) and
self._is_group_chainable(context, context.current)):
self._handle_redirect_action(
context, curr['provided_policy_rule_sets'],
providing_ptg=context.current)
self._handle_prs_updated(context)
self._handle_provider_updated(context)
@log.log_method_call
def update_policy_target_group_precommit(self, context):
self._validate_ptg_prss(context, context.current)
self._stash_ptg_modified_chains(context)
self.precommit_wrapper_for_chain_mapping(
'update_policy_target_group_precommit', context)
@log.log_method_call
def update_policy_target_group_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_policy_target_group_postcommit', context)
@log.log_method_call
def get_policy_target_group_status(self, context):
try:
if (context.current['provided_policy_rule_sets'] and not
context.current.get('proxied_group_id')):
ptg_status = []
for sci in self._get_chains_by_prs(
context,
context.current['provided_policy_rule_sets']):
servicechain_instance = self._get_servicechain_instance(
context._plugin_context,
sci)
if (servicechain_instance['provider_ptg_id'] ==
context.current['id']):
ptg_status.append(
{'status': servicechain_instance['status'],
'status_details': servicechain_instance[
'status_details']})
# REVISIT: For now assuming there will be only
# one sci associated with this ptg
if ptg_status:
context.current['status'] = ptg_status[0]['status']
context.current['status_details'] = ptg_status[0][
'status_details']
except Exception:
LOG.error('Failed to update ptg status')
@log.log_method_call
def _delete_policy_target_group_postcommit(self, context):
self._handle_prs_removed(context)
@log.log_method_call
def delete_policy_target_group_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'delete_policy_target_group_precommit', context)
@log.log_method_call
def delete_policy_target_group_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'delete_policy_target_group_postcommit', context)
@log.log_method_call
def _update_policy_classifier_postcommit(self, context):
self._handle_classifier_update_notification(context)
@log.log_method_call
def update_policy_classifier_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_policy_classifier_precommit', context)
@log.log_method_call
def update_policy_classifier_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_policy_classifier_postcommit', context)
@log.log_method_call
def create_policy_action_precommit(self, context):
spec_id = context.current['action_value']
if spec_id:
specs = self._get_servicechain_specs(
context._plugin_context, filters={'id': [spec_id]})
for spec in specs:
if not spec.get('shared', False):
self._reject_shared(context.current, 'policy_action')
@log.log_method_call
def _update_policy_action_postcommit(self, context):
self._handle_redirect_spec_id_update(context)
@log.log_method_call
def update_policy_action_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_policy_action_precommit', context)
@log.log_method_call
def update_policy_action_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_policy_action_postcommit', context)
@log.log_method_call
def create_policy_rule_precommit(self, context):
self._reject_multiple_redirects_in_rule(context)
@log.log_method_call
def _update_policy_rule_postcommit(self, context):
old_classifier_id = context.original['policy_classifier_id']
new_classifier_id = context.current['policy_classifier_id']
old_action_set = set(context.current['policy_actions'])
new_action_set = set(context.original['policy_actions'])
if (old_classifier_id != new_classifier_id or
old_action_set != new_action_set):
policy_rule_sets = (
context._plugin._get_policy_rule_policy_rule_sets(
context._plugin_context, context.current['id']))
old_redirect_policy_actions = context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': context.original['policy_actions'],
'action_type': [gconst.GP_ACTION_REDIRECT]})
new_redirect_policy_actions = context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': context.current['policy_actions'],
'action_type': [gconst.GP_ACTION_REDIRECT]})
if old_redirect_policy_actions or new_redirect_policy_actions:
self._handle_redirect_action(context, policy_rule_sets)
@log.log_method_call
def update_policy_rule_precommit(self, context):
self._reject_multiple_redirects_in_rule(context)
old_redirect = self._get_redirect_action(context, context.original)
new_redirect = self._get_redirect_action(context, context.current)
if not old_redirect and new_redirect:
# If redirect action is added, check that there's no contract that
# already has a redirect action
for prs in context._plugin.get_policy_rule_sets(
context._plugin_context,
{'id': context.current['policy_rule_sets']}):
# Make sure the PRS can have a new redirect action
self._validate_new_prs_redirect(context, prs)
self.precommit_wrapper_for_chain_mapping(
'update_policy_rule_precommit', context)
@log.log_method_call
def update_policy_rule_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_policy_rule_postcommit', context)
@log.log_method_call
def _create_policy_rule_set_postcommit(self, context):
if context.current['child_policy_rule_sets']:
self._handle_redirect_action(
context, context.current['child_policy_rule_sets'])
@log.log_method_call
def create_policy_rule_set_precommit(self, context):
self._reject_multiple_redirects_in_prs(context)
self.precommit_wrapper_for_chain_mapping(
'create_policy_rule_set_precommit', context)
@log.log_method_call
def create_policy_rule_set_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'create_policy_rule_set_postcommit', context)
@log.log_method_call
def _update_policy_rule_set_postcommit(self, context):
if self._is_redirect_rule_updated(context):
# Handle any Redirects from the current Policy Rule Set
self._handle_redirect_action(context, [context.current['id']])
# Handle Update/Delete of Redirects for any child Rule Sets
if (set(context.original['child_policy_rule_sets']) !=
set(context.current['child_policy_rule_sets'])):
if context.original['child_policy_rule_sets']:
self._handle_redirect_action(
context, context.original['child_policy_rule_sets'])
if context.current['child_policy_rule_sets']:
self._handle_redirect_action(
context, context.current['child_policy_rule_sets'])
@log.log_method_call
def update_policy_rule_set_precommit(self, context):
self._reject_multiple_redirects_in_prs(context)
# If a redirect action is added (from 0 to one) we have to validate
# the providing and consuming PTGs. Not needed at creation time since
# no PTG could be possibly providing or consuming it
old_red_count = self._multiple_pr_redirect_action_number(
context._plugin_context.session, context.original['policy_rules'])
new_red_count = self._multiple_pr_redirect_action_number(
context._plugin_context.session, context.current['policy_rules'])
if new_red_count > old_red_count:
self._validate_new_prs_redirect(context, context.current)
self.precommit_wrapper_for_chain_mapping(
'update_policy_rule_set_precommit', context)
@log.log_method_call
def update_policy_rule_set_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_policy_rule_set_postcommit', context)
@log.log_method_call
def _delete_policy_rule_set_postcommit(self, context):
if context.current['child_policy_rule_sets']:
self._handle_redirect_action(
context, context.current['child_policy_rule_sets'])
@log.log_method_call
def delete_policy_rule_set_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'delete_policy_rule_set_precommit', context)
@log.log_method_call
def delete_policy_rule_set_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'delete_policy_rule_set_postcommit', context)
@log.log_method_call
def _create_external_policy_postcommit(self, context):
self._handle_prs_added(context)
@log.log_method_call
def create_external_policy_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'create_external_policy_precommit', context)
@log.log_method_call
def create_external_policy_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'create_external_policy_postcommit', context)
@log.log_method_call
def _update_external_policy_postcommit(self, context):
self._handle_prs_updated(context)
@log.log_method_call
def update_external_policy_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_external_policy_precommit', context)
@log.log_method_call
def update_external_policy_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'update_external_policy_postcommit', context)
@log.log_method_call
def _delete_external_policy_postcommit(self, context):
self._handle_prs_removed(context)
@log.log_method_call
def delete_external_policy_precommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'delete_external_policy_precommit', context)
@log.log_method_call
def delete_external_policy_postcommit(self, context):
self.precommit_wrapper_for_chain_mapping(
'delete_external_policy_postcommit', context)
def _is_redirect_rule_updated(self, context):
if (not context.original['child_policy_rule_sets']) and (
not context.current['child_policy_rule_sets']):
'''
This method checks if a rule with a REDIRECT action is added to or
removed from the PRS. If no REDIRECT action has been added or
removed, then the processing for REDIRECT does not have to be
performed and existing service chain instances should be
left untouched.
'''
old_redirect_count = self._multiple_pr_redirect_action_number(
context._plugin_context.session,
context.original['policy_rules'])
new_redirect_count = self._multiple_pr_redirect_action_number(
context._plugin_context.session,
context.current['policy_rules'])
if (new_redirect_count == 0) and (old_redirect_count == 0):
return False
elif (new_redirect_count != old_redirect_count):
return True
else:
original_policy_rules = context.original['policy_rules']
current_policy_rules = context.current['policy_rules']
policy_rules = context._plugin.get_policy_rules(
context._plugin_context,
filters={'id': original_policy_rules})
for policy_rule in policy_rules:
original_redirect_policy_action = (
self._get_redirect_action(
context, policy_rule))
if (original_redirect_policy_action) and (
policy_rule['id'] not in current_policy_rules):
return True
policy_rules = context._plugin.get_policy_rules(
context._plugin_context,
filters={'id': current_policy_rules})
for policy_rule in policy_rules:
current_redirect_policy_action = self._get_redirect_action(
context, policy_rule)
if (current_redirect_policy_action) and (
policy_rule['id'] not in original_policy_rules):
return True
return False
return True
def _handle_prs_added(self, context):
# Expecting either a PTG or EP context
if context.current['consumed_policy_rule_sets']:
for sci in self._get_chains_by_prs(
context, context.current['consumed_policy_rule_sets']):
chain_context = self._get_chain_admin_context(
context._plugin_context, instance_id=sci)
self._notify_sc_consumer_added(
chain_context, context.current, sci)
def _handle_prs_removed(self, context):
# Expecting either a PTG or EP context
if context.current['consumed_policy_rule_sets']:
for sci in self._get_chains_by_prs(
context, context.current['consumed_policy_rule_sets']):
chain_context = self._get_chain_admin_context(
context._plugin_context, instance_id=sci)
self._notify_sc_consumer_removed(
chain_context, context.current, sci)
def _handle_prs_updated(self, context):
# Expecting either a PTG or EP context
if (context.current['consumed_policy_rule_sets'] !=
context.original['consumed_policy_rule_sets']):
added, removed = utils.set_difference(
context.current['consumed_policy_rule_sets'],
context.original['consumed_policy_rule_sets'])
if removed:
for sci in self._get_chains_by_prs(context, removed):
chain_context = self._get_chain_admin_context(
context._plugin_context, instance_id=sci)
self._notify_sc_consumer_removed(
chain_context, context.current, sci)
if added:
for sci in self._get_chains_by_prs(context, added):
chain_context = self._get_chain_admin_context(
context._plugin_context, instance_id=sci)
self._notify_sc_consumer_removed(
chain_context, context.current, sci)
def _handle_redirect_spec_id_update(self, context):
if (context.current['action_type'] != gconst.GP_ACTION_REDIRECT
or context.current['action_value'] ==
context.original['action_value']):
return
spec = self._servicechain_plugin._get_servicechain_spec(
context._plugin_context, context.original['action_value'])
for servicechain_instance in spec.instances:
sc_instance_id = servicechain_instance.servicechain_instance_id
sc_instance = self._servicechain_plugin.get_servicechain_instance(
context._plugin_context, sc_instance_id)
old_specs = sc_instance['servicechain_specs']
# Use the parent/child redirect spec as it is. Only replace the
# current one
new_specs = [context.current['action_value'] if
x == context.original['action_value'] else
x for x in old_specs]
self._update_servicechain_instance(
context._plugin_context,
servicechain_instance.servicechain_instance_id,
sc_specs=new_specs)
def _update_servicechain_instance(self, plugin_context, sc_instance_id,
classifier_id=None, sc_specs=None):
sc_instance_update_data = {}
if sc_specs:
sc_instance_update_data.update({'servicechain_specs': sc_specs})
if classifier_id:
sc_instance_update_data.update({'classifier_id': classifier_id})
super(ChainMappingDriver, self)._update_servicechain_instance(
self._get_chain_admin_context(
plugin_context, instance_id=sc_instance_id),
sc_instance_id, sc_instance_update_data)
# This method would either update an existing chain instance, or creates a
# new chain instance or delete the existing instance. In case of updates,
# the parameters that can be updated are service chain spec and
# classifier ID.
def _handle_redirect_action(self, context, policy_rule_set_ids,
providing_ptg=None):
policy_rule_sets = context._plugin.get_policy_rule_sets(
context._plugin_context, filters={'id': policy_rule_set_ids})
for policy_rule_set in policy_rule_sets:
if providing_ptg:
ptgs_providing_prs = [providing_ptg]
else:
if not policy_rule_set['providing_policy_target_groups']:
continue
ptgs_providing_prs = context._plugin.get_policy_target_groups(
context._plugin_context.elevated(),
{'id': policy_rule_set['providing_policy_target_groups']})
parent_classifier_id = None
parent_spec_id = None
if policy_rule_set['parent_id']:
parent = context._plugin.get_policy_rule_set(
context._plugin_context, policy_rule_set['parent_id'])
policy_rules = context._plugin.get_policy_rules(
context._plugin_context,
filters={'id': parent['policy_rules']})
for policy_rule in policy_rules:
policy_actions = context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': policy_rule["policy_actions"],
'action_type': [gconst.GP_ACTION_REDIRECT]})
if policy_actions:
parent_spec_id = policy_actions[0].get("action_value")
parent_classifier_id = policy_rule.get(
"policy_classifier_id")
break # only one redirect action is supported
policy_rules = context._plugin.get_policy_rules(
context._plugin_context,
filters={'id': policy_rule_set['policy_rules']})
for policy_rule in policy_rules:
hierarchial_classifier_mismatch = False
classifier_id = policy_rule.get("policy_classifier_id")
if parent_classifier_id and (parent_classifier_id !=
classifier_id):
hierarchial_classifier_mismatch = True
policy_actions = context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': policy_rule.get("policy_actions"),
'action_type': [gconst.GP_ACTION_REDIRECT]})
# Only one Redirect action per PRS. The chain may belong to
# another PRS in which case the chain should not be deleted
if (self._is_redirect_in_policy_rule_sets(
context, policy_rule_set_ids) and not policy_actions):
continue
spec_id = (policy_actions and policy_actions[0]['action_value']
or None)
for ptg_providing_prs in ptgs_providing_prs:
# REVISIT(Magesh): There are concurrency issues here with
# concurrent updates to the same PRS, Policy Rule or Action
# value
if self._is_group_chainable(context, ptg_providing_prs):
self._create_or_update_chain(
context, ptg_providing_prs['id'],
SCI_CONSUMER_NOT_AVAILABLE, spec_id,
parent_spec_id, classifier_id,
hierarchial_classifier_mismatch,
policy_rule_set)
def _create_or_update_chain(self, context, provider, consumer, spec_id,
parent_spec_id, classifier_id,
hierarchial_classifier_mismatch, prs_id):
ptg_chain_map = self._get_ptg_servicechain_mapping(
context._plugin_context.session, provider)
if ptg_chain_map:
if hierarchial_classifier_mismatch or not spec_id:
ctx = self._get_chain_admin_context(
context._plugin_context,
tenant_id=ptg_chain_map[0].tenant_id)
self._delete_servicechain_instance(
ctx, ptg_chain_map[0].servicechain_instance_id)
else:
sc_specs = [spec_id]
if parent_spec_id:
sc_specs.insert(0, parent_spec_id)
# One chain per providing PTG
self._update_servicechain_instance(
context._plugin_context,
ptg_chain_map[0].servicechain_instance_id,
classifier_id=classifier_id,
sc_specs=sc_specs)
elif spec_id and not hierarchial_classifier_mismatch:
self._create_servicechain_instance(
context, spec_id, parent_spec_id, provider,
SCI_CONSUMER_NOT_AVAILABLE, classifier_id, prs_id)
def _cleanup_redirect_action(self, context):
for ptg_chain in context.ptg_chain_map:
ctx = self._get_chain_admin_context(context._plugin_context,
tenant_id=ptg_chain.tenant_id)
self._delete_servicechain_instance(
ctx, ptg_chain.servicechain_instance_id)
def _create_servicechain_instance(self, context, servicechain_spec,
parent_servicechain_spec,
provider_ptg_id, consumer_ptg_id,
classifier_id, policy_rule_set):
sc_spec = [servicechain_spec]
if parent_servicechain_spec:
sc_spec.insert(0, parent_servicechain_spec)
config_param_values = {}
provider_ptg = context._plugin.get_policy_target_group(
utils.admin_context(context._plugin_context), provider_ptg_id)
p_ctx = self._get_chain_admin_context(
context._plugin_context,
provider_tenant_id=provider_ptg['tenant_id'])
session = context._plugin_context.session
network_service_policy_id = provider_ptg.get(
"network_service_policy_id")
if network_service_policy_id:
nsp = context._plugin.get_network_service_policy(
p_ctx, network_service_policy_id)
service_params = nsp.get("network_service_params")
for service_parameter in service_params:
param_type = service_parameter.get("type")
param_value = service_parameter.get("value")
if param_type == "ip_single" and param_value == "self_subnet":
key = service_parameter.get("name")
servicepolicy_ptg_ip_map = (
self._get_ptg_policy_ipaddress_mapping(
session, provider_ptg_id))
servicepolicy_ip = servicepolicy_ptg_ip_map.get(
"ipaddress")
config_param_values[key] = servicepolicy_ip
elif param_type == "ip_single" and param_value == "nat_pool":
key = service_parameter.get("name")
fip_maps = (
self._get_ptg_policy_fip_mapping(
context._plugin_context.session,
provider_ptg_id))
servicepolicy_fip_ids = []
for fip_map in fip_maps:
servicepolicy_fip_ids.append(fip_map.floatingip_id)
config_param_values[key] = servicepolicy_fip_ids
name = 'gbp_%s_%s' % (policy_rule_set['name'], provider_ptg['name'])
attrs = {'tenant_id': p_ctx.tenant,
'name': name,
'description': "",
'servicechain_specs': sc_spec,
'provider_ptg_id': provider_ptg_id,
'consumer_ptg_id': SCI_CONSUMER_NOT_AVAILABLE,
'management_ptg_id': None,
'classifier_id': classifier_id,
'config_param_values': jsonutils.dumps(config_param_values)}
context.provider_context = p_ctx
context.servicechain_attrs = attrs
sc_instance = super(
ChainMappingDriver, self)._create_servicechain_instance(
p_ctx, attrs)
context._plugin_context.servicechain_instance = sc_instance
self._set_ptg_servicechain_instance_mapping(
session, provider_ptg_id, SCI_CONSUMER_NOT_AVAILABLE,
sc_instance['id'], p_ctx.tenant)
return sc_instance
def _set_ptg_servicechain_instance_mapping(self, session, provider_ptg_id,
consumer_ptg_id,
servicechain_instance_id,
provider_tenant_id):
with session.begin(subtransactions=True):
mapping = PtgServiceChainInstanceMapping(
provider_ptg_id=provider_ptg_id,
consumer_ptg_id=consumer_ptg_id,
servicechain_instance_id=servicechain_instance_id,
tenant_id=provider_tenant_id)
session.add(mapping)
def _get_ptg_servicechain_mapping(self, session, provider_ptg_id=None,
consumer_ptg_id=None, tenant_id=None,
servicechain_instance_id=None,
provider_ptg_ids=None):
with session.begin(subtransactions=True):
query = session.query(PtgServiceChainInstanceMapping)
if provider_ptg_id:
query = query.filter_by(provider_ptg_id=provider_ptg_id)
elif provider_ptg_ids:
query = query.filter(
PtgServiceChainInstanceMapping.provider_ptg_id.in_(
list(provider_ptg_ids)))
if consumer_ptg_id:
query = query.filter_by(consumer_ptg_id=consumer_ptg_id)
if servicechain_instance_id:
query = query.filter_by(
servicechain_instance_id=servicechain_instance_id)
if tenant_id:
query = query.filter_by(consumer_ptg_id=tenant_id)
all = query.all()
return [utils.DictClass([('provider_ptg_id', x.provider_ptg_id),
('consumer_ptg_id', x.consumer_ptg_id),
('servicechain_instance_id',
x.servicechain_instance_id),
('tenant_id', x.tenant_id)])
for x in all]
def _get_chain_admin_context(self, plugin_context, tenant_id=None,
provider_tenant_id=None, instance_id=None):
ctx = plugin_context.elevated()
# REVISIT(Ivar): Any particular implication when a provider owned PT
# exist in the consumer PTG? Especially when the consumer PTG belongs
# to another tenant? We may want to consider a strong convention
# for reference plumbers to absolutely avoid this kind of inter tenant
# object creation when the owner is the provider (in which case, the
# context can as well be a normal context without admin capabilities).
ctx.tenant_id = None
if instance_id:
cmap = self._get_ptg_servicechain_mapping(
ctx.session, servicechain_instance_id=instance_id)
if cmap:
ctx.tenant_id = cmap[0].tenant_id
if not self.chain_owner:
self.chain_owner = ChainMappingDriver.chain_tenant_id(reraise=True)
if not ctx.tenant_id:
ctx.tenant_id = tenant_id or self.chain_owner or provider_tenant_id
if self.chain_owner == ctx.tenant_id:
ctx.auth_token = self.chain_tenant_keystone_client().get_token(
self.chain_owner)
return ctx
def _is_redirect_in_policy_rule_sets(self, context, policy_rule_sets):
policy_rule_ids = []
for prs in context._plugin.get_policy_rule_sets(
context._plugin_context, filters={'id': policy_rule_sets}):
policy_rule_ids.extend(prs['policy_rules'])
for rule in context._plugin.get_policy_rules(
context._plugin_context, filters={'id': policy_rule_ids}):
redirect_actions = context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': rule["policy_actions"],
'action_type': [gconst.GP_ACTION_REDIRECT]})
if redirect_actions:
return True
return False
def _get_redirect_action(self, context, policy_rule):
for action in context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': policy_rule['policy_actions']}):
if action['action_type'] == gconst.GP_ACTION_REDIRECT:
return action
def _validate_new_prs_redirect(self, context, prs):
if self._prss_redirect_rules(context._plugin_context.session,
[prs['id']]) > 1:
raise exc.MultipleRedirectActionsNotSupportedForPRS()
for ptg in context._plugin.get_policy_target_groups(
context._plugin_context,
{'id': prs['providing_policy_target_groups']}):
self._validate_ptg_prss(context, ptg)
def _prss_redirect_rules(self, session, prs_ids):
if len(prs_ids) == 0:
# No result will be found in this case
return 0
query = (session.query(gpdb.gpdb.PolicyAction).
join(gpdb.gpdb.PolicyRuleActionAssociation).
join(gpdb.gpdb.PolicyRule).
join(gpdb.gpdb.PRSToPRAssociation).
filter(
gpdb.gpdb.PRSToPRAssociation.policy_rule_set_id.in_(prs_ids)).
filter(gpdb.gpdb.PolicyAction.action_type ==
gconst.GP_ACTION_REDIRECT))
return query.count()
def _multiple_pr_redirect_action_number(self, session, pr_ids):
# Given a set of rules, gives the total number of redirect actions
# found
if len(pr_ids) == 0:
# No result will be found in this case
return 0
return (session.query(gpdb.gpdb.PolicyAction).
join(gpdb.gpdb.PolicyRuleActionAssociation).
filter(
gpdb.gpdb.PolicyRuleActionAssociation.policy_rule_id.in_(
pr_ids)).
filter(gpdb.gpdb.PolicyAction.action_type ==
gconst.GP_ACTION_REDIRECT)).count()
def _reject_shared(self, object, type):
if object.get('shared'):
raise exc.InvalidSharedResource(type=type,
driver='chain_mapping')
def _reject_multiple_redirects_in_rule(self, context):
policy_actions = context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': context.current['policy_actions'],
'action_type': [gconst.GP_ACTION_REDIRECT]})
if len(policy_actions) > 1:
raise exc.MultipleRedirectActionsNotSupportedForRule()
def _reject_multiple_redirects_in_prs(self, context):
policy_rules = context._plugin.get_policy_rules(
context._plugin_context,
filters={'id': context.current['policy_rules']})
redirect_actions_list = []
for policy_rule in policy_rules:
policy_actions = context._plugin.get_policy_actions(
context._plugin_context,
filters={'id': policy_rule['policy_actions'],
'action_type': [gconst.GP_ACTION_REDIRECT]})
redirect_actions_list.extend(policy_actions)
if len(redirect_actions_list) > 1:
raise exc.MultipleRedirectActionsNotSupportedForPRS()
def _validate_ptg_prss(self, context, ptg):
# If the PTG is providing a redirect PRS, it can't provide any more
# redirect rules
if self._is_group_chainable(context, ptg):
if self._prss_redirect_rules(context._plugin_context.session,
ptg['provided_policy_rule_sets']) > 1:
raise exc.PTGAlreadyProvidingRedirectPRS(ptg_id=ptg['id'])
def _handle_classifier_update_notification(self, context):
# Invoke Service chain update notify hook if protocol or port or
# direction is updated. The SC side will have to reclassify the chain
# and update the traffic steering programming
if (context.original['port_range'] != context.current['port_range'] or
context.original['protocol'] != context.current['protocol'] or
context.original['direction'] != context.current['direction']):
sc_instances = (
self._servicechain_plugin.get_servicechain_instances(
context._plugin_context.elevated(),
filters={'classifier_id': [context.current['id']]}))
for sc_instance in sc_instances:
cmap = self._get_ptg_servicechain_mapping(
context._plugin_context.session,
servicechain_instance_id=sc_instance['id'])
ctx = self._get_chain_admin_context(context._plugin_context,
cmap[0].tenant_id)
self._servicechain_plugin.notify_chain_parameters_updated(
ctx, sc_instance['id'])
def _stash_ptg_modified_chains(self, context):
#Update service chain instance when any ruleset is changed
orig_provided_policy_rule_sets = context.original[
'provided_policy_rule_sets']
curr_provided_policy_rule_sets = context.current[
'provided_policy_rule_sets']
removed_provided_prs = (set(orig_provided_policy_rule_sets) -
set(curr_provided_policy_rule_sets))
added_provided_prs = (set(curr_provided_policy_rule_sets) -
set(orig_provided_policy_rule_sets))
context.ptg_chain_map = []
# If the Redirect is removed, delete the chain. If the spec is
# changed, then update the existing instance with new spec
if (self._is_redirect_in_policy_rule_sets(
context, removed_provided_prs) and not
self._is_redirect_in_policy_rule_sets(
context, added_provided_prs)):
context.ptg_chain_map += self._get_ptg_servicechain_mapping(
context._plugin_context.session, context.current['id'])
def _get_chains_by_prs(self, context, prs_ids):
# REVISIT(ivar): only works under the assumption that only -one- chain
# can be provided by a given group. A more direct way of retrieving
# this info must be implemented before we drop this limitation
result = set()
for prs in self._get_policy_rule_sets(
context._plugin_context.elevated(), {'id': prs_ids}):
if prs['providing_policy_target_groups']:
result |= set(
[x.servicechain_instance_id for x in
self._get_ptg_servicechain_mapping(
context._plugin_context.session,
provider_ptg_ids=prs[
'providing_policy_target_groups'])])
return result
def _is_group_chainable(self, context, group):
"""Determines whether a group should trigger a chain.
Non chainable groups:
- Proxy groups;
:param context:
:param group:
:return: boolean
"""
return (not group.get('proxied_group_id') and
group.get('enforce_service_chains', True) is True)
def _get_ptg_provided_chain_id(self, context, ptg):
try:
return [x.servicechain_instance_id for x in
self._get_ptg_servicechain_mapping(
context._plugin_context.session,
provider_ptg_ids=[ptg['id']])][0]
except IndexError:
return None
def _handle_provider_updated(self, context):
sci = self._get_ptg_provided_chain_id(context, context.current)
if sci:
chain_context = self._get_chain_admin_context(
context._plugin_context, instance_id=sci)
self._notify_ptg_updated(chain_context, context.original,
context.current, sci)