NSX-V Service insertion support

The service insertion feature allows us to redirect some of the NSX traffic to an external
security vendor like Palo-Alto or checkpoint for advanced inspection.

The implementation contains:
Enable the flow classifier plugin, and use it to create redirect rules on NSX
When the flow classifier plugin is initialized a new security group is created
and added to the configured service profile

When a vm port with port security is created/updated, it is added to this security group
When the admin user create a flow classifier entry, a backed redirect rule will be created.

DocImpact: new NSXV Configuration parameters:
service_insertion_profile_id = <service profile id, i.e. serviceprofile-1>

DocImpact: The flow classifier methods should be added to the policy.json as admin only

Change-Id: I67a132d4b35764c6940516a8365a2749d574aad2
This commit is contained in:
Adit Sarfaty 2016-06-09 13:35:53 +03:00
parent e64909eac9
commit ce9003f498
15 changed files with 825 additions and 1 deletions

View File

@ -143,5 +143,10 @@
"create_security_group:logging": "rule:admin_only",
"update_security_group:logging": "rule:admin_only",
"get_security_group:logging": "rule:admin_only"
"get_security_group:logging": "rule:admin_only",
"create_flow_classifier": "rule:admin_only",
"update_flow_classifier": "rule:admin_only",
"delete_flow_classifier": "rule:admin_only",
"get_flow_classifier": "rule:admin_only"
}

View File

@ -44,6 +44,8 @@ tempest.test_plugins =
vmware-nsx-tempest-plugin = vmware_nsx_tempest.plugin:VMwareNsxTempestPlugin
oslo.config.opts =
nsx = vmware_nsx.opts:list_opts
networking_sfc.flowclassifier.drivers =
vmware-nsxv-sfc = vmware_nsx.services.flowclassifier.nsx_v.driver:NsxvFlowClassifierDriver
[build_sphinx]
source-dir = doc/source

View File

@ -16,6 +16,7 @@ deps = -r{toxinidir}/requirements.txt
# release (branch) tags
git+https://git.openstack.org/openstack/neutron.git@master#egg=neutron
git+https://git.openstack.org/openstack/networking-l2gw.git@master#egg=networking-l2gw
git+https://git.openstack.org/openstack/networking-sfc.git@master#egg=networking-sfc
git+https://git.openstack.org/openstack/neutron-lbaas.git@master#egg=neutron-lbaas
whitelist_externals = sh
commands =

View File

@ -593,6 +593,10 @@ nsxv_opts = [
"all the dhcp enabled networks.\nNote: this option can "
"only be supported at NSX manager version 6.2.3 or "
"higher.")),
cfg.StrOpt('service_insertion_profile_id',
help=_("(Optional) The profile id of the redirect firewall "
"rules that will be used for the Service Insertion "
"feature.")),
]
# Register the configuration options

View File

@ -103,6 +103,7 @@ from vmware_nsx.plugins.nsx_v.vshield import edge_firewall_driver
from vmware_nsx.plugins.nsx_v.vshield import edge_utils
from vmware_nsx.plugins.nsx_v.vshield import securitygroup_utils
from vmware_nsx.plugins.nsx_v.vshield import vcns_driver
from vmware_nsx.services.flowclassifier.nsx_v import utils as fc_utils
LOG = logging.getLogger(__name__)
PORTGROUP_PREFIX = 'dvportgroup'
@ -223,6 +224,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self.metadata_proxy_handler = (
nsx_v_md_proxy.NsxVMetadataProxyHandler(self))
self._si_handler = fc_utils.NsxvServiceInsertionHandler(self)
def init_complete(self, resource, event, trigger, **kwargs):
self.init_is_complete = True
@ -1382,6 +1385,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
elif cfg.CONF.nsxv.spoofguard_enabled:
# Add vm to the exclusion list, since it has no port security
self._add_vm_to_exclude_list(context, device_id, id)
# if service insertion is enabled - add this vnic to the service
# insertion security group
if self._si_handler.enabled and original_port[psec.PORTSECURITY]:
self._add_member_to_security_group(self._si_handler.sg_id,
vnic_id)
delete_security_groups = self._check_update_deletes_security_groups(
port)
@ -1497,6 +1505,14 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
context, device_id, id)
self._delete_port_vnic_index_mapping(context, id)
self._delete_dhcp_static_binding(context, original_port)
# if service insertion is enabled - remove this vnic from the
# service insertion security group
if (self._si_handler.enabled and
original_port[psec.PORTSECURITY]):
self._remove_member_from_security_group(
self._si_handler.sg_id,
vnic_id)
else:
# port security enabled / disabled
if port_sec_change:
@ -1512,6 +1528,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# port security
self._remove_vm_from_exclude_list(context, device_id,
id)
# add the vm to the service insertion
if self._si_handler.enabled:
self._add_member_to_security_group(
self._si_handler.sg_id, vnic_id)
elif cfg.CONF.nsxv.spoofguard_enabled:
try:
self._remove_vnic_from_spoofguard_policy(
@ -1523,6 +1543,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Add vm to the exclusion list, since it has no port
# security now
self._add_vm_to_exclude_list(context, device_id, id)
# remove the vm from the service insertion
if self._si_handler.enabled:
self._remove_member_from_security_group(
self._si_handler.sg_id, vnic_id)
# Update vnic with the newest approved IP addresses
if (has_port_security and
@ -1593,6 +1617,13 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
sgids = neutron_db_port.get(ext_sg.SECURITYGROUPS)
self._delete_security_groups_port_mapping(
context.session, vnic_id, sgids)
# if service insertion is enabled - remove this vnic from the
# service insertion security group
if self._si_handler.enabled and neutron_db_port[psec.PORTSECURITY]:
self._remove_member_from_security_group(self._si_handler.sg_id,
vnic_id)
if (cfg.CONF.nsxv.spoofguard_enabled and
neutron_db_port[psec.PORTSECURITY]):
try:

View File

@ -42,12 +42,14 @@ FIREWALL_RULE_RESOURCE = "rules"
#NSXv Constants
FIREWALL_PREFIX = '/api/4.0/firewall/globalroot-0/config'
FIREWALL_REDIRECT_SEC_TYPE = 'layer3redirectsections'
SECURITYGROUP_PREFIX = '/api/2.0/services/securitygroup'
VDN_PREFIX = '/api/2.0/vdn'
SERVICES_PREFIX = '/api/2.0/services'
SPOOFGUARD_PREFIX = '/api/4.0/services/spoofguard'
TRUSTSTORE_PREFIX = '%s/%s' % (SERVICES_PREFIX, 'truststore')
EXCLUDELIST_PREFIX = '/api/2.1/app/excludelist'
SERVICE_INSERTION_PROFILE_PREFIX = '/api/2.0/si/serviceprofile'
#LbaaS Constants
LOADBALANCER_SERVICE = "loadbalancer/config"
@ -547,6 +549,17 @@ class Vcns(object):
uri = self._build_uri_path(edge_id, BRIDGE)
return self.do_request(HTTP_DELETE, uri, format='xml', decode=False)
def create_redirect_section(self, request):
"""Creates a layer 3 redirect section in nsx rule table.
The method will return the uri to newly created section.
"""
sec_type = FIREWALL_REDIRECT_SEC_TYPE
uri = '%s/%s?autoSaveDraft=false' % (FIREWALL_PREFIX, sec_type)
uri += '&operation=insert_before&anchorId=1002'
return self.do_request(HTTP_POST, uri, request, format='xml',
decode=False, encode=False)
def create_section(self, type, request, insert_before=None):
"""Creates a layer 3 or layer 2 section in nsx rule table.
@ -911,3 +924,14 @@ class Vcns(object):
uri = '%s/%s/%s?noOfDays=%s' % (TRUSTSTORE_PREFIX, CSR, csr_id,
nsxv_constants.CERT_NUMBER_OF_DAYS)
return self.do_request(HTTP_PUT, uri)
def get_service_insertion_profile(self, profile_id):
profiles_uri = '%s/%s' % (SERVICE_INSERTION_PROFILE_PREFIX, profile_id)
return self.do_request(HTTP_GET, profiles_uri, format='xml',
decode=False)
def update_service_insertion_profile_binding(self, profile_id, request):
profiles_uri = '%s/%s/%s' % (SERVICE_INSERTION_PROFILE_PREFIX,
profile_id, 'binding')
return self.do_request(HTTP_POST, profiles_uri, request, format='xml',
decode=False)

View File

@ -0,0 +1,30 @@
===============================================================
Enabling NSX Flow Classifier for service insertion in DevStack
===============================================================
1. Download DevStack
2. Update the ``local.conf`` file::
[[local|localrc]]
enable_plugin networking-sfc https://git.openstack.org/openstack/networking-sfc master
[[post-config|$NEUTRON_CONF]]
[DEFAULT]
service_plugins = networking_sfc.services.flowclassifier.plugin.FlowClassifierPlugin
[flowclassifier]
drivers = vmware-nsxv-sfc
[nsxv]
service_insertion_profile_id = <service profile id. i.e. serviceprofile-1>
3. In order to prevent tenants from changing the flow classifier, please add the following
lines to the policy.json file:
"create_flow_classifier": "rule:admin_only",
"update_flow_classifier": "rule:admin_only",
"delete_flow_classifier": "rule:admin_only",
"get_flow_classifier": "rule:admin_only"
4. run ``stack.sh``

View File

@ -0,0 +1,341 @@
# Copyright 2016 VMware, Inc.
#
# All Rights Reserved
#
# 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 xml.etree.ElementTree as et
from networking_sfc.services.flowclassifier.common import exceptions as exc
from networking_sfc.services.flowclassifier.drivers import base as fc_driver
from oslo_config import cfg
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from vmware_nsx._i18n import _, _LE
from vmware_nsx.common import config # noqa
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import locking
from vmware_nsx.plugins.nsx_v.vshield import vcns as nsxv_api
from vmware_nsx.plugins.nsx_v.vshield import vcns_driver
from vmware_nsx.services.flowclassifier.nsx_v import utils as fc_utils
LOG = logging.getLogger(__name__)
REDIRECT_FW_SECTION_NAME = 'OS Flow Classifier Rules'
MAX_PORTS_IN_RANGE = 15
class NsxvFlowClassifierDriver(fc_driver.FlowClassifierDriverBase):
"""FlowClassifier Driver For NSX-V."""
_redirect_section_id = None
def initialize(self):
self._nsxv = vcns_driver.VcnsDriver(None)
self.init_profile_id()
self.init_security_group()
self.init_security_group_in_profile()
#TODO(asarfaty) - Add a new config for any->any redirect:
# create any->any flow classifier entry (and backed rule)
# if not exist yet
def init_profile_id(self):
"""Init the service insertion profile ID
Initialize the profile id that should be assigned to the redirect
rules from the nsx configuration and verify that it exists on backend.
"""
if not cfg.CONF.nsxv.service_insertion_profile_id:
raise cfg.RequiredOptError("service_profile_id")
self._profile_id = cfg.CONF.nsxv.service_insertion_profile_id
# Verify that this moref exists
if not self._nsxv.vcns.validate_inventory(self._profile_id):
error = (_("Configured service profile ID: %s not found") %
self._profile_id)
raise nsx_exc.NsxPluginException(err_msg=error)
def init_security_group(self):
"""Init the service insertion security group
Look for the service insertion security group in the backend.
If it was not found - create it
This security group will contain all the VMs vnics that should
be inspected by the redirect rules
"""
# check if this group exist, and create it if not.
sg_name = fc_utils.SERVICE_INSERTION_SG_NAME
sg_id = self._nsxv.vcns.get_security_group_id(sg_name)
if not sg_id:
description = ("OpenStack Service Insertion Security Group, "
"managed by Neutron nsx-v plugin.")
sg = {"securitygroup": {"name": sg_name,
"description": description}}
h, sg_id = (
self._nsxv.vcns.create_security_group(sg))
# TODO(asarfaty) - if the security group was just created
# also add all the current compute ports with port-security
# to this security group (for upgrades scenarios)
self._security_group_id = sg_id
def init_security_group_in_profile(self):
"""Attach the security group to the service profile
"""
data = self._nsxv.vcns.get_service_insertion_profile(self._profile_id)
if data and len(data) > 1:
profile = et.fromstring(data[1])
profile_binding = profile.find('serviceProfileBinding')
sec_groups = profile_binding.find('securityGroups')
for sec in sec_groups.iter('string'):
if sec.text == self._security_group_id:
# Already there
return
# add the security group to the binding
et.SubElement(sec_groups, 'string').text = self._security_group_id
self._nsxv.vcns.update_service_insertion_profile_binding(
self._profile_id,
et.tostring(profile_binding, encoding="us-ascii"))
def get_redirect_fw_section_id(self):
if not self._redirect_section_id:
# try to find it
self._redirect_section_id = self._nsxv.vcns.get_section_id(
REDIRECT_FW_SECTION_NAME)
if not self._redirect_section_id:
# create it for the first time
section = et.Element('section')
section.attrib['name'] = REDIRECT_FW_SECTION_NAME
self._nsxv.vcns.create_redirect_section(et.tostring(section))
self._redirect_section_id = self._nsxv.vcns.get_section_id(
REDIRECT_FW_SECTION_NAME)
return self._redirect_section_id
def get_redirect_fw_section_uri(self):
return '%s/%s/%s' % (nsxv_api.FIREWALL_PREFIX,
nsxv_api.FIREWALL_REDIRECT_SEC_TYPE,
self.get_redirect_fw_section_id())
def get_redirect_fw_section_from_backend(self):
section_uri = self.get_redirect_fw_section_uri()
section_resp = self._nsxv.vcns.get_section(section_uri)
if section_resp and len(section_resp) > 1:
xml_section = section_resp[1]
return et.fromstring(xml_section)
def update_redirect_section_in_backed(self, section):
section_uri = self.get_redirect_fw_section_uri()
self._nsxv.vcns.update_section(
section_uri,
et.tostring(section, encoding="us-ascii"),
None)
def _rule_ip_type(self, flow_classifier):
if flow_classifier.get('ethertype') == 'IPv6':
return 'Ipv6Address'
return 'Ipv4Address'
def _rule_ports(self, type, flow_classifier):
min_port = flow_classifier.get(type + '_port_range_min')
max_port = flow_classifier.get(type + '_port_range_max')
return self._ports_list(min_port, max_port)
def _ports_list(self, min_port, max_port):
"""Return a string of comma separated ports. i.e. '80,81'
"""
# convert the range into a string, and remove the '[]' around it
return str(range(min_port, max_port + 1))[1:-1]
def _rule_name(self, flow_classifier):
# The name of the rule will include the name & id of the classifier
# so we can later find it in order to update/delete it.
# Both the flow classifier DB & the backend has max name length of 255
# so we may have to trim the name a bit
return (flow_classifier.get('name')[:200] + '-' +
flow_classifier.get('id'))
def _is_the_same_rule(self, rule, flow_classifier_id):
return rule.find('name').text.endswith(flow_classifier_id)
def init_redirect_fw_rule(self, redirect_rule, flow_classifier):
et.SubElement(redirect_rule, 'name').text = self._rule_name(
flow_classifier)
et.SubElement(redirect_rule, 'action').text = 'redirect'
et.SubElement(redirect_rule, 'direction').text = 'inout'
si_profile = et.SubElement(redirect_rule, 'siProfile')
et.SubElement(si_profile, 'objectId').text = self._profile_id
et.SubElement(redirect_rule, 'packetType').text = flow_classifier.get(
'ethertype').lower()
# init the source & destination
if flow_classifier.get('source_ip_prefix'):
sources = et.SubElement(redirect_rule, 'sources')
sources.attrib['excluded'] = 'false'
source = et.SubElement(sources, 'source')
et.SubElement(source, 'type').text = self._rule_ip_type(
flow_classifier)
et.SubElement(source, 'value').text = flow_classifier.get(
'source_ip_prefix')
if flow_classifier.get('destination_ip_prefix'):
destinations = et.SubElement(redirect_rule, 'destinations')
destinations.attrib['excluded'] = 'false'
destination = et.SubElement(destinations, 'destination')
et.SubElement(destination, 'type').text = self._rule_ip_type(
flow_classifier)
et.SubElement(destination, 'value').text = flow_classifier.get(
'destination_ip_prefix')
# init the service
if (flow_classifier.get('destination_port_range_min') or
flow_classifier.get('source_port_range_min')):
services = et.SubElement(redirect_rule, 'services')
service = et.SubElement(services, 'service')
et.SubElement(service, 'isValid').text = 'true'
if flow_classifier.get('source_port_range_min'):
source_port = et.SubElement(service, 'sourcePort')
source_port.text = self._rule_ports('source',
flow_classifier)
if flow_classifier.get('destination_port_range_min'):
dest_port = et.SubElement(service, 'destinationPort')
dest_port.text = self._rule_ports('destination',
flow_classifier)
prot = et.SubElement(service, 'protocolName')
prot.text = flow_classifier.get('protocol').upper()
# Add the classifier description
if flow_classifier.get('description'):
notes = et.SubElement(redirect_rule, 'notes')
notes.text = flow_classifier.get('description')
def _loc_fw_section(self):
return locking.LockManager.get_lock('redirect-fw-section')
@log_helpers.log_method_call
def create_flow_classifier(self, context):
"""Create a redirect rule at the backend
"""
flow_classifier = context.current
with self._loc_fw_section():
section = self.get_redirect_fw_section_from_backend()
new_rule = et.SubElement(section, 'rule')
self.init_redirect_fw_rule(new_rule, flow_classifier)
self.update_redirect_section_in_backed(section)
@log_helpers.log_method_call
def update_flow_classifier(self, context):
"""Update the backend redirect rule
"""
flow_classifier = context.current
with self._loc_fw_section():
section = self.get_redirect_fw_section_from_backend()
redirect_rule = None
for rule in section.iter('rule'):
if self._is_the_same_rule(rule, flow_classifier['id']):
redirect_rule = rule
break
if redirect_rule is None:
msg = _("Failed to find redirect rule %s "
"on backed") % flow_classifier['id']
raise exc.FlowClassifierException(message=msg)
else:
# The flowclassifier plugin currently supports updating only
# name or description
name = redirect_rule.find('name')
name.text = self._rule_name(flow_classifier)
notes = redirect_rule.find('notes')
notes.text = flow_classifier.get('description') or ''
self.update_redirect_section_in_backed(section)
@log_helpers.log_method_call
def delete_flow_classifier(self, context):
"""Delete the backend redirect rule
"""
flow_classifier_id = context.current['id']
with self._loc_fw_section():
section = self.get_redirect_fw_section_from_backend()
redirect_rule = None
for rule in section.iter('rule'):
if self._is_the_same_rule(rule, flow_classifier_id):
redirect_rule = rule
section.remove(redirect_rule)
break
if redirect_rule is None:
LOG.error(_LE("Failed to delete redirect rule %s: "
"Could not find rule on backed"),
flow_classifier_id)
# should not fail the deletion
else:
self.update_redirect_section_in_backed(section)
@log_helpers.log_method_call
def create_flow_classifier_precommit(self, context):
"""Validate the flow classifier data before committing the transaction
The NSX-v redirect rules does not support:
- logical ports
- l7 parameters
- source ports range / destination port range with more than 15 ports
"""
flow_classifier = context.current
# Logical source port
logical_source_port = flow_classifier['logical_source_port']
if logical_source_port is not None:
msg = _('The NSXv driver does not support setting '
'logical source port in FlowClassifier')
raise exc.FlowClassifierBadRequest(message=msg)
# Logical destination port
logical_destination_port = flow_classifier['logical_destination_port']
if logical_destination_port is not None:
msg = _('The NSXv driver does not support setting '
'logical destination port in FlowClassifier')
raise exc.FlowClassifierBadRequest(message=msg)
# L7 parameters
l7_params = flow_classifier['l7_parameters']
if l7_params is not None and len(l7_params.keys()) > 0:
msg = _('The NSXv driver does not support setting '
'L7 parameters in FlowClassifier')
raise exc.FlowClassifierBadRequest(message=msg)
# Source ports range - up to 15 ports.
sport_min = flow_classifier['source_port_range_min']
sport_max = flow_classifier['source_port_range_max']
if (sport_min is not None and sport_max is not None and
(sport_max + 1 - sport_min) > MAX_PORTS_IN_RANGE):
msg = _('The NSXv driver does not support setting '
'more than %d source ports in a '
'FlowClassifier') % MAX_PORTS_IN_RANGE
raise exc.FlowClassifierBadRequest(message=msg)
# Destination ports range - up to 15 ports.
dport_min = flow_classifier['destination_port_range_min']
dport_max = flow_classifier['destination_port_range_max']
if (dport_min is not None and dport_max is not None and
(dport_max + 1 - dport_min) > MAX_PORTS_IN_RANGE):
msg = _('The NSXv driver does not support setting '
'more than %d destination ports in a '
'FlowClassifier') % MAX_PORTS_IN_RANGE
raise exc.FlowClassifierBadRequest(message=msg)

View File

@ -0,0 +1,62 @@
# Copyright 2016 VMware, Inc.
#
# All Rights Reserved
#
# 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 networking_sfc.extensions import flowclassifier
from neutron import manager
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
SERVICE_INSERTION_SG_NAME = 'Service Insertion Security Group'
class NsxvServiceInsertionHandler(object):
def __init__(self, core_plugin):
super(NsxvServiceInsertionHandler, self).__init__()
self._nsxv = core_plugin.nsx_v
self._initialized = False
def _initialize_handler(self):
if not self._initialized:
self._enabled = False
self._sg_id = None
if self.is_service_insertion_enabled():
self._enabled = True
self._sg_id = self.get_service_inserion_sg_id()
self._initialized = True
def is_service_insertion_enabled(self):
# Note - this cannot be called during init, since the manager is busy
if (manager.NeutronManager.get_service_plugins().get(
flowclassifier.FLOW_CLASSIFIER_EXT)):
return True
return False
def get_service_inserion_sg_id(self):
# Note - this cannot be called during init, since the nsxv flow
# classifier driver creates this group
return self._nsxv.vcns.get_security_group_id(
SERVICE_INSERTION_SG_NAME)
@property
def enabled(self):
self._initialize_handler()
return self._enabled
@property
def sg_id(self):
self._initialize_handler()
return self._sg_id

View File

@ -3826,6 +3826,39 @@ class TestNSXPortSecurity(test_psec.TestPortSecurity,
self._toggle_port_security(port1['port']['id'], False, True)
self._toggle_port_security(port2['port']['id'], False, False)
def test_service_insertion(self):
# init the plugin mocks
p = manager.NeutronManager.get_plugin()
self.fc2.add_member_to_security_group = (
mock.Mock().add_member_to_security_group)
self.fc2.remove_member_from_security_group = (
mock.Mock().remove_member_from_security_group)
# mock the service insertion handler
p._si_handler = mock.Mock()
p._si_handler.enabled = True
p._si_handler.sg_id = '11'
# create a compute port with port security
device_id = _uuid()
port = self._create_compute_port('net1', device_id, True)
# add vnic to the port, and verify that the port was added to the
# service insertion security group
vnic_id = 3
vnic_index = '%s.%03d' % (device_id, vnic_id)
self.fc2.add_member_to_security_group.reset_mock()
self._add_vnic_to_port(port['port']['id'], False, vnic_id)
self.fc2.add_member_to_security_group.assert_any_call(
p._si_handler.sg_id, vnic_index)
# disable the port security and make sure it is removed from the
# security group
self.fc2.remove_member_from_security_group.reset_mock()
self._toggle_port_security(port['port']['id'], False, True)
self.fc2.remove_member_from_security_group.assert_any_call(
p._si_handler.sg_id, vnic_index)
class TestSharedRouterTestCase(L3NatTest, L3NatTestCaseBase,
test_l3_plugin.L3NatTestCaseMixin,

View File

@ -919,6 +919,9 @@ class FakeVcns(object):
response += self.get_security_group(k)
return header, response
def create_redirect_section(self, request):
return self.create_section('layer3redirect', request)
def create_section(self, type, request, insert_before=None):
section = ET.fromstring(request)
section_name = section.attrib.get('name')
@ -1197,3 +1200,25 @@ class FakeVcns(object):
}
response = {'staticRoutes': {'staticRoutes': []}}
return (header, response)
def get_service_insertion_profile(self, profile_id):
headers = {'status': 200}
response = """
<serviceProfile><objectId>%s</objectId>
<objectTypeName>ServiceProfile</objectTypeName>
<type><typeName>ServiceProfile</typeName></type>
<name>Service_Vendor</name>
<serviceProfileBinding><distributedVirtualPortGroups/>
<virtualWires/><excludedVnics/><virtualServers/>
<securityGroups><string>securitygroup-30</string>
</securityGroups></serviceProfileBinding>
</serviceProfile>
"""
response_format = response % profile_id
return (headers, response_format)
def update_service_insertion_profile_binding(self, profile_id, request):
response = ''
headers = {'status': 200}
return (headers, response)

View File

@ -0,0 +1,266 @@
# Copyright 2016 VMware, Inc.
# All Rights Reserved
#
# 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 oslo_config import cfg
from oslo_utils import importutils
from vmware_nsx.services.flowclassifier.nsx_v import driver as nsx_v_driver
from vmware_nsx.tests import unit as vmware
from vmware_nsx.tests.unit.nsx_v.vshield import fake_vcns
from neutron.api import extensions as api_ext
from neutron.common import config
from neutron import context
from neutron.extensions import portbindings
from networking_sfc.db import flowclassifier_db as fdb
from networking_sfc.extensions import flowclassifier
from networking_sfc.services.flowclassifier.common import context as fc_ctx
from networking_sfc.services.flowclassifier.common import exceptions as fc_exc
from networking_sfc.tests import base
from networking_sfc.tests.unit.db import test_flowclassifier_db
class TestNsxvFlowClassifierDriver(
test_flowclassifier_db.FlowClassifierDbPluginTestCaseBase,
base.NeutronDbPluginV2TestCase):
resource_prefix_map = dict([
(k, flowclassifier.FLOW_CLASSIFIER_PREFIX)
for k in flowclassifier.RESOURCE_ATTRIBUTE_MAP.keys()
])
def setUp(self):
# init the flow classifier plugin
flowclassifier_plugin = (
test_flowclassifier_db.DB_FLOWCLASSIFIER_PLUGIN_CLASS)
service_plugins = {
flowclassifier.FLOW_CLASSIFIER_EXT: flowclassifier_plugin
}
fdb.FlowClassifierDbPlugin.supported_extension_aliases = [
flowclassifier.FLOW_CLASSIFIER_EXT]
fdb.FlowClassifierDbPlugin.path_prefix = (
flowclassifier.FLOW_CLASSIFIER_PREFIX
)
super(TestNsxvFlowClassifierDriver, self).setUp(
ext_mgr=None,
plugin=None,
service_plugins=service_plugins
)
self.flowclassifier_plugin = importutils.import_object(
flowclassifier_plugin)
ext_mgr = api_ext.PluginAwareExtensionManager(
test_flowclassifier_db.extensions_path,
{
flowclassifier.FLOW_CLASSIFIER_EXT: self.flowclassifier_plugin
}
)
app = config.load_paste_app('extensions_test_app')
self.ext_api = api_ext.ExtensionMiddleware(app, ext_mgr=ext_mgr)
self.ctx = context.get_admin_context()
# use the fake vcns
mock_vcns = mock.patch(vmware.VCNS_NAME, autospec=True)
mock_vcns_instance = mock_vcns.start()
self.fc2 = fake_vcns.FakeVcns()
mock_vcns_instance.return_value = self.fc2
# use the nsxv flow classifier driver
self._profile_id = 'serviceprofile-1'
cfg.CONF.set_override('service_insertion_profile_id',
self._profile_id, 'nsxv')
self.driver = nsx_v_driver.NsxvFlowClassifierDriver()
self.driver.initialize()
self._fc_name = 'test1'
self._fc_description = 'test 1'
self._fc_source = '10.10.0.0/24'
self._fc_dest = '20.10.0.0/24'
self._fc_prot = 'TCP'
self._fc_source_ports = range(100, 115)
self._fc_dest_ports = range(80, 81)
self._fc = {'name': self._fc_name,
'description': self._fc_description,
'logical_source_port': None,
'logical_destination_port': None,
'source_ip_prefix': self._fc_source,
'destination_ip_prefix': self._fc_dest,
'protocol': self._fc_prot,
'source_port_range_min': self._fc_source_ports[0],
'source_port_range_max': self._fc_source_ports[-1],
'destination_port_range_min': self._fc_dest_ports[0],
'destination_port_range_max': self._fc_dest_ports[-1]}
def tearDown(self):
super(TestNsxvFlowClassifierDriver, self).tearDown()
def test_driver_init(self):
self.assertEqual(self.driver._profile_id, self._profile_id)
self.assertEqual(self.driver._security_group_id, '0')
def test_create_flow_classifier_precommit(self):
with self.flow_classifier(flow_classifier=self._fc) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
# just make sure it does not raise an exception
self.driver.create_flow_classifier_precommit(fc_context)
def test_create_flow_classifier_precommit_logical_source_port(self):
with self.port(
name='port1',
device_owner='compute',
device_id='test',
arg_list=(
portbindings.HOST_ID,
),
**{portbindings.HOST_ID: 'test'}
) as src_port:
with self.flow_classifier(flow_classifier={
'name': 'test1',
'logical_source_port': src_port['port']['id']
}) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
self.assertRaises(
fc_exc.FlowClassifierBadRequest,
self.driver.create_flow_classifier_precommit,
fc_context)
def test_create_flow_classifier_precommit_logical_dest_port(self):
with self.port(
name='port1',
device_owner='compute',
device_id='test',
arg_list=(
portbindings.HOST_ID,
),
**{portbindings.HOST_ID: 'test'}
) as dst_port:
with self.flow_classifier(flow_classifier={
'name': 'test1',
'logical_destination_port': dst_port['port']['id']
}) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
self.assertRaises(
fc_exc.FlowClassifierBadRequest,
self.driver.create_flow_classifier_precommit,
fc_context)
def test_create_flow_classifier_precommit_src_port_range(self):
with self.flow_classifier(flow_classifier={
'name': 'test1',
'protocol': 'tcp',
'source_port_range_min': 100,
'source_port_range_max': 116,
}) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
self.assertRaises(
fc_exc.FlowClassifierBadRequest,
self.driver.create_flow_classifier_precommit,
fc_context)
def test_create_flow_classifier_precommit_dst_port_range(self):
with self.flow_classifier(flow_classifier={
'name': 'test1',
'protocol': 'tcp',
'destination_port_range_min': 100,
'destination_port_range_max': 116,
}) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
self.assertRaises(
fc_exc.FlowClassifierBadRequest,
self.driver.create_flow_classifier_precommit,
fc_context)
def _validate_rule_structure(self, rule):
self.assertEqual(self._fc_description, rule.find('notes').text)
self.assertEqual('ipv4', rule.find('packetType').text)
self.assertEqual(
self._fc_source,
rule.find('sources').find('source').find('value').text)
self.assertEqual(
self._fc_dest,
rule.find('destinations').find('destination').find('value').text)
self.assertEqual(
str(self._fc_source_ports)[1:-1],
rule.find('services').find('service').find('sourcePort').text)
self.assertEqual(
str(self._fc_dest_ports)[1:-1],
rule.find('services').find('service').find('destinationPort').text)
self.assertEqual(
self._fc_prot,
rule.find('services').find('service').find('protocolName').text)
self.assertTrue(rule.find('name').text.startswith(self._fc_name))
def test_create_flow_classifier(self):
with self.flow_classifier(flow_classifier=self._fc) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
with mock.patch.object(
self.driver,
'update_redirect_section_in_backed') as mock_update_section:
self.driver.create_flow_classifier(fc_context)
self.assertTrue(mock_update_section.called)
section = mock_update_section.call_args[0][0]
self._validate_rule_structure(section.find('rule'))
def test_update_flow_classifier(self):
with self.flow_classifier(flow_classifier=self._fc) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
self.driver.create_flow_classifier(fc_context)
with mock.patch.object(
self.driver,
'update_redirect_section_in_backed') as mock_update_section:
self.driver.update_flow_classifier(fc_context)
self.assertTrue(mock_update_section.called)
section = mock_update_section.call_args[0][0]
self._validate_rule_structure(section.find('rule'))
def test_delete_flow_classifier(self):
with self.flow_classifier(flow_classifier=self._fc) as fc:
fc_context = fc_ctx.FlowClassifierContext(
self.flowclassifier_plugin, self.ctx,
fc['flow_classifier']
)
self.driver.create_flow_classifier(fc_context)
with mock.patch.object(
self.driver,
'update_redirect_section_in_backed') as mock_update_section:
self.driver.delete_flow_classifier(fc_context)
self.assertTrue(mock_update_section.called)
section = mock_update_section.call_args[0][0]
# make sure the rule is not there
self.assertEqual(None, section.find('rule'))