1032 lines
49 KiB
Python
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)
|