Network Service Policy Driver Implementation
Partially-implements: blueprint group-based-policy-abstraction See spec update: https://review.openstack.org/#/c/127913 for details relevant to this patch. Change-Id: I0bac6f2c34d68967c1201dfc9b34e9611f4b8bce
This commit is contained in:
committed by
Magesh GV
parent
b870cb9dab
commit
0eed91b406
@@ -1 +1 @@
|
||||
577bb4469944
|
||||
ceba6e091b2a
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright 2014 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""gbp_rule_servicechain_mapping
|
||||
|
||||
Revision ID: ceba6e091b2a
|
||||
Revises: 577bb4469944
|
||||
Create Date: 2014-10-25 17:43:08.98888
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ceba6e091b2a'
|
||||
down_revision = '577bb4469944'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.create_table(
|
||||
'gpm_service_policy_ipaddress_mappings',
|
||||
sa.Column('service_policy_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('ipaddress', sa.String(length=36)),
|
||||
sa.Column('endpoint_group', sa.String(length=36)),
|
||||
sa.PrimaryKeyConstraint('endpoint_group'),
|
||||
sa.ForeignKeyConstraint(['endpoint_group'],
|
||||
['gp_endpoint_groups.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('service_policy_id'),
|
||||
sa.ForeignKeyConstraint(['service_policy_id'],
|
||||
['gp_network_service_policies.id'],
|
||||
ondelete='CASCADE'),
|
||||
)
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
op.drop_table('gpm_service_policy_ipaddress_mappings')
|
||||
@@ -465,7 +465,7 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||
'validate': {'type:uuid_list': None},
|
||||
'convert_to': attr.convert_none_to_empty_list,
|
||||
'default': None, 'is_visible': True},
|
||||
'network_service_params': {'allow_post': True, 'allow_put': True,
|
||||
'network_service_params': {'allow_post': True, 'allow_put': False,
|
||||
'validate':
|
||||
{'type:network_service_params': None},
|
||||
'default': None, 'is_visible': True},
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ast
|
||||
import netaddr
|
||||
|
||||
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
|
||||
@@ -19,6 +18,7 @@ from neutron.common import constants as const
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.common import log
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import securitygroup as ext_sg
|
||||
from neutron import manager
|
||||
from neutron.notifiers import nova
|
||||
@@ -100,6 +100,19 @@ class RuleServiceChainInstanceMapping(model_base.BASEV2):
|
||||
ondelete='CASCADE'))
|
||||
|
||||
|
||||
class ServicePolicyEPGIpAddressMapping(model_base.BASEV2):
|
||||
"""Service Policy to IP Address mapping DB."""
|
||||
|
||||
__tablename__ = 'gpm_service_policy_ipaddress_mappings'
|
||||
service_policy_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('gp_network_service_policies.id'),
|
||||
nullable=False, primary_key=True)
|
||||
endpoint_group = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('gp_network_service_policies.id'),
|
||||
nullable=False, primary_key=True)
|
||||
ipaddress = sa.Column(sa.String(36))
|
||||
|
||||
|
||||
class ResourceMappingDriver(api.PolicyDriver):
|
||||
"""Resource Mapping driver for Group Policy plugin.
|
||||
|
||||
@@ -171,8 +184,52 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
router_id)
|
||||
else:
|
||||
self._use_implicit_subnet(context)
|
||||
self._handle_network_service_policy(context)
|
||||
self._handle_contracts(context)
|
||||
|
||||
def _handle_network_service_policy(self, context):
|
||||
network_service_policy_id = context.current.get(
|
||||
"network_service_policy_id")
|
||||
if not network_service_policy_id:
|
||||
return
|
||||
|
||||
nsp = context._plugin.get_network_service_policy(
|
||||
context._plugin_context, network_service_policy_id)
|
||||
nsp_params = nsp.get("network_service_params")
|
||||
if not nsp_params or not nsp_params.get("ip_single") or not (
|
||||
nsp_params["ip_single"].get("value") == "self_subnet"):
|
||||
return
|
||||
#TODO(Magesh):Handle concurrency issues
|
||||
free_ip = self._get_last_free_ip(context._plugin_context,
|
||||
context.current['subnets'])
|
||||
if not free_ip:
|
||||
LOG.error(_("Reserving IP Addresses failed for Network Service "
|
||||
"Policy. No more IP Addresses on subnet"))
|
||||
return
|
||||
#TODO(Magesh):Fetch subnet from EPG to which Service Policy is attached
|
||||
self._remove_ip_from_allocation_pool(context,
|
||||
context.current['subnets'][0],
|
||||
free_ip)
|
||||
self._set_policy_ipaddress_mapping(context._plugin_context.session,
|
||||
network_service_policy_id,
|
||||
context.current['id'],
|
||||
free_ip)
|
||||
|
||||
def _get_service_policy_ipaddress(self, context, endpoint_group):
|
||||
ipaddress = self._get_epg_policy_ipaddress_mapping(
|
||||
context._plugin_context.session,
|
||||
endpoint_group)
|
||||
return ipaddress
|
||||
|
||||
def _cleanup_network_service_policy(self, context, subnet, epg_id):
|
||||
ipaddress = self._get_epg_policy_ipaddress_mapping(
|
||||
context._plugin_context.session,
|
||||
epg_id)
|
||||
if ipaddress:
|
||||
self._restore_ip_to_allocation_pool(context, subnet, ipaddress)
|
||||
self._delete_policy_ipaddress_mapping(
|
||||
context._plugin_context.session, epg_id)
|
||||
|
||||
@log.log
|
||||
def update_endpoint_group_precommit(self, context):
|
||||
if set(context.original['subnets']) - set(context.current['subnets']):
|
||||
@@ -229,6 +286,9 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
|
||||
@log.log
|
||||
def delete_endpoint_group_postcommit(self, context):
|
||||
self._cleanup_network_service_policy(context,
|
||||
context.current['subnets'][0],
|
||||
context.current['id'])
|
||||
self._cleanup_redirect_action(context)
|
||||
l2p_id = context.current['l2_policy_id']
|
||||
router_id = self._get_routerid_for_l2policy(context, l2p_id)
|
||||
@@ -444,6 +504,14 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
for sg in sg_list:
|
||||
self._delete_sg(context._plugin_context, sg)
|
||||
|
||||
@log.log
|
||||
def delete_network_service_policy_postcommit(self, context):
|
||||
for epg_id in context.current.get("endpoint_groups"):
|
||||
epg = context._plugin.get_endpoint_group(context._plugin_context,
|
||||
epg_id)
|
||||
subnet = epg.get('subnets')[0]
|
||||
self._cleanup_network_service_policy(self, context, subnet, epg_id)
|
||||
|
||||
def _get_routerid_for_l2policy(self, context, l2p_id):
|
||||
l2p = context._plugin.get_l2_policy(context._plugin_context, l2p_id)
|
||||
l3p_id = l2p['l3_policy_id']
|
||||
@@ -595,13 +663,10 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
consumed_contracts, "ASSOCIATE")
|
||||
|
||||
def _get_epgs_providing_contract(self, session, contract_id):
|
||||
try:
|
||||
with session.begin(subtransactions=True):
|
||||
return (session.query(
|
||||
gpdb.EndpointGroupContractProvidingAssociation).
|
||||
filter_by(contract_id=contract_id).one())
|
||||
except sql_exc.NoResultFound:
|
||||
return None
|
||||
with session.begin(subtransactions=True):
|
||||
return (session.query(
|
||||
gpdb.EndpointGroupContractProvidingAssociation).
|
||||
filter_by(contract_id=contract_id).first())
|
||||
|
||||
def _get_epgs_consuming_contract(self, session, contract_id):
|
||||
try:
|
||||
@@ -612,6 +677,28 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
except sql_exc.NoResultFound:
|
||||
return None
|
||||
|
||||
def _set_policy_ipaddress_mapping(self, session, service_policy_id,
|
||||
endpoint_group, ipaddress):
|
||||
with session.begin(subtransactions=True):
|
||||
mapping = ServicePolicyEPGIpAddressMapping(
|
||||
service_policy_id=service_policy_id,
|
||||
endpoint_group=endpoint_group,
|
||||
ipaddress=ipaddress)
|
||||
session.add(mapping)
|
||||
|
||||
def _get_epg_policy_ipaddress_mapping(self, session, endpoint_group):
|
||||
with session.begin(subtransactions=True):
|
||||
return (session.query(ServicePolicyEPGIpAddressMapping).
|
||||
filter_by(endpoint_group=endpoint_group).first())
|
||||
|
||||
def _delete_policy_ipaddress_mapping(self, session, endpoint_group):
|
||||
with session.begin(subtransactions=True):
|
||||
mappings = session.query(
|
||||
ServicePolicyEPGIpAddressMapping).filter_by(
|
||||
endpoint_group=endpoint_group).first()
|
||||
for ip_map in mappings:
|
||||
session.delete(ip_map)
|
||||
|
||||
def _handle_redirect_action(self, context, contracts):
|
||||
for contract_id in contracts:
|
||||
epgs_consuming_contract = self._get_epgs_consuming_contract(
|
||||
@@ -707,6 +794,10 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
return self._create_resource(self._core_plugin, plugin_context,
|
||||
'subnet', attrs)
|
||||
|
||||
def _update_subnet(self, plugin_context, subnet_id, attrs):
|
||||
return self._update_resource(self._core_plugin, plugin_context,
|
||||
'subnet', subnet_id, attrs)
|
||||
|
||||
def _delete_subnet(self, plugin_context, subnet_id):
|
||||
self._delete_resource(self._core_plugin, plugin_context, 'subnet',
|
||||
subnet_id)
|
||||
@@ -765,18 +856,74 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
self._delete_resource(self._core_plugin, plugin_context,
|
||||
'security_group_rule', sg_rule_id)
|
||||
|
||||
def _restore_ip_to_allocation_pool(self, context, subnet_id, ip_address):
|
||||
#TODO(Magesh):Pass subnets and loop on subnets. Better to add logic
|
||||
#to Merge the pools together after Fragmentation
|
||||
subnet = self._core_plugin.get_subnet(context._plugin_context,
|
||||
subnet_id)
|
||||
allocation_pools = subnet['allocation_pools']
|
||||
for allocation_pool in allocation_pools:
|
||||
pool_end_ip = allocation_pool.get('end')
|
||||
if ip_address == str(netaddr.IPAddress(pool_end_ip) + 1):
|
||||
new_last_ip = ip_address
|
||||
allocation_pool['end'] = new_last_ip
|
||||
del subnet['gateway_ip']
|
||||
subnet = self._update_subnet(context._plugin_context,
|
||||
subnet['id'], subnet)
|
||||
return
|
||||
#TODO(Magesh):Have to test this logic. Add proper unit tests
|
||||
subnet['allocation_pools'].append({"start": ip_address,
|
||||
"end": ip_address})
|
||||
del subnet['gateway_ip']
|
||||
subnet = self._update_subnet(context._plugin_context,
|
||||
subnet['id'], subnet)
|
||||
|
||||
def _remove_ip_from_allocation_pool(self, context, subnet_id, ip_address):
|
||||
#TODO(Magesh):Pass subnets and loop on subnets
|
||||
subnet = self._core_plugin.get_subnet(context._plugin_context,
|
||||
subnet_id)
|
||||
allocation_pools = subnet['allocation_pools']
|
||||
for allocation_pool in reversed(allocation_pools):
|
||||
if ip_address == allocation_pool.get('end'):
|
||||
new_last_ip = str(netaddr.IPAddress(ip_address) - 1)
|
||||
allocation_pool['end'] = new_last_ip
|
||||
del subnet['gateway_ip']
|
||||
self._update_subnet(context._plugin_context,
|
||||
subnet['id'], subnet)
|
||||
break
|
||||
|
||||
def _get_last_free_ip(self, context, subnets):
|
||||
#Hope lock_mode update is not needed
|
||||
range_qry = context.session.query(
|
||||
models_v2.IPAvailabilityRange).join(
|
||||
models_v2.IPAllocationPool)
|
||||
for subnet_id in subnets:
|
||||
ip_range = range_qry.filter_by(subnet_id=subnet_id).first()
|
||||
if not ip_range:
|
||||
continue
|
||||
ip_address = ip_range['last_ip']
|
||||
return ip_address
|
||||
|
||||
def _create_servicechain_instance(self, context, servicechain_spec,
|
||||
provider_epg, consumer_epg,
|
||||
classifier_id, config_params=None):
|
||||
chain_spec = self._get_resource(self._servicechain_plugin,
|
||||
context._plugin_context,
|
||||
'servicechain_spec',
|
||||
servicechain_spec)
|
||||
config_param_names = chain_spec.get('config_param_names', [])
|
||||
if config_param_names:
|
||||
config_param_names = ast.literal_eval(config_param_names)
|
||||
config_param_values = {}
|
||||
|
||||
epg = context._plugin.get_endpoint_group(context._plugin_context,
|
||||
provider_epg)
|
||||
network_service_policy_id = epg.get("network_service_policy_id")
|
||||
if network_service_policy_id:
|
||||
nsp = context._plugin.get_network_service_policy(
|
||||
context._plugin_context, network_service_policy_id)
|
||||
service_params = nsp.get("network_service_params")
|
||||
ip_single = service_params.get("ip_single")
|
||||
if ip_single:
|
||||
key = ip_single['name']
|
||||
servicepolicy_epg_ip_map = self._get_service_policy_ipaddress(
|
||||
context, provider_epg)
|
||||
servicepolicy_ip = servicepolicy_epg_ip_map.get("ipaddress")
|
||||
config_param_values[key] = servicepolicy_ip
|
||||
|
||||
attrs = {'tenant_id': context.current['tenant_id'],
|
||||
'name': 'gbp_' + context.current['name'],
|
||||
'description': "",
|
||||
@@ -1214,9 +1361,6 @@ class ResourceMappingDriver(api.PolicyDriver):
|
||||
session.add(mapping)
|
||||
|
||||
def _get_rule_servicechain_mapping(self, session, rule_id):
|
||||
try:
|
||||
with session.begin(subtransactions=True):
|
||||
return (session.query(RuleServiceChainInstanceMapping).
|
||||
filter_by(rule_id=rule_id).one())
|
||||
except sql_exc.NoResultFound:
|
||||
return None
|
||||
with session.begin(subtransactions=True):
|
||||
return (session.query(RuleServiceChainInstanceMapping).
|
||||
filter_by(rule_id=rule_id).first())
|
||||
|
||||
@@ -172,7 +172,7 @@ class SimpleChainDriver(object):
|
||||
def _fetch_template_and_params(self, context, sc_instance,
|
||||
sc_spec, sc_node):
|
||||
stack_template = sc_node.get('config')
|
||||
#TODO(magesh):Throw an exception ??
|
||||
#TODO(magesh):Raise an exception ??
|
||||
if not stack_template:
|
||||
LOG.error(_("Service Config is not defined for the service"
|
||||
" chain Node"))
|
||||
@@ -187,24 +187,33 @@ class SimpleChainDriver(object):
|
||||
config_param_names = sc_spec.get('config_param_names', [])
|
||||
if config_param_names:
|
||||
config_param_names = ast.literal_eval(config_param_names)
|
||||
#TODO(magesh):Process on the basis of ResourceType rather than Name
|
||||
provider_epg = sc_instance.get("provider_epg")
|
||||
node_params = (stack_template.get('Parameters')
|
||||
or stack_template.get('parameters'))
|
||||
for key in config_param_names:
|
||||
if key == "PoolMemberIPs":
|
||||
value = self._get_member_ips(context, provider_epg)
|
||||
#TODO(Magesh):Return one value for now
|
||||
value = value[0] if value else ""
|
||||
|
||||
#This service chain driver knows how to fill in two parameter values
|
||||
#for the template at present.
|
||||
#1)Subnet -> Provider EPG subnet is used
|
||||
#2)PoolMemberIPs -> List of IP Addresses of all EPs in Provider EPG
|
||||
|
||||
#TODO(magesh):Process on the basis of ResourceType rather than Name
|
||||
#eg: Type: OS::Neutron::PoolMember
|
||||
#Variable number of pool members is not handled yet. We may have to
|
||||
#dynamically modify the template json to achieve that
|
||||
provider_epg = sc_instance.get("provider_epg")
|
||||
for key in config_param_names or []:
|
||||
if key == "PoolMemberIPs":
|
||||
value = self._get_member_ips(context, provider_epg)
|
||||
#TODO(Magesh):Return one value for now
|
||||
if value:
|
||||
value = value[0]
|
||||
config_param_values[key] = value
|
||||
elif key == "Subnet":
|
||||
value = self._get_epg_subnet(context, provider_epg)
|
||||
config_param_values[key] = value
|
||||
if node_params:
|
||||
for parameter in config_param_values.keys():
|
||||
if parameter in node_params.keys():
|
||||
stack_params[parameter] = config_param_values[
|
||||
parameter]
|
||||
elif key == "Subnet":
|
||||
value = self._get_epg_subnet(context, provider_epg)
|
||||
config_param_values[key] = value
|
||||
node_params = (stack_template.get('Parameters')
|
||||
or stack_template.get('parameters'))
|
||||
if node_params:
|
||||
for parameter in config_param_values.keys():
|
||||
if parameter in node_params.keys():
|
||||
stack_params[parameter] = config_param_values[parameter]
|
||||
return (stack_template, stack_params)
|
||||
|
||||
def _create_servicechain_instance_stacks(self, context, sc_node_ids,
|
||||
|
||||
Reference in New Issue
Block a user