]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
DB Mappings for NSX security groups
authorSalvatore Orlando <salv.orlando@gmail.com>
Fri, 27 Dec 2013 23:36:22 +0000 (15:36 -0800)
committerThomas Goirand <thomas@goirand.fr>
Thu, 13 Mar 2014 07:20:34 +0000 (15:20 +0800)
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

neutron/db/migration/alembic_migrations/versions/1b2580001654_nsx_sec_group_mappin.py [new file with mode: 0644]
neutron/plugins/nicira/NeutronPlugin.py
neutron/plugins/nicira/common/nsx_utils.py
neutron/plugins/nicira/common/securitygroups.py
neutron/plugins/nicira/dbexts/db.py
neutron/plugins/nicira/dbexts/models.py
neutron/plugins/nicira/nsxlib/secgroup.py
neutron/tests/unit/vmware/nsxlib/test_secgroup.py
neutron/tests/unit/vmware/test_nsx_utils.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 (file)
index 0000000..76e072c
--- /dev/null
@@ -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')
index fade1bb21223d3efa50999e7ec5f90d9a124fdd6..fd5a5753f91a5fef0935f71f29bad4f9d9f93fe0 100644 (file)
@@ -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)
 
index 054bd3a4b538bdd9ea641789c820db78b15b3225..8dcfec5462d51705d1dd083805622c0bf85cef1b 100644 (file)
@@ -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
index 3ca99a9d8249dd15076276057dfef5ddd632e1cc..8efc476e7db0202fbe0c9a2b5cbbb13fbbd2d18b 100644 (file)
 #
 # @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)
index b02b0bb65505a59a974b8874a7f2ef4c6475ecf4..2af27248499b038e6b40db81739b0a59d75aa656 100644 (file)
@@ -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()
 
index b9ec6823b291d0be519880c8bca95b4460901265..4c294de68be274b52b8d73f8355bacf3ce8251a3 100644 (file)
@@ -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."""
 
index 0657b64a42ea71d7fc99d2c7e6f87b8620d90d9e..15bb8a8b637a1a3cd746ced31e86f16e4db4582e 100644 (file)
@@ -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']
index d45c60cea91cd68dd3e0e46f3f04135fb120abce..0db7e032c25d3e7433332e7eb11753b7e1489097 100644 (file)
 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,
index 9c1607d694422a774c90498f959d2e3ea67c6790..79f1e031ed0f453467d8d7bf4e0af3394d09c47c 100644 (file)
@@ -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):