diff --git a/vmware_nsx/neutron/db/migration/alembic_migrations/versions/HEADS b/vmware_nsx/neutron/db/migration/alembic_migrations/versions/HEADS index da6f10e20f..30d4e4e270 100644 --- a/vmware_nsx/neutron/db/migration/alembic_migrations/versions/HEADS +++ b/vmware_nsx/neutron/db/migration/alembic_migrations/versions/HEADS @@ -1,2 +1,2 @@ +28430956782d 393bf843b96 -53a3254aa95e diff --git a/vmware_nsx/neutron/db/migration/alembic_migrations/versions/liberty/expand/28430956782d_nsxv3_security_groups.py b/vmware_nsx/neutron/db/migration/alembic_migrations/versions/liberty/expand/28430956782d_nsxv3_security_groups.py new file mode 100644 index 0000000000..412459c3a0 --- /dev/null +++ b/vmware_nsx/neutron/db/migration/alembic_migrations/versions/liberty/expand/28430956782d_nsxv3_security_groups.py @@ -0,0 +1,47 @@ +# Copyright 2015 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. +# + +"""nsxv3_security_groups + +Revision ID: 28430956782d +Revises: 53a3254aa95e +Create Date: 2015-08-24 18:19:09.397813 + +""" + +# revision identifiers, used by Alembic. +revision = '28430956782d' +down_revision = '53a3254aa95e' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table( + 'neutron_nsx_firewall_section_mappings', + sa.Column('neutron_id', sa.String(36), nullable=False), + sa.Column('nsx_id', sa.String(36), nullable=False), + sa.ForeignKeyConstraint(['neutron_id'], ['securitygroups.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('neutron_id')) + + op.create_table( + 'neutron_nsx_rule_mappings', + sa.Column('neutron_id', sa.String(36), nullable=False), + sa.Column('nsx_id', sa.String(36), nullable=False), + sa.ForeignKeyConstraint(['neutron_id'], ['securitygrouprules.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('neutron_id')) diff --git a/vmware_nsx/neutron/plugins/vmware/dbexts/nsx_models.py b/vmware_nsx/neutron/plugins/vmware/dbexts/nsx_models.py index 7ca671323f..db71822df1 100644 --- a/vmware_nsx/neutron/plugins/vmware/dbexts/nsx_models.py +++ b/vmware_nsx/neutron/plugins/vmware/dbexts/nsx_models.py @@ -89,6 +89,34 @@ class NeutronNsxSecurityGroupMapping(model_base.BASEV2): nsx_id = sa.Column(sa.String(36), primary_key=True) +class NeutronNsxFirewallSectionMapping(model_base.BASEV2): + """Backend mappings for Neutron Security-group associated fw sections.""" + + __tablename__ = 'neutron_nsx_firewall_section_mappings' + neutron_id = sa.Column(sa.String(36), + sa.ForeignKey('securitygroups.id', + ondelete='CASCADE'), + primary_key=True, + nullable=False) + nsx_id = sa.Column(sa.String(36), nullable=False) + + +class NeutronNsxRuleMapping(model_base.BASEV2): + """Backend mappings for firewall rules. + + This class maps a neutron security group rule with NSX firewall rule. + """ + + __tablename__ = 'neutron_nsx_rule_mappings' + + neutron_id = sa.Column(sa.String(36), + sa.ForeignKey('securitygrouprules.id', + ondelete="CASCADE"), + primary_key=True, + nullable=False) + nsx_id = sa.Column(sa.String(36), nullable=False) + + class NeutronNsxPortMapping(model_base.BASEV2): """Represents the mapping between neutron and nsx port uuids.""" diff --git a/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/client.py b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/client.py index a66cdd672b..8c05f45cda 100644 --- a/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/client.py +++ b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/client.py @@ -86,7 +86,7 @@ def create_resource(resource, data): verify=verify, headers=headers, data=jsonutils.dumps(data), cert=cfg.CONF.nsx_v3.ca_file) - _validate_result(result, [requests.codes.created], + _validate_result(result, [requests.codes.created, requests.codes.ok], _("creating resource at: %s") % resource) return result.json() diff --git a/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/dfw_api.py b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/dfw_api.py new file mode 100644 index 0000000000..374dc76917 --- /dev/null +++ b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/dfw_api.py @@ -0,0 +1,193 @@ +# Copyright 2015 OpenStack Foundation +# 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. + + +""" +NSX-V3 Distributed Firewall +""" + +from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc +from vmware_nsx.neutron.plugins.vmware.common import utils +from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import client as nsclient + + +# firewall section types +LAYER3 = 'LAYER3' + +# firewall rule actions +ALLOW = 'ALLOW' +DROP = 'DROP' +REJECT = 'REJECT' + +# filtering operators +EQUALS = 'EQUALS' + +NSGROUP = 'NSGroup' +LOGICAL_SWITCH = 'LogicalSwitch' +LOGICAL_PORT = 'LogicalPort' +IPV4ADDRESS = 'IPv4Address' +IPV6ADDRESS = 'IPv6Address' + +IN = 'IN' +OUT = 'OUT' + +# NSServices resource types +L4_PORT_SET_NSSERVICE = 'L4PortSetNSService' +ICMP_TYPE_NSSERVICE = 'ICMPTypeNSService' +IP_PROTOCOL_NSSERVICE = 'IPProtocolNSService' + +TCP = 'TCP' +UDP = 'UDP' +ICMPV4 = 'ICMPv4' +ICMPV6 = 'ICMPv6' + +IPV4 = 'IPV4' +IPV6 = 'IPV6' + + +def get_nsservice(resource_type, **properties): + service = {'resource_type': resource_type} + service.update(properties) + return {'service': service} + + +def create_nsgroup(display_name, description, tags): + body = {'display_name': display_name, + 'description': description, + 'tags': tags} + return nsclient.create_resource('ns-groups', body) + + +def list_nsgroups(): + return nsclient.get_resource('ns-groups') + + +@utils.retry_upon_exception_nsxv3(nsx_exc.StaleRevision) +def update_nsgroup(nsgroup_id, display_name, description): + nsgroup = read_nsgroup(nsgroup_id) + nsgroup.update({'display_name': display_name, + 'description': description}) + return nsclient.update_resource('ns-groups/%s' % nsgroup_id, nsgroup) + + +@utils.retry_upon_exception_nsxv3(nsx_exc.StaleRevision) +def add_nsgroup_member(nsgroup_id, target_type, target_id): + nsgroup = read_nsgroup(nsgroup_id) + if 'members' not in nsgroup: + nsgroup['members'] = [] + nsgroup['members'].append({'target_property': 'id', + 'target_type': target_type, + 'op': EQUALS, + 'value': target_id}) + return nsclient.update_resource('ns-groups/%s' % nsgroup_id, nsgroup) + + +@utils.retry_upon_exception_nsxv3(nsx_exc.StaleRevision) +def remove_nsgroup_member(nsgroup_id, target_id): + nsgroup = read_nsgroup(nsgroup_id) + for i, member in enumerate(nsgroup.get('members', [])): + if target_id == member['value']: + break + else: + return + del nsgroup['members'][i] + return nsclient.update_resource('ns-groups/%s' % nsgroup_id, nsgroup) + + +def read_nsgroup(nsgroup_id): + return nsclient.get_resource('ns-groups/%s' % nsgroup_id) + + +def delete_nsgroup(nsgroup_id): + return nsclient.delete_resource('ns-groups/%s' % nsgroup_id) + + +def _build_section(display_name, description, applied_tos, tags): + return {'display_name': display_name, + 'description': description, + 'stateful': True, + 'section_type': LAYER3, + 'applied_tos': [get_nsgroup_reference(t_id) + for t_id in applied_tos], + 'tags': tags} + + +def create_empty_section(display_name, description, applied_tos, tags): + resource = 'firewall/sections' + body = _build_section(display_name, description, applied_tos, tags) + return nsclient.create_resource(resource, body) + + +@utils.retry_upon_exception_nsxv3(nsx_exc.StaleRevision) +def update_section(section_id, display_name, description, applied_tos=None): + resource = 'firewall/sections/%s' % section_id + section = read_section(section_id) + section.update({'display_name': display_name, + 'description': description}) + if applied_tos is not None: + section['applied_tos'] = applied_tos + return nsclient.update_resource(resource, section) + + +def read_section(section_id): + resource = 'firewall/sections/%s' % section_id + return nsclient.get_resource(resource) + + +def list_sections(): + resource = 'firewall/sections' + return nsclient.get_resource(resource) + + +def delete_section(section_id): + resource = 'firewall/sections/%s?cascade=true' % section_id + return nsclient.delete_resource(resource) + + +def get_nsgroup_reference(nsgroup_id): + return {'target_id': nsgroup_id, + 'target_type': NSGROUP} + + +def get_ip_cidr_reference(ip_cidr_block, ip_protocol): + target_type = IPV4ADDRESS if ip_protocol == IPV4 else IPV6ADDRESS + return {'target_id': ip_cidr_block, + 'target_type': target_type} + + +def get_firewall_rule_dict(display_name, source, destination, direction, + ip_protocol, service, action): + return {'display_name': display_name, + 'sources': [source] if source else [], + 'destinations': [destination] if destination else [], + 'direction': direction, + 'ip_protocol': ip_protocol, + 'services': [service] if service else [], + 'action': action} + + +def add_rule_in_section(rule, section_id): + resource = 'firewall/sections/%s/rules' % section_id + return nsclient.create_resource(resource, rule) + + +def add_rules_in_section(rules, section_id): + resource = 'firewall/sections/%s/rules?action=create_multiple' % section_id + return nsclient.create_resource(resource, {'rules': rules}) + + +def delete_rule(section_id, rule_id): + resource = 'firewall/sections/%s/rules/%s' % (section_id, rule_id) + return nsclient.delete_resource(resource) diff --git a/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/security.py b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/security.py new file mode 100644 index 0000000000..041cc6259a --- /dev/null +++ b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/security.py @@ -0,0 +1,172 @@ +# Copyright 2015 OpenStack Foundation + +# 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. + +""" +NSX-V3 Plugin security integration module +""" + +from neutron.db import securitygroups_db + +from vmware_nsx.neutron.plugins.vmware.dbexts import nsx_models +from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import dfw_api as firewall + + +def _get_l4_protocol_name(proto_num): + if proto_num == 6: + return firewall.TCP + elif proto_num == 17: + return firewall.UDP + elif proto_num == 1: + return firewall.ICMPV4 + + +def _decide_service(sg_rule): + ip_proto = securitygroups_db.IP_PROTOCOL_MAP.get(sg_rule['protocol'], + sg_rule['protocol']) + l4_protocol = _get_l4_protocol_name(ip_proto) + + if l4_protocol in [firewall.TCP, firewall.UDP]: + # If port_range_min is not specified then we assume all ports are + # matched, relying on neutron to perform validation. + if sg_rule['port_range_min'] is None: + source_ports = [] + else: + source_ports = ['%(port_range_min)s-%(port_range_max)s' % sg_rule] + return firewall.get_nsservice(firewall.L4_PORT_SET_NSSERVICE, + l4_protocol=l4_protocol, + source_ports=source_ports) + elif l4_protocol == firewall.ICMPV4: + return firewall.get_nsservice(firewall.ICMP_TYPE_NSSERVICE, + protocol=l4_protocol, + icmp_type=sg_rule['port_range_min'], + icmp_code=sg_rule['port_range_max']) + elif ip_proto is not None: + return firewall.get_nsservice(firewall.IP_PROTOCOL_NSSERVICE, + protocol_number=ip_proto) + + +def _get_fw_rule_from_sg_rule(sg_rule, nsgroup_id, rmt_nsgroup_id): + # IPV4 or IPV6 + ip_protocol = sg_rule['ethertype'].upper() + direction = ( + firewall.IN if sg_rule['direction'] == 'ingress' else firewall.OUT) + + source = None + local_group = firewall.get_nsgroup_reference(nsgroup_id) + if sg_rule['remote_ip_prefix'] is not None: + source = firewall.get_ip_cidr_reference(sg_rule['remote_ip_prefix'], + ip_protocol) + destination = local_group + else: + if rmt_nsgroup_id: + source = firewall.get_nsgroup_reference(rmt_nsgroup_id) + destination = local_group + if direction == firewall.OUT: + source, destination = destination, source + + service = _decide_service(sg_rule) + name = sg_rule['id'] + + return firewall.get_firewall_rule_dict(name, source, + destination, direction, + ip_protocol, service, + firewall.ALLOW) + + +def create_firewall_rules(context, section_id, nsgroup_id, + security_group_rules): + + # 1. translate rules + # 2. insert in section + # 3. save mappings + + firewall_rules = [] + for sg_rule in security_group_rules: + remote_nsgroup_id = _get_remote_nsg_mapping( + context, sg_rule, nsgroup_id) + + fw_rule = _get_fw_rule_from_sg_rule( + sg_rule, nsgroup_id, remote_nsgroup_id) + + firewall_rules.append( + firewall.add_rule_in_section(fw_rule, section_id)) + return {'rules': firewall_rules} + + +def get_nsgroup_name(security_group): + # NOTE(roeyc): We add the security-group id to the NSGroup name, + # for usability purposes. + return '%(name)s - %(id)s' % security_group + + +def save_sg_rule_mappings(session, firewall_rules): + # REVISIT(roeyc): This method should take care db access only. + rules = [(rule['display_name'], rule['id']) for rule in firewall_rules] + with session.begin(subtransactions=True): + for neutron_id, nsx_id in rules: + mapping = nsx_models.NeutronNsxRuleMapping( + neutron_id=neutron_id, nsx_id=nsx_id) + session.add(mapping) + return mapping + + +def save_sg_mappings(session, sg_id, nsgroup_id, section_id): + with session.begin(subtransactions=True): + session.add( + nsx_models.NeutronNsxFirewallSectionMapping(neutron_id=sg_id, + nsx_id=section_id)) + session.add( + nsx_models.NeutronNsxSecurityGroupMapping(neutron_id=sg_id, + nsx_id=nsgroup_id)) + + +def get_sg_rule_mapping(session, rule_id): + rule_mapping = session.query(nsx_models.NeutronNsxRuleMapping).filter_by( + neutron_id=rule_id).one() + return rule_mapping.nsx_id + + +def get_sg_mappings(session, sg_id): + nsgroup_mapping = session.query(nsx_models.NeutronNsxSecurityGroupMapping + ).filter_by(neutron_id=sg_id).one() + section_mapping = session.query(nsx_models.NeutronNsxFirewallSectionMapping + ).filter_by(neutron_id=sg_id).one() + return nsgroup_mapping.nsx_id, section_mapping.nsx_id + + +def _get_remote_nsg_mapping(context, sg_rule, nsgroup_id): + remote_nsgroup_id = None + remote_group_id = sg_rule.get('remote_group_id') + # skip unnecessary db access when possible + if remote_group_id == sg_rule['security_group_id']: + remote_nsgroup_id = nsgroup_id + elif remote_group_id: + remote_nsgroup_id, _ = get_sg_mappings(context.session, + remote_group_id) + return remote_nsgroup_id + + +def update_lport_with_security_groups(context, lport_id, original, updated): + added = set(updated) - set(original) + removed = set(original) - set(updated) + for sg_id in added: + nsgroup_id, _ = get_sg_mappings(context.session, sg_id) + firewall.add_nsgroup_member( + nsgroup_id, firewall.LOGICAL_PORT, lport_id) + for sg_id in removed: + nsgroup_id, _ = get_sg_mappings(context.session, sg_id) + firewall.remove_nsgroup_member( + nsgroup_id, lport_id) diff --git a/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py b/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py index 7470723f36..34ef64dddb 100644 --- a/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py +++ b/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py @@ -32,6 +32,7 @@ from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import l3 from neutron.extensions import portbindings as pbin from neutron.extensions import providernet as pnet +from neutron.extensions import securitygroup as ext_sg from neutron.common import constants as const from neutron.common import exceptions as n_exc @@ -55,6 +56,8 @@ from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc from vmware_nsx.neutron.plugins.vmware.common import utils from vmware_nsx.neutron.plugins.vmware.dbexts import db as nsx_db from vmware_nsx.neutron.plugins.vmware.nsxlib import v3 as nsxlib +from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import dfw_api as firewall +from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import security LOG = log.getLogger(__name__) @@ -66,8 +69,6 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, portbindings_db.PortBindingMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin, extradhcpopt_db.ExtraDhcpOptMixin): - # NOTE(salv-orlando): Security groups are not actually implemented by this - # plugin at the moment __native_bulk_support = True __native_pagination_support = True @@ -438,6 +439,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, nsx_db.add_neutron_nsx_port_mapping( context.session, neutron_db['id'], neutron_db['network_id'], result['id']) + return result def create_port(self, context, port): dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) @@ -456,7 +458,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, if not self._network_is_external( context, port['port']['network_id']): - self._create_port_at_the_backend( + lport = self._create_port_at_the_backend( context, neutron_db, port['port']) self._process_portbindings_create_and_update(context, port['port'], @@ -466,11 +468,15 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, if (pbin.PROFILE in port['port'] and attributes.is_attr_set(port['port'][pbin.PROFILE])): neutron_db[pbin.PROFILE] = port['port'][pbin.PROFILE] - sgids = self._get_security_groups_on_port(context, port) - self._process_port_create_security_group( - context, neutron_db, sgids) self._process_port_create_extra_dhcp_opts(context, neutron_db, dhcp_opts) + + sgids = self._get_security_groups_on_port(context, port) + if sgids is not None: + self._process_port_create_security_group( + context, neutron_db, sgids) + security.update_lport_with_security_groups( + context, lport['id'], [], sgids) return neutron_db def delete_port(self, context, port_id, l3_port_check=True): @@ -497,12 +503,15 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, self._update_extra_dhcp_opts_on_port(context, id, port, updated_port) sec_grp_updated = self.update_security_group_on_port( - context, id, port, original_port, - updated_port) + context, id, port, original_port, updated_port) try: nsxlib.update_logical_port( nsx_lport_id, name=port['port'].get('name'), admin_state=port['port'].get('admin_state_up')) + security.update_lport_with_security_groups( + context, nsx_lport_id, + original_port.get(ext_sg.SECURITYGROUPS, []), + updated_port.get(ext_sg.SECURITYGROUPS, [])) except nsx_exc.ManagerError: # In case if there is a failure on NSX-v3 backend, rollback the # previous update operation on neutron side. @@ -517,6 +526,8 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, context, id, {'port': original_port}, updated_port, original_port) + #TODO(roeyc): add port to nsgroups + return updated_port def create_router(self, context, router): @@ -616,10 +627,128 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, return super(NsxV3Plugin, self).remove_router_interface( context, router_id, interface_info) - def create_security_group_rule_bulk(self, context, security_group_rules): - return super(NsxV3Plugin, self).create_security_group_rule_bulk_native( - context, security_group_rules) - def extend_port_dict_binding(self, port_res, port_db): super(NsxV3Plugin, self).extend_port_dict_binding(port_res, port_db) port_res[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL + + def create_security_group(self, context, security_group, default_sg=False): + secgroup = security_group['security_group'] + secgroup['id'] = uuidutils.generate_uuid() + + tags = utils.build_v3_tags_payload(secgroup) + name = security.get_nsgroup_name(secgroup) + ns_group = None + + try: + # NOTE(roeyc): We first create the nsgroup so that once the sg is + # saved into db its already backed up by an nsx resource. + ns_group = firewall.create_nsgroup( + name, secgroup['description'], tags) + # security-group rules are located in a dedicated firewall section. + firewall_section = firewall.create_empty_section( + name, secgroup.get('description', ''), [ns_group['id']], tags) + + # REVISIT(roeyc): Idealy, at this point we need not be under an + # open db transactions, however, unittests fail if omitting + # subtransactions=True. + with context.session.begin(subtransactions=True): + secgroup_db = ( + super(NsxV3Plugin, self).create_security_group( + context, security_group, default_sg)) + + security.save_sg_mappings(context.session, + secgroup_db['id'], + ns_group['id'], + firewall_section['id']) + except nsx_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Unable to create security-group on the " + "backend.")) + if ns_group: + firewall.delete_nsgroup(ns_group['id']) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.debug("Neutron failed to create security-group, " + "deleting backend resources: " + "section %s, ns-group %s.", + firewall_section['id'], ns_group['id']) + firewall.delete_nsgroup(ns_group['id']) + firewall.delete_section(firewall_section['id']) + try: + sg_rules = secgroup_db['security_group_rules'] + # translate and creates firewall rules. + rules = security.create_firewall_rules( + context, firewall_section['id'], ns_group['id'], sg_rules) + security.save_sg_rule_mappings(context.session, rules['rules']) + except nsx_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Failed to create backend firewall rules " + " for security-group %(name)s (%(id)s), " + "rolling back changes."), secgroup_db) + # default security group deletion requires admin context + if default_sg: + context = context.elevated() + super(NsxV3Plugin, self).delete_security_group( + context, secgroup_db['id']) + firewall.delete_nsgroup(ns_group['id']) + firewall.delete_section(firewall_section['id']) + + return secgroup_db + + def update_security_group(self, context, id, security_group): + nsgroup_id, section_id = security.get_sg_mappings(context.session, id) + original_security_group = self.get_security_group( + context, id, fields=['id', 'name', 'description']) + updated_security_group = ( + super(NsxV3Plugin, self).update_security_group(context, id, + security_group)) + name = security.get_nsgroup_name(updated_security_group) + description = updated_security_group['description'] + try: + firewall.update_nsgroup(nsgroup_id, name, description) + firewall.update_section(section_id, name, description) + except nsx_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Failed to update security-group %(name)s " + "(%(id)s), rolling back changes in " + "Neutron."), original_security_group) + super(NsxV3Plugin, self).update_security_group( + context, id, {'security_group': original_security_group}) + + return updated_security_group + + def delete_security_group(self, context, id): + nsgroup_id, section_id = security.get_sg_mappings(context.session, id) + super(NsxV3Plugin, self).delete_security_group(context, id) + firewall.delete_section(section_id) + firewall.delete_nsgroup(nsgroup_id) + + def create_security_group_rule(self, context, security_group_rule): + bulk_rule = {'security_group_rules': [security_group_rule]} + return self.create_security_group_rule_bulk(context, bulk_rule)[0] + + def create_security_group_rule_bulk(self, context, security_group_rules): + security_group_rules_db = ( + super(NsxV3Plugin, self).create_security_group_rule_bulk_native( + context, security_group_rules)) + sg_id = security_group_rules_db[0]['security_group_id'] + nsgroup_id, section_id = security.get_sg_mappings(context.session, + sg_id) + try: + rules = security.create_firewall_rules( + context, section_id, nsgroup_id, security_group_rules_db) + except nsx_exc.ManagerError: + with excutils.save_and_reraise_exception(): + for rule in security_group_rules_db: + super(NsxV3Plugin, self).delete_security_group_rule( + context, rule['id']) + security.save_sg_rule_mappings(context.session, rules['rules']) + return security_group_rules_db + + def delete_security_group_rule(self, context, id): + rule_db = self._get_security_group_rule(context, id) + sg_id = rule_db['security_group_id'] + _, section_id = security.get_sg_mappings(context.session, sg_id) + fw_rule_id = security.get_sg_rule_mapping(context.session, id) + super(NsxV3Plugin, self).delete_security_group_rule(context, id) + firewall.delete_rule(section_id, fw_rule_id) diff --git a/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py b/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py index 8de5e642eb..7b7d4932f4 100644 --- a/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py +++ b/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py @@ -193,3 +193,26 @@ def get_logical_router(lrouter_uuid): "id": lrouter_uuid, "edge_cluster_uuid": uuidutils.generate_uuid()} return FAKE_LROUTER + + +def add_rules_in_section(rules, section_id): + for rule in rules: + rule['id'] = uuidutils.generate_uuid() + return {'rules': rules} + + +def get_resource(resource): + return {'id': resource.split('/')[-1]} + + +def create_resource(resource, data): + data['id'] = uuidutils.generate_uuid() + return data + + +def update_resource(resource, data): + return resource + + +def delete_resource(resource): + pass diff --git a/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py b/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py index d54069c762..b0d6c3fe79 100644 --- a/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py +++ b/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py @@ -20,6 +20,7 @@ import neutron.tests.unit.db.test_db_base_plugin_v2 as test_plugin from neutron.tests.unit.extensions import test_extra_dhcp_opt as test_dhcpopts import neutron.tests.unit.extensions.test_securitygroup as ext_sg from vmware_nsx.neutron.plugins.vmware.nsxlib import v3 as nsxlib +from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import dfw_api as firewall from vmware_nsx.neutron.tests.unit.vmware import nsx_v3_mocks PLUGIN_NAME = ('vmware_nsx.neutron.plugins.vmware.' @@ -49,6 +50,11 @@ class NsxPluginV3TestCase(test_plugin.NeutronDbPluginV2TestCase): # TODO(berlin): fill valid data nsxlib.get_edge_cluster = nsx_v3_mocks.get_edge_cluster nsxlib.get_logical_router = nsx_v3_mocks.get_logical_router + firewall.add_rules_in_section = nsx_v3_mocks.add_rules_in_section + firewall.nsclient.create_resource = nsx_v3_mocks.create_resource + firewall.nsclient.update_resource = nsx_v3_mocks.update_resource + firewall.nsclient.get_resource = nsx_v3_mocks.get_resource + firewall.nsclient.delete_resource = nsx_v3_mocks.delete_resource class TestNetworksV2(test_plugin.TestNetworksV2, NsxPluginV3TestCase): @@ -64,14 +70,20 @@ class SecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase): def setUp(self, plugin=PLUGIN_NAME, ext_mgr=None): + super(SecurityGroupsTestCase, self).setUp(plugin=PLUGIN_NAME, + ext_mgr=ext_mgr) nsxlib.create_logical_switch = nsx_v3_mocks.create_logical_switch nsxlib.create_logical_port = nsx_v3_mocks.create_logical_port nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port nsxlib.delete_logical_port = mock.Mock() nsxlib.delete_logical_switch = mock.Mock() - - super(SecurityGroupsTestCase, self).setUp(plugin=PLUGIN_NAME, - ext_mgr=ext_mgr) + nsxlib.get_logical_port = nsx_v3_mocks.get_logical_port + nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port + firewall.add_rules_in_section = nsx_v3_mocks.add_rules_in_section + firewall.nsclient.create_resource = nsx_v3_mocks.create_resource + firewall.nsclient.update_resource = nsx_v3_mocks.update_resource + firewall.nsclient.get_resource = nsx_v3_mocks.get_resource + firewall.nsclient.delete_resource = nsx_v3_mocks.delete_resource class TestSecurityGroups(ext_sg.TestSecurityGroups, SecurityGroupsTestCase):