IPv4 = 'IPv4'
IPv6 = 'IPv6'
+UDP_PROTOCOL = 17
+DHCP_RESPONSE_PORT = 68
+
EXT_NS = '_extension_ns'
XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"
# Change to ['*'] if this migration applies to all 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
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.common import constants
from quantum.common import exceptions as q_exc
from quantum.db import portsecurity_db
# NOTE: quota_db cannot be removed, it is for db model
from quantum.db import quota_db
+from quantum.db import securitygroups_db
from quantum.extensions import portsecurity as psec
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 rpc
+from quantum.plugins.nicira.nicira_nvp_plugin.common import (securitygroups
+ as nvp_sec)
from quantum import policy
from quantum.plugins.nicira.nicira_nvp_plugin.common import config
from quantum.plugins.nicira.nicira_nvp_plugin.common import (exceptions
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
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_cluster = None
network_type = attrs.get(pnet.NETWORK_TYPE)
physical_network = attrs.get(pnet.PHYSICAL_NETWORK)
segmentation_id = attrs.get(pnet.SEGMENTATION_ID)
- network_type_set = attributes.is_attr_set(network_type)
- physical_network_set = attributes.is_attr_set(physical_network)
- segmentation_id_set = attributes.is_attr_set(segmentation_id)
+ network_type_set = attr.is_attr_set(network_type)
+ physical_network_set = attr.is_attr_set(physical_network)
+ segmentation_id_set = attr.is_attr_set(segmentation_id)
if not (network_type_set or physical_network_set or
segmentation_id_set):
return
def create_network(self, context, network):
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
self._handle_provider_create(context, net_data)
# Replace ATTR_NOT_SPECIFIED with None before sending to NVP
- for attr, value in network['network'].iteritems():
- if value is attributes.ATTR_NOT_SPECIFIED:
- net_data[attr] = None
+ for key, value in network['network'].iteritems():
+ if value is attr.ATTR_NOT_SPECIFIED:
+ net_data[key] = None
# FIXME(arosen) implement admin_state_up = False in NVP
if net_data['admin_state_up'] is False:
LOG.warning(_("Network with admin_state_up=False are not yet "
"supported by this plugin. Ignoring setting for "
"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)
nvp_binding_type = net_data.get(pnet.NETWORK_TYPE)
if nvp_binding_type in ('flat', 'vlan'):
context, filters)
for quantum_lport in quantum_lports:
self._extend_port_port_security_dict(context, quantum_lport)
+ self._extend_port_dict_security_group(context, quantum_lport)
vm_filter = ""
tenant_filter = ""
# ATTR_NOT_SPECIFIED is for the case where a port is created on a
# shared network that is not owned by the tenant.
# 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.port_security_enabled_create)
port_data = port['port']
context, port_data)
port_data[psec.PORTSECURITY] = port_security
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
# Fetch the network and network binding from Quantum db
network = self._get_network(context, port_data['network_id'])
port_data['admin_state_up'],
port_data['mac_address'],
port_data['fixed_ips'],
- port_data[psec.PORTSECURITY])
+ port_data[psec.PORTSECURITY],
+ port_data[ext_sg.SECURITYGROUPS])
# Get NVP ls uuid for quantum network
nvplib.plug_interface(cluster, selected_lswitch['uuid'],
lport['uuid'], "VifAttachment",
"%(tenant_id)s: (%(id)s)"), port_data)
self._extend_port_port_security_dict(context, port_data)
+ self._extend_port_dict_security_group(context, port_data)
return port_data
def update_port(self, context, id, port):
self._enforce_set_auth(context, port,
self.port_security_enabled_update)
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):
ret_port = super(NvpPluginV2, self).update_port(
context, id, port)
# copy values over
ret_port.update(port['port'])
+ tenant_id = self._get_tenant_id_for_create(context, ret_port)
- # Handle port security
- if psec.PORTSECURITY in port['port']:
- self._update_port_security_binding(
- context, id, ret_port[psec.PORTSECURITY])
- # populate with value
- else:
+ # populate port_security setting
+ if psec.PORTSECURITY not in port['port']:
ret_port[psec.PORTSECURITY] = self._get_port_security_binding(
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 = (
nvplib.get_port_by_quantum_tag(self.clusters.itervalues(),
ret_port["network_id"], id))
ret_port['admin_state_up'],
ret_port['mac_address'],
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
# the port was successfully updated but we were not able to retrieve
return super(NvpPluginV2, self).delete_port(context, id)
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
#Look for port in all lswitches
def get_plugin_version(self):
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)
--- /dev/null
+# 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)
def _configure_extensions(lport_obj, mac_address, fixed_ips,
- port_security_enabled):
+ port_security_enabled, security_profiles):
lport_obj['allowed_address_pairs'] = []
if port_security_enabled:
for fixed_ip in fixed_ips:
lport_obj["allowed_address_pairs"].append(
{"mac_address": mac_address,
"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,
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
hashed_device_id = hashlib.sha1(device_id).hexdigest()
dict(scope='vm_id', tag=hashed_device_id)])
_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
try:
def create_lport(cluster, lswitch_uuid, tenant_id, quantum_port_id,
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 """
# device_id can be longer than 40 so we rehash it
hashed_device_id = hashlib.sha1(device_id).hexdigest()
)
_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)
try:
result = json.dumps(resp_obj)
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()
--- /dev/null
+{
+ "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"
+}
FAKE_POST_RESPONSES = {
"lswitch": "fake_post_lswitch.json",
- "lport": "fake_post_lport.json"
+ "lport": "fake_post_lport.json",
+ "securityprofile": "fake_post_security_profile.json"
}
FAKE_PUT_RESPONSES = {
"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_lport_dict = {}
_fake_lportstatus_dict = {}
+ _fake_securityprofile_dict = {}
def __init__(self, fake_files_path):
self.fake_files_path = fake_files_path
self._fake_lportstatus_dict[fake_lport['uuid']] = fake_lport_status
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):
uri_split = path.split('/')
resource_type = ('status' in uri_split and
'lport' in uri_split and 'lportstatus'
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
len(uri_split) > 3 and uri_split[3])
port_uuid = ('lport' in uri_split and
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,
switch_uuid=None, query=None):
def handle_get(self, url):
#TODO(salvatore-orlando): handle field selection
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)
if not response_file:
raise Exception("resource not found")
def handle_post(self, url, body):
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)
if not response_file:
raise Exception("resource not found")
def handle_put(self, url, body):
parsedurl = urlparse.urlparse(url)
- (res_type, s_uuid, p_uuid) = self._get_resource_type(parsedurl.path)
- target_uuid = p_uuid or s_uuid
+ (res_type, s_uuid, p_uuid, sec_uuid) = self._get_resource_type(
+ parsedurl.path)
+ target_uuid = p_uuid or s_uuid or sec_uuid
response_file = self.FAKE_PUT_RESPONSES.get(res_type)
if not response_file:
raise Exception("resource not found")
def handle_delete(self, url):
parsedurl = urlparse.urlparse(url)
- (res_type, s_uuid, p_uuid) = self._get_resource_type(parsedurl.path)
- target_uuid = p_uuid or s_uuid
+ (res_type, s_uuid, p_uuid, sec_uuid) = self._get_resource_type(
+ parsedurl.path)
+ target_uuid = p_uuid or s_uuid or sec_uuid
response_file = self.FAKE_PUT_RESPONSES.get(res_type)
if not response_file:
raise Exception("resource not found")
import quantum.common.test_lib as test_lib
from quantum import context
from quantum.extensions import providernet as pnet
+from quantum.extensions import securitygroup as secgrp
from quantum import manager
from quantum.openstack.common import cfg
from quantum.plugins.nicira.nicira_nvp_plugin import nvplib
from quantum.tests.unit.nicira import fake_nvpapiclient
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_security_group as ext_sg
LOG = logging.getLogger(__name__)
NICIRA_PKG_PATH = 'quantum.plugins.nicira.nicira_nvp_plugin'
net['network']['id'])
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,
NiciraPluginV2TestCase):
class TestNiciraPortSecurity(psec.TestPortSecurity,
NiciraPortSecurityTestCase):
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
# Copyright (c) 2012 OpenStack, LLC.
-#
+
# 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
import mock
import webob.exc
+from quantum.api.v2 import attributes as attr
from quantum.common.test_lib import test_config
from quantum import context
from quantum.db import db_base_plugin_v2
def create_port(self, context, port):
tenant_id = self._get_tenant_id_for_create(context, port['port'])
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]
session = context.session
with session.begin(subtransactions=True):
return super(SecurityGroupTestPlugin, self).create_network(context,
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):
def setUp(self, plugin=None):
test_config['extension_manager'] = ext_mgr
super(SecurityGroupDBTestCase, self).setUp(plugin)
+ def tearDown(self):
+ del test_config['plugin_name_v2']
+ super(SecurityGroupDBTestCase, self).tearDown()
+
class TestSecurityGroups(SecurityGroupDBTestCase):
def test_create_security_group(self):
self.deserialize(self.fmt, res)
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):
with self.network() as n:
with self.subnet(n):