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
This commit is contained in:
Salvatore Orlando 2013-12-27 15:36:22 -08:00
parent e33dad2461
commit 6e2f3ecf1e
9 changed files with 411 additions and 152 deletions

View File

@ -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')

View 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 config # noqa
from neutron.plugins.nicira.common import exceptions as nvp_exc from neutron.plugins.nicira.common import exceptions as nvp_exc
from neutron.plugins.nicira.common import nsx_utils 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.common import sync
from neutron.plugins.nicira.dbexts import db as nsx_db from neutron.plugins.nicira.dbexts import db as nsx_db
from neutron.plugins.nicira.dbexts import distributedrouter as dist_rtr 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, l3_gwmode_db.L3_NAT_db_mixin,
mac_db.MacLearningDbMixin, mac_db.MacLearningDbMixin,
networkgw_db.NetworkGatewayMixin, networkgw_db.NetworkGatewayMixin,
nvp_sec.NVPSecurityGroups,
portbindings_db.PortBindingMixin, portbindings_db.PortBindingMixin,
portsecurity_db.PortSecurityDbMixin, portsecurity_db.PortSecurityDbMixin,
qos_db.NVPQoSDbMixin, qos_db.NVPQoSDbMixin,
@ -410,16 +409,25 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
LOG.exception(err_desc) LOG.exception(err_desc)
raise nvp_exc.NvpPluginException(err_msg=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): do_port_security=True):
return switchlib.create_lport(cluster, ls_uuid, port_data['tenant_id'], # Convert Neutron security groups identifiers into NSX security
port_data['id'], port_data['name'], # 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['device_id'],
port_data['admin_state_up'], port_data['admin_state_up'],
port_data['mac_address'], port_data['mac_address'],
port_data['fixed_ips'], port_data['fixed_ips'],
port_data[psec.PORTSECURITY], port_data[psec.PORTSECURITY],
port_data[ext_sg.SECURITYGROUPS], nsx_sec_profile_ids,
port_data.get(qos.QUEUE), port_data.get(qos.QUEUE),
port_data.get(mac_ext.MAC_LEARNING), port_data.get(mac_ext.MAC_LEARNING),
port_data.get(addr_pair.ADDRESS_PAIRS)) port_data.get(addr_pair.ADDRESS_PAIRS))
@ -458,7 +466,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
try: try:
selected_lswitch = self._nvp_find_lswitch_for_port(context, selected_lswitch = self._nvp_find_lswitch_for_port(context,
port_data) port_data)
lport = self._nvp_create_port_helper(self.cluster, lport = self._nvp_create_port_helper(context.session,
selected_lswitch['uuid'], selected_lswitch['uuid'],
port_data, port_data,
True) True)
@ -565,7 +573,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
context, port_data) context, port_data)
# Do not apply port security here! # Do not apply port security here!
ls_port = self._nvp_create_port_helper( ls_port = self._nvp_create_port_helper(
self.cluster, selected_lswitch['uuid'], context.session, selected_lswitch['uuid'],
port_data, False) port_data, False)
# Assuming subnet being attached is on first fixed ip # Assuming subnet being attached is on first fixed ip
# element in port data # element in port data
@ -708,7 +716,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
selected_lswitch = self._nvp_find_lswitch_for_port( selected_lswitch = self._nvp_find_lswitch_for_port(
context, port_data) context, port_data)
lport = self._nvp_create_port_helper( lport = self._nvp_create_port_helper(
self.cluster, context.session,
selected_lswitch['uuid'], selected_lswitch['uuid'],
port_data, port_data,
True) True)
@ -2107,12 +2115,19 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
tenant_id = self._get_tenant_id_for_create(context, s) tenant_id = self._get_tenant_id_for_create(context, s)
if not default_sg: if not default_sg:
self._ensure_default_security_group(context, tenant_id) self._ensure_default_security_group(context, tenant_id)
# NOTE(salv-orlando): Pre-generating Neutron ID for security group.
nsx_secgroup = secgrouplib.create_security_profile(self.cluster, neutron_id = str(uuid.uuid4())
tenant_id, s) nvp_secgroup = secgrouplib.create_security_profile(
security_group['security_group']['id'] = nsx_secgroup['uuid'] self.cluster, neutron_id, tenant_id, s)
return super(NvpPluginV2, self).create_security_group( with context.session.begin(subtransactions=True):
context, security_group, default_sg) 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): def delete_security_group(self, context, security_group_id):
"""Delete a security group. """Delete a security group.
@ -2132,12 +2147,32 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
if super(NvpPluginV2, self)._get_port_security_group_bindings( if super(NvpPluginV2, self)._get_port_security_group_bindings(
context, filters): context, filters):
raise ext_sg.SecurityGroupInUse(id=security_group['id']) 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: try:
secgrouplib.delete_security_profile( secgrouplib.delete_security_profile(
self.cluster, security_group['id']) self.cluster, nsx_sec_profile_id)
except q_exc.NotFound: except q_exc.NotFound:
LOG.info(_("Security group: %s was already deleted " # The security profile was not found on the backend
"from backend"), security_group_id) # 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( return super(NvpPluginV2, self).delete_security_group(
context, security_group_id) context, security_group_id)
@ -2175,7 +2210,6 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._ensure_default_security_group(context, tenant_id) self._ensure_default_security_group(context, tenant_id)
security_group_id = self._validate_security_group_rules( security_group_id = self._validate_security_group_rules(
context, security_group_rule) context, security_group_rule)
# Check to make sure security group exists # Check to make sure security group exists
security_group = super(NvpPluginV2, self).get_security_group( security_group = super(NvpPluginV2, self).get_security_group(
context, security_group_id) context, security_group_id)
@ -2185,11 +2219,15 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# Check for duplicate rules # Check for duplicate rules
self._check_for_duplicate_rules(context, s) self._check_for_duplicate_rules(context, s)
# gather all the existing security group rules since we need all # gather all the existing security group rules since we need all
# of them to PUT to NVP. # of them to PUT to NSX.
combined_rules = self._merge_security_group_rules_with_current( existing_rules = self.get_security_group_rules(
context, s, security_group['id']) 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, secgrouplib.update_security_group_rules(self.cluster,
security_group['id'], nsx_sec_profile_id,
combined_rules) combined_rules)
return super( return super(
NvpPluginV2, self).create_security_group_rule_bulk_native( NvpPluginV2, self).create_security_group_rule_bulk_native(
@ -2208,13 +2246,17 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
raise ext_sg.SecurityGroupRuleNotFound(id=sgrid) raise ext_sg.SecurityGroupRuleNotFound(id=sgrid)
sgid = security_group_rule['security_group_id'] sgid = security_group_rule['security_group_id']
current_rules = self._get_security_group_rules_nvp_format( current_rules = self.get_security_group_rules(
context, sgid, True) context, {'security_group_id': [sgid]})
current_rules_nsx = sg_utils.get_security_group_rules_nsx_format(
context.session, self.cluster, current_rules, True)
self._remove_security_group_with_id_and_id_field( sg_utils.remove_security_group_with_id_and_id_field(
current_rules, sgrid) 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( 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, return super(NvpPluginV2, self).delete_security_group_rule(context,
sgrid) sgrid)

View 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.dbexts import db as nsx_db
from neutron.plugins.nicira import nsx_cluster from neutron.plugins.nicira import nsx_cluster
from neutron.plugins.nicira.nsxlib import router as routerlib 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 from neutron.plugins.nicira.nsxlib import switch as switchlib
LOG = log.getLogger(__name__) 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 return nsx_switch_id, nsx_port_id
def create_nsx_cluster(cluster_opts, concurrent_connections, gen_timeout): def get_nsx_security_group_id(session, cluster, neutron_id):
cluster = nsx_cluster.NSXCluster(**cluster_opts) """Return the NSX sec profile uuid for a given neutron sec group.
def _ctrl_split(x, y): First, look up the Neutron database. If not found, execute
return (x, int(y), True) a query on NSX platform as the mapping might be missing.
NOTE: Security groups are called 'security profiles' on the NSX backend.
api_providers = [_ctrl_split(*ctrl.split(':')) """
for ctrl in cluster.nsx_controllers] nsx_id = nsx_db.get_nsx_security_group_id(session, neutron_id)
cluster.api_client = client.NsxApiClient( if not nsx_id:
api_providers, cluster.nsx_user, cluster.nsx_password, # Find security profile on backend.
concurrent_connections=concurrent_connections, # This is a rather expensive query, but it won't be executed
gen_timeout=gen_timeout, # more than once for each security group in Neutron's lifetime
request_timeout=cluster.req_timeout, nsx_sec_profiles = secgrouplib.query_security_profiles(
http_timeout=cluster.http_timeout, cluster, '*',
retries=cluster.retries, filters={'tag': neutron_id,
redirects=cluster.redirects) 'tag_scope': 'q_sec_group_id'})
return cluster # 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): 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, neutron_router_id,
nsx_router_id) nsx_router_id)
return 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

View File

@ -17,112 +17,122 @@
# #
# @author: Aaron Rosen, Nicira Networks, Inc. # @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 number look up for supported protocols
protocol_num_look_up = {'tcp': 6, 'icmp': 1, 'udp': 17} 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): This routine also replaces Neutron IDs with NSX UUIDs.
"""Converts Neutron API security group rule to NVP API.""" """
nvp_rule = {} nsx_rule = {}
params = ['remote_ip_prefix', 'protocol', params = ['remote_ip_prefix', 'protocol',
'remote_group_id', 'port_range_min', 'remote_group_id', 'port_range_min',
'port_range_max', 'ethertype'] '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: if with_id:
params.append('id') _fields.append('id')
return dict((k, v) for k, v in rule.iteritems() if k in _fields)
for param in params: ingress_rules = []
value = rule.get(param) egress_rules = []
if param not in rule: for rule in security_group_rules:
nvp_rule[param] = value if rule.get('souce_group_id'):
elif not value: rule['remote_group_id'] = nsx_utils.get_nsx_security_group_id(
pass session, cluster, rule['remote_group_id'])
elif param == 'remote_ip_prefix':
nvp_rule['ip_prefix'] = rule['remote_ip_prefix'] if rule['direction'] == 'ingress':
elif param == 'remote_group_id': ingress_rules.append(fields(rule))
nvp_rule['profile_uuid'] = rule['remote_group_id'] elif rule['direction'] == 'egress':
elif param == 'protocol': egress_rules.append(fields(rule))
try: rules = {'logical_port_ingress_rules': egress_rules,
nvp_rule['protocol'] = int(rule['protocol']) 'logical_port_egress_rules': ingress_rules}
except (ValueError, TypeError): return _convert_to_nsx_rules(session, cluster, rules, with_id)
nvp_rule['protocol'] = (
protocol_num_look_up[rule['protocol']])
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: else:
nvp_rule[param] = value # remove key from dictionary for NVP
return nvp_rule del port_rule['id']
if item_to_remove:
def _convert_to_nvp_rules(self, rules, with_id=False): rule_direction.remove(item_to_remove)
"""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)

View File

@ -89,6 +89,20 @@ def add_neutron_nsx_router_mapping(session, neutron_id, nsx_router_id):
return mapping 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): def get_nsx_switch_ids(session, neutron_id):
# This function returns a list of NSX switch identifiers because of # This function returns a list of NSX switch identifiers because of
# the possibility of chained logical switches # the possibility of chained logical switches
@ -119,6 +133,22 @@ def get_nsx_router_id(session, neutron_id):
"stored in Neutron DB"), 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): def _delete_by_neutron_id(session, model, neutron_id):
return session.query(model).filter_by(neutron_id=neutron_id).delete() return session.query(model).filter_by(neutron_id=neutron_id).delete()

View File

@ -69,6 +69,20 @@ class NeutronNsxNetworkMapping(model_base.BASEV2):
nsx_id = Column(String(36), primary_key=True) 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): class NeutronNsxPortMapping(model_base.BASEV2):
"""Represents the mapping between neutron and nvp port uuids.""" """Represents the mapping between neutron and nvp port uuids."""

View File

@ -19,14 +19,18 @@ from neutron.common import constants
from neutron.common import exceptions from neutron.common import exceptions
from neutron.openstack.common import log from neutron.openstack.common import log
from neutron.plugins.nicira.common import utils 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 do_request
from neutron.plugins.nicira.nvplib import format_exception from neutron.plugins.nicira.nvplib import format_exception
from neutron.plugins.nicira.nvplib import get_all_query_pages
HTTP_GET = "GET" HTTP_GET = "GET"
HTTP_POST = "POST" HTTP_POST = "POST"
HTTP_DELETE = "DELETE" HTTP_DELETE = "DELETE"
HTTP_PUT = "PUT" HTTP_PUT = "PUT"
SECPROF_RESOURCE = "security-profile"
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -39,7 +43,23 @@ def mk_body(**kwargs):
return json.dumps(kwargs, ensure_ascii=False) 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" path = "/ws.v1/security-profile"
# Allow all dhcp responses and all ingress traffic # Allow all dhcp responses and all ingress traffic
hidden_rules = {'logical_port_egress_rules': hidden_rules = {'logical_port_egress_rules':
@ -52,8 +72,11 @@ def create_security_profile(cluster, tenant_id, security_profile):
[{'ethertype': 'IPv4'}, [{'ethertype': 'IPv4'},
{'ethertype': 'IPv6'}]} {'ethertype': 'IPv6'}]}
display_name = utils.check_and_truncate(security_profile.get('name')) 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( 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=( logical_port_ingress_rules=(
hidden_rules['logical_port_ingress_rules']), hidden_rules['logical_port_ingress_rules']),
logical_port_egress_rules=hidden_rules['logical_port_egress_rules'] logical_port_egress_rules=hidden_rules['logical_port_egress_rules']

View File

@ -17,14 +17,17 @@
from neutron.common import exceptions from neutron.common import exceptions
from neutron.plugins.nicira.nsxlib import secgroup as secgrouplib from neutron.plugins.nicira.nsxlib import secgroup as secgrouplib
from neutron.plugins.nicira import nvplib as nsx_utils 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 from neutron.tests.unit.vmware.nsxlib import base
_uuid = test_api_v2._uuid
class SecurityProfileTestCase(base.NsxlibTestCase): class SecurityProfileTestCase(base.NsxlibTestCase):
def test_create_and_get_security_profile(self): def test_create_and_get_security_profile(self):
sec_prof = secgrouplib.create_security_profile( 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( sec_prof_res = secgrouplib.do_request(
secgrouplib.HTTP_GET, secgrouplib.HTTP_GET,
nsx_utils._build_uri_path('security-profile', nsx_utils._build_uri_path('security-profile',
@ -37,7 +40,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase):
def test_create_and_get_default_security_profile(self): def test_create_and_get_default_security_profile(self):
sec_prof = secgrouplib.create_security_profile( 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( sec_prof_res = nsx_utils.do_request(
secgrouplib.HTTP_GET, secgrouplib.HTTP_GET,
nsx_utils._build_uri_path('security-profile', nsx_utils._build_uri_path('security-profile',
@ -50,7 +53,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase):
def test_update_security_profile_rules(self): def test_update_security_profile_rules(self):
sec_prof = secgrouplib.create_security_profile( sec_prof = secgrouplib.create_security_profile(
self.fake_cluster, 'pippo', {'name': 'test'}) self.fake_cluster, _uuid(), 'pippo', {'name': 'test'})
ingress_rule = {'ethertype': 'IPv4'} ingress_rule = {'ethertype': 'IPv4'}
egress_rule = {'ethertype': 'IPv4', 'profile_uuid': 'xyz'} egress_rule = {'ethertype': 'IPv4', 'profile_uuid': 'xyz'}
new_rules = {'logical_port_egress_rules': [egress_rule], new_rules = {'logical_port_egress_rules': [egress_rule],
@ -73,7 +76,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase):
def test_update_security_profile_rules_noingress(self): def test_update_security_profile_rules_noingress(self):
sec_prof = secgrouplib.create_security_profile( sec_prof = secgrouplib.create_security_profile(
self.fake_cluster, 'pippo', {'name': 'test'}) self.fake_cluster, _uuid(), 'pippo', {'name': 'test'})
hidden_ingress_rule = {'ethertype': 'IPv4', hidden_ingress_rule = {'ethertype': 'IPv4',
'ip_prefix': '127.0.0.1/32'} 'ip_prefix': '127.0.0.1/32'}
egress_rule = {'ethertype': 'IPv4', 'profile_uuid': 'xyz'} egress_rule = {'ethertype': 'IPv4', 'profile_uuid': 'xyz'}
@ -104,7 +107,7 @@ class SecurityProfileTestCase(base.NsxlibTestCase):
def test_delete_security_profile(self): def test_delete_security_profile(self):
sec_prof = secgrouplib.create_security_profile( sec_prof = secgrouplib.create_security_profile(
self.fake_cluster, 'pippo', {'name': 'test'}) self.fake_cluster, _uuid(), 'pippo', {'name': 'test'})
secgrouplib.delete_security_profile( secgrouplib.delete_security_profile(
self.fake_cluster, sec_prof['uuid']) self.fake_cluster, sec_prof['uuid'])
self.assertRaises(exceptions.NotFound, self.assertRaises(exceptions.NotFound,

View File

@ -272,6 +272,47 @@ class NsxUtilsTestCase(base.BaseTestCase):
par_id, child_res, res_id, 'doh')) par_id, child_res, res_id, 'doh'))
self.assertEqual(expected, result) 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): class ClusterManagementTestCase(nsx_base.NsxlibTestCase):