Add NVP Security group support

Implements blueprint security-groups-nvp

Change-Id: Idfa7a756c7a2845e9aa9e7de4c7bceeec94b036f
This commit is contained in:
Aaron Rosen 2013-01-16 10:06:52 -08:00 committed by Salvatore Orlando
parent af33f1d051
commit c0e9c2791f
9 changed files with 595 additions and 39 deletions

View File

@ -34,6 +34,9 @@ INTERFACE_KEY = '_interfaces'
IPv4 = 'IPv4' IPv4 = 'IPv4'
IPv6 = 'IPv6' IPv6 = 'IPv6'
UDP_PROTOCOL = 17
DHCP_RESPONSE_PORT = 68
EXT_NS = '_extension_ns' EXT_NS = '_extension_ns'
XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0' XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance" XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"

View File

@ -30,7 +30,8 @@ down_revision = '48b6f43f7471'
# Change to ['*'] if this migration applies to all plugins # Change to ['*'] if this migration applies to all plugins
migration_for_plugins = [ migration_for_plugins = [
'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2' 'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2',
'quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2'
] ]
from alembic import op from alembic import op

View File

@ -25,7 +25,7 @@ import logging
import webob.exc import webob.exc
from quantum.api.v2 import attributes from quantum.api.v2 import attributes as attr
from quantum.api.v2 import base from quantum.api.v2 import base
from quantum.common import constants from quantum.common import constants
from quantum.common import exceptions as q_exc from quantum.common import exceptions as q_exc
@ -37,10 +37,14 @@ from quantum.db import dhcp_rpc_base
from quantum.db import portsecurity_db from quantum.db import portsecurity_db
# NOTE: quota_db cannot be removed, it is for db model # NOTE: quota_db cannot be removed, it is for db model
from quantum.db import quota_db from quantum.db import quota_db
from quantum.db import securitygroups_db
from quantum.extensions import portsecurity as psec from quantum.extensions import portsecurity as psec
from quantum.extensions import providernet as pnet from quantum.extensions import providernet as pnet
from quantum.extensions import securitygroup as ext_sg
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
from quantum.openstack.common import rpc from quantum.openstack.common import rpc
from quantum.plugins.nicira.nicira_nvp_plugin.common import (securitygroups
as nvp_sec)
from quantum import policy from quantum import policy
from quantum.plugins.nicira.nicira_nvp_plugin.common import config from quantum.plugins.nicira.nicira_nvp_plugin.common import config
from quantum.plugins.nicira.nicira_nvp_plugin.common import (exceptions from quantum.plugins.nicira.nicira_nvp_plugin.common import (exceptions
@ -108,13 +112,18 @@ class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
portsecurity_db.PortSecurityDbMixin): portsecurity_db.PortSecurityDbMixin,
securitygroups_db.SecurityGroupDbMixin,
nvp_sec.NVPSecurityGroups):
""" """
NvpPluginV2 is a Quantum plugin that provides L2 Virtual Network NvpPluginV2 is a Quantum plugin that provides L2 Virtual Network
functionality using NVP. functionality using NVP.
""" """
supported_extension_aliases = ["provider", "quotas", "port-security"] supported_extension_aliases = ["provider", "quotas", "port-security",
"security-group"]
__native_bulk_support = True
# Default controller cluster # Default controller cluster
default_cluster = None default_cluster = None
@ -236,9 +245,9 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
network_type = attrs.get(pnet.NETWORK_TYPE) network_type = attrs.get(pnet.NETWORK_TYPE)
physical_network = attrs.get(pnet.PHYSICAL_NETWORK) physical_network = attrs.get(pnet.PHYSICAL_NETWORK)
segmentation_id = attrs.get(pnet.SEGMENTATION_ID) segmentation_id = attrs.get(pnet.SEGMENTATION_ID)
network_type_set = attributes.is_attr_set(network_type) network_type_set = attr.is_attr_set(network_type)
physical_network_set = attributes.is_attr_set(physical_network) physical_network_set = attr.is_attr_set(physical_network)
segmentation_id_set = attributes.is_attr_set(segmentation_id) segmentation_id_set = attr.is_attr_set(segmentation_id)
if not (network_type_set or physical_network_set or if not (network_type_set or physical_network_set or
segmentation_id_set): segmentation_id_set):
return return
@ -345,18 +354,19 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
def create_network(self, context, network): def create_network(self, context, network):
net_data = network['network'].copy() net_data = network['network'].copy()
tenant_id = self._get_tenant_id_for_create(context, net_data)
self._ensure_default_security_group(context, tenant_id)
# Process the provider network extension # Process the provider network extension
self._handle_provider_create(context, net_data) self._handle_provider_create(context, net_data)
# Replace ATTR_NOT_SPECIFIED with None before sending to NVP # Replace ATTR_NOT_SPECIFIED with None before sending to NVP
for attr, value in network['network'].iteritems(): for key, value in network['network'].iteritems():
if value is attributes.ATTR_NOT_SPECIFIED: if value is attr.ATTR_NOT_SPECIFIED:
net_data[attr] = None net_data[key] = None
# FIXME(arosen) implement admin_state_up = False in NVP # FIXME(arosen) implement admin_state_up = False in NVP
if net_data['admin_state_up'] is False: if net_data['admin_state_up'] is False:
LOG.warning(_("Network with admin_state_up=False are not yet " LOG.warning(_("Network with admin_state_up=False are not yet "
"supported by this plugin. Ignoring setting for " "supported by this plugin. Ignoring setting for "
"network %s"), net_data.get('name', '<unknown>')) "network %s"), net_data.get('name', '<unknown>'))
tenant_id = self._get_tenant_id_for_create(context, net_data)
target_cluster = self._find_target_cluster(net_data) target_cluster = self._find_target_cluster(net_data)
nvp_binding_type = net_data.get(pnet.NETWORK_TYPE) nvp_binding_type = net_data.get(pnet.NETWORK_TYPE)
if nvp_binding_type in ('flat', 'vlan'): if nvp_binding_type in ('flat', 'vlan'):
@ -544,6 +554,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
context, filters) context, filters)
for quantum_lport in quantum_lports: for quantum_lport in quantum_lports:
self._extend_port_port_security_dict(context, quantum_lport) self._extend_port_port_security_dict(context, quantum_lport)
self._extend_port_dict_security_group(context, quantum_lport)
vm_filter = "" vm_filter = ""
tenant_filter = "" tenant_filter = ""
@ -638,7 +649,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
# ATTR_NOT_SPECIFIED is for the case where a port is created on a # ATTR_NOT_SPECIFIED is for the case where a port is created on a
# shared network that is not owned by the tenant. # shared network that is not owned by the tenant.
# TODO(arosen) fix policy engine to do this for us automatically. # TODO(arosen) fix policy engine to do this for us automatically.
if attributes.is_attr_set(port['port'].get(psec.PORTSECURITY)): if attr.is_attr_set(port['port'].get(psec.PORTSECURITY)):
self._enforce_set_auth(context, port, self._enforce_set_auth(context, port,
self.port_security_enabled_create) self.port_security_enabled_create)
port_data = port['port'] port_data = port['port']
@ -653,6 +664,15 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
context, port_data) context, port_data)
port_data[psec.PORTSECURITY] = port_security port_data[psec.PORTSECURITY] = port_security
self._process_port_security_create(context, port_data) self._process_port_security_create(context, port_data)
# security group extension checks
if port_security and has_ip:
self._ensure_default_security_group_on_port(context, port)
elif attr.is_attr_set(port_data.get(ext_sg.SECURITYGROUPS)):
raise psec.PortSecurityAndIPRequiredForSecurityGroups()
port_data[ext_sg.SECURITYGROUPS] = (
self._get_security_groups_on_port(context, port))
self._process_port_create_security_group(
context, quantum_db['id'], port_data[ext_sg.SECURITYGROUPS])
# provider networking extension checks # provider networking extension checks
# Fetch the network and network binding from Quantum db # Fetch the network and network binding from Quantum db
network = self._get_network(context, port_data['network_id']) network = self._get_network(context, port_data['network_id'])
@ -681,7 +701,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
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])
# Get NVP ls uuid for quantum network # Get NVP ls uuid for quantum network
nvplib.plug_interface(cluster, selected_lswitch['uuid'], nvplib.plug_interface(cluster, selected_lswitch['uuid'],
lport['uuid'], "VifAttachment", lport['uuid'], "VifAttachment",
@ -703,27 +724,55 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
"%(tenant_id)s: (%(id)s)"), port_data) "%(tenant_id)s: (%(id)s)"), port_data)
self._extend_port_port_security_dict(context, port_data) self._extend_port_port_security_dict(context, port_data)
self._extend_port_dict_security_group(context, port_data)
return port_data return port_data
def update_port(self, context, id, port): def update_port(self, context, id, port):
self._enforce_set_auth(context, port, self._enforce_set_auth(context, port,
self.port_security_enabled_update) self.port_security_enabled_update)
tenant_id = self._get_tenant_id_for_create(context, port) tenant_id = self._get_tenant_id_for_create(context, port)
delete_security_groups = self._check_update_deletes_security_groups(
port)
has_security_groups = self._check_update_has_security_groups(port)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
ret_port = super(NvpPluginV2, self).update_port( ret_port = super(NvpPluginV2, self).update_port(
context, id, port) context, id, port)
# copy values over # copy values over
ret_port.update(port['port']) ret_port.update(port['port'])
tenant_id = self._get_tenant_id_for_create(context, ret_port)
# Handle port security # populate port_security setting
if psec.PORTSECURITY in port['port']: if psec.PORTSECURITY not in port['port']:
self._update_port_security_binding(
context, id, ret_port[psec.PORTSECURITY])
# populate with value
else:
ret_port[psec.PORTSECURITY] = self._get_port_security_binding( ret_port[psec.PORTSECURITY] = self._get_port_security_binding(
context, id) context, id)
has_ip = self._ip_on_port(ret_port)
# checks if security groups were updated adding/modifying
# security groups, port security is set and port has ip
if not (has_ip and ret_port[psec.PORTSECURITY]):
if has_security_groups:
raise psec.PortSecurityAndIPRequiredForSecurityGroups()
# Update did not have security groups passed in. Check
# that port does not have any security groups already on it.
filters = {'port_id': [id]}
security_groups = (
super(NvpPluginV2, self)._get_port_security_group_bindings(
context, filters)
)
if security_groups and not delete_security_groups:
raise psec.PortSecurityPortHasSecurityGroup()
if (delete_security_groups or has_security_groups):
# delete the port binding and read it with the new rules.
self._delete_port_security_group_bindings(context, id)
sgids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, id, sgids)
if psec.PORTSECURITY in port['port']:
self._update_port_security_binding(
context, id, ret_port[psec.PORTSECURITY])
self._extend_port_port_security_dict(context, ret_port)
self._extend_port_dict_security_group(context, ret_port)
port_nvp, cluster = ( port_nvp, cluster = (
nvplib.get_port_by_quantum_tag(self.clusters.itervalues(), nvplib.get_port_by_quantum_tag(self.clusters.itervalues(),
ret_port["network_id"], id)) ret_port["network_id"], id))
@ -734,7 +783,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
ret_port['admin_state_up'], ret_port['admin_state_up'],
ret_port['mac_address'], ret_port['mac_address'],
ret_port['fixed_ips'], ret_port['fixed_ips'],
ret_port[psec.PORTSECURITY]) ret_port[psec.PORTSECURITY],
ret_port[ext_sg.SECURITYGROUPS])
# Update the port status from nvp. If we fail here hide it since # Update the port status from nvp. If we fail here hide it since
# the port was successfully updated but we were not able to retrieve # the port was successfully updated but we were not able to retrieve
@ -763,7 +813,10 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
return super(NvpPluginV2, self).delete_port(context, id) return super(NvpPluginV2, self).delete_port(context, id)
def get_port(self, context, id, fields=None): def get_port(self, context, id, fields=None):
quantum_db = super(NvpPluginV2, self).get_port(context, id, fields) with context.session.begin(subtransactions=True):
quantum_db = super(NvpPluginV2, self).get_port(context, id, fields)
self._extend_port_port_security_dict(context, quantum_db)
self._extend_port_dict_security_group(context, quantum_db)
#TODO: pass only the appropriate cluster here #TODO: pass only the appropriate cluster here
#Look for port in all lswitches #Look for port in all lswitches
@ -783,3 +836,124 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
def get_plugin_version(self): def get_plugin_version(self):
return PLUGIN_VERSION return PLUGIN_VERSION
def create_security_group(self, context, security_group, default_sg=False):
"""Create security group.
If default_sg is true that means a we are creating a default security
group and we don't need to check if one exists.
"""
s = security_group.get('security_group')
if cfg.CONF.SECURITYGROUP.proxy_mode:
if not context.is_admin:
raise ext_sg.SecurityGroupProxyModeNotAdmin()
elif not s.get('external_id'):
raise ext_sg.SecurityGroupProxyMode()
elif s.get('external_id'):
raise ext_sg.SecurityGroupNotProxyMode()
tenant_id = self._get_tenant_id_for_create(context, s)
if not default_sg and not cfg.CONF.SECURITYGROUP.proxy_mode:
self._ensure_default_security_group(context, tenant_id,
security_group)
if s.get('external_id'):
filters = {'external_id': [s.get('external_id')]}
security_groups = super(NvpPluginV2, self).get_security_groups(
context, filters=filters)
if security_groups:
raise ext_sg.SecurityGroupAlreadyExists(
name=s.get('name', ''), external_id=s.get('external_id'))
nvp_secgroup = nvplib.create_security_profile(self.default_cluster,
tenant_id, s)
security_group['security_group']['id'] = nvp_secgroup['uuid']
return super(NvpPluginV2, self).create_security_group(
context, security_group, default_sg)
def delete_security_group(self, context, security_group_id):
"""Delete a security group
:param security_group_id: security group rule to remove.
"""
if (cfg.CONF.SECURITYGROUP.proxy_mode and not context.is_admin):
raise ext_sg.SecurityGroupProxyModeNotAdmin()
with context.session.begin(subtransactions=True):
security_group = super(NvpPluginV2, self).get_security_group(
context, security_group_id)
if not security_group:
raise ext_sg.SecurityGroupNotFound(id=security_group_id)
if security_group['name'] == 'default':
raise ext_sg.SecurityGroupCannotRemoveDefault()
filters = {'security_group_id': [security_group['id']]}
if super(NvpPluginV2, self)._get_port_security_group_bindings(
context, filters):
raise ext_sg.SecurityGroupInUse(id=security_group['id'])
nvplib.delete_security_profile(self.default_cluster,
security_group['id'])
return super(NvpPluginV2, self).delete_security_group(
context, security_group_id)
def create_security_group_rule(self, context, security_group_rule):
"""create a single security group rule"""
bulk_rule = {'security_group_rules': [security_group_rule]}
return self.create_security_group_rule_bulk(context, bulk_rule)[0]
def create_security_group_rule_bulk(self, context, security_group_rule):
""" create security group rules
:param security_group_rule: list of rules to create
"""
s = security_group_rule.get('security_group_rules')
tenant_id = self._get_tenant_id_for_create(context, s)
# TODO(arosen) is there anyway we could avoid having the update of
# the security group rules in nvp outside of this transaction?
with context.session.begin(subtransactions=True):
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 and retrieve
# security_group['id'] needed incase it only has an external_id
security_group = super(NvpPluginV2, self).get_security_group(
context, security_group_id)
if not security_group:
raise ext_sg.SecurityGroupNotFound(id=security_group_id)
# 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'])
nvplib.update_security_group_rules(self.default_cluster,
security_group['id'],
combined_rules)
return super(
NvpPluginV2, self).create_security_group_rule_bulk_native(
context, security_group_rule)
def delete_security_group_rule(self, context, sgrid):
""" Delete a security group rule
:param sgrid: security group id to remove.
"""
if (cfg.CONF.SECURITYGROUP.proxy_mode and not context.is_admin):
raise ext_sg.SecurityGroupProxyModeNotAdmin()
with context.session.begin(subtransactions=True):
# determine security profile id
security_group_rule = (
super(NvpPluginV2, self).get_security_group_rule(
context, sgrid))
if not security_group_rule:
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)
nvplib.update_security_group_rules(
self.default_cluster, sgid, current_rules)
return super(NvpPluginV2, self).delete_security_group_rule(context,
sgrid)

View File

@ -0,0 +1,124 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Nicira, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless equired 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.
#
# @author: Aaron Rosen, Nicira Networks, Inc.
from quantum.extensions import securitygroup as ext_sg
# Protocol number look up for supported protocols
protocol_num_look_up = {'tcp': 6, 'icmp': 1, 'udp': 17}
class NVPSecurityGroups(object):
def _convert_to_nvp_rule(self, rule, with_id=False):
"""Converts Quantum API security group rule to NVP API."""
nvp_rule = {}
params = ['source_ip_prefix', 'protocol',
'source_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:
nvp_rule[param] = value
elif not value:
pass
elif param == 'source_ip_prefix':
nvp_rule['ip_prefix'] = rule['source_ip_prefix']
elif param == 'source_group_id':
nvp_rule['profile_uuid'] = rule['source_group_id']
elif param == 'protocol':
nvp_rule['protocol'] = protocol_num_look_up[rule['protocol']]
else:
nvp_rule[param] = value
return nvp_rule
def _convert_to_nvp_rules(self, rules, with_id=False):
"""Converts a list of Quantum 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 quantum db for security group rules. If external_id is
provided the external_id will also be returned.
"""
fields = ['source_ip_prefix', 'source_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, source_group_id):
"""Return profile id from novas group id. """
security_group = self.get_security_group(context, source_group_id)
if not security_group:
raise ext_sg.SecurityGroupNotFound(id=source_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['source_group_id'] = self._get_profile_uuid(
context, rule['source_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):
"""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

@ -417,7 +417,7 @@ def get_port(cluster, network, port, relations=None):
def _configure_extensions(lport_obj, mac_address, fixed_ips, def _configure_extensions(lport_obj, mac_address, fixed_ips,
port_security_enabled): port_security_enabled, security_profiles):
lport_obj['allowed_address_pairs'] = [] lport_obj['allowed_address_pairs'] = []
if port_security_enabled: if port_security_enabled:
for fixed_ip in fixed_ips: for fixed_ip in fixed_ips:
@ -430,11 +430,13 @@ def _configure_extensions(lport_obj, mac_address, fixed_ips,
lport_obj["allowed_address_pairs"].append( lport_obj["allowed_address_pairs"].append(
{"mac_address": mac_address, {"mac_address": mac_address,
"ip_address": "0.0.0.0"}) "ip_address": "0.0.0.0"})
lport_obj['security_profiles'] = list(security_profiles or [])
def update_port(cluster, lswitch_uuid, lport_uuid, quantum_port_id, tenant_id, def update_port(cluster, lswitch_uuid, lport_uuid, quantum_port_id, tenant_id,
display_name, device_id, admin_status_enabled, display_name, device_id, admin_status_enabled,
mac_address=None, fixed_ips=None, port_security_enabled=None): mac_address=None, fixed_ips=None, port_security_enabled=None,
security_profiles=None):
# device_id can be longer than 40 so we rehash it # device_id can be longer than 40 so we rehash it
hashed_device_id = hashlib.sha1(device_id).hexdigest() hashed_device_id = hashlib.sha1(device_id).hexdigest()
@ -446,7 +448,7 @@ def update_port(cluster, lswitch_uuid, lport_uuid, quantum_port_id, tenant_id,
dict(scope='vm_id', tag=hashed_device_id)]) dict(scope='vm_id', tag=hashed_device_id)])
_configure_extensions(lport_obj, mac_address, fixed_ips, _configure_extensions(lport_obj, mac_address, fixed_ips,
port_security_enabled) port_security_enabled, security_profiles)
path = "/ws.v1/lswitch/" + lswitch_uuid + "/lport/" + lport_uuid path = "/ws.v1/lswitch/" + lswitch_uuid + "/lport/" + lport_uuid
try: try:
@ -465,7 +467,8 @@ def update_port(cluster, lswitch_uuid, lport_uuid, quantum_port_id, tenant_id,
def create_lport(cluster, lswitch_uuid, tenant_id, quantum_port_id, def create_lport(cluster, lswitch_uuid, tenant_id, quantum_port_id,
display_name, device_id, admin_status_enabled, display_name, device_id, admin_status_enabled,
mac_address=None, fixed_ips=None, port_security_enabled=None): mac_address=None, fixed_ips=None, port_security_enabled=None,
security_profiles=None):
""" Creates a logical port on the assigned logical switch """ """ Creates a logical port on the assigned logical switch """
# device_id can be longer than 40 so we rehash it # device_id can be longer than 40 so we rehash it
hashed_device_id = hashlib.sha1(device_id).hexdigest() hashed_device_id = hashlib.sha1(device_id).hexdigest()
@ -478,7 +481,7 @@ def create_lport(cluster, lswitch_uuid, tenant_id, quantum_port_id,
) )
_configure_extensions(lport_obj, mac_address, fixed_ips, _configure_extensions(lport_obj, mac_address, fixed_ips,
port_security_enabled) port_security_enabled, security_profiles)
path = _build_uri_path(LPORT_RESOURCE, parent_resource_id=lswitch_uuid) path = _build_uri_path(LPORT_RESOURCE, parent_resource_id=lswitch_uuid)
try: try:
@ -538,3 +541,148 @@ def plug_interface(cluster, lswitch_id, port, type, attachment=None):
result = json.dumps(resp_obj) result = json.dumps(resp_obj)
return result return result
#------------------------------------------------------------------------------
# Security Profile convenience functions.
#------------------------------------------------------------------------------
EXT_SECURITY_PROFILE_ID_SCOPE = 'nova_spid'
TENANT_ID_SCOPE = 'os_tid'
def format_exception(etype, e, execption_locals, request=None):
"""Consistent formatting for exceptions.
:param etype: a string describing the exception type.
:param e: the exception.
:param request: the request object.
:param execption_locals: calling context local variable dict.
:returns: a formatted string.
"""
msg = ["Error. %s exception: %s." % (etype, e)]
if request:
msg.append("request=[%s]" % request)
if request.body:
msg.append("request.body=[%s]" % str(request.body))
l = dict((k, v) for k, v in execption_locals if k != 'request')
msg.append("locals=[%s]" % str(l))
return ' '.join(msg)
def do_request(*args, **kwargs):
"""Convenience function wraps do_single_request.
:param args: a list of positional arguments.
:param kwargs: a list of keyworkds arguments.
:returns: the result of do_single_request loaded into a python object
or None."""
res = do_single_request(*args, **kwargs)
if res:
return json.loads(res)
return res
def mk_body(**kwargs):
"""Convenience function creates and dumps dictionary to string.
:param kwargs: the key/value pirs to be dumped into a json string.
:returns: a json string."""
return json.dumps(kwargs, ensure_ascii=False)
def set_tenant_id_tag(tenant_id, taglist=None):
"""Convenience function to add tenant_id tag to taglist.
:param tenant_id: the tenant_id to set.
:param taglist: the taglist to append to (or None).
:returns: a new taglist that includes the old taglist with the new
tenant_id tag set."""
new_taglist = []
if taglist:
new_taglist = [x for x in taglist if x['scope'] != TENANT_ID_SCOPE]
new_taglist.append(dict(scope=TENANT_ID_SCOPE, tag=tenant_id))
return new_taglist
def set_ext_security_profile_id_tag(external_id, taglist=None):
"""Convenience function to add spid tag to taglist.
:param external_id: the security_profile id from nova
:param taglist: the taglist to append to (or None).
:returns: a new taglist that includes the old taglist with the new
spid tag set."""
new_taglist = []
if taglist:
new_taglist = [x for x in taglist if x['scope'] !=
EXT_SECURITY_PROFILE_ID_SCOPE]
if external_id:
new_taglist.append(dict(scope=EXT_SECURITY_PROFILE_ID_SCOPE,
tag=str(external_id)))
return new_taglist
# -----------------------------------------------------------------------------
# Security Group API Calls
# -----------------------------------------------------------------------------
def create_security_profile(cluster, tenant_id, security_profile):
path = "/ws.v1/security-profile"
tags = set_tenant_id_tag(tenant_id)
tags = set_ext_security_profile_id_tag(
security_profile.get('external_id'), tags)
# Allow all dhcp responses in
dhcp = {'logical_port_egress_rules': [{'ethertype': 'IPv4',
'protocol': 17,
'port_range_min': 68,
'port_range_max': 68,
'ip_prefix': '0.0.0.0/0'}],
'logical_port_ingress_rules': []}
try:
body = mk_body(
tags=tags, display_name=security_profile.get('name'),
logical_port_ingress_rules=dhcp['logical_port_ingress_rules'],
logical_port_egress_rules=dhcp['logical_port_egress_rules'])
rsp = do_request("POST", path, body, cluster=cluster)
except NvpApiClient.NvpApiException as e:
LOG.error(format_exception("Unknown", e, locals()))
raise exception.QuantumException()
if security_profile.get('name') == 'default':
# If security group is default allow ip traffic between
# members of the same security profile.
rules = {'logical_port_egress_rules': [{'ethertype': 'IPv4',
'profile_uuid': rsp['uuid']},
{'ethertype': 'IPv6',
'profile_uuid': rsp['uuid']}],
'logical_port_ingress_rules': []}
update_security_group_rules(cluster, rsp['uuid'], rules)
LOG.debug("Created Security Profile: %s" % rsp)
return rsp
def update_security_group_rules(cluster, spid, rules):
path = "/ws.v1/security-profile/%s" % spid
# Allow all dhcp responses in
rules['logical_port_egress_rules'].append(
{'ethertype': 'IPv4', 'protocol': constants.UDP_PROTOCOL,
'port_range_min': constants.DHCP_RESPONSE_PORT,
'port_range_max': constants.DHCP_RESPONSE_PORT,
'ip_prefix': '0.0.0.0/0'})
try:
body = mk_body(
logical_port_ingress_rules=rules['logical_port_ingress_rules'],
logical_port_egress_rules=rules['logical_port_egress_rules'])
rsp = do_request("PUT", path, body, cluster=cluster)
except NvpApiClient.NvpApiException as e:
LOG.error(format_exception("Unknown", e, locals()))
raise exception.QuantumException()
LOG.debug("Updated Security Profile: %s" % rsp)
return rsp
def delete_security_profile(cluster, spid):
path = "/ws.v1/security-profile/%s" % spid
try:
do_request("DELETE", path, cluster=cluster)
except NvpApiClient.NvpApiException as e:
LOG.error(format_exception("Unknown", e, locals()))
raise exception.QuantumException()

View File

@ -0,0 +1,10 @@
{
"display_name": "%(display_name)s",
"_href": "/ws.v1/security-profile/%(uuid)s",
"tags": [{"scope": "os_tid", "tag": "%(tenant_id)s"},
{"scope": "nova_spid", "tag": "%(nova_spid)s"}],
"logical_port_egress_rules": [],
"_schema": "/ws.v1/schema/SecurityProfileConfig",
"logical_port_ingress_rules": [],
"uuid": "%(uuid)s"
}

View File

@ -35,17 +35,20 @@ class FakeClient:
FAKE_POST_RESPONSES = { FAKE_POST_RESPONSES = {
"lswitch": "fake_post_lswitch.json", "lswitch": "fake_post_lswitch.json",
"lport": "fake_post_lport.json" "lport": "fake_post_lport.json",
"securityprofile": "fake_post_security_profile.json"
} }
FAKE_PUT_RESPONSES = { FAKE_PUT_RESPONSES = {
"lswitch": "fake_post_lswitch.json", "lswitch": "fake_post_lswitch.json",
"lport": "fake_post_lport.json" "lport": "fake_post_lport.json",
"securityprofile": "fake_post_security_profile.json"
} }
_fake_lswitch_dict = {} _fake_lswitch_dict = {}
_fake_lport_dict = {} _fake_lport_dict = {}
_fake_lportstatus_dict = {} _fake_lportstatus_dict = {}
_fake_securityprofile_dict = {}
def __init__(self, fake_files_path): def __init__(self, fake_files_path):
self.fake_files_path = fake_files_path self.fake_files_path = fake_files_path
@ -102,17 +105,32 @@ class FakeClient:
self._fake_lportstatus_dict[fake_lport['uuid']] = fake_lport_status self._fake_lportstatus_dict[fake_lport['uuid']] = fake_lport_status
return fake_lport return fake_lport
def _add_securityprofile(self, body):
fake_securityprofile = json.loads(body)
fake_securityprofile['uuid'] = uuidutils.generate_uuid()
fake_securityprofile['tenant_id'] = self._get_tag(
fake_securityprofile, 'os_tid')
fake_securityprofile['nova_spid'] = self._get_tag(fake_securityprofile,
'nova_spid')
self._fake_securityprofile_dict[fake_securityprofile['uuid']] = (
fake_securityprofile)
return fake_securityprofile
def _get_resource_type(self, path): def _get_resource_type(self, path):
uri_split = path.split('/') uri_split = path.split('/')
resource_type = ('status' in uri_split and resource_type = ('status' in uri_split and
'lport' in uri_split and 'lportstatus' 'lport' in uri_split and 'lportstatus'
or 'lport' in uri_split and 'lport' or 'lport' in uri_split and 'lport'
or 'lswitch' in uri_split and 'lswitch') or 'lswitch' in uri_split and 'lswitch' or
'security-profile' in uri_split and 'securityprofile')
switch_uuid = ('lswitch' in uri_split and switch_uuid = ('lswitch' in uri_split and
len(uri_split) > 3 and uri_split[3]) len(uri_split) > 3 and uri_split[3])
port_uuid = ('lport' in uri_split and port_uuid = ('lport' in uri_split and
len(uri_split) > 5 and uri_split[5]) len(uri_split) > 5 and uri_split[5])
return (resource_type, switch_uuid, port_uuid) securityprofile_uuid = ('security-profile' in uri_split and
len(uri_split) > 3 and uri_split[3])
return (resource_type, switch_uuid, port_uuid, securityprofile_uuid)
def _list(self, resource_type, response_file, def _list(self, resource_type, response_file,
switch_uuid=None, query=None): switch_uuid=None, query=None):
@ -176,7 +194,8 @@ class FakeClient:
def handle_get(self, url): def handle_get(self, url):
#TODO(salvatore-orlando): handle field selection #TODO(salvatore-orlando): handle field selection
parsedurl = urlparse.urlparse(url) parsedurl = urlparse.urlparse(url)
(res_type, s_uuid, p_uuid) = self._get_resource_type(parsedurl.path) (res_type, s_uuid, p_uuid, sec_uuid) = self._get_resource_type(
parsedurl.path)
response_file = self.FAKE_GET_RESPONSES.get(res_type) response_file = self.FAKE_GET_RESPONSES.get(res_type)
if not response_file: if not response_file:
raise Exception("resource not found") raise Exception("resource not found")
@ -199,7 +218,8 @@ class FakeClient:
def handle_post(self, url, body): def handle_post(self, url, body):
parsedurl = urlparse.urlparse(url) parsedurl = urlparse.urlparse(url)
(res_type, s_uuid, _p) = self._get_resource_type(parsedurl.path) (res_type, s_uuid, _p, sec_uuid) = self._get_resource_type(
parsedurl.path)
response_file = self.FAKE_POST_RESPONSES.get(res_type) response_file = self.FAKE_POST_RESPONSES.get(res_type)
if not response_file: if not response_file:
raise Exception("resource not found") raise Exception("resource not found")
@ -214,8 +234,9 @@ class FakeClient:
def handle_put(self, url, body): def handle_put(self, url, body):
parsedurl = urlparse.urlparse(url) parsedurl = urlparse.urlparse(url)
(res_type, s_uuid, p_uuid) = self._get_resource_type(parsedurl.path) (res_type, s_uuid, p_uuid, sec_uuid) = self._get_resource_type(
target_uuid = p_uuid or s_uuid parsedurl.path)
target_uuid = p_uuid or s_uuid or sec_uuid
response_file = self.FAKE_PUT_RESPONSES.get(res_type) response_file = self.FAKE_PUT_RESPONSES.get(res_type)
if not response_file: if not response_file:
raise Exception("resource not found") raise Exception("resource not found")
@ -229,8 +250,9 @@ class FakeClient:
def handle_delete(self, url): def handle_delete(self, url):
parsedurl = urlparse.urlparse(url) parsedurl = urlparse.urlparse(url)
(res_type, s_uuid, p_uuid) = self._get_resource_type(parsedurl.path) (res_type, s_uuid, p_uuid, sec_uuid) = self._get_resource_type(
target_uuid = p_uuid or s_uuid parsedurl.path)
target_uuid = p_uuid or s_uuid or sec_uuid
response_file = self.FAKE_PUT_RESPONSES.get(res_type) response_file = self.FAKE_PUT_RESPONSES.get(res_type)
if not response_file: if not response_file:
raise Exception("resource not found") raise Exception("resource not found")

View File

@ -22,13 +22,14 @@ import webob.exc
import quantum.common.test_lib as test_lib import quantum.common.test_lib as test_lib
from quantum import context from quantum import context
from quantum.extensions import providernet as pnet from quantum.extensions import providernet as pnet
from quantum.extensions import securitygroup as secgrp
from quantum import manager from quantum import manager
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
from quantum.plugins.nicira.nicira_nvp_plugin import nvplib from quantum.plugins.nicira.nicira_nvp_plugin import nvplib
from quantum.tests.unit.nicira import fake_nvpapiclient from quantum.tests.unit.nicira import fake_nvpapiclient
import quantum.tests.unit.test_db_plugin as test_plugin import quantum.tests.unit.test_db_plugin as test_plugin
import quantum.tests.unit.test_extension_portsecurity as psec import quantum.tests.unit.test_extension_portsecurity as psec
import quantum.tests.unit.test_extension_security_group as ext_sg
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
NICIRA_PKG_PATH = 'quantum.plugins.nicira.nicira_nvp_plugin' NICIRA_PKG_PATH = 'quantum.plugins.nicira.nicira_nvp_plugin'
@ -120,6 +121,24 @@ class TestNiciraPortsV2(test_plugin.TestPortsV2, NiciraPluginV2TestCase):
net['network']['id']) net['network']['id'])
self.assertEqual(len(ls), 2) self.assertEqual(len(ls), 2)
def test_update_port_delete_ip(self):
# This test case overrides the default because the nvp plugin
# implements port_security/security groups and it is not allowed
# to remove an ip address from a port unless the security group
# is first removed.
with self.subnet() as subnet:
with self.port(subnet=subnet) as port:
data = {'port': {'admin_state_up': False,
'fixed_ips': [],
secgrp.SECURITYGROUPS: []}}
req = self.new_update_request('ports',
data, port['port']['id'])
res = self.deserialize('json', req.get_response(self.api))
self.assertEqual(res['port']['admin_state_up'],
data['port']['admin_state_up'])
self.assertEqual(res['port']['fixed_ips'],
data['port']['fixed_ips'])
class TestNiciraNetworksV2(test_plugin.TestNetworksV2, class TestNiciraNetworksV2(test_plugin.TestNetworksV2,
NiciraPluginV2TestCase): NiciraPluginV2TestCase):
@ -185,3 +204,34 @@ class NiciraPortSecurityTestCase(psec.PortSecurityDBTestCase):
class TestNiciraPortSecurity(psec.TestPortSecurity, class TestNiciraPortSecurity(psec.TestPortSecurity,
NiciraPortSecurityTestCase): NiciraPortSecurityTestCase):
pass pass
class NiciraSecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase):
_plugin_name = ('%s.QuantumPlugin.NvpPluginV2' % NICIRA_PKG_PATH)
def setUp(self):
etc_path = os.path.join(os.path.dirname(__file__), 'etc')
test_lib.test_config['config_files'] = [os.path.join(etc_path,
'nvp.ini.test')]
# mock nvp api client
fc = fake_nvpapiclient.FakeClient(etc_path)
self.mock_nvpapi = mock.patch('%s.NvpApiClient.NVPApiHelper'
% NICIRA_PKG_PATH, autospec=True)
instance = self.mock_nvpapi.start()
instance.return_value.login.return_value = "the_cookie"
def _fake_request(*args, **kwargs):
return fc.fake_request(*args, **kwargs)
instance.return_value.request.side_effect = _fake_request
super(NiciraSecurityGroupsTestCase, self).setUp(self._plugin_name)
def tearDown(self):
super(NiciraSecurityGroupsTestCase, self).tearDown()
self.mock_nvpapi.stop()
class TestNiciraSecurityGroup(ext_sg.TestSecurityGroups,
NiciraSecurityGroupsTestCase):
pass

View File

@ -1,5 +1,5 @@
# Copyright (c) 2012 OpenStack, LLC. # Copyright (c) 2012 OpenStack, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
@ -19,6 +19,7 @@ import os
import mock import mock
import webob.exc import webob.exc
from quantum.api.v2 import attributes as attr
from quantum.common.test_lib import test_config from quantum.common.test_lib import test_config
from quantum import context from quantum import context
from quantum.db import db_base_plugin_v2 from quantum.db import db_base_plugin_v2
@ -174,7 +175,7 @@ class SecurityGroupTestPlugin(db_base_plugin_v2.QuantumDbPluginV2,
def create_port(self, context, port): def create_port(self, context, port):
tenant_id = self._get_tenant_id_for_create(context, port['port']) tenant_id = self._get_tenant_id_for_create(context, port['port'])
default_sg = self._ensure_default_security_group(context, tenant_id) default_sg = self._ensure_default_security_group(context, tenant_id)
if not port['port'].get(ext_sg.SECURITYGROUPS): if not attr.is_attr_set(port['port'].get(ext_sg.SECURITYGROUPS)):
port['port'][ext_sg.SECURITYGROUPS] = [default_sg] port['port'][ext_sg.SECURITYGROUPS] = [default_sg]
session = context.session session = context.session
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
@ -207,6 +208,13 @@ class SecurityGroupTestPlugin(db_base_plugin_v2.QuantumDbPluginV2,
return super(SecurityGroupTestPlugin, self).create_network(context, return super(SecurityGroupTestPlugin, self).create_network(context,
network) network)
def get_ports(self, context, filters=None, fields=None):
quantum_lports = super(SecurityGroupTestPlugin, self).get_ports(
context, filters)
for quantum_lport in quantum_lports:
self._extend_port_dict_security_group(context, quantum_lport)
return quantum_lports
class SecurityGroupDBTestCase(SecurityGroupsTestCase): class SecurityGroupDBTestCase(SecurityGroupsTestCase):
def setUp(self, plugin=None): def setUp(self, plugin=None):
@ -215,6 +223,10 @@ class SecurityGroupDBTestCase(SecurityGroupsTestCase):
test_config['extension_manager'] = ext_mgr test_config['extension_manager'] = ext_mgr
super(SecurityGroupDBTestCase, self).setUp(plugin) super(SecurityGroupDBTestCase, self).setUp(plugin)
def tearDown(self):
del test_config['plugin_name_v2']
super(SecurityGroupDBTestCase, self).tearDown()
class TestSecurityGroups(SecurityGroupDBTestCase): class TestSecurityGroups(SecurityGroupDBTestCase):
def test_create_security_group(self): def test_create_security_group(self):
@ -649,6 +661,18 @@ class TestSecurityGroups(SecurityGroupDBTestCase):
self.deserialize(self.fmt, res) self.deserialize(self.fmt, res)
self.assertEqual(res.status_int, 400) self.assertEqual(res.status_int, 400)
def test_list_ports_security_group(self):
with self.network() as n:
with self.subnet(n):
res = self._create_port(self.fmt, n['network']['id'])
self.deserialize(self.fmt, res)
res = self.new_list_request('ports')
ports = self.deserialize(self.fmt,
res.get_response(self.api))
port = ports['ports'][0]
self.assertEquals(len(port[ext_sg.SECURITYGROUPS]), 1)
self._delete('ports', port['id'])
def test_update_port_with_security_group(self): def test_update_port_with_security_group(self):
with self.network() as n: with self.network() as n:
with self.subnet(n): with self.subnet(n):