NSXv - Support provider security-groups

This patch implements the provider security-groups extension for NsxV
Neutron plugin.
For more details, please refer to the feature
change: I57b130437327b0bbe5cc0068695f226b76b4e2ba.

Change-Id: I0efa29893eff7d76ee69496210cda33f79742cfd
This commit is contained in:
Roey Chen 2016-07-17 01:57:46 -07:00 committed by garyk
parent d1103e24f2
commit 4d7b6a305c
6 changed files with 124 additions and 42 deletions

View File

@ -74,6 +74,11 @@ def is_nsx_version_1_1_0(nsx_version):
version.LooseVersion(NSXV3_VERSION_1_1_0)) version.LooseVersion(NSXV3_VERSION_1_1_0))
def is_nsxv_version_6_2(nsx_version):
return (version.LooseVersion(nsx_version) >=
version.LooseVersion('6.2'))
def get_tags(**kwargs): def get_tags(**kwargs):
tags = ([dict(tag=value, scope=key) tags = ([dict(tag=value, scope=key)
for key, value in six.iteritems(kwargs)]) for key, value in six.iteritems(kwargs)])

View File

@ -91,6 +91,7 @@ from vmware_nsx.extensions import (
vnicindex as ext_vnic_idx) vnicindex as ext_vnic_idx)
from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu
from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain
from vmware_nsx.extensions import providersecuritygroup as provider_sg
from vmware_nsx.extensions import routersize from vmware_nsx.extensions import routersize
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix from vmware_nsx.extensions import secgroup_rule_local_ip_prefix
from vmware_nsx.extensions import securitygrouplogging as sg_logging from vmware_nsx.extensions import securitygrouplogging as sg_logging
@ -238,6 +239,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
fc_utils.SERVICE_INSERTION_RESOURCE, fc_utils.SERVICE_INSERTION_RESOURCE,
events.AFTER_CREATE) events.AFTER_CREATE)
if c_utils.is_nsxv_version_6_2(self.nsx_v.vcns.get_version()):
self.supported_extension_aliases.append("provider-security-group")
def init_complete(self, resource, event, trigger, **kwargs): def init_complete(self, resource, event, trigger, **kwargs):
self.init_is_complete = True self.init_is_complete = True
@ -377,8 +381,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self.nsx_v.vcns.update_section_by_id( self.nsx_v.vcns.update_section_by_id(
section_id, 'ip', section_req_body) section_id, 'ip', section_req_body)
else: else:
h, c = self.nsx_v.vcns.create_section( h, c = self.nsx_v.vcns.create_section('ip', section_req_body)
'ip', section_req_body)
section_id = self.nsx_sg_utils.parse_and_get_section_id(c) section_id = self.nsx_sg_utils.parse_and_get_section_id(c)
return section_id return section_id
@ -1245,6 +1248,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Update fields obtained from neutron db (eg: MAC address) # Update fields obtained from neutron db (eg: MAC address)
port["port"].update(neutron_db) port["port"].update(neutron_db)
has_ip = self._ip_on_port(neutron_db) has_ip = self._ip_on_port(neutron_db)
provider_sg_specified = (validators.is_attr_set(
port_data.get(provider_sg.PROVIDER_SECURITYGROUPS))
and port_data[provider_sg.PROVIDER_SECURITYGROUPS] != [])
has_security_groups = (
self._check_update_has_security_groups(port))
# allowed address pair checks # allowed address pair checks
attrs = port[attr.PORT] attrs = port[attr.PORT]
@ -1259,12 +1267,18 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# security group extension checks # security group extension checks
if has_ip: if has_ip:
self._ensure_default_security_group_on_port(context, port) self._ensure_default_security_group_on_port(context, port)
elif validators.is_attr_set(port_data.get(ext_sg.SECURITYGROUPS)): elif (has_security_groups or provider_sg_specified):
raise psec.PortSecurityAndIPRequiredForSecurityGroups() raise psec.PortSecurityAndIPRequiredForSecurityGroups()
port_data[ext_sg.SECURITYGROUPS] = ( else:
self._get_security_groups_on_port(context, port)) port_data[provider_sg.PROVIDER_SECURITYGROUPS] = []
self._process_port_create_security_group(
context, port_data, port_data[ext_sg.SECURITYGROUPS]) sgids = self._get_security_groups_on_port(context, port)
ssgids = self._get_provider_security_groups_on_port(context, port)
self._process_port_create_security_group(context, port_data, sgids)
self._process_port_create_provider_security_group(context,
port_data,
ssgids)
self._process_portbindings_create_and_update(context, self._process_portbindings_create_and_update(context,
port['port'], port['port'],
port_data) port_data)
@ -1430,7 +1444,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
context, id, device_id, vnic_idx) context, id, device_id, vnic_idx)
vnic_id = self._get_port_vnic_id(vnic_idx, device_id) vnic_id = self._get_port_vnic_id(vnic_idx, device_id)
self._add_security_groups_port_mapping( self._add_security_groups_port_mapping(
context.session, vnic_id, original_port['security_groups']) context.session, vnic_id,
original_port[ext_sg.SECURITYGROUPS] +
original_port[provider_sg.PROVIDER_SECURITYGROUPS])
if has_port_security: if has_port_security:
LOG.debug("Assigning vnic port fixed-ips: port %s, " LOG.debug("Assigning vnic port fixed-ips: port %s, "
"vnic %s, with fixed-ips %s", id, vnic_id, "vnic %s, with fixed-ips %s", id, vnic_id,
@ -1446,6 +1462,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._add_member_to_security_group(self._si_handler.sg_id, self._add_member_to_security_group(self._si_handler.sg_id,
vnic_id) vnic_id)
provider_sgs_specified = validators.is_attr_set(
port_data.get(provider_sg.PROVIDER_SECURITYGROUPS))
delete_provider_sg = provider_sgs_specified and (
port_data[provider_sg.PROVIDER_SECURITYGROUPS] != [])
delete_security_groups = self._check_update_deletes_security_groups( delete_security_groups = self._check_update_deletes_security_groups(
port) port)
has_security_groups = self._check_update_has_security_groups(port) has_security_groups = self._check_update_has_security_groups(port)
@ -1464,22 +1484,22 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# checks that if update adds/modify security groups, # checks that if update adds/modify security groups,
# then port has ip # then port has ip
if not has_ip: if not has_ip:
if has_security_groups: if (has_security_groups or provider_sgs_specified):
raise psec.PortSecurityAndIPRequiredForSecurityGroups() raise psec.PortSecurityAndIPRequiredForSecurityGroups()
security_groups = ( if ((not delete_security_groups
super(NsxVPluginV2, and original_port[ext_sg.SECURITYGROUPS]) or
self)._get_port_security_group_bindings( (not delete_provider_sg and
context, {'port_id': [id]}) original_port[provider_sg.PROVIDER_SECURITYGROUPS])):
) raise psec.PortSecurityAndIPRequiredForSecurityGroups()
if security_groups and not delete_security_groups:
raise psec.PortSecurityAndIPRequiredForSecurityGroups()
if delete_security_groups or has_security_groups: if delete_security_groups or has_security_groups:
# delete the port binding and read it with the new rules. self.update_security_group_on_port(context, id, port,
self._delete_port_security_group_bindings(context, id) original_port, ret_port)
new_sgids = self._get_security_groups_on_port(context, port) # NOTE(roeyc): Should call this method only after
self._process_port_create_security_group(context, ret_port, # update_security_group_on_port was called.
new_sgids) self._process_port_update_provider_security_group(context, port,
original_port,
ret_port)
LOG.debug("Updating port: %s", port) LOG.debug("Updating port: %s", port)
self._process_portbindings_create_and_update(context, self._process_portbindings_create_and_update(context,
@ -1537,7 +1557,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
vnic_idx = original_port.get(ext_vnic_idx.VNIC_INDEX) vnic_idx = original_port.get(ext_vnic_idx.VNIC_INDEX)
if validators.is_attr_set(vnic_idx) and is_compute_port: if validators.is_attr_set(vnic_idx) and is_compute_port:
vnic_id = self._get_port_vnic_id(vnic_idx, device_id) vnic_id = self._get_port_vnic_id(vnic_idx, device_id)
curr_sgids = original_port.get(ext_sg.SECURITYGROUPS) curr_sgids = (
original_port[provider_sg.PROVIDER_SECURITYGROUPS] +
original_port[ext_sg.SECURITYGROUPS])
if ret_port['device_id'] != device_id: if ret_port['device_id'] != device_id:
# Update change device_id - remove port-vnic association and # Update change device_id - remove port-vnic association and
# delete security-groups memberships for the vnic # delete security-groups memberships for the vnic
@ -1623,6 +1645,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Update security-groups, # Update security-groups,
# calculate differences and update vnic membership # calculate differences and update vnic membership
# accordingly. # accordingly.
new_sgids = (
ret_port[provider_sg.PROVIDER_SECURITYGROUPS] +
ret_port[ext_sg.SECURITYGROUPS])
self._update_security_groups_port_mapping( self._update_security_groups_port_mapping(
context.session, id, vnic_id, curr_sgids, new_sgids) context.session, id, vnic_id, curr_sgids, new_sgids)
@ -2797,19 +2822,22 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
nsx_sg_id): nsx_sg_id):
logging = (cfg.CONF.nsxv.log_security_groups_allowed_traffic or logging = (cfg.CONF.nsxv.log_security_groups_allowed_traffic or
securitygroup[sg_logging.LOGGING]) securitygroup[sg_logging.LOGGING])
action = 'deny' if securitygroup[provider_sg.PROVIDER] else 'allow'
section_name = self.nsx_sg_utils.get_nsx_section_name(securitygroup) section_name = self.nsx_sg_utils.get_nsx_section_name(securitygroup)
nsx_rules = [] nsx_rules = []
# Translate Neutron rules to NSXv fw rules and construct the fw section # Translate Neutron rules to NSXv fw rules and construct the fw section
for rule in securitygroup['security_group_rules']: for rule in securitygroup['security_group_rules']:
nsx_rule = self._create_nsx_rule( nsx_rule = self._create_nsx_rule(
context, rule, nsx_sg_id, logged=logging) context, rule, nsx_sg_id, logged=logging, action=action)
nsx_rules.append(nsx_rule) nsx_rules.append(nsx_rule)
section = self.nsx_sg_utils.get_section_with_rules( section = self.nsx_sg_utils.get_section_with_rules(
section_name, nsx_rules) section_name, nsx_rules)
# Execute REST API for creating the section # Execute REST API for creating the section
h, c = self.nsx_v.vcns.create_section( h, c = self.nsx_v.vcns.create_section(
'ip', self.nsx_sg_utils.to_xml_string(section), 'ip', self.nsx_sg_utils.to_xml_string(section),
insert_top=securitygroup[provider_sg.PROVIDER],
insert_before=self.default_section) insert_before=self.default_section)
rule_pairs = self.nsx_sg_utils.get_rule_id_pair_from_section(c) rule_pairs = self.nsx_sg_utils.get_rule_id_pair_from_section(c)
# Add database associations for fw section and rules # Add database associations for fw section and rules
nsxv_db.add_neutron_nsx_section_mapping( nsxv_db.add_neutron_nsx_section_mapping(
@ -2844,22 +2872,29 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
self._delete_nsx_security_group(nsx_sg_id) self._delete_nsx_security_group(nsx_sg_id)
# Add this Security Group to the Security Groups container if not securitygroup[provider_sg.PROVIDER]:
self._add_member_to_security_group(self.sg_container_id, nsx_sg_id) # Add Security Group to the Security Groups container inorder to
# apply the default block rule. provider security-groups should not
# have a default blocking rule.
self._add_member_to_security_group(self.sg_container_id, nsx_sg_id)
def create_security_group(self, context, security_group, default_sg=False): def create_security_group(self, context, security_group, default_sg=False):
"""Create a security group.""" """Create a security group."""
sg_data = security_group['security_group'] sg_data = security_group['security_group']
sg_id = sg_data["id"] = str(uuid.uuid4()) sg_id = sg_data["id"] = str(uuid.uuid4())
new_security_group = super(NsxVPluginV2, self).create_security_group( with context.session.begin(subtransactions=True):
context, security_group, default_sg) if sg_data.get(provider_sg.PROVIDER):
self._process_security_group_properties_create( new_sg = self.create_provider_security_group(
context, new_security_group, sg_data) context, security_group)
else:
new_sg = super(NsxVPluginV2, self).create_security_group(
context, security_group, default_sg)
self._process_security_group_properties_create(
context, new_sg, sg_data, default_sg)
try: try:
self._process_security_group_create_backend_resources( self._process_security_group_create_backend_resources(
context, new_security_group) context, new_sg)
except Exception: except Exception:
# Couldn't create backend resources, rolling back neutron db # Couldn't create backend resources, rolling back neutron db
# changes. # changes.
@ -2870,7 +2905,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
context = context.elevated() context = context.elevated()
super(NsxVPluginV2, self).delete_security_group(context, sg_id) super(NsxVPluginV2, self).delete_security_group(context, sg_id)
LOG.exception(_LE('Failed to create security group')) LOG.exception(_LE('Failed to create security group'))
return new_security_group return new_sg
def update_security_group(self, context, id, security_group): def update_security_group(self, context, id, security_group):
s = security_group['security_group'] s = security_group['security_group']
@ -2925,7 +2960,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to delete security group")) LOG.exception(_LE("Failed to delete security group"))
def _create_nsx_rule(self, context, rule, nsx_sg_id=None, logged=False): def _create_nsx_rule(self, context, rule,
nsx_sg_id=None, logged=False, action='allow'):
src = None src = None
dest = None dest = None
port = None port = None
@ -2987,6 +3023,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
destination=dest, destination=dest,
services=services, services=services,
flags=flags, flags=flags,
action=action,
logged=logged) logged=logged)
return nsx_rule return nsx_rule
@ -3010,6 +3047,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Querying DB for associated dfw section id # Querying DB for associated dfw section id
section_uri = self._get_section_uri(context.session, sg_id) section_uri = self._get_section_uri(context.session, sg_id)
logging = self._is_security_group_logged(context, sg_id) logging = self._is_security_group_logged(context, sg_id)
provider = self._is_provider_security_group(context, sg_id)
log_all_rules = cfg.CONF.nsxv.log_security_groups_allowed_traffic log_all_rules = cfg.CONF.nsxv.log_security_groups_allowed_traffic
# Translating Neutron rules to Nsx DFW rules # Translating Neutron rules to Nsx DFW rules
@ -3019,8 +3057,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
rule[secgroup_rule_local_ip_prefix.LOCAL_IP_PREFIX] = None rule[secgroup_rule_local_ip_prefix.LOCAL_IP_PREFIX] = None
rule['id'] = uuidutils.generate_uuid() rule['id'] = uuidutils.generate_uuid()
ruleids.add(rule['id']) ruleids.add(rule['id'])
nsx_rules.append(self._create_nsx_rule( nsx_rules.append(
context, rule, logged=log_all_rules or logging)) self._create_nsx_rule(context, rule,
logged=log_all_rules or logging,
action='deny' if provider else 'allow')
)
_h, _c = self.nsx_v.vcns.get_section(section_uri) _h, _c = self.nsx_v.vcns.get_section(section_uri)
section = self.nsx_sg_utils.parse_section(_c) section = self.nsx_sg_utils.parse_section(_c)

View File

@ -554,7 +554,8 @@ class Vcns(object):
return self.do_request(HTTP_POST, uri, request, format='xml', return self.do_request(HTTP_POST, uri, request, format='xml',
decode=False, encode=False) decode=False, encode=False)
def create_section(self, type, request, insert_before=None): def create_section(self, type, request,
insert_top=False, insert_before=None):
"""Creates a layer 3 or layer 2 section in nsx rule table. """Creates a layer 3 or layer 2 section in nsx rule table.
The method will return the uri to newly created section. The method will return the uri to newly created section.
@ -564,10 +565,12 @@ class Vcns(object):
else: else:
sec_type = 'layer2sections' sec_type = 'layer2sections'
uri = '%s/%s?autoSaveDraft=false' % (FIREWALL_PREFIX, sec_type) uri = '%s/%s?autoSaveDraft=false' % (FIREWALL_PREFIX, sec_type)
if insert_top:
uri += '&operation=insert_top'
# We want to place security-group sections before the default cluster # We want to place security-group sections before the default cluster
# section, and we want to place the default cluster section before the # section, and we want to place the default cluster section before the
# global default section. # global default section.
if insert_before: elif insert_before:
uri += '&operation=insert_before&anchorId=%s' % insert_before uri += '&operation=insert_before&anchorId=%s' % insert_before
else: else:
uri += '&operation=insert_before&anchorId=1003' uri += '&operation=insert_before&anchorId=1003'

View File

@ -12,16 +12,18 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
import webob.exc
from neutron.api.v2 import attributes as attr from neutron.api.v2 import attributes as attr
from neutron import context from neutron import context
from neutron.db import db_base_plugin_v2 from neutron.db import db_base_plugin_v2
from neutron.db import securitygroups_db from neutron.db import securitygroups_db
from neutron.tests.unit.extensions import test_securitygroup from neutron.tests.unit.extensions import test_securitygroup
import webob.exc
from vmware_nsx.db import extended_security_group from vmware_nsx.db import extended_security_group
from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import providersecuritygroup as provider_sg
from vmware_nsx.tests.unit.nsx_v import test_plugin as test_nsxv_plugin
from vmware_nsx.tests.unit.nsx_v3 import test_plugin as test_nsxv3_plugin from vmware_nsx.tests.unit.nsx_v3 import test_plugin as test_nsxv3_plugin
@ -246,4 +248,34 @@ class TestNSXv3ProviderSecurityGrp(test_nsxv3_plugin.NsxV3PluginTestCaseMixin,
ProviderSecurityGroupExtTestCase): ProviderSecurityGroupExtTestCase):
pass pass
# TODO(roeyc): add nsxv test case mixin when ready
class TestNSXvProviderSecurityGroup(test_nsxv_plugin.NsxVPluginV2TestCase,
ProviderSecurityGroupExtTestCase):
def test_create_provider_security_group(self):
_create_section_tmp = self.fc2.create_section
def _create_section(*args, **kwargs):
return _create_section_tmp(*args, **kwargs)
with mock.patch.object(self.fc2, 'create_section',
side_effect=_create_section) as create_sec_mock:
super(TestNSXvProviderSecurityGroup,
self).test_create_provider_security_group()
create_sec_mock.assert_called_with('ip', mock.ANY,
insert_top=True,
insert_before=mock.ANY)
def test_create_provider_security_group_rule(self):
provider_secgroup = self._create_provider_security_group()
sg_id = provider_secgroup['security_group']['id']
_create_nsx_rule_tmp = self.plugin._create_nsx_rule
def m_create_nsx_rule(*args, **kwargs):
return _create_nsx_rule_tmp(*args, **kwargs)
with mock.patch.object(self.plugin, '_create_nsx_rule',
side_effect=m_create_nsx_rule) as create_rule_m:
with self.security_group_rule(security_group_id=sg_id):
create_rule_m.assert_called_with(mock.ANY, mock.ANY,
logged=mock.ANY,
action='deny')

View File

@ -118,7 +118,7 @@ class TestNsxVExtendedSGRule(test_nsxv_plugin.NsxVSecurityGroupsTestCase,
plugin.nsx_sg_utils.get_rule_config.assert_called_with( plugin.nsx_sg_utils.get_rule_config.assert_called_with(
source=mock.ANY, destination=dest, services=mock.ANY, source=mock.ANY, destination=dest, services=mock.ANY,
name=mock.ANY, applied_to_ids=mock.ANY, flags=mock.ANY, name=mock.ANY, applied_to_ids=mock.ANY, flags=mock.ANY,
logged=mock.ANY) logged=mock.ANY, action=mock.ANY)
class TestNSXv3ExtendedSGRule(test_nsxv3_plugin.NsxV3PluginTestCaseMixin, class TestNSXv3ExtendedSGRule(test_nsxv3_plugin.NsxV3PluginTestCaseMixin,

View File

@ -903,7 +903,8 @@ class FakeVcns(object):
def create_redirect_section(self, request): def create_redirect_section(self, request):
return self.create_section('layer3redirect', request) return self.create_section('layer3redirect', request)
def create_section(self, type, request, insert_before=None): def create_section(self, type, request,
insert_top=False, insert_before=None):
section = ET.fromstring(request) section = ET.fromstring(request)
section_name = section.attrib.get('name') section_name = section.attrib.get('name')
if section_name in self._sections['names']: if section_name in self._sections['names']: