Refactor GBP resource mapping with Neutron RESTful APIs

1. Decouple GBP resource mapping from neutron plugins and use
Neutron RESTful APIs instead.

2. Move neutron_client related calls to neutron_api_mixin for
easier removing in the future.

3. Add mock_neutronv2_api to provide methods to patch the
Neutron RESTful API calls with WSGI calls.

4. Modify the existing 'UT' for the resource mapping driver, by
patching the RESTful APIs with the WSGI calls, to ensure the
test scenarios are still examined and passed.

5. Modify the existing 'UT' for the APIC mapping driver, by
patching the RESTful APIs with the WSGI calls, to ensure the
test scenarios are still examined and passed.

Change-Id: I9ec6d159b87e77fcd034b2ff6d4e5c0969798b34
Author: Yi Yang <yyos1999@gmail.com>
Co-Authored-By: Yapeng Wu <yapengwu@gmail.com>
Co-Authored-By: Ivar Lazzaro <ivarlazzaro@gmail.com>
Co-Authored-By: Robert Kukura <kukura@noironetworks.com>
This commit is contained in:
Yi Yang 2015-02-17 19:23:45 -05:00
parent c33954fb86
commit 84afe597d7
6 changed files with 444 additions and 180 deletions

View File

@ -0,0 +1,194 @@
# 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.
import itertools
from oslo_log import log as logging
from gbpservice.neutron.tests.unit import common as cm
LOG = logging.getLogger(__name__)
# While plugin APIs support ORing filters, they are not supported in REST APIs
# or WSGI. A combination of these filters have to be generated to emulate the
# ORing behavior.
# For example, filters = {'foo': [a1, a2], 'bar': [b1, b2]} will be mapped to
# a list of filters
# [{'foo': a1, 'bar': b1},
# {'foo': a1, 'bar': b2},
# {'foo': a2, 'bar': b1},
# {'foo': a2, 'bar': b2}]
def get_filter_combinations(filters):
formatted_filters = {}
for key, value in filters.iteritems():
formatted_filters[key] = value if isinstance(value, list) else [value]
keys = sorted(formatted_filters)
return [dict(zip(keys, prod)) for prod in
itertools.product(*(formatted_filters[key] for key in keys))]
class NeutronAPIMixin(object):
"""A Wrapper class of Neutronv2 Client APIs.
Ideally, we want to call Neutronv2 Client APIs directly in resource mapping
and other drivers. But there are some existing APIs as previously plugin
APIs were used, and we need to keep these APIs so we don't need to touch
too many codes. Later when we clean up the resource mappings, this Mixin
wrapper should be removed as well.
"""
def _create_port(self, plugin_context, attrs):
return self._create_neutron_resource(plugin_context, 'port', attrs)
def _get_port(self, plugin_context, port_id):
return self._get_neutron_resource(plugin_context, 'port', port_id)
def _update_port(self, plugin_context, port_id, attrs):
return self._update_neutron_resource(
plugin_context, 'port', port_id, attrs)
def _delete_port(self, plugin_context, port_id):
self._delete_neutron_resource(plugin_context, 'port', port_id)
def _create_subnet(self, plugin_context, attrs):
return self._create_neutron_resource(plugin_context, 'subnet', attrs)
def _get_subnet(self, plugin_context, subnet_id):
return self._get_neutron_resource(plugin_context, 'subnet', subnet_id)
def _get_subnets(self, plugin_context, filters={}):
return self._get_neutron_resources(plugin_context, 'subnet', filters)
def _update_subnet(self, plugin_context, subnet_id, attrs):
return self._update_neutron_resource(
plugin_context, 'subnet', subnet_id, attrs)
def _delete_subnet(self, plugin_context, subnet_id):
self._delete_neutron_resource(plugin_context, 'subnet', subnet_id)
def _create_network(self, plugin_context, attrs):
return self._create_neutron_resource(plugin_context, 'network', attrs)
def _get_network(self, plugin_context, network_id):
return self._get_neutron_resource(
plugin_context, 'network', network_id)
def _delete_network(self, plugin_context, network_id):
self._delete_neutron_resource(plugin_context, 'network', network_id)
def _create_router(self, plugin_context, attrs):
return self._create_neutron_resource(plugin_context, 'router', attrs)
def _get_router(self, plugin_context, router_id):
return self._get_neutron_resource(plugin_context, 'router', router_id)
def _update_router(self, plugin_context, router_id, attrs):
return self._update_neutron_resource(
plugin_context, 'router', router_id, attrs)
def _delete_router(self, plugin_context, router_id):
self._delete_neutron_resource(plugin_context, 'router', router_id)
def _add_router_interface(self, plugin_context, router_id, interface):
self._neutron.add_router_interface(
plugin_context, router_id, interface)
def _remove_router_interface(self, plugin_context, router_id, interface):
self._neutron.remove_router_interface(
plugin_context, router_id, interface)
def _add_router_gw_interface(self, plugin_context, router_id, gw_info):
return self._update_router(
plugin_context, router_id, {'external_gateway_info': gw_info})
def _remove_router_gw_interface(
self, plugin_context, router_id, interface_info):
self._update_router(
plugin_context, router_id, {'external_gateway_info': None})
def _create_sg(self, plugin_context, attrs):
return self._create_neutron_resource(
plugin_context, 'security_group', attrs)
def _get_sg(self, plugin_context, sg_id):
return self._get_neutron_resource(
plugin_context, 'security_group', sg_id)
def _get_sgs(self, plugin_context, filters={}):
return self._get_neutron_resources(
plugin_context, 'security_group', filters)
def _update_sg(self, plugin_context, sg_id, attrs):
return self._update_neutron_resource(
plugin_context, 'security_group', sg_id, attrs)
def _delete_sg(self, plugin_context, sg_id):
self._delete_neutron_resource(
plugin_context, 'security_group', sg_id)
def _create_sg_rule(self, plugin_context, attrs):
return self._create_neutron_resource(
plugin_context, 'security_group_rule', attrs)
def _get_sg_rule(self, plugin_context, sg_rule_id):
return self._get_neutron_resource(
plugin_context, 'security_group_rule', sg_rule_id)
def _get_sg_rules(self, plugin_context, filters={}):
return self._get_neutron_resources(
plugin_context, 'security_group_rule', filters)
# REVISIT(yi): update_security_group_rule not supported in neutron yet
# def _update_security_group_rule(self, plugin_context, sg_rule_id, attrs):
# return self._update_neutron_resource(
# plugin_context, 'security_group_rule', sg_rule_id, attrs)
def _delete_sg_rule(self, plugin_context, sg_rule_id):
self._delete_neutron_resource(
plugin_context, 'security_group_rule', sg_rule_id)
def _create_neutron_resource(self, context, resource, attrs):
action = 'create_' + resource
obj_creator = getattr(self._neutron, action)
obj = obj_creator(context, {resource: attrs})
return obj
def _get_neutron_resource(self, context, resource, resource_id):
obj_getter = getattr(self._neutron, 'show_' + resource)
obj = obj_getter(context, resource_id)
return obj
def _get_neutron_resources(self, context, resource, filters={}):
# REST APIs does not support ORing filtering
# Has to handle the combination of filters instead
filter_list = get_filter_combinations(filters)
resources = cm.get_resource_plural(resource)
obj_getter = getattr(self._neutron, 'list_' + resources)
res = []
for filter in filter_list:
obj = obj_getter(context, filter)
# merge the result and remove the duplicate
res.extend(x for x in obj if x not in res)
return res
def _update_neutron_resource(self, context, resource, resource_id, attrs):
action = 'update_' + resource
obj_updater = getattr(self._neutron, action)
obj = obj_updater(context, resource_id, {resource: attrs})
return obj
def _delete_neutron_resource(self, context, resource, resource_id):
action = 'delete_' + resource
obj_deleter = getattr(self._neutron, action)
obj_deleter(context, resource_id)

View File

@ -13,9 +13,7 @@
import netaddr
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.v2 import attributes
from neutron.common import constants as const
from neutron.common import exceptions as n_exc
from neutron.common import log
from neutron import context as n_context
from neutron.db import model_base
@ -24,17 +22,20 @@ from neutron.extensions import securitygroup as ext_sg
from neutron import manager
from neutron.notifiers import nova
from neutron.plugins.common import constants as pconst
from neutronclient.common import exceptions as neutron_client_exc
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
import sqlalchemy as sa
from gbpservice.network.neutronv2 import api as neutron_api
from gbpservice.neutron.db.grouppolicy import group_policy_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.drivers import neutron_api_mixin
LOG = logging.getLogger(__name__)
@ -119,7 +120,8 @@ class ServicePolicyPTGIpAddressMapping(model_base.BASEV2):
ipaddress = sa.Column(sa.String(36))
class ResourceMappingDriver(api.PolicyDriver):
class ResourceMappingDriver(api.PolicyDriver,
neutron_api_mixin.NeutronAPIMixin):
"""Resource Mapping driver for Group Policy plugin.
This driver implements group policy semantics by mapping group
@ -130,6 +132,7 @@ class ResourceMappingDriver(api.PolicyDriver):
def initialize(self):
self._cached_agent_notifier = None
self._nova_notifier = nova.Notifier()
self._neutron = neutron_api.API()
def _reject_shared(self, object, type):
if object.get('shared'):
@ -156,7 +159,7 @@ class ResourceMappingDriver(api.PolicyDriver):
def _reject_non_shared_net_on_shared_l2p(self, context):
if context.current.get('shared') and context.current['network_id']:
net = self._core_plugin.get_network(
net = self._get_network(
context._plugin_context, context.current['network_id'])
if not net.get('shared'):
raise exc.NonSharedNetworkOnSharedL2PolicyNotSupported()
@ -170,9 +173,8 @@ class ResourceMappingDriver(api.PolicyDriver):
plugin_context = context._plugin_context
network = None
try:
network = self._core_plugin.get_network(plugin_context,
network_id)
except n_exc.NetworkNotFound:
network = self._get_network(plugin_context, network_id)
except neutron_client_exc.NotFound:
raise exc.InvalidNetworkAccess(
msg="Can't access other tenants networks",
network_id=context.current['network_id'],
@ -193,9 +195,8 @@ class ResourceMappingDriver(api.PolicyDriver):
for router_id in context.current['routers']:
router = None
try:
router = self._l3_plugin.get_router(context._plugin_context,
router_id)
except n_exc.NotFound:
router = self._get_router(context._plugin_context, router_id)
except neutron_client_exc.NotFound:
raise exc.InvalidRouterAccess(
msg="Can't access other tenants router",
router_id=router_id,
@ -265,12 +266,13 @@ class ResourceMappingDriver(api.PolicyDriver):
@log.log
def delete_policy_target_postcommit(self, context):
sg_list = self._generate_list_of_sg_from_ptg(
context, context.current['policy_target_group_id'])
self._disassoc_sgs_from_port(context._plugin_context,
context.current['port_id'], sg_list)
port_id = context.current['port_id']
self._cleanup_port(context._plugin_context, port_id)
if port_id:
sg_list = self._generate_list_of_sg_from_ptg(
context, context.current['policy_target_group_id'])
self._disassoc_sgs_from_port(
context._plugin_context, port_id, sg_list)
self._cleanup_port(context._plugin_context, port_id)
@log.log
def create_policy_target_group_precommit(self, context):
@ -824,10 +826,10 @@ class ResourceMappingDriver(api.PolicyDriver):
def create_external_segment_precommit(self, context):
if context.current['subnet_id']:
subnet = self._core_plugin.get_subnet(context._plugin_context,
context.current['subnet_id'])
network = self._core_plugin.get_network(context._plugin_context,
subnet['network_id'])
subnet = self._get_subnet(context._plugin_context,
context.current['subnet_id'])
network = self._get_network(context._plugin_context,
subnet['network_id'])
if not network['router:external']:
raise exc.InvalidSubnetForES(sub_id=subnet['id'],
net_id=network['id'])
@ -1029,8 +1031,6 @@ class ResourceMappingDriver(api.PolicyDriver):
attrs = {'tenant_id': context.current['tenant_id'],
'name': 'pt_' + context.current['name'],
'network_id': l2p['network_id'],
'mac_address': attributes.ATTR_NOT_SPECIFIED,
'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
'device_id': '',
'device_owner': '',
'security_groups': [sg_id] if sg_id else None,
@ -1044,7 +1044,7 @@ class ResourceMappingDriver(api.PolicyDriver):
if self._port_is_owned(plugin_context.session, port_id):
try:
self._delete_port(plugin_context, port_id)
except n_exc.PortNotFound:
except neutron_client_exc.NotFound:
LOG.warn(_("Port %s is missing") % port_id)
def _plug_router_to_external_segment(self, context, es_dict):
@ -1053,8 +1053,8 @@ class ResourceMappingDriver(api.PolicyDriver):
if context.current['routers']:
router_id = context.current['routers'][0]
for es in es_list:
subnet = self._core_plugin.get_subnet(context._plugin_context,
es['subnet_id'])
subnet = self._get_subnet(context._plugin_context,
es['subnet_id'])
external_fixed_ips = [
{'subnet_id': es['subnet_id'],
'ip_address': x} for x in es_dict[es['id']]
@ -1081,8 +1081,8 @@ class ResourceMappingDriver(api.PolicyDriver):
if context.current['routers']:
router_id = context.current['routers'][0]
for es in es_list:
subnet = self._core_plugin.get_subnet(context._plugin_context,
es['subnet_id'])
subnet = self._get_subnet(context._plugin_context,
es['subnet_id'])
interface_info = {'network_id': subnet['network_id']}
self._remove_router_gw_interface(context._plugin_context,
router_id, interface_info)
@ -1107,8 +1107,8 @@ class ResourceMappingDriver(api.PolicyDriver):
subnets = []
for ptg in ptgs:
subnets.extend(ptg['subnets'])
subnets = self._core_plugin.get_subnets(context._plugin_context,
filters={'id': subnets})
subnets = self._get_subnets(context._plugin_context,
filters={'id': subnets})
for cidr in pool.subnet(l3p['subnet_prefix_length']):
if not self._validate_subnet_overlap_for_l3p(subnets,
cidr.__str__()):
@ -1119,11 +1119,7 @@ class ResourceMappingDriver(api.PolicyDriver):
'network_id': l2p['network_id'],
'ip_version': l3p['ip_version'],
'cidr': cidr.__str__(),
'enable_dhcp': True,
'gateway_ip': attributes.ATTR_NOT_SPECIFIED,
'allocation_pools': attributes.ATTR_NOT_SPECIFIED,
'dns_nameservers': attributes.ATTR_NOT_SPECIFIED,
'host_routes': attributes.ATTR_NOT_SPECIFIED}
'enable_dhcp': True}
subnet = self._create_subnet(context._plugin_context, attrs)
subnet_id = subnet['id']
try:
@ -1136,18 +1132,19 @@ class ResourceMappingDriver(api.PolicyDriver):
context._plugin_context.session, subnet_id)
context.add_subnet(subnet_id)
return
except n_exc.InvalidInput:
except neutron_client_exc.BadRequest:
# This exception is not expected. We catch this
# here so that it isn't caught below and handled
# as if the CIDR is already in use.
LOG.exception(_("adding subnet to router failed"))
self._delete_subnet(context._plugin_context, subnet['id'])
raise exc.GroupPolicyInternalError()
except n_exc.BadRequest:
except neutron_client_exc.BadRequest:
# This is expected (CIDR overlap) until we have a
# proper subnet allocation algorithm. We ignore the
# exception and repeat with the next CIDR.
pass
raise exc.NoSubnetAvailable()
def _validate_subnet_overlap_for_l3p(self, subnets, subnet_cidr):
@ -1208,14 +1205,13 @@ class ResourceMappingDriver(api.PolicyDriver):
# This method sets up the attributes of security group
attrs = {'tenant_id': context.current['tenant_id'],
'name': sg_name_prefix + '_' + context.current['name'],
'description': '',
'security_group_rules': ''}
'description': ''}
sg = self._create_sg(context._plugin_context, attrs)
# Cleanup default rules
for rule in self._core_plugin.get_security_group_rules(
for rule in self._get_sg_rules(
context._plugin_context,
filters={'security_group_id': [sg['id']]}):
self._core_plugin.delete_security_group_rule(
self._delete_sg_rule(
context._plugin_context, rule['id'])
return sg
@ -1388,142 +1384,43 @@ class ResourceMappingDriver(api.PolicyDriver):
def _cleanup_redirect_action(self, context):
for ptg_chain in context.ptg_chain_map:
self._delete_servicechain_instance(
context, ptg_chain.servicechain_instance_id)
# The following methods perform the necessary subset of
# functionality from neutron.api.v2.base.Controller.
#
# REVISIT(rkukura): Can we just use the WSGI Controller? Using
# neutronclient is also a possibility, but presents significant
# issues to unit testing as well as overhead and failure modes.
def _create_port(self, plugin_context, attrs):
return self._create_resource(self._core_plugin, plugin_context, 'port',
attrs)
def _update_port(self, plugin_context, port_id, attrs):
return self._update_resource(self._core_plugin, plugin_context, 'port',
port_id, attrs)
def _delete_port(self, plugin_context, port_id):
self._delete_resource(self._core_plugin,
plugin_context, 'port', port_id)
def _create_subnet(self, plugin_context, attrs):
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)
def _create_network(self, plugin_context, attrs):
return self._create_resource(self._core_plugin, plugin_context,
'network', attrs)
def _delete_network(self, plugin_context, network_id):
self._delete_resource(self._core_plugin, plugin_context,
'network', network_id)
def _create_router(self, plugin_context, attrs):
return self._create_resource(self._l3_plugin, plugin_context, 'router',
attrs)
def _update_router(self, plugin_context, router_id, attrs):
return self._update_resource(self._l3_plugin, plugin_context, 'router',
router_id, attrs)
def _add_router_interface(self, plugin_context, router_id, interface_info):
self._l3_plugin.add_router_interface(plugin_context,
router_id, interface_info)
def _remove_router_interface(self, plugin_context, router_id,
interface_info):
self._l3_plugin.remove_router_interface(plugin_context, router_id,
interface_info)
def _add_router_gw_interface(self, plugin_context, router_id, gw_info):
return self._l3_plugin.update_router(
plugin_context, router_id,
{'router': {'external_gateway_info': gw_info}})
def _remove_router_gw_interface(self, plugin_context, router_id,
interface_info):
self._l3_plugin.update_router(
plugin_context, router_id,
{'router': {'external_gateway_info': None}})
def _delete_router(self, plugin_context, router_id):
self._delete_resource(self._l3_plugin, plugin_context, 'router',
router_id)
def _create_sg(self, plugin_context, attrs):
return self._create_resource(self._core_plugin, plugin_context,
'security_group', attrs)
def _update_sg(self, plugin_context, sg_id, attrs):
return self._update_resouce(self._core_plugin, plugin_context,
'security_group', sg_id, attrs)
def _delete_sg(self, plugin_context, sg_id):
self._delete_resource(self._core_plugin, plugin_context,
'security_group', sg_id)
def _create_sg_rule(self, plugin_context, attrs):
try:
return self._create_resource(self._core_plugin, plugin_context,
'security_group_rule', attrs)
except ext_sg.SecurityGroupRuleExists as ex:
LOG.warn(_('Security Group already exists %s'), ex.message)
return
def _update_sg_rule(self, plugin_context, sg_rule_id, attrs):
return self._update_resource(self._core_plugin, plugin_context,
'security_group_rule', sg_rule_id,
attrs)
def _delete_sg_rule(self, plugin_context, sg_rule_id):
self._delete_resource(self._core_plugin, plugin_context,
'security_group_rule', sg_rule_id)
context, ptg_chain.servicechain_instance_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)
subnet = self._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)
subnet_update = {'gateway_ip': None}
subnet_update['allocation_pools'] = allocation_pools
self._update_subnet(context._plugin_context,
subnet['id'], subnet_update)
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)
subnet_update = {'gateway_ip': None}
subnet_update['allocation_pools'] = subnet['allocation_pools']
self._update_subnet(context._plugin_context,
subnet['id'], subnet_update)
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)
subnet = self._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']
subnet_update = {'gateway_ip': None}
subnet_update['allocation_pools'] = allocation_pools
self._update_subnet(context._plugin_context,
subnet['id'], subnet)
subnet['id'], subnet_update)
break
def _get_last_free_ip(self, context, subnets):
@ -1759,12 +1656,15 @@ class ResourceMappingDriver(api.PolicyDriver):
value = attrs[key]
if value:
filters[key] = [value]
rule = self._core_plugin.get_security_group_rules(
rule = self._get_sg_rules(
plugin_context, filters)
if rule:
self._delete_sg_rule(plugin_context, rule[0]['id'])
else:
return self._create_sg_rule(plugin_context, attrs)
try:
return self._create_sg_rule(plugin_context, attrs)
except neutron_client_exc.Conflict:
LOG.warn(_('Security Group Rule already exists'))
def _sg_ingress_rule(self, context, sg_id, protocol, port_range, cidr,
unset=False):
@ -1781,11 +1681,11 @@ class ResourceMappingDriver(api.PolicyDriver):
def _assoc_sgs_to_pt(self, context, pt_id, sg_list):
pt = context._plugin.get_policy_target(context._plugin_context, pt_id)
port_id = pt['port_id']
port = self._core_plugin.get_port(context._plugin_context, port_id)
port = self._get_port(context._plugin_context, port_id)
cur_sg_list = port[ext_sg.SECURITYGROUPS]
new_sg_list = cur_sg_list + sg_list
port[ext_sg.SECURITYGROUPS] = new_sg_list
self._update_port(context._plugin_context, port_id, port)
port_update = {ext_sg.SECURITYGROUPS: new_sg_list}
self._update_port(context._plugin_context, port_id, port_update)
def _disassoc_sgs_from_pt(self, context, pt_id, sg_list):
pt = context._plugin.get_policy_target(context._plugin_context, pt_id)
@ -1794,12 +1694,12 @@ class ResourceMappingDriver(api.PolicyDriver):
def _disassoc_sgs_from_port(self, plugin_context, port_id, sg_list):
try:
port = self._core_plugin.get_port(plugin_context, port_id)
port = self._get_port(plugin_context, port_id)
cur_sg_list = port[ext_sg.SECURITYGROUPS]
new_sg_list = list(set(cur_sg_list) - set(sg_list))
port[ext_sg.SECURITYGROUPS] = new_sg_list
self._update_port(plugin_context, port_id, port)
except n_exc.PortNotFound:
port_update = {ext_sg.SECURITYGROUPS: new_sg_list}
self._update_port(plugin_context, port_id, port_update)
except neutron_client_exc.NotFound:
LOG.warn(_("Port %s is missing") % port_id)
def _generate_list_of_sg_from_ptg(self, context, ptg_id):
@ -1860,8 +1760,7 @@ class ResourceMappingDriver(api.PolicyDriver):
cidr_list = []
for subnet_id in subnets:
subnet = self._core_plugin.get_subnet(context._plugin_context,
subnet_id)
subnet = self._get_subnet(context._plugin_context, subnet_id)
cidr = subnet['cidr']
cidr_list.append(cidr)
self._set_or_unset_rules_for_cidrs(
@ -2015,7 +1914,7 @@ class ResourceMappingDriver(api.PolicyDriver):
tenant_id):
port_name = 'gbp_%s' % ptg_id
filters = {'name': [port_name], 'tenant_id': [tenant_id]}
default_group = self._core_plugin.get_security_groups(
default_group = self._get_sgs(
plugin_context, filters)
return default_group[0]['id'] if default_group else None
@ -2031,7 +1930,7 @@ class ResourceMappingDriver(api.PolicyDriver):
'description': 'default'}
sg_id = self._create_sg(plugin_context, attrs)['id']
for subnet in self._core_plugin.get_subnets(
for subnet in self._get_subnets(
plugin_context, filters={'id': subnets or []}):
self._sg_rule(plugin_context, tenant_id, sg_id,
'ingress', cidr=subnet['cidr'],
@ -2058,7 +1957,7 @@ class ResourceMappingDriver(api.PolicyDriver):
ptgs = context._plugin.get_policy_target_groups(
context._plugin_context, filters={'id': ptgs})
for ptg in ptgs:
cidrs.extend([self._core_plugin.get_subnet(
cidrs.extend([self._get_subnet(
context._plugin_context, x)['cidr'] for x in ptg['subnets']])
return cidrs
@ -2145,7 +2044,7 @@ class ResourceMappingDriver(api.PolicyDriver):
l3ps = context._plugin.get_l3_policies(
admin_context, filters={'id': context.current['l3_policies']})
for l3p in l3ps:
router = self._l3_plugin.get_routes(
router = self._get_router(
admin_context, l3p['router_id'])
current_routes = set((x['destination'], x['nexthop']) for x in
router['routes'])
@ -2219,8 +2118,7 @@ class ResourceMappingDriver(api.PolicyDriver):
l2p_id)
# Validate explicit subnet belongs to L2P's network
network_id = l2p['network_id']
network = self._core_plugin.get_network(context._plugin_context,
network_id)
network = self._get_network(context._plugin_context, network_id)
for subnet_id in subnets or context.current['subnets']:
if subnet_id not in network['subnets']:
raise exc.InvalidSubnetForPTG(subnet_id=subnet_id,
@ -2255,8 +2153,7 @@ class ResourceMappingDriver(api.PolicyDriver):
# Validate if explicit port's subnet
# is same as the subnet of PTG.
port_id = context.current['port_id']
core_plugin = self._core_plugin
port = core_plugin.get_port(context._plugin_context, port_id)
port = self._get_port(context._plugin_context, port_id)
port_subnet_id = None
fixed_ips = port['fixed_ips']

View File

@ -6,6 +6,7 @@
"regular_user": "",
"default": "rule:admin_or_owner",
"gbp_shared": "field:policy_target_groups:shared=True",
"neutron_shared": "field:networks:shared=True",
"create_policy_target_group": "",
"get_policy_target_group": "rule:admin_or_owner or rule:gbp_shared",
@ -48,5 +49,11 @@
"gbp_nat_pool_shared": "field:nat_pools:shared=True",
"create_nat_pool": "",
"get_nat_pool": "rule:admin_or_owner or rule:gbp_nat_pool_shared"
"get_nat_pool": "rule:admin_or_owner or rule:gbp_nat_pool_shared",
"create_network": "",
"get_network": "rule:admin_or_owner or rule:neutron_shared",
"create_subnet": "",
"get_subnet": "rule:admin_or_owner or rule:neutron_shared"
}

View File

@ -0,0 +1,159 @@
# 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.
import mock
from neutronclient.common import exceptions as neutron_client_exc
from gbpservice.neutron.services.grouppolicy.drivers import (
neutron_api_mixin as nm)
from gbpservice.neutron.services.grouppolicy.drivers import resource_mapping
from gbpservice.neutron.tests.unit import common as cm
# Based on the resource types, Neutron REST API calls need to be patched to
# different neutron-plugins.
PLUGIN_MAP = {
'network': '_core_plugin',
'subnet': '_core_plugin',
'port': '_core_plugin',
'security_group': '_core_plugin',
'security_group_rule': '_core_plugin',
'router': '_l3_plugin',
}
# Based on the resource types, WSGI calls need to be patched to
# different APIs.
API_MAP = {
'network': 'api',
'subnet': 'api',
'port': 'api',
'security_group': 'ext_api',
'security_group_rule': 'ext_api',
'router': 'ext_api',
}
# security-groups and security-group-rules appear in WSGI resource,
# but security_groups and security_group_rules appear in request body.
def _replace_sg_underscore(resources):
return resources.replace('_', '-') if (
'security_group' in resources) else resources
class Neutronv2MockMixin(object):
""" A mixin class to mock Neutron REST APIs with WSGI calls.
The _*_resource APIs defined in neutron_api_mixin.NeutronAPIMixin are
patched here.
"""
def _setUp_mock(self):
mock_create = mock.patch.object(resource_mapping.ResourceMappingDriver,
'_create_neutron_resource',
autospec=True).start()
mock_create.side_effect = self._patched_create_resource
mock_show = mock.patch.object(resource_mapping.ResourceMappingDriver,
'_get_neutron_resource',
autospec=True).start()
mock_show.side_effect = self._patched_get_resource
mock_list = mock.patch.object(resource_mapping.ResourceMappingDriver,
'_get_neutron_resources',
autospec=True).start()
mock_list.side_effect = self._patched_get_resources
mock_update = mock.patch.object(resource_mapping.ResourceMappingDriver,
'_update_neutron_resource',
autospec=True).start()
mock_update.side_effect = self._patched_update_resource
mock_delete = mock.patch.object(resource_mapping.ResourceMappingDriver,
'_delete_neutron_resource',
autospec=True).start()
mock_delete.side_effect = self._patched_delete_resource
mock_add = mock.patch.object(resource_mapping.ResourceMappingDriver,
'_add_router_interface',
autospec=True).start()
mock_add.side_effect = self._patched_add_router_interface
mock_remove = mock.patch.object(resource_mapping.ResourceMappingDriver,
'_remove_router_interface',
autospec=True).start()
mock_remove.side_effect = self._patched_remove_router_interface
def _wsgi_req(self, req, api, context, return_flag=True):
req.environ['neutron.context'] = context
ret = req.get_response(api)
if ret.status_int >= neutron_client_exc.BadRequest.status_code:
raise neutron_client_exc.HTTP_EXCEPTION_MAP.get(ret.status_int)
if return_flag:
return self.deserialize(self.fmt, ret)
def _patched_create_resource(self, obj, context, resource, attrs):
resources = _replace_sg_underscore(cm.get_resource_plural(resource))
data = {resource: attrs}
api = getattr(self, API_MAP[resource])
req = self.new_create_request(resources, data, self.fmt)
return self._wsgi_req(req, api, context)[resource]
def _patched_get_resource(self, obj, context, resource, resource_id):
resources = _replace_sg_underscore(cm.get_resource_plural(resource))
api = getattr(self, API_MAP[resource])
req = self.new_show_request(resources, resource_id)
return self._wsgi_req(req, api, context)[resource]
def _patched_get_resources(self, obj, context, resource, filters={}):
resources = cm.get_resource_plural(resource)
formatted_resources = _replace_sg_underscore(resources)
api = getattr(self, API_MAP[resource])
filter_list = nm.get_filter_combinations(filters)
res = []
for filter in filter_list:
params = None
if filter:
param_list = []
for key, value in filter.iteritems():
# REVISIT(yi): need to replace equal sign = with %3D if
# present in value
param_list.append("%s=%s" % (key, value))
params = '&'.join(param_list)
req = self.new_list_request(formatted_resources, self.fmt, params)
obj = self._wsgi_req(req, api, context)[resources]
res.extend(x for x in obj if x not in res)
return res
def _patched_update_resource(self, obj, context,
resource, resource_id, attrs):
resources = _replace_sg_underscore(cm.get_resource_plural(resource))
data = {resource: attrs}
api = getattr(self, API_MAP[resource])
req = self.new_update_request(resources, data, resource_id, self.fmt)
return self._wsgi_req(req, api, context)[resource]
def _patched_delete_resource(self, obj, context, resource, resource_id):
resources = _replace_sg_underscore(cm.get_resource_plural(resource))
api = getattr(self, API_MAP[resource])
req = self.new_delete_request(resources, resource_id)
self._wsgi_req(req, api, context, False)
def _patched_add_router_interface(
self, obj, context, router_id, interface):
req = self.new_action_request(
'routers', interface, router_id, 'add_router_interface')
return self._wsgi_req(req, self.ext_api, context)
def _patched_remove_router_interface(
self, obj, context, router_id, interface):
req = self.new_action_request(
'routers', interface, router_id, 'remove_router_interface')
return self._wsgi_req(req, self.ext_api, context)

View File

@ -33,6 +33,8 @@ sys.modules["apicapi"] = mock.Mock()
from gbpservice.neutron.services.grouppolicy import config
from gbpservice.neutron.services.grouppolicy.drivers.cisco.apic import (
apic_mapping as amap)
from gbpservice.neutron.tests.unit.services.grouppolicy import (
mock_neutronv2_api as mock_neutron)
from gbpservice.neutron.tests.unit.services.grouppolicy import (
test_grouppolicy_plugin as test_gp_plugin)
@ -70,7 +72,8 @@ class MockCallRecorder(mock.Mock):
class ApicMappingTestCase(
test_gp_plugin.GroupPolicyPluginTestCase,
mocked.ControllerMixin, mocked.ConfigMixin):
mocked.ControllerMixin, mocked.ConfigMixin,
mock_neutron.Neutronv2MockMixin):
def setUp(self):
config.cfg.CONF.set_override('policy_drivers',
@ -109,6 +112,7 @@ class ApicMappingTestCase(
self.driver.apic_manager.apic.transaction = self.fake_transaction
amap.apic_manager.TENANT_COMMON = 'common'
self.common_tenant = amap.apic_manager.TENANT_COMMON
self._setUp_mock()
def _get_object(self, type, id, api):
req = self.new_show_request(type, id, self.fmt)

View File

@ -13,9 +13,9 @@
import contextlib
import itertools
import mock
import netaddr
import mock
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.common import constants as cst
from neutron import context as nctx
@ -37,9 +37,14 @@ from gbpservice.neutron.services.grouppolicy.common import constants as gconst
from gbpservice.neutron.services.grouppolicy import config
from gbpservice.neutron.services.grouppolicy.drivers import resource_mapping
from gbpservice.neutron.services.servicechain import config as sc_cfg
from gbpservice.neutron.tests.unit.services.grouppolicy import (
mock_neutronv2_api as mock_neutron)
from gbpservice.neutron.tests.unit.services.grouppolicy import (
test_grouppolicy_plugin as test_plugin)
CORE_PLUGIN = ('gbpservice.neutron.tests.unit.services.grouppolicy.'
'test_resource_mapping.NoL3NatSGTestPlugin')
SERVICECHAIN_NODES = 'servicechain/servicechain_nodes'
SERVICECHAIN_SPECS = 'servicechain/servicechain_specs'
SERVICECHAIN_INSTANCES = 'servicechain/servicechain_instances'
@ -52,11 +57,8 @@ class NoL3NatSGTestPlugin(
supported_extension_aliases = ["external-net", "security-group"]
CORE_PLUGIN = ('gbpservice.neutron.tests.unit.services.grouppolicy.'
'test_resource_mapping.NoL3NatSGTestPlugin')
class ResourceMappingTestCase(test_plugin.GroupPolicyPluginTestCase):
class ResourceMappingTestCase(test_plugin.GroupPolicyPluginTestCase,
mock_neutron.Neutronv2MockMixin):
def setUp(self, policy_drivers=None):
policy_drivers = policy_drivers or ['implicit_policy',
@ -78,6 +80,7 @@ class ResourceMappingTestCase(test_plugin.GroupPolicyPluginTestCase):
self._context = nctx.get_admin_context()
plugins = manager.NeutronManager.get_service_plugins()
self._gbp_plugin = plugins.get(pconst.GROUP_POLICY)
self._setUp_mock()
def get_plugin_context(self):
return self._plugin, self._context
@ -2731,4 +2734,4 @@ class TestNetworkServicePolicy(ResourceMappingTestCase):
subnet = self._show_subnet(ptg_subnet_id)['subnet']
allocation_pool_after_nsp_cleanup = subnet['allocation_pools']
self.assertEqual(
initial_allocation_pool, allocation_pool_after_nsp_cleanup)
initial_allocation_pool, allocation_pool_after_nsp_cleanup)