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:
Sumit Naiksatam
2014-10-25 08:42:50 +05:30
committed by Magesh GV
parent b870cb9dab
commit 0eed91b406
5 changed files with 243 additions and 41 deletions

View File

@@ -1 +1 @@
577bb4469944
ceba6e091b2a

View File

@@ -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')

View File

@@ -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},

View File

@@ -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())

View File

@@ -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,