From d35d4a4b16b81d1f3d1c141f32f04684c09c8f1f Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 27 Dec 2013 15:36:22 -0800 Subject: [PATCH] DB Mappings for NSX security groups This patch introduces DB mappings between neutron security groups and NSX security profiles, thus not requiring anymore the Neutron router ID to be equal to the NSX one. This change is needed for enabling asynchronous operations in the NSX plugin. Related to blueprint nvp-async-backend-communication Change-Id: I3b28d535c93cd2bfc776aabe0d99be18fce4454d --- .../1b2580001654_nsx_sec_group_mappin.py | 61 +++++ neutron/plugins/nicira/NeutronPlugin.py | 100 ++++++--- neutron/plugins/nicira/common/nsx_utils.py | 69 ++++-- .../plugins/nicira/common/securitygroups.py | 212 +++++++++--------- neutron/plugins/nicira/dbexts/db.py | 30 +++ neutron/plugins/nicira/dbexts/models.py | 14 ++ neutron/plugins/nicira/nsxlib/secgroup.py | 27 ++- .../tests/unit/vmware/nsxlib/test_secgroup.py | 13 +- neutron/tests/unit/vmware/test_nsx_utils.py | 41 ++++ 9 files changed, 413 insertions(+), 154 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/1b2580001654_nsx_sec_group_mappin.py diff --git a/neutron/db/migration/alembic_migrations/versions/1b2580001654_nsx_sec_group_mappin.py b/neutron/db/migration/alembic_migrations/versions/1b2580001654_nsx_sec_group_mappin.py new file mode 100644 index 000000000..76e072ca3 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/1b2580001654_nsx_sec_group_mappin.py @@ -0,0 +1,61 @@ +# 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_sec_group_mapping + +Revision ID: 1b2580001654 +Revises: abc88c33f74f +Create Date: 2013-12-27 13:02:42.894648 + +""" + +# revision identifiers, used by Alembic. +revision = '1b2580001654' +down_revision = 'abc88c33f74f' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2', + 'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin', + 'neutron.plugins.vmware.plugin.NsxPlugin', + 'neutron.plugins.vmware.plugin.NsxServicePlugin' +] + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + # Create table for security group mappings + op.create_table( + 'neutron_nsx_security_group_mappings', + sa.Column('neutron_id', sa.String(length=36), nullable=False), + sa.Column('nsx_id', sa.String(length=36), nullable=False), + sa.ForeignKeyConstraint(['neutron_id'], ['securitygroups.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('neutron_id', 'nsx_id')) + # Execute statement to add a record in security group mappings for + # each record in securitygroups + op.execute("INSERT INTO neutron_nsx_security_group_mappings SELECT id,id " + "from securitygroups") + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + op.drop_table('neutron_nsx_security_group_mappings') diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index fade1bb21..fd5a5753f 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -65,7 +65,7 @@ from neutron.plugins.nicira.api_client import exception as api_exc from neutron.plugins.nicira.common import config # noqa from neutron.plugins.nicira.common import exceptions as nvp_exc from neutron.plugins.nicira.common import nsx_utils -from neutron.plugins.nicira.common import securitygroups as nvp_sec +from neutron.plugins.nicira.common import securitygroups as sg_utils from neutron.plugins.nicira.common import sync from neutron.plugins.nicira.dbexts import db as nsx_db from neutron.plugins.nicira.dbexts import distributedrouter as dist_rtr @@ -112,7 +112,6 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, l3_gwmode_db.L3_NAT_db_mixin, mac_db.MacLearningDbMixin, networkgw_db.NetworkGatewayMixin, - nvp_sec.NVPSecurityGroups, portbindings_db.PortBindingMixin, portsecurity_db.PortSecurityDbMixin, qos_db.NVPQoSDbMixin, @@ -410,16 +409,25 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, LOG.exception(err_desc) raise nvp_exc.NvpPluginException(err_msg=err_desc) - def _nvp_create_port_helper(self, cluster, ls_uuid, port_data, + def _nvp_create_port_helper(self, session, ls_uuid, port_data, do_port_security=True): - return switchlib.create_lport(cluster, ls_uuid, port_data['tenant_id'], - port_data['id'], port_data['name'], + # Convert Neutron security groups identifiers into NSX security + # profiles identifiers + nsx_sec_profile_ids = [ + nsx_utils.get_nsx_security_group_id( + session, self.cluster, neutron_sg_id) for + neutron_sg_id in (port_data[ext_sg.SECURITYGROUPS] or [])] + return switchlib.create_lport(self.cluster, + ls_uuid, + port_data['tenant_id'], + port_data['id'], + port_data['name'], port_data['device_id'], port_data['admin_state_up'], port_data['mac_address'], port_data['fixed_ips'], port_data[psec.PORTSECURITY], - port_data[ext_sg.SECURITYGROUPS], + nsx_sec_profile_ids, port_data.get(qos.QUEUE), port_data.get(mac_ext.MAC_LEARNING), port_data.get(addr_pair.ADDRESS_PAIRS)) @@ -458,7 +466,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, try: selected_lswitch = self._nvp_find_lswitch_for_port(context, port_data) - lport = self._nvp_create_port_helper(self.cluster, + lport = self._nvp_create_port_helper(context.session, selected_lswitch['uuid'], port_data, True) @@ -565,7 +573,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, context, port_data) # Do not apply port security here! ls_port = self._nvp_create_port_helper( - self.cluster, selected_lswitch['uuid'], + context.session, selected_lswitch['uuid'], port_data, False) # Assuming subnet being attached is on first fixed ip # element in port data @@ -708,7 +716,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, selected_lswitch = self._nvp_find_lswitch_for_port( context, port_data) lport = self._nvp_create_port_helper( - self.cluster, + context.session, selected_lswitch['uuid'], port_data, True) @@ -2107,12 +2115,19 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, tenant_id = self._get_tenant_id_for_create(context, s) if not default_sg: self._ensure_default_security_group(context, tenant_id) - - nsx_secgroup = secgrouplib.create_security_profile(self.cluster, - tenant_id, s) - security_group['security_group']['id'] = nsx_secgroup['uuid'] - return super(NvpPluginV2, self).create_security_group( - context, security_group, default_sg) + # NOTE(salv-orlando): Pre-generating Neutron ID for security group. + neutron_id = str(uuid.uuid4()) + nvp_secgroup = secgrouplib.create_security_profile( + self.cluster, neutron_id, tenant_id, s) + with context.session.begin(subtransactions=True): + s['id'] = neutron_id + sec_group = super(NvpPluginV2, self).create_security_group( + context, security_group, default_sg) + context.session.flush() + # Add mapping between neutron and nsx identifiers + nsx_db.add_neutron_nsx_security_group_mapping( + context.session, neutron_id, nvp_secgroup['uuid']) + return sec_group def delete_security_group(self, context, security_group_id): """Delete a security group. @@ -2132,12 +2147,32 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, if super(NvpPluginV2, self)._get_port_security_group_bindings( context, filters): raise ext_sg.SecurityGroupInUse(id=security_group['id']) + nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id( + context.session, self.cluster, security_group_id) + try: secgrouplib.delete_security_profile( - self.cluster, security_group['id']) + self.cluster, nsx_sec_profile_id) except q_exc.NotFound: - LOG.info(_("Security group: %s was already deleted " - "from backend"), security_group_id) + # The security profile was not found on the backend + # do not fail in this case. + LOG.warning(_("The NSX security profile %(sec_profile_id)s, " + "associated with the Neutron security group " + "%(sec_group_id)s was not found on the backend"), + {'sec_profile_id': nsx_sec_profile_id, + 'sec_group_id': security_group_id}) + except api_exc.NsxApiException: + # Raise and fail the operation, as there is a problem which + # prevented the sec group from being removed from the backend + LOG.exception(_("An exception occurred while removing the " + "NSX security profile %(sec_profile_id)s, " + "associated with Netron security group " + "%(sec_group_id)s"), + {'sec_profile_id': nsx_sec_profile_id, + 'sec_group_id': security_group_id}) + raise nvp_exc.NvpPluginException( + _("Unable to remove security group %s from backend"), + security_group['id']) return super(NvpPluginV2, self).delete_security_group( context, security_group_id) @@ -2175,7 +2210,6 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._ensure_default_security_group(context, tenant_id) security_group_id = self._validate_security_group_rules( context, security_group_rule) - # Check to make sure security group exists security_group = super(NvpPluginV2, self).get_security_group( context, security_group_id) @@ -2185,11 +2219,15 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Check for duplicate rules self._check_for_duplicate_rules(context, s) # gather all the existing security group rules since we need all - # of them to PUT to NVP. - combined_rules = self._merge_security_group_rules_with_current( - context, s, security_group['id']) + # of them to PUT to NSX. + existing_rules = self.get_security_group_rules( + context, {'security_group_id': [security_group['id']]}) + combined_rules = sg_utils.merge_security_group_rules_with_current( + context.session, self.cluster, s, existing_rules) + nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id( + context.session, self.cluster, security_group_id) secgrouplib.update_security_group_rules(self.cluster, - security_group['id'], + nsx_sec_profile_id, combined_rules) return super( NvpPluginV2, self).create_security_group_rule_bulk_native( @@ -2208,13 +2246,17 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, raise ext_sg.SecurityGroupRuleNotFound(id=sgrid) sgid = security_group_rule['security_group_id'] - current_rules = self._get_security_group_rules_nvp_format( - context, sgid, True) - - self._remove_security_group_with_id_and_id_field( - current_rules, sgrid) + current_rules = self.get_security_group_rules( + context, {'security_group_id': [sgid]}) + current_rules_nsx = sg_utils.get_security_group_rules_nsx_format( + context.session, self.cluster, current_rules, True) + + sg_utils.remove_security_group_with_id_and_id_field( + current_rules_nsx, sgrid) + nsx_sec_profile_id = nsx_utils.get_nsx_security_group_id( + context.session, self.cluster, sgid) secgrouplib.update_security_group_rules( - self.cluster, sgid, current_rules) + self.cluster, nsx_sec_profile_id, current_rules_nsx) return super(NvpPluginV2, self).delete_security_group_rule(context, sgrid) diff --git a/neutron/plugins/nicira/common/nsx_utils.py b/neutron/plugins/nicira/common/nsx_utils.py index 054bd3a4b..8dcfec546 100644 --- a/neutron/plugins/nicira/common/nsx_utils.py +++ b/neutron/plugins/nicira/common/nsx_utils.py @@ -20,9 +20,9 @@ from neutron.plugins.nicira.api_client import client from neutron.plugins.nicira.dbexts import db as nsx_db from neutron.plugins.nicira import nsx_cluster from neutron.plugins.nicira.nsxlib import router as routerlib +from neutron.plugins.nicira.nsxlib import secgroup as secgrouplib from neutron.plugins.nicira.nsxlib import switch as switchlib - LOG = log.getLogger(__name__) @@ -125,23 +125,39 @@ def get_nsx_switch_and_port_id(session, cluster, neutron_port_id): return nsx_switch_id, nsx_port_id -def create_nsx_cluster(cluster_opts, concurrent_connections, gen_timeout): - cluster = nsx_cluster.NSXCluster(**cluster_opts) - - def _ctrl_split(x, y): - return (x, int(y), True) +def get_nsx_security_group_id(session, cluster, neutron_id): + """Return the NSX sec profile uuid for a given neutron sec group. - api_providers = [_ctrl_split(*ctrl.split(':')) - for ctrl in cluster.nsx_controllers] - cluster.api_client = client.NsxApiClient( - api_providers, cluster.nsx_user, cluster.nsx_password, - concurrent_connections=concurrent_connections, - gen_timeout=gen_timeout, - request_timeout=cluster.req_timeout, - http_timeout=cluster.http_timeout, - retries=cluster.retries, - redirects=cluster.redirects) - return cluster + First, look up the Neutron database. If not found, execute + a query on NSX platform as the mapping might be missing. + NOTE: Security groups are called 'security profiles' on the NSX backend. + """ + nsx_id = nsx_db.get_nsx_security_group_id(session, neutron_id) + if not nsx_id: + # Find security profile on backend. + # This is a rather expensive query, but it won't be executed + # more than once for each security group in Neutron's lifetime + nsx_sec_profiles = secgrouplib.query_security_profiles( + cluster, '*', + filters={'tag': neutron_id, + 'tag_scope': 'q_sec_group_id'}) + # Only one result expected + # NOTE(salv-orlando): Not handling the case where more than one + # security profile is found with the same neutron port tag + if not nsx_sec_profiles: + LOG.warn(_("Unable to find NSX security profile for Neutron " + "security group %s"), neutron_id) + return + elif len(nsx_sec_profiles) > 1: + LOG.warn(_("Multiple NSX security profiles found for Neutron " + "security group %s"), neutron_id) + nsx_sec_profile = nsx_sec_profiles[0] + nsx_id = nsx_sec_profile['uuid'] + with session.begin(subtransactions=True): + # Create DB mapping + nsx_db.add_neutron_nsx_security_group_mapping( + session, neutron_id, nsx_id) + return nsx_id def get_nsx_router_id(session, cluster, neutron_router_id): @@ -176,3 +192,22 @@ def get_nsx_router_id(session, cluster, neutron_router_id): neutron_router_id, nsx_router_id) return nsx_router_id + + +def create_nsx_cluster(cluster_opts, concurrent_connections, gen_timeout): + cluster = nsx_cluster.NSXCluster(**cluster_opts) + + def _ctrl_split(x, y): + return (x, int(y), True) + + api_providers = [_ctrl_split(*ctrl.split(':')) + for ctrl in cluster.nsx_controllers] + cluster.api_client = client.NsxApiClient( + api_providers, cluster.nsx_user, cluster.nsx_password, + request_timeout=cluster.req_timeout, + http_timeout=cluster.http_timeout, + retries=cluster.retries, + redirects=cluster.redirects, + concurrent_connections=concurrent_connections, + gen_timeout=gen_timeout) + return cluster diff --git a/neutron/plugins/nicira/common/securitygroups.py b/neutron/plugins/nicira/common/securitygroups.py index 3ca99a9d8..8efc476e7 100644 --- a/neutron/plugins/nicira/common/securitygroups.py +++ b/neutron/plugins/nicira/common/securitygroups.py @@ -17,112 +17,122 @@ # # @author: Aaron Rosen, Nicira Networks, Inc. -from neutron.extensions import securitygroup as ext_sg +from neutron.openstack.common import log +from neutron.plugins.nicira.common import nsx_utils +LOG = log.getLogger(__name__) # Protocol number look up for supported protocols protocol_num_look_up = {'tcp': 6, 'icmp': 1, 'udp': 17} -class NVPSecurityGroups(object): +def _convert_to_nsx_rule(session, cluster, rule, with_id=False): + """Converts a Neutron security group rule to the NSX format. - def _convert_to_nvp_rule(self, rule, with_id=False): - """Converts Neutron API security group rule to NVP API.""" - nvp_rule = {} - params = ['remote_ip_prefix', 'protocol', - 'remote_group_id', 'port_range_min', - 'port_range_max', 'ethertype'] + This routine also replaces Neutron IDs with NSX UUIDs. + """ + nsx_rule = {} + params = ['remote_ip_prefix', 'protocol', + 'remote_group_id', 'port_range_min', + 'port_range_max', 'ethertype'] + if with_id: + params.append('id') + + for param in params: + value = rule.get(param) + if param not in rule: + nsx_rule[param] = value + elif not value: + pass + elif param == 'remote_ip_prefix': + nsx_rule['ip_prefix'] = rule['remote_ip_prefix'] + elif param == 'remote_group_id': + nsx_rule['profile_uuid'] = nsx_utils.get_nsx_security_group_id( + session, cluster, rule['remote_group_id']) + + elif param == 'protocol': + try: + nsx_rule['protocol'] = int(rule['protocol']) + except (ValueError, TypeError): + nsx_rule['protocol'] = ( + protocol_num_look_up[rule['protocol']]) + else: + nsx_rule[param] = value + return nsx_rule + + +def _convert_to_nsx_rules(session, cluster, rules, with_id=False): + """Converts a list of Neutron security group rules to the NSX format.""" + nsx_rules = {'logical_port_ingress_rules': [], + 'logical_port_egress_rules': []} + for direction in ['logical_port_ingress_rules', + 'logical_port_egress_rules']: + for rule in rules[direction]: + nsx_rules[direction].append( + _convert_to_nsx_rule(session, cluster, rule, with_id)) + return nsx_rules + + +def get_security_group_rules_nsx_format(session, cluster, + security_group_rules, with_id=False): + """Convert neutron security group rules into NSX format. + + This routine splits Neutron security group rules into two lists, one + for ingress rules and the other for egress rules. + """ + + def fields(rule): + _fields = ['remote_ip_prefix', 'remote_group_id', 'protocol', + 'port_range_min', 'port_range_max', 'protocol', 'ethertype'] if with_id: - params.append('id') - - for param in params: - value = rule.get(param) - if param not in rule: - nvp_rule[param] = value - elif not value: - pass - elif param == 'remote_ip_prefix': - nvp_rule['ip_prefix'] = rule['remote_ip_prefix'] - elif param == 'remote_group_id': - nvp_rule['profile_uuid'] = rule['remote_group_id'] - elif param == 'protocol': - try: - nvp_rule['protocol'] = int(rule['protocol']) - except (ValueError, TypeError): - nvp_rule['protocol'] = ( - protocol_num_look_up[rule['protocol']]) + _fields.append('id') + return dict((k, v) for k, v in rule.iteritems() if k in _fields) + + ingress_rules = [] + egress_rules = [] + for rule in security_group_rules: + if rule.get('souce_group_id'): + rule['remote_group_id'] = nsx_utils.get_nsx_security_group_id( + session, cluster, rule['remote_group_id']) + + if rule['direction'] == 'ingress': + ingress_rules.append(fields(rule)) + elif rule['direction'] == 'egress': + egress_rules.append(fields(rule)) + rules = {'logical_port_ingress_rules': egress_rules, + 'logical_port_egress_rules': ingress_rules} + return _convert_to_nsx_rules(session, cluster, rules, with_id) + + +def merge_security_group_rules_with_current(session, cluster, + new_rules, current_rules): + merged_rules = get_security_group_rules_nsx_format( + session, cluster, current_rules) + for new_rule in new_rules: + rule = new_rule['security_group_rule'] + if rule['direction'] == 'ingress': + merged_rules['logical_port_egress_rules'].append( + _convert_to_nsx_rule(session, cluster, rule)) + elif rule['direction'] == 'egress': + merged_rules['logical_port_ingress_rules'].append( + _convert_to_nsx_rule(session, cluster, rule)) + return merged_rules + + +def remove_security_group_with_id_and_id_field(rules, rule_id): + """Remove rule by rule_id. + + This function receives all of the current rule associated with a + security group and then removes the rule that matches the rule_id. In + addition it removes the id field in the dict with each rule since that + should not be passed to nvp. + """ + for rule_direction in rules.values(): + item_to_remove = None + for port_rule in rule_direction: + if port_rule['id'] == rule_id: + item_to_remove = port_rule else: - nvp_rule[param] = value - return nvp_rule - - def _convert_to_nvp_rules(self, rules, with_id=False): - """Converts a list of Neutron API security group rules to NVP API.""" - nvp_rules = {'logical_port_ingress_rules': [], - 'logical_port_egress_rules': []} - for direction in ['logical_port_ingress_rules', - 'logical_port_egress_rules']: - for rule in rules[direction]: - nvp_rules[direction].append( - self._convert_to_nvp_rule(rule, with_id)) - return nvp_rules - - def _get_security_group_rules_nvp_format(self, context, security_group_id, - with_id=False): - """Query neutron db for security group rules.""" - fields = ['remote_ip_prefix', 'remote_group_id', 'protocol', - 'port_range_min', 'port_range_max', 'protocol', 'ethertype'] - if with_id: - fields.append('id') - - filters = {'security_group_id': [security_group_id], - 'direction': ['ingress']} - ingress_rules = self.get_security_group_rules(context, filters, fields) - filters = {'security_group_id': [security_group_id], - 'direction': ['egress']} - egress_rules = self.get_security_group_rules(context, filters, fields) - rules = {'logical_port_ingress_rules': egress_rules, - 'logical_port_egress_rules': ingress_rules} - return self._convert_to_nvp_rules(rules, with_id) - - def _get_profile_uuid(self, context, remote_group_id): - """Return profile id from novas group id.""" - security_group = self.get_security_group(context, remote_group_id) - if not security_group: - raise ext_sg.SecurityGroupNotFound(id=remote_group_id) - return security_group['id'] - - def _merge_security_group_rules_with_current(self, context, new_rules, - security_group_id): - merged_rules = self._get_security_group_rules_nvp_format( - context, security_group_id) - for new_rule in new_rules: - rule = new_rule['security_group_rule'] - rule['security_group_id'] = security_group_id - if rule.get('souce_group_id'): - rule['remote_group_id'] = self._get_profile_uuid( - context, rule['remote_group_id']) - if rule['direction'] == 'ingress': - merged_rules['logical_port_egress_rules'].append( - self._convert_to_nvp_rule(rule)) - elif rule['direction'] == 'egress': - merged_rules['logical_port_ingress_rules'].append( - self._convert_to_nvp_rule(rule)) - return merged_rules - - def _remove_security_group_with_id_and_id_field(self, rules, rule_id): - """Remove rule by rule_id. - - This function receives all of the current rule associated with a - security group and then removes the rule that matches the rule_id. In - addition it removes the id field in the dict with each rule since that - should not be passed to nvp. - """ - for rule_direction in rules.values(): - item_to_remove = None - for port_rule in rule_direction: - if port_rule['id'] == rule_id: - item_to_remove = port_rule - else: - # remove key from dictionary for NVP - del port_rule['id'] - if item_to_remove: - rule_direction.remove(item_to_remove) + # remove key from dictionary for NVP + del port_rule['id'] + if item_to_remove: + rule_direction.remove(item_to_remove) diff --git a/neutron/plugins/nicira/dbexts/db.py b/neutron/plugins/nicira/dbexts/db.py index b02b0bb65..2af272484 100644 --- a/neutron/plugins/nicira/dbexts/db.py +++ b/neutron/plugins/nicira/dbexts/db.py @@ -89,6 +89,20 @@ def add_neutron_nsx_router_mapping(session, neutron_id, nsx_router_id): return mapping +def add_neutron_nsx_security_group_mapping(session, neutron_id, nsx_id): + """Map a Neutron security group to a NSX security profile. + + :param session: a valid database session object + :param neutron_id: a neutron security group identifier + :param nsx_id: a nsx security profile identifier + """ + with session.begin(subtransactions=True): + mapping = models.NeutronNsxSecurityGroupMapping( + neutron_id=neutron_id, nsx_id=nsx_id) + session.add(mapping) + return mapping + + def get_nsx_switch_ids(session, neutron_id): # This function returns a list of NSX switch identifiers because of # the possibility of chained logical switches @@ -119,6 +133,22 @@ def get_nsx_router_id(session, neutron_id): "stored in Neutron DB"), neutron_id) +def get_nsx_security_group_id(session, neutron_id): + """Return the id of a security group in the NSX backend. + + Note: security groups are called 'security profiles' in NSX + """ + try: + mapping = (session.query(models.NeutronNsxSecurityGroupMapping). + filter_by(neutron_id=neutron_id). + one()) + return mapping['nsx_id'] + except exc.NoResultFound: + LOG.debug(_("NSX identifiers for neutron security group %s not yet " + "stored in Neutron DB"), neutron_id) + return None + + def _delete_by_neutron_id(session, model, neutron_id): return session.query(model).filter_by(neutron_id=neutron_id).delete() diff --git a/neutron/plugins/nicira/dbexts/models.py b/neutron/plugins/nicira/dbexts/models.py index b9ec6823b..4c294de68 100644 --- a/neutron/plugins/nicira/dbexts/models.py +++ b/neutron/plugins/nicira/dbexts/models.py @@ -69,6 +69,20 @@ class NeutronNsxNetworkMapping(model_base.BASEV2): nsx_id = Column(String(36), primary_key=True) +class NeutronNsxSecurityGroupMapping(model_base.BASEV2): + """Backend mappings for Neutron Security Group identifiers. + + This class maps a neutron security group identifier to the corresponding + NSX security profile identifier. + """ + + __tablename__ = 'neutron_nsx_security_group_mappings' + neutron_id = Column(String(36), + ForeignKey('securitygroups.id', ondelete="CASCADE"), + primary_key=True) + nsx_id = Column(String(36), primary_key=True) + + class NeutronNsxPortMapping(model_base.BASEV2): """Represents the mapping between neutron and nvp port uuids.""" diff --git a/neutron/plugins/nicira/nsxlib/secgroup.py b/neutron/plugins/nicira/nsxlib/secgroup.py index 0657b64a4..15bb8a8b6 100644 --- a/neutron/plugins/nicira/nsxlib/secgroup.py +++ b/neutron/plugins/nicira/nsxlib/secgroup.py @@ -19,14 +19,18 @@ from neutron.common import constants from neutron.common import exceptions from neutron.openstack.common import log from neutron.plugins.nicira.common import utils +from neutron.plugins.nicira.nvplib import _build_uri_path from neutron.plugins.nicira.nvplib import do_request from neutron.plugins.nicira.nvplib import format_exception +from neutron.plugins.nicira.nvplib import get_all_query_pages HTTP_GET = "GET" HTTP_POST = "POST" HTTP_DELETE = "DELETE" HTTP_PUT = "PUT" +SECPROF_RESOURCE = "security-profile" + LOG = log.getLogger(__name__) @@ -39,7 +43,23 @@ def mk_body(**kwargs): return json.dumps(kwargs, ensure_ascii=False) -def create_security_profile(cluster, tenant_id, security_profile): +def query_security_profiles(cluster, fields=None, filters=None): + return get_all_query_pages( + _build_uri_path(SECPROF_RESOURCE, + fields=fields, + filters=filters), + cluster) + + +def create_security_profile(cluster, tenant_id, neutron_id, security_profile): + """Create a security profile on the NSX backend. + + :param cluster: a NSX cluster object reference + :param tenant_id: identifier of the Neutron tenant + :param neutron_id: neutron security group identifier + :param security_profile: dictionary with data for + configuring the NSX security profile. + """ path = "/ws.v1/security-profile" # Allow all dhcp responses and all ingress traffic hidden_rules = {'logical_port_egress_rules': @@ -52,8 +72,11 @@ def create_security_profile(cluster, tenant_id, security_profile): [{'ethertype': 'IPv4'}, {'ethertype': 'IPv6'}]} display_name = utils.check_and_truncate(security_profile.get('name')) + # NOTE(salv-orlando): neutron-id tags are prepended with 'q' for + # historical reasons body = mk_body( - tags=utils.get_tags(os_tid=tenant_id), display_name=display_name, + tags=utils.get_tags(os_tid=tenant_id, q_sec_group_id=neutron_id), + display_name=display_name, logical_port_ingress_rules=( hidden_rules['logical_port_ingress_rules']), logical_port_egress_rules=hidden_rules['logical_port_egress_rules'] diff --git a/neutron/tests/unit/vmware/nsxlib/test_secgroup.py b/neutron/tests/unit/vmware/nsxlib/test_secgroup.py index d45c60cea..0db7e032c 100644 --- a/neutron/tests/unit/vmware/nsxlib/test_secgroup.py +++ b/neutron/tests/unit/vmware/nsxlib/test_secgroup.py @@ -17,14 +17,17 @@ from neutron.common import exceptions from neutron.plugins.nicira.nsxlib import secgroup as secgrouplib from neutron.plugins.nicira import nvplib as nsx_utils +from neutron.tests.unit import test_api_v2 from neutron.tests.unit.vmware.nsxlib import base +_uuid = test_api_v2._uuid + class SecurityProfileTestCase(base.NsxlibTestCase): def test_create_and_get_security_profile(self): sec_prof = secgrouplib.create_security_profile( - self.fake_cluster, 'pippo', {'name': 'test'}) + self.fake_cluster, _uuid(), 'pippo', {'name': 'test'}) sec_prof_res = secgrouplib.do_request( secgrouplib.HTTP_GET, nsx_utils._build_uri_path('security-profile', @@ -37,7 +40,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase): def test_create_and_get_default_security_profile(self): sec_prof = secgrouplib.create_security_profile( - self.fake_cluster, 'pippo', {'name': 'default'}) + self.fake_cluster, _uuid(), 'pippo', {'name': 'default'}) sec_prof_res = nsx_utils.do_request( secgrouplib.HTTP_GET, nsx_utils._build_uri_path('security-profile', @@ -50,7 +53,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase): def test_update_security_profile_rules(self): sec_prof = secgrouplib.create_security_profile( - self.fake_cluster, 'pippo', {'name': 'test'}) + self.fake_cluster, _uuid(), 'pippo', {'name': 'test'}) ingress_rule = {'ethertype': 'IPv4'} egress_rule = {'ethertype': 'IPv4', 'profile_uuid': 'xyz'} new_rules = {'logical_port_egress_rules': [egress_rule], @@ -73,7 +76,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase): def test_update_security_profile_rules_noingress(self): sec_prof = secgrouplib.create_security_profile( - self.fake_cluster, 'pippo', {'name': 'test'}) + self.fake_cluster, _uuid(), 'pippo', {'name': 'test'}) hidden_ingress_rule = {'ethertype': 'IPv4', 'ip_prefix': '127.0.0.1/32'} egress_rule = {'ethertype': 'IPv4', 'profile_uuid': 'xyz'} @@ -104,7 +107,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase): def test_delete_security_profile(self): sec_prof = secgrouplib.create_security_profile( - self.fake_cluster, 'pippo', {'name': 'test'}) + self.fake_cluster, _uuid(), 'pippo', {'name': 'test'}) secgrouplib.delete_security_profile( self.fake_cluster, sec_prof['uuid']) self.assertRaises(exceptions.NotFound, diff --git a/neutron/tests/unit/vmware/test_nsx_utils.py b/neutron/tests/unit/vmware/test_nsx_utils.py index 9c1607d69..79f1e031e 100644 --- a/neutron/tests/unit/vmware/test_nsx_utils.py +++ b/neutron/tests/unit/vmware/test_nsx_utils.py @@ -272,6 +272,47 @@ class NsxUtilsTestCase(base.BaseTestCase): par_id, child_res, res_id, 'doh')) self.assertEqual(expected, result) + def _mock_sec_group_mapping_db_calls(self, ret_value): + mock.patch(nsx_method('get_nsx_security_group_id', + module_name='dbexts.db'), + return_value=ret_value).start() + mock.patch(nsx_method('add_neutron_nsx_security_group_mapping', + module_name='dbexts.db')).start() + self.addCleanup(mock.patch.stopall) + + def _verify_get_nsx_sec_profile_id(self, exp_sec_prof_uuid): + # The nvplib and db calls are mocked, therefore the cluster + # and the neutron_id parameters can be set to None + sec_prof_uuid = nsx_utils.get_nsx_security_group_id( + db_api.get_session(), None, None) + self.assertEqual(exp_sec_prof_uuid, sec_prof_uuid) + + def test_get_nsx_sec_profile_id_from_db_mappings(self): + # This test is representative of the 'standard' case in which the + # security group mapping was stored in the neutron db + exp_sec_prof_uuid = uuidutils.generate_uuid() + self._mock_sec_group_mapping_db_calls(exp_sec_prof_uuid) + self._verify_get_nsx_sec_profile_id(exp_sec_prof_uuid) + + def test_get_nsx_sec_profile_id_no_db_mapping(self): + # This test is representative of the case where db mappings where not + # found for a given security profile identifier + exp_sec_prof_uuid = uuidutils.generate_uuid() + self._mock_sec_group_mapping_db_calls(None) + with mock.patch(nsx_method('query_security_profiles', + module_name='nsxlib.secgroup'), + return_value=[{'uuid': exp_sec_prof_uuid}]): + self._verify_get_nsx_sec_profile_id(exp_sec_prof_uuid) + + def test_get_nsx_sec_profile_id_no_mapping_returns_None(self): + # This test verifies that the function returns None if the mapping + # are not found both in the db and in the backend + self._mock_sec_group_mapping_db_calls(None) + with mock.patch(nsx_method('query_security_profiles', + module_name='nsxlib.secgroup'), + return_value=[]): + self._verify_get_nsx_sec_profile_id(None) + class ClusterManagementTestCase(nsx_base.NsxlibTestCase): -- 2.45.2