Merge "L3 API support for nicira plugin"

This commit is contained in:
Jenkins 2013-02-11 17:33:23 +00:00 committed by Gerrit Code Review
commit 75ca98567d
27 changed files with 2202 additions and 302 deletions

View File

@ -1,5 +1,10 @@
[DEFAULT] [DEFAULT]
# The following flag will cause a host route to the metadata server
# to be injected into instances. The metadata server will be reached
# via the dhcp server.
metadata_dhcp_host_route = False
[DATABASE] [DATABASE]
# This line MUST be changed to actually run the plugin. # This line MUST be changed to actually run the plugin.
# Example: # Example:
@ -28,9 +33,6 @@ reconnect_interval = 2
# max_lp_per_bridged_ls = 64 # max_lp_per_bridged_ls = 64
# Maximum number of ports for each overlay (stt, gre) logical switch # Maximum number of ports for each overlay (stt, gre) logical switch
# max_lp_per_overlay_ls = 256 # max_lp_per_overlay_ls = 256
# Time from when a connection pool is switched to another controller
# during failure.
# failover_time = 5
# Number of connects to each controller node. # Number of connects to each controller node.
# concurrent_connections = 3 # concurrent_connections = 3
# Name of the default cluster where requests should be sent if a nova zone id # Name of the default cluster where requests should be sent if a nova zone id
@ -53,6 +55,11 @@ reconnect_interval = 2
# console "admin" section. # console "admin" section.
# nvp_cluster_uuid = 615be8e4-82e9-4fd2-b4b3-fd141e51a5a7 # (Optional) # nvp_cluster_uuid = 615be8e4-82e9-4fd2-b4b3-fd141e51a5a7 # (Optional)
# UUID of the default layer 3 gateway service to use for this cluster
# This is optional, but should be filled if planning to use logical routers
# with external gateways
# default_l3_gw_service_uuid =
# This parameter describes a connection to a single NVP controller. Format: # This parameter describes a connection to a single NVP controller. Format:
# <ip>:<port>:<user>:<pw>:<req_timeout>:<http_timeout>:<retries>:<redirects> # <ip>:<port>:<user>:<pw>:<req_timeout>:<http_timeout>:<retries>:<redirects>
# <ip> is the ip address of the controller # <ip> is the ip address of the controller

View File

@ -0,0 +1,60 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 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
#
# 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.
#
"""nvp_portmap
Revision ID: 38335592a0dc
Revises: 49332180ca96
Create Date: 2013-01-15 06:04:56.328991
"""
# revision identifiers, used by Alembic.
revision = '38335592a0dc'
down_revision = '49332180ca96'
# Change to ['*'] if this migration applies to all plugins
migration_for_plugins = [
'quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2'
]
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
from quantum.db import migration
def upgrade(active_plugin=None, options=None):
if not migration.should_run(active_plugin, migration_for_plugins):
return
op.create_table(
'quantum_nvp_port_mapping',
sa.Column('quantum_id', sa.String(length=36), nullable=False),
sa.Column('nvp_id', sa.String(length=36), nullable=True),
sa.ForeignKeyConstraint(['quantum_id'], ['ports.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('quantum_id'))
def downgrade(active_plugin=None, options=None):
if not migration.should_run(active_plugin, migration_for_plugins):
return
op.drop_table('quantum_nvp_port_mapping')

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,10 @@
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
core_opts = [
cfg.BoolOpt('metadata_dhcp_host_route', default=False),
]
nvp_opts = [ nvp_opts = [
cfg.IntOpt('max_lp_per_bridged_ls', default=64, cfg.IntOpt('max_lp_per_bridged_ls', default=64,
help=_("Maximum number of ports of a logical switch on a " help=_("Maximum number of ports of a logical switch on a "
@ -51,8 +55,15 @@ cluster_opts = [
"controller. A different connection for each " "controller. A different connection for each "
"controller in the cluster can be specified; " "controller in the cluster can be specified; "
"there must be at least one connection per " "there must be at least one connection per "
"cluster.")) "cluster.")),
cfg.StrOpt('default_l3_gw_service_uuid',
help=_("Unique identifier of the NVP L3 Gateway service "
"which will be used for implementing routers and "
"floating IPs"))
] ]
# Register the configuration options
cfg.CONF.register_opts(core_opts)
cfg.CONF.register_opts(nvp_opts, "NVP") cfg.CONF.register_opts(nvp_opts, "NVP")

View File

@ -40,3 +40,9 @@ class NvpNoMorePortsException(NvpPluginException):
class NvpOutOfSyncException(NvpPluginException): class NvpOutOfSyncException(NvpPluginException):
message = _("Quantum state has diverged from the networking backend!") message = _("Quantum state has diverged from the networking backend!")
class NvpNatRuleMismatch(NvpPluginException):
message = _("While retrieving NAT rules, %(actual_rules)s were found "
"whereas rules in the (%(min_rules)s,%(max_rules)s) interval "
"were expected")

View File

@ -54,3 +54,20 @@ def add_network_binding(session, network_id, binding_type, tz_uuid, vlan_id):
tz_uuid, vlan_id) tz_uuid, vlan_id)
session.add(binding) session.add(binding)
return binding return binding
def add_quantum_nvp_port_mapping(session, quantum_id, nvp_id):
with session.begin(subtransactions=True):
mapping = nicira_models.QuantumNvpPortMapping(quantum_id, nvp_id)
session.add(mapping)
return mapping
def get_nvp_port_id(session, quantum_id):
try:
mapping = (session.query(nicira_models.QuantumNvpPortMapping).
filter_by(quantum_id=quantum_id).
one())
return mapping['nvp_id']
except exc.NoResultFound:
return

View File

@ -50,3 +50,17 @@ class NvpNetworkBinding(model_base.BASEV2):
self.binding_type, self.binding_type,
self.tz_uuid, self.tz_uuid,
self.vlan_id) self.vlan_id)
class QuantumNvpPortMapping(model_base.BASEV2):
"""Represents the mapping between quantum and nvp port uuids."""
__tablename__ = 'quantum_nvp_port_mapping'
quantum_id = Column(String(36),
ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
nvp_id = Column(String(36))
def __init__(self, quantum_id, nvp_id):
self.quantum_id = quantum_id
self.nvp_id = nvp_id

View File

@ -14,6 +14,16 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
import re
from quantum.api.v2 import attributes
from quantum.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class NVPCluster(object): class NVPCluster(object):
@ -45,8 +55,9 @@ class NVPCluster(object):
return ''.join(ss) return ''.join(ss)
def add_controller(self, ip, port, user, password, request_timeout, def add_controller(self, ip, port, user, password, request_timeout,
http_timeout, retries, redirects, http_timeout, retries, redirects, default_tz_uuid,
default_tz_uuid, uuid=None, zone=None): uuid=None, zone=None,
default_l3_gw_service_uuid=None):
"""Add a new set of controller parameters. """Add a new set of controller parameters.
:param ip: IP address of controller. :param ip: IP address of controller.
@ -59,14 +70,33 @@ class NVPCluster(object):
:param redirects: maximum number of server redirect responses to :param redirects: maximum number of server redirect responses to
follow. follow.
:param default_tz_uuid: default transport zone uuid. :param default_tz_uuid: default transport zone uuid.
:param default_next_hop: default next hop for routers in this cluster.
:param uuid: UUID of this cluster (used in MDI configs). :param uuid: UUID of this cluster (used in MDI configs).
:param zone: Zone of this cluster (used in MDI configs). :param zone: Zone of this cluster (used in MDI configs).
""" """
keys = [ keys = ['ip', 'user', 'password', 'default_tz_uuid',
'ip', 'user', 'password', 'default_tz_uuid', 'uuid', 'zone'] 'default_l3_gw_service_uuid', 'uuid', 'zone']
controller_dict = dict([(k, locals()[k]) for k in keys]) controller_dict = dict([(k, locals()[k]) for k in keys])
default_tz_uuid = controller_dict.get('default_tz_uuid')
if not re.match(attributes.UUID_PATTERN, default_tz_uuid):
LOG.warning(_("default_tz_uuid:%(default_tz_uuid)s is not a "
"valid UUID in the cluster %(cluster_name)s. "
"Network operations might not work "
"properly in this cluster"),
{'default_tz_uuid': default_tz_uuid,
'cluster_name': self.name})
# default_l3_gw_service_uuid is an optional parameter
# validate only if specified
l3_gw_service_uuid = controller_dict.get('default_l3_gw_service_uuid')
if (l3_gw_service_uuid and
not re.match(attributes.UUID_PATTERN, l3_gw_service_uuid)):
LOG.warning(_("default_l3_gw_service_uuid:%(l3_gw_service_uuid)s "
"is not a valid UUID in the cluster "
"%(cluster_name)s. Logical router operations "
"might not work properly in this cluster"),
{'l3_gw_service_uuid': l3_gw_service_uuid,
'cluster_name': self.name})
int_keys = [ int_keys = [
'port', 'request_timeout', 'http_timeout', 'retries', 'redirects'] 'port', 'request_timeout', 'http_timeout', 'retries', 'redirects']
for k in int_keys: for k in int_keys:
@ -121,6 +151,10 @@ class NVPCluster(object):
def default_tz_uuid(self): def default_tz_uuid(self):
return self.controllers[0]['default_tz_uuid'] return self.controllers[0]['default_tz_uuid']
@property
def default_l3_gw_service_uuid(self):
return self.controllers[0]['default_l3_gw_service_uuid']
@property @property
def zone(self): def zone(self):
return self.controllers[0]['zone'] return self.controllers[0]['zone']

View File

@ -22,7 +22,6 @@
from copy import copy from copy import copy
import hashlib import hashlib
import itertools
import json import json
import logging import logging
@ -30,6 +29,8 @@ import logging
# no quantum-specific logic in it # no quantum-specific logic in it
from quantum.common import constants from quantum.common import constants
from quantum.common import exceptions as exception from quantum.common import exceptions as exception
from quantum.plugins.nicira.nicira_nvp_plugin.common import (
exceptions as nvp_exc)
from quantum.plugins.nicira.nicira_nvp_plugin import NvpApiClient from quantum.plugins.nicira.nicira_nvp_plugin import NvpApiClient
@ -42,7 +43,21 @@ DEF_TRANSPORT_TYPE = "stt"
URI_PREFIX = "/ws.v1" URI_PREFIX = "/ws.v1"
# Resources exposed by NVP API # Resources exposed by NVP API
LSWITCH_RESOURCE = "lswitch" LSWITCH_RESOURCE = "lswitch"
LPORT_RESOURCE = "lport" LSWITCHPORT_RESOURCE = "lport-%s" % LSWITCH_RESOURCE
LROUTER_RESOURCE = "lrouter"
LROUTERPORT_RESOURCE = "lport-%s" % LROUTER_RESOURCE
LROUTERNAT_RESOURCE = "nat-lrouter"
# Constants for NAT rules
MATCH_KEYS = ["destination_ip_addresses", "destination_port_max",
"destination_port_min", "source_ip_addresses",
"source_port_max", "source_port_min", "protocol"]
SNAT_KEYS = ["to_src_port_min", "to_src_port_max", "to_src_ip_min",
"to_src_ip_max"]
DNAT_KEYS = ["to_dst_port", "to_dst_ip_min", "to_dst_ip_max"]
LOCAL_LOGGING = False LOCAL_LOGGING = False
if LOCAL_LOGGING: if LOCAL_LOGGING:
@ -71,27 +86,25 @@ def _build_uri_path(resource,
resource_id=None, resource_id=None,
parent_resource_id=None, parent_resource_id=None,
fields=None, fields=None,
relations=None, filters=None): relations=None, filters=None, is_attachment=False):
# TODO(salvatore-orlando): This is ugly. do something more clever resources = resource.split('-')
# and aovid the if statement res_path = resources[0] + (resource_id and "/%s" % resource_id or '')
if resource == LPORT_RESOURCE: if len(resources) > 1:
res_path = ("%s/%s/%s" % (LSWITCH_RESOURCE, # There is also a parent resource to account for in the uri
res_path = "%s/%s/%s" % (resources[1],
parent_resource_id, parent_resource_id,
resource) + res_path)
(resource_id and "/%s" % resource_id or '')) if is_attachment:
else: res_path = "%s/attachment" % res_path
res_path = resource + (resource_id and
"/%s" % resource_id or '')
params = [] params = []
params.append(fields and "fields=%s" % fields) params.append(fields and "fields=%s" % fields)
params.append(relations and "relations=%s" % relations) params.append(relations and "relations=%s" % relations)
if filters: if filters:
params.extend(['%s=%s' % (k, v) for (k, v) in filters.iteritems()]) params.extend(['%s=%s' % (k, v) for (k, v) in filters.iteritems()])
uri_path = "%s/%s" % (URI_PREFIX, res_path) uri_path = "%s/%s" % (URI_PREFIX, res_path)
query_string = reduce(lambda x, y: "%s&%s" % (x, y), non_empty_params = [x for x in params if x is not None]
itertools.ifilter(lambda x: x is not None, params), if len(non_empty_params):
"") query_string = '&'.join(non_empty_params)
if query_string: if query_string:
uri_path += "?%s" % query_string uri_path += "?%s" % query_string
return uri_path return uri_path
@ -279,6 +292,110 @@ def update_lswitch(cluster, lswitch_id, display_name,
return obj return obj
def create_lrouter(cluster, tenant_id, display_name, nexthop):
""" Create a NVP logical router on the specified cluster.
:param cluster: The target NVP cluster
:param tenant_id: Identifier of the Openstack tenant for which
the logical router is being created
:param display_name: Descriptive name of this logical router
:param nexthop: External gateway IP address for the logical router
:raise NvpApiException: if there is a problem while communicating
with the NVP controller
"""
tags = [{"tag": tenant_id, "scope": "os_tid"}]
lrouter_obj = {
"display_name": display_name,
"tags": tags,
"routing_config": {
"default_route_next_hop": {
"gateway_ip_address": nexthop,
"type": "RouterNextHop"
},
"type": "SingleDefaultRouteImplicitRoutingConfig"
},
"type": "LogicalRouterConfig"
}
try:
return json.loads(do_single_request("POST",
_build_uri_path(LROUTER_RESOURCE),
json.dumps(lrouter_obj),
cluster=cluster))
except NvpApiClient.NvpApiException:
# just log and re-raise - let the caller handle it
LOG.exception(_("An exception occured while communicating with "
"the NVP controller for cluster:%s"), cluster.name)
raise
def delete_lrouter(cluster, lrouter_id):
try:
do_single_request("DELETE",
_build_uri_path(LROUTER_RESOURCE,
resource_id=lrouter_id),
cluster=cluster)
except NvpApiClient.NvpApiException:
# just log and re-raise - let the caller handle it
LOG.exception(_("An exception occured while communicating with "
"the NVP controller for cluster:%s"), cluster.name)
raise
def get_lrouter(cluster, lrouter_id):
try:
return json.loads(do_single_request("GET",
_build_uri_path(LROUTER_RESOURCE,
resource_id=lrouter_id,
relations='LogicalRouterStatus'),
cluster=cluster))
except NvpApiClient.NvpApiException:
# just log and re-raise - let the caller handle it
LOG.exception(_("An exception occured while communicating with "
"the NVP controller for cluster:%s"), cluster.name)
raise
def get_lrouters(cluster, tenant_id, fields=None, filters=None):
actual_filters = {}
if filters:
actual_filters.update(filters)
if tenant_id:
actual_filters['tag'] = tenant_id
actual_filters['tag_scope'] = 'os_tid'
lrouter_fields = "uuid,display_name,fabric_status,tags"
return get_all_query_pages(
_build_uri_path(LROUTER_RESOURCE,
fields=lrouter_fields,
relations='LogicalRouterStatus',
filters=actual_filters),
cluster)
def update_lrouter(cluster, lrouter_id, display_name, nexthop):
lrouter_obj = get_lrouter(cluster, lrouter_id)
if not display_name and not nexthop:
# Nothing to update
return lrouter_obj
# It seems that this is faster than the doing an if on display_name
lrouter_obj["display_name"] = display_name or lrouter_obj["display_name"]
if nexthop:
nh_element = lrouter_obj["routing_config"].get(
"default_route_next_hop")
if nh_element:
nh_element["gateway_ip_address"] = nexthop
try:
return json.loads(do_single_request("PUT",
_build_uri_path(LROUTER_RESOURCE,
resource_id=lrouter_id),
json.dumps(lrouter_obj),
cluster=cluster))
except NvpApiClient.NvpApiException:
# just log and re-raise - let the caller handle it
LOG.exception(_("An exception occured while communicating with "
"the NVP controller for cluster:%s"), cluster.name)
raise
def get_all_networks(cluster, tenant_id, networks): def get_all_networks(cluster, tenant_id, networks):
"""Append the quantum network uuids we can find in the given cluster to """Append the quantum network uuids we can find in the given cluster to
"networks" "networks"
@ -330,26 +447,46 @@ def delete_networks(cluster, net_id, lswitch_ids):
raise exception.QuantumException() raise exception.QuantumException()
def query_ports(cluster, network, relations=None, fields="*", filters=None): def query_lswitch_lports(cluster, ls_uuid, fields="*",
uri = "/ws.v1/lswitch/" + network + "/lport?" filters=None, relations=None):
if relations: # Fix filter for attachments
uri += "relations=%s" % relations
uri += "&fields=%s" % fields
if filters and "attachment" in filters: if filters and "attachment" in filters:
uri += "&attachment_vif_uuid=%s" % filters["attachment"] filters['attachment_vif_uuid'] = filters["attachment"]
del filters['attachment']
uri = _build_uri_path(LSWITCHPORT_RESOURCE, parent_resource_id=ls_uuid,
fields=fields, filters=filters, relations=relations)
try: try:
resp_obj = do_single_request("GET", uri, cluster=cluster) resp_obj = do_single_request("GET", uri, cluster=cluster)
except NvpApiClient.ResourceNotFound as e: except NvpApiClient.ResourceNotFound:
LOG.error(_("Network not found, Error: %s"), str(e)) LOG.exception(_("Logical switch: %s not found"), ls_uuid)
raise exception.NetworkNotFound(net_id=network) raise
except NvpApiClient.NvpApiException as e: except NvpApiClient.NvpApiException:
raise exception.QuantumException() LOG.exception(_("An error occurred while querying logical ports on "
"the NVP platform"))
raise
return json.loads(resp_obj)["results"] return json.loads(resp_obj)["results"]
def delete_port(cluster, port): def query_lrouter_lports(cluster, lr_uuid, fields="*",
filters=None, relations=None):
uri = _build_uri_path(LROUTERPORT_RESOURCE, parent_resource_id=lr_uuid,
fields=fields, filters=filters, relations=relations)
try: try:
do_single_request("DELETE", port['_href'], cluster=cluster) resp_obj = do_single_request("GET", uri, cluster=cluster)
except NvpApiClient.ResourceNotFound:
LOG.exception(_("Logical router: %s not found"), lr_uuid)
raise
except NvpApiClient.NvpApiException:
LOG.exception(_("An error occured while querying logical router "
"ports on the NVP platfom"))
raise
return json.loads(resp_obj)["results"]
def delete_port(cluster, switch, port):
uri = "/ws.v1/lswitch/" + switch + "/lport/" + port
try:
do_single_request("DELETE", uri, cluster=cluster)
except NvpApiClient.ResourceNotFound as e: except NvpApiClient.ResourceNotFound as e:
LOG.error(_("Port or Network not found, Error: %s"), str(e)) LOG.error(_("Port or Network not found, Error: %s"), str(e))
raise exception.PortNotFound(port_id=port['uuid']) raise exception.PortNotFound(port_id=port['uuid'])
@ -357,27 +494,21 @@ def delete_port(cluster, port):
raise exception.QuantumException() raise exception.QuantumException()
def get_port_by_quantum_tag(clusters, lswitch, quantum_tag): def get_logical_port_status(cluster, switch, port):
"""Return (url, cluster_id) of port or raises ResourceNotFound query = ("/ws.v1/lswitch/" + switch + "/lport/"
""" + port + "?relations=LogicalPortStatus")
query = ("/ws.v1/lswitch/%s/lport?fields=admin_status_enabled,"
"fabric_status_up,uuid&tag=%s&tag_scope=q_port_id"
"&relations=LogicalPortStatus" % (lswitch, quantum_tag))
LOG.debug(_("Looking for port with q_tag '%(quantum_tag)s' "
"on: %(lswitch)s"),
locals())
for c in clusters:
try: try:
res_obj = do_single_request('GET', query, cluster=c) res_obj = do_single_request('GET', query, cluster=cluster)
except Exception: except NvpApiClient.ResourceNotFound as e:
continue LOG.error(_("Port or Network not found, Error: %s"), str(e))
raise exception.PortNotFound(port_id=port, net_id=switch)
except NvpApiClient.NvpApiException as e:
raise exception.QuantumException()
res = json.loads(res_obj) res = json.loads(res_obj)
if len(res["results"]) == 1: # copy over admin_status_enabled
return (res["results"][0], c) res["_relations"]["LogicalPortStatus"]["admin_status_enabled"] = (
res["admin_status_enabled"])
LOG.error(_("Port or Network not found")) return res["_relations"]["LogicalPortStatus"]
raise exception.PortNotFound(port_id=quantum_tag, net_id=lswitch)
def get_port_by_display_name(clusters, lswitch, display_name): def get_port_by_display_name(clusters, lswitch, display_name):
@ -483,7 +614,8 @@ 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, security_profiles) port_security_enabled, security_profiles)
path = _build_uri_path(LPORT_RESOURCE, parent_resource_id=lswitch_uuid) path = _build_uri_path(LSWITCHPORT_RESOURCE,
parent_resource_id=lswitch_uuid)
try: try:
resp_obj = do_single_request("POST", path, resp_obj = do_single_request("POST", path,
json.dumps(lport_obj), json.dumps(lport_obj),
@ -498,6 +630,150 @@ def create_lport(cluster, lswitch_uuid, tenant_id, quantum_port_id,
return result return result
def create_router_lport(cluster, lrouter_uuid, tenant_id, quantum_port_id,
display_name, admin_status_enabled, ip_addresses):
""" Creates a logical port on the assigned logical router """
tags = [dict(scope='os_tid', tag=tenant_id),
dict(scope='q_port_id', tag=quantum_port_id)]
lport_obj = dict(
admin_status_enabled=admin_status_enabled,
display_name=display_name,
tags=tags,
ip_addresses=ip_addresses,
type="LogicalRouterPortConfig"
)
path = _build_uri_path(LROUTERPORT_RESOURCE,
parent_resource_id=lrouter_uuid)
try:
resp_obj = do_single_request("POST", path,
json.dumps(lport_obj),
cluster=cluster)
except NvpApiClient.ResourceNotFound as e:
LOG.error(_("Logical router not found, Error: %s"), str(e))
raise
result = json.loads(resp_obj)
LOG.debug(_("Created logical port %(lport_uuid)s on "
"logical router %(lrouter_uuid)s"),
{'lport_uuid': result['uuid'],
'lrouter_uuid': lrouter_uuid})
return result
def update_router_lport(cluster, lrouter_uuid, lrouter_port_uuid,
tenant_id, quantum_port_id, display_name,
admin_status_enabled, ip_addresses):
""" Updates a logical port on the assigned logical router """
lport_obj = dict(
admin_status_enabled=admin_status_enabled,
display_name=display_name,
tags=[dict(scope='os_tid', tag=tenant_id),
dict(scope='q_port_id', tag=quantum_port_id)],
ip_addresses=ip_addresses,
type="LogicalRouterPortConfig"
)
# Do not pass null items to NVP
for key in lport_obj.keys():
if lport_obj[key] is None:
del lport_obj[key]
path = _build_uri_path(LROUTERPORT_RESOURCE,
lrouter_port_uuid,
parent_resource_id=lrouter_uuid)
try:
resp_obj = do_single_request("PUT", path,
json.dumps(lport_obj),
cluster=cluster)
except NvpApiClient.ResourceNotFound as e:
LOG.error(_("Logical router or router port not found, "
"Error: %s"), str(e))
raise
result = json.loads(resp_obj)
LOG.debug(_("Updated logical port %(lport_uuid)s on "
"logical router %(lrouter_uuid)s"),
{'lport_uuid': lrouter_port_uuid, 'lrouter_uuid': lrouter_uuid})
return result
def delete_router_lport(cluster, lrouter_uuid, lport_uuid):
""" Creates a logical port on the assigned logical router """
path = _build_uri_path(LROUTERPORT_RESOURCE, lport_uuid, lrouter_uuid)
try:
do_single_request("DELETE", path, cluster=cluster)
except NvpApiClient.ResourceNotFound as e:
LOG.error(_("Logical router not found, Error: %s"), str(e))
raise
LOG.debug(_("Delete logical router port %(lport_uuid)s on "
"logical router %(lrouter_uuid)s"),
{'lport_uuid': lport_uuid,
'lrouter_uuid': lrouter_uuid})
def delete_peer_router_lport(cluster, lr_uuid, ls_uuid, lp_uuid):
nvp_port = get_port(cluster, ls_uuid, lp_uuid,
relations="LogicalPortAttachment")
try:
relations = nvp_port.get('_relations')
if relations:
att_data = relations.get('LogicalPortAttachment')
if att_data:
lrp_uuid = att_data.get('peer_port_uuid')
if lrp_uuid:
delete_router_lport(cluster, lr_uuid, lrp_uuid)
except (NvpApiClient.NvpApiException, NvpApiClient.ResourceNotFound):
LOG.exception(_("Unable to fetch and delete peer logical "
"router port for logical switch port:%s"),
lp_uuid)
raise
def find_router_gw_port(context, cluster, router_id):
""" Retrieves the external gateway port for a NVP logical router """
# Find the uuid of nvp ext gw logical router port
# TODO(salvatore-orlando): Consider storing it in Quantum DB
results = query_lrouter_lports(
cluster, router_id,
filters={'attachment_gwsvc_uuid': cluster.default_l3_gw_service_uuid})
if len(results):
# Return logical router port
return results[0]
def plug_router_port_attachment(cluster, router_id, port_id,
attachment_uuid, nvp_attachment_type):
"""Attach a router port to the given attachment.
Current attachment types:
- PatchAttachment [-> logical switch port uuid]
- L3GatewayAttachment [-> L3GatewayService uuid]
"""
uri = _build_uri_path(LROUTERPORT_RESOURCE, port_id, router_id,
is_attachment=True)
attach_obj = {}
attach_obj["type"] = nvp_attachment_type
if nvp_attachment_type == "PatchAttachment":
attach_obj["peer_port_uuid"] = attachment_uuid
elif nvp_attachment_type == "L3GatewayAttachment":
attach_obj["l3_gateway_service_uuid"] = attachment_uuid
else:
raise Exception(_("Invalid NVP attachment type '%s'"),
nvp_attachment_type)
try:
resp_obj = do_single_request(
"PUT", uri, json.dumps(attach_obj), cluster=cluster)
except NvpApiClient.ResourceNotFound as e:
LOG.exception(_("Router Port not found, Error: %s"), str(e))
raise
except NvpApiClient.Conflict as e:
LOG.exception(_("Conflict while setting router port attachment"))
raise
except NvpApiClient.NvpApiException as e:
LOG.exception(_("Unable to plug attachment into logical router port"))
raise
result = json.loads(resp_obj)
return result
def get_port_status(cluster, lswitch_id, port_id): def get_port_status(cluster, lswitch_id, port_id):
"""Retrieve the operational status of the port""" """Retrieve the operational status of the port"""
try: try:
@ -653,7 +929,7 @@ def create_security_profile(cluster, tenant_id, security_profile):
'logical_port_ingress_rules': []} 'logical_port_ingress_rules': []}
update_security_group_rules(cluster, rsp['uuid'], rules) update_security_group_rules(cluster, rsp['uuid'], rules)
LOG.debug("Created Security Profile: %s" % rsp) LOG.debug(_("Created Security Profile: %s"), rsp)
return rsp return rsp
@ -674,7 +950,7 @@ def update_security_group_rules(cluster, spid, rules):
except NvpApiClient.NvpApiException as e: except NvpApiClient.NvpApiException as e:
LOG.error(format_exception("Unknown", e, locals())) LOG.error(format_exception("Unknown", e, locals()))
raise exception.QuantumException() raise exception.QuantumException()
LOG.debug("Updated Security Profile: %s" % rsp) LOG.debug(_("Updated Security Profile: %s"), rsp)
return rsp return rsp
@ -686,3 +962,154 @@ def delete_security_profile(cluster, spid):
except NvpApiClient.NvpApiException as e: except NvpApiClient.NvpApiException as e:
LOG.error(format_exception("Unknown", e, locals())) LOG.error(format_exception("Unknown", e, locals()))
raise exception.QuantumException() raise exception.QuantumException()
def _create_nat_match_obj(**kwargs):
nat_match_obj = {'ethertype': 'IPv4'}
delta = set(kwargs.keys()) - set(MATCH_KEYS)
if delta:
raise Exception(_("Invalid keys for NAT match: %s"), delta)
nat_match_obj.update(kwargs)
return nat_match_obj
def _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj):
LOG.debug(_("Creating NAT rule: %s"), nat_rule_obj)
uri = _build_uri_path(LROUTERNAT_RESOURCE, parent_resource_id=router_id)
try:
resp = do_single_request("POST", uri, json.dumps(nat_rule_obj),
cluster=cluster)
except NvpApiClient.ResourceNotFound:
LOG.exception(_("NVP Logical Router %s not found"), router_id)
raise
except NvpApiClient.NvpApiException:
LOG.exception(_("An error occurred while creating the NAT rule "
"on the NVP platform"))
raise
rule = json.loads(resp)
return rule
def create_lrouter_snat_rule(cluster, router_id,
min_src_ip, max_src_ip, **kwargs):
nat_match_obj = _create_nat_match_obj(**kwargs)
nat_rule_obj = {
"to_source_ip_address_min": min_src_ip,
"to_source_ip_address_max": max_src_ip,
"type": "SourceNatRule",
"match": nat_match_obj
}
return _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj)
def create_lrouter_dnat_rule(cluster, router_id, to_min_dst_ip,
to_max_dst_ip, to_dst_port=None, **kwargs):
nat_match_obj = _create_nat_match_obj(**kwargs)
nat_rule_obj = {
"to_destination_ip_address_min": to_min_dst_ip,
"to_destination_ip_address_max": to_max_dst_ip,
"type": "DestinationNatRule",
"match": nat_match_obj
}
if to_dst_port:
nat_rule_obj['to_destination_port'] = to_dst_port
return _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj)
def delete_nat_rules_by_match(cluster, router_id, rule_type,
max_num_expected,
min_num_expected=0,
**kwargs):
# remove nat rules
nat_rules = query_nat_rules(cluster, router_id)
to_delete_ids = []
for r in nat_rules:
if (r['type'] != rule_type):
continue
for key, value in kwargs.iteritems():
if not (key in r['match'] and r['match'][key] == value):
break
else:
to_delete_ids.append(r['uuid'])
if not (len(to_delete_ids) in
range(min_num_expected, max_num_expected + 1)):
raise nvp_exc.NvpNatRuleMismatch(actual_rules=len(to_delete_ids),
min_rules=min_num_expected,
max_rules=max_num_expected)
for rule_id in to_delete_ids:
delete_router_nat_rule(cluster, router_id, rule_id)
def delete_router_nat_rule(cluster, router_id, rule_id):
uri = _build_uri_path(LROUTERNAT_RESOURCE, rule_id, router_id)
try:
do_single_request("DELETE", uri, cluster=cluster)
except NvpApiClient.NvpApiException:
LOG.exception(_("An error occurred while removing NAT rule "
"'%(nat_rule_uuid)s' for logical "
"router '%(lrouter_uuid)s'"),
{'nat_rule_uuid': rule_id, 'lrouter_uuid': router_id})
raise
def get_router_nat_rule(cluster, tenant_id, router_id, rule_id):
uri = _build_uri_path(LROUTERNAT_RESOURCE, rule_id, router_id)
try:
resp = do_single_request("GET", uri, cluster=cluster)
except NvpApiClient.ResourceNotFound:
LOG.exception(_("NAT rule %s not found"), rule_id)
raise
except NvpApiClient.NvpApiException:
LOG.exception(_("An error occured while retrieving NAT rule '%s'"
"from NVP platform"), rule_id)
raise
res = json.loads(resp)
return res
def query_nat_rules(cluster, router_id, fields="*", filters=None):
uri = _build_uri_path(LROUTERNAT_RESOURCE, parent_resource_id=router_id,
fields=fields, filters=filters)
try:
resp = do_single_request("GET", uri, cluster=cluster)
except NvpApiClient.ResourceNotFound:
LOG.exception(_("NVP Logical Router '%s' not found"), router_id)
raise
except NvpApiClient.NvpApiException:
LOG.exception(_("An error occured while retrieving NAT rules for "
"NVP logical router '%s'"), router_id)
raise
res = json.loads(resp)
return res["results"]
# NOTE(salvatore-orlando): The following FIXME applies in general to
# each operation on list attributes.
# FIXME(salvatore-orlando): need a lock around the list of IPs on an iface
def update_lrouter_port_ips(cluster, lrouter_id, lport_id,
ips_to_add, ips_to_remove):
uri = _build_uri_path(LROUTERPORT_RESOURCE, lport_id, lrouter_id)
try:
port = json.loads(do_single_request("GET", uri, cluster=cluster))
# TODO(salvatore-orlando): Enforce ips_to_add intersection with
# ips_to_remove is empty
ip_address_set = set(port['ip_addresses'])
ip_address_set = ip_address_set - set(ips_to_remove)
ip_address_set = ip_address_set | set(ips_to_add)
# Set is not JSON serializable - convert to list
port['ip_addresses'] = list(ip_address_set)
do_single_request("PUT", uri, json.dumps(port), cluster=cluster)
except NvpApiClient.ResourceNotFound as e:
msg = (_("Router Port %(lport_id)s not found on router "
"%(lrouter_id)s") % locals())
LOG.exception(msg)
raise nvp_exc.NvpPluginException(err_desc=msg)
except NvpApiClient.NvpApiException as e:
msg = _("An exception occurred while updating IP addresses on a "
"router logical port:%s") % str(e)
LOG.exception(msg)
raise nvp_exc.NvpPluginException(err_desc=msg)

View File

@ -0,0 +1,28 @@
{
"display_name": "%(display_name)s",
"uuid": "%(uuid)s",
"tags": %(tags_json)s,
"routing_config": {
"type": "SingleDefaultRouteImplicitRoutingConfig",
"_schema": "/ws.v1/schema/SingleDefaultRouteImplicitRoutingConfig",
"default_route_next_hop": {
"type": "RouterNextHop",
"_schema": "/ws.v1/schema/RouterNextHop",
"gateway_ip_address": "%(default_next_hop)s"
}
},
"_schema": "/ws.v1/schema/LogicalRouterConfig",
"_relations": {
"LogicalRouterStatus": {
"_href": "/ws.v1/lrouter/%(uuid)s/status",
"lport_admin_up_count": %(lport_count)d,
"_schema": "/ws.v1/schema/LogicalRouterStatus",
"lport_count": %(lport_count)d,
"fabric_status": true,
"type": "LogicalRouterStatus",
"lport_link_up_count": %(lport_count)d
}
},
"type": "LogicalRouterConfig",
"_href": "/ws.v1/lrouter/%(uuid)s"
}

View File

@ -0,0 +1,11 @@
{
"display_name": "%(display_name)s",
"_href": "/ws.v1/lrouter/%(lr_uuid)s/lport/%(uuid)s",
"tags":
[{"scope": "q_port_id", "tag": "%(quantum_port_id)s"},
{"scope": "os_tid", "tag": "%(tenant_id)s"}],
"ip_addresses": %(ip_addresses_json)s,
"_schema": "/ws.v1/schema/LogicalRouterPortConfig",
"type": "LogicalRouterPortConfig",
"uuid": "%(uuid)s"
}

View File

@ -0,0 +1,9 @@
{
"LogicalPortAttachment":
{
%(peer_port_href_field)s
%(peer_port_uuid_field)s
"type": "%(type)s",
"schema": "/ws.v1/schema/%(type)s"
}
}

View File

@ -0,0 +1,6 @@
{
"_href": "/ws.v1/lrouter/%(lr_uuid)s/nat/%(uuid)s",
"type": "%(type)s",
"match": %(match_json)s,
"uuid": "%(uuid)s"
}

View File

@ -0,0 +1,10 @@
{
"LogicalPortAttachment":
{
%(peer_port_href_field)s
%(peer_port_uuid_field)s
%(vif_uuid_field)s
"type": "%(type)s",
"schema": "/ws.v1/schema/%(type)s"
}
}

View File

@ -0,0 +1,22 @@
{
"display_name": "%(display_name)s",
"uuid": "%(uuid)s",
"tags": [
{
"scope": "os_tid",
"tag": "%(tenant_id)s"
}
],
"routing_config": {
"type": "SingleDefaultRouteImplicitRoutingConfig",
"_schema": "/ws.v1/schema/SingleDefaultRouteImplicitRoutingConfig",
"default_route_next_hop": {
"type": "RouterNextHop",
"_schema": "/ws.v1/schema/RouterNextHop",
"gateway_ip_address": "%(default_next_hop)s"
}
},
"_schema": "/ws.v1/schema/LogicalRouterConfig",
"type": "LogicalRouterConfig",
"_href": "/ws.v1/lrouter/%(uuid)s"
}

View File

@ -0,0 +1,10 @@
{
"display_name": "%(display_name)s",
"_href": "/ws.v1/lrouter/%(lr_uuid)s/lport/%(uuid)s",
"_schema": "/ws.v1/schema/LogicalRouterPortConfig",
"mac_address": "00:00:00:00:00:00",
"admin_status_enabled": true,
"ip_addresses": %(ip_addresses_json)s,
"type": "LogicalRouterPortConfig",
"uuid": "%(uuid)s"
}

View File

@ -0,0 +1,6 @@
{
"_href": "/ws.v1/lrouter/%(lr_uuid)s/nat/%(uuid)s",
"type": "%(type)s",
"match": %(match_json)s,
"uuid": "%(uuid)s"
}

View File

@ -0,0 +1,10 @@
{
"LogicalPortAttachment":
{
%(peer_port_href_field)s
%(peer_port_uuid_field)s
"_href": "/ws.v1/lrouter/%(lr_uuid)s/lport/%(lp_uuid)s/attachment",
"type": "%(type)s",
"schema": "/ws.v1/schema/%(type)s"
}
}

View File

@ -0,0 +1,11 @@
{
"LogicalPortAttachment":
{
%(peer_port_href_field)s
%(peer_port_uuid_field)s
%(vif_uuid_field)s
"_href": "/ws.v1/lswitch/%(ls_uuid)s/lport/%(lp_uuid)s/attachment",
"type": "%(type)s",
"schema": "/ws.v1/schema/%(type)s"
}
}

View File

@ -5,3 +5,4 @@ default_tz_uuid = fake_tz_uuid
nova_zone_id = whatever nova_zone_id = whatever
nvp_cluster_uuid = fake_cluster_uuid nvp_cluster_uuid = fake_cluster_uuid
nvp_controller_connection=fake:443:admin:admin:30:10:2:2 nvp_controller_connection=fake:443:admin:admin:30:10:2:2
default_l3_gw_uuid = whatever

View File

@ -15,39 +15,82 @@
# under the License. # under the License.
import json import json
import logging
import urlparse import urlparse
from quantum.openstack.common import log as logging
from quantum.openstack.common import uuidutils from quantum.openstack.common import uuidutils
LOG = logging.getLogger("fake_nvpapiclient") LOG = logging.getLogger(__name__)
LOG.setLevel(logging.DEBUG)
class FakeClient: class FakeClient:
LSWITCH_RESOURCE = 'lswitch'
LPORT_RESOURCE = 'lport'
LROUTER_RESOURCE = 'lrouter'
NAT_RESOURCE = 'nat'
SECPROF_RESOURCE = 'securityprofile'
LSWITCH_STATUS = 'lswitchstatus'
LROUTER_STATUS = 'lrouterstatus'
LSWITCH_LPORT_RESOURCE = 'lswitch_lport'
LROUTER_LPORT_RESOURCE = 'lrouter_lport'
LROUTER_NAT_RESOURCE = 'lrouter_nat'
LSWITCH_LPORT_STATUS = 'lswitch_lportstatus'
LSWITCH_LPORT_ATT = 'lswitch_lportattachment'
LROUTER_LPORT_STATUS = 'lrouter_lportstatus'
LROUTER_LPORT_ATT = 'lrouter_lportattachment'
RESOURCES = [LSWITCH_RESOURCE, LROUTER_RESOURCE,
LPORT_RESOURCE, NAT_RESOURCE, SECPROF_RESOURCE]
FAKE_GET_RESPONSES = { FAKE_GET_RESPONSES = {
"lswitch": "fake_get_lswitch.json", LSWITCH_RESOURCE: "fake_get_lswitch.json",
"lport": "fake_get_lport.json", LSWITCH_LPORT_RESOURCE: "fake_get_lswitch_lport.json",
"lportstatus": "fake_get_lport_status.json" LSWITCH_LPORT_STATUS: "fake_get_lswitch_lport_status.json",
LSWITCH_LPORT_ATT: "fake_get_lswitch_lport_att.json",
LROUTER_RESOURCE: "fake_get_lrouter.json",
LROUTER_LPORT_RESOURCE: "fake_get_lrouter_lport.json",
LROUTER_LPORT_STATUS: "fake_get_lrouter_lport_status.json",
LROUTER_LPORT_ATT: "fake_get_lrouter_lport_att.json",
LROUTER_STATUS: "fake_get_lrouter_status.json",
LROUTER_NAT_RESOURCE: "fake_get_lrouter_nat.json"
} }
FAKE_POST_RESPONSES = { FAKE_POST_RESPONSES = {
"lswitch": "fake_post_lswitch.json", LSWITCH_RESOURCE: "fake_post_lswitch.json",
"lport": "fake_post_lport.json", LROUTER_RESOURCE: "fake_post_lrouter.json",
"securityprofile": "fake_post_security_profile.json" LSWITCH_LPORT_RESOURCE: "fake_post_lswitch_lport.json",
LROUTER_LPORT_RESOURCE: "fake_post_lrouter_lport.json",
LROUTER_NAT_RESOURCE: "fake_post_lrouter_nat.json",
SECPROF_RESOURCE: "fake_post_security_profile.json"
} }
FAKE_PUT_RESPONSES = { FAKE_PUT_RESPONSES = {
"lswitch": "fake_post_lswitch.json", LSWITCH_RESOURCE: "fake_post_lswitch.json",
"lport": "fake_post_lport.json", LROUTER_RESOURCE: "fake_post_lrouter.json",
"securityprofile": "fake_post_security_profile.json" LSWITCH_LPORT_RESOURCE: "fake_post_lswitch_lport.json",
LROUTER_LPORT_RESOURCE: "fake_post_lrouter_lport.json",
LROUTER_NAT_RESOURCE: "fake_post_lrouter_nat.json",
LSWITCH_LPORT_ATT: "fake_put_lswitch_lport_att.json",
LROUTER_LPORT_ATT: "fake_put_lrouter_lport_att.json",
SECPROF_RESOURCE: "fake_post_security_profile.json"
}
MANAGED_RELATIONS = {
LSWITCH_RESOURCE: [],
LROUTER_RESOURCE: [],
LSWITCH_LPORT_RESOURCE: ['LogicalPortAttachment'],
LROUTER_LPORT_RESOURCE: ['LogicalPortAttachment'],
} }
_fake_lswitch_dict = {} _fake_lswitch_dict = {}
_fake_lport_dict = {} _fake_lrouter_dict = {}
_fake_lportstatus_dict = {} _fake_lswitch_lport_dict = {}
_fake_lrouter_lport_dict = {}
_fake_lrouter_nat_dict = {}
_fake_lswitch_lportstatus_dict = {}
_fake_lrouter_lportstatus_dict = {}
_fake_securityprofile_dict = {} _fake_securityprofile_dict = {}
def __init__(self, fake_files_path): def __init__(self, fake_files_path):
@ -83,9 +126,22 @@ class FakeClient:
fake_lswitch['lport_count'] = 0 fake_lswitch['lport_count'] = 0
return fake_lswitch return fake_lswitch
def _add_lport(self, body, ls_uuid): def _add_lrouter(self, body):
fake_lrouter = json.loads(body)
fake_lrouter['uuid'] = uuidutils.generate_uuid()
self._fake_lrouter_dict[fake_lrouter['uuid']] = fake_lrouter
fake_lrouter['tenant_id'] = self._get_tag(fake_lrouter, 'os_tid')
fake_lrouter['lport_count'] = 0
default_nexthop = fake_lrouter['routing_config'].get(
'default_route_next_hop')
fake_lrouter['default_next_hop'] = default_nexthop.get(
'gateway_ip_address', '0.0.0.0')
return fake_lrouter
def _add_lswitch_lport(self, body, ls_uuid):
fake_lport = json.loads(body) fake_lport = json.loads(body)
fake_lport['uuid'] = uuidutils.generate_uuid() new_uuid = uuidutils.generate_uuid()
fake_lport['uuid'] = new_uuid
# put the tenant_id and the ls_uuid in the main dict # put the tenant_id and the ls_uuid in the main dict
# for simplyfying templating # for simplyfying templating
fake_lport['ls_uuid'] = ls_uuid fake_lport['ls_uuid'] = ls_uuid
@ -93,7 +149,7 @@ class FakeClient:
fake_lport['quantum_port_id'] = self._get_tag(fake_lport, fake_lport['quantum_port_id'] = self._get_tag(fake_lport,
'q_port_id') 'q_port_id')
fake_lport['quantum_device_id'] = self._get_tag(fake_lport, 'vm_id') fake_lport['quantum_device_id'] = self._get_tag(fake_lport, 'vm_id')
self._fake_lport_dict[fake_lport['uuid']] = fake_lport self._fake_lswitch_lport_dict[fake_lport['uuid']] = fake_lport
fake_lswitch = self._fake_lswitch_dict[ls_uuid] fake_lswitch = self._fake_lswitch_dict[ls_uuid]
fake_lswitch['lport_count'] += 1 fake_lswitch['lport_count'] += 1
@ -102,7 +158,31 @@ class FakeClient:
fake_lport_status['ls_uuid'] = fake_lswitch['uuid'] fake_lport_status['ls_uuid'] = fake_lswitch['uuid']
fake_lport_status['ls_name'] = fake_lswitch['display_name'] fake_lport_status['ls_name'] = fake_lswitch['display_name']
fake_lport_status['ls_zone_uuid'] = fake_lswitch['zone_uuid'] fake_lport_status['ls_zone_uuid'] = fake_lswitch['zone_uuid']
self._fake_lportstatus_dict[fake_lport['uuid']] = fake_lport_status self._fake_lswitch_lportstatus_dict[new_uuid] = fake_lport_status
return fake_lport
def _add_lrouter_lport(self, body, lr_uuid):
fake_lport = json.loads(body)
new_uuid = uuidutils.generate_uuid()
fake_lport['uuid'] = new_uuid
# put the tenant_id and the ls_uuid in the main dict
# for simplyfying templating
fake_lport['lr_uuid'] = lr_uuid
fake_lport['tenant_id'] = self._get_tag(fake_lport, 'os_tid')
fake_lport['quantum_port_id'] = self._get_tag(fake_lport,
'q_port_id')
# replace ip_address with its json dump
if 'ip_addresses' in fake_lport:
ip_addresses_json = json.dumps(fake_lport['ip_addresses'])
fake_lport['ip_addresses_json'] = ip_addresses_json
self._fake_lrouter_lport_dict[fake_lport['uuid']] = fake_lport
fake_lrouter = self._fake_lrouter_dict[lr_uuid]
fake_lrouter['lport_count'] += 1
fake_lport_status = fake_lport.copy()
fake_lport_status['lr_tenant_id'] = fake_lrouter['tenant_id']
fake_lport_status['lr_uuid'] = fake_lrouter['uuid']
fake_lport_status['lr_name'] = fake_lrouter['display_name']
self._fake_lrouter_lportstatus_dict[new_uuid] = fake_lport_status
return fake_lport return fake_lport
def _add_securityprofile(self, body): def _add_securityprofile(self, body):
@ -117,29 +197,91 @@ class FakeClient:
fake_securityprofile) fake_securityprofile)
return fake_securityprofile return fake_securityprofile
def _add_lrouter_nat(self, body, lr_uuid):
fake_nat = json.loads(body)
new_uuid = uuidutils.generate_uuid()
fake_nat['uuid'] = new_uuid
fake_nat['lr_uuid'] = lr_uuid
self._fake_lrouter_nat_dict[fake_nat['uuid']] = fake_nat
if 'match' in fake_nat:
match_json = json.dumps(fake_nat['match'])
fake_nat['match_json'] = match_json
return fake_nat
def _build_relation(self, src, dst, resource_type, relation):
if not relation in self.MANAGED_RELATIONS[resource_type]:
return # Relation is not desired in output
if not '_relations' in src or not src['_relations'].get(relation):
return # Item does not have relation
relation_data = src['_relations'].get(relation)
dst_relations = dst.get('_relations')
if not dst_relations:
dst_relations = {}
dst_relations[relation] = relation_data
def _fill_attachment(self, att_data, ls_uuid=None,
lr_uuid=None, lp_uuid=None):
new_data = att_data.copy()
for k in ('ls_uuid', 'lr_uuid', 'lp_uuid'):
if locals().get(k):
new_data[k] = locals()[k]
def populate_field(field_name):
if field_name in att_data:
new_data['%s_field' % field_name] = ('"%s" : "%s",'
% (field_name,
att_data[field_name]))
del new_data[field_name]
else:
new_data['%s_field' % field_name] = ""
for field in ['vif_uuid', 'peer_port_href', 'peer_port_uuid']:
populate_field(field)
return new_data
def _get_resource_type(self, path): def _get_resource_type(self, path):
uri_split = path.split('/') """
resource_type = ('status' in uri_split and Identifies resource type and relevant uuids in the uri
'lport' in uri_split and 'lportstatus'
or 'lport' in uri_split and 'lport' /ws.v1/lswitch/xxx
or 'lswitch' in uri_split and 'lswitch' or /ws.v1/lswitch/xxx/status
'security-profile' in uri_split and 'securityprofile') /ws.v1/lswitch/xxx/lport/yyy
switch_uuid = ('lswitch' in uri_split and /ws.v1/lswitch/xxx/lport/yyy/status
len(uri_split) > 3 and uri_split[3]) /ws.v1/lrouter/zzz
port_uuid = ('lport' in uri_split and /ws.v1/lrouter/zzz/status
len(uri_split) > 5 and uri_split[5]) /ws.v1/lrouter/zzz/lport/www
securityprofile_uuid = ('security-profile' in uri_split and /ws.v1/lrouter/zzz/lport/www/status
len(uri_split) > 3 and uri_split[3]) """
return (resource_type, switch_uuid, port_uuid, securityprofile_uuid) # The first element will always be 'ws.v1' - so we just discard it
uri_split = path.split('/')[1:]
# parse uri_split backwards
suffix = ""
idx = len(uri_split) - 1
if 'status' in uri_split[idx]:
suffix = "status"
idx = idx - 1
elif 'attachment' in uri_split[idx]:
suffix = "attachment"
idx = idx - 1
# then check if we have an uuid
uuids = []
if uri_split[idx].replace('-', '') not in self.RESOURCES:
uuids.append(uri_split[idx])
idx = idx - 1
resource_type = "%s%s" % (uri_split[idx], suffix)
if idx > 1:
uuids.insert(0, uri_split[idx - 1])
resource_type = "%s_%s" % (uri_split[idx - 2], resource_type)
return (resource_type.replace('-', ''), uuids)
def _list(self, resource_type, response_file, def _list(self, resource_type, response_file,
switch_uuid=None, query=None): parent_uuid=None, query=None, relations=None):
(tag_filter, attr_filter) = self._get_filters(query) (tag_filter, attr_filter) = self._get_filters(query)
with open("%s/%s" % (self.fake_files_path, response_file)) as f: with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read() response_template = f.read()
res_dict = getattr(self, '_fake_%s_dict' % resource_type) res_dict = getattr(self, '_fake_%s_dict' % resource_type)
if switch_uuid == "*": if parent_uuid == '*':
switch_uuid = None parent_uuid = None
def _attr_match(res_uuid): def _attr_match(res_uuid):
if not attr_filter: if not attr_filter:
@ -158,16 +300,49 @@ class FakeClient:
for x in res_dict[res_uuid]['tags']]) for x in res_dict[res_uuid]['tags']])
def _lswitch_match(res_uuid): def _lswitch_match(res_uuid):
if (not switch_uuid or # verify that the switch exist
res_dict[res_uuid].get('ls_uuid') == switch_uuid): if parent_uuid and not parent_uuid in self._fake_lswitch_dict:
raise Exception(_("lswitch:%s not found" % parent_uuid))
if (not parent_uuid
or res_dict[res_uuid].get('ls_uuid') == parent_uuid):
return True return True
return False return False
def _lrouter_match(res_uuid):
# verify that the router exist
if parent_uuid and not parent_uuid in self._fake_lrouter_dict:
raise Exception(_("lrouter:%s not found" % parent_uuid))
if (not parent_uuid or
res_dict[res_uuid].get('lr_uuid') == parent_uuid):
return True
return False
def _build_item(resource):
item = json.loads(response_template % resource)
if relations:
for relation in relations:
self._build_relation(resource, item,
resource_type, relation)
return item
for item in res_dict.itervalues(): for item in res_dict.itervalues():
if 'tags' in item: if 'tags' in item:
item['tags_json'] = json.dumps(item['tags']) item['tags_json'] = json.dumps(item['tags'])
items = [json.loads(response_template % res_dict[res_uuid]) if resource_type in (self.LSWITCH_LPORT_RESOURCE,
self.LSWITCH_LPORT_ATT,
self.LSWITCH_LPORT_STATUS):
parent_func = _lswitch_match
elif resource_type in (self.LROUTER_LPORT_RESOURCE,
self.LROUTER_LPORT_ATT,
self.LROUTER_NAT_RESOURCE,
self.LROUTER_LPORT_STATUS):
parent_func = _lrouter_match
else:
parent_func = lambda x: True
items = [_build_item(res_dict[res_uuid])
for res_uuid in res_dict for res_uuid in res_dict
if (_lswitch_match(res_uuid) and if (parent_func(res_uuid) and
_tag_match(res_uuid) and _tag_match(res_uuid) and
_attr_match(res_uuid))] _attr_match(res_uuid))]
@ -175,8 +350,8 @@ class FakeClient:
'result_count': len(items)}) 'result_count': len(items)})
def _show(self, resource_type, response_file, def _show(self, resource_type, response_file,
switch_uuid, port_uuid=None): uuid1, uuid2=None, relations=None):
target_uuid = port_uuid or switch_uuid target_uuid = uuid2 or uuid1
with open("%s/%s" % (self.fake_files_path, response_file)) as f: with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read() response_template = f.read()
res_dict = getattr(self, '_fake_%s_dict' % resource_type) res_dict = getattr(self, '_fake_%s_dict' % resource_type)
@ -194,32 +369,33 @@ 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, sec_uuid) = self._get_resource_type( (res_type, uuids) = self._get_resource_type(parsedurl.path)
parsedurl.path) relations = urlparse.parse_qs(parsedurl.query).get('relations')
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")
if res_type == 'lport': if 'lport' in res_type or 'nat' in res_type:
if p_uuid: if len(uuids) > 1:
return self._show(res_type, response_file, s_uuid, p_uuid) return self._show(res_type, response_file, uuids[0],
uuids[1], relations=relations)
else: else:
return self._list(res_type, response_file, s_uuid, return self._list(res_type, response_file, uuids[0],
query=parsedurl.query) query=parsedurl.query, relations=relations)
elif res_type == 'lportstatus': elif ('lswitch' in res_type or 'lrouter' in res_type
return self._show(res_type, response_file, s_uuid, p_uuid) or self.SECPROF_RESOURCE in res_type):
elif res_type == 'lswitch': if len(uuids) > 0:
if s_uuid: return self._show(res_type, response_file, uuids[0],
return self._show(res_type, response_file, s_uuid) relations=relations)
else: else:
return self._list(res_type, response_file, return self._list(res_type, response_file,
query=parsedurl.query) query=parsedurl.query,
relations=relations)
else: else:
raise Exception("unknown resource:%s" % res_type) raise Exception("unknown resource:%s" % res_type)
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, sec_uuid) = self._get_resource_type( (res_type, uuids) = self._get_resource_type(parsedurl.path)
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")
@ -227,37 +403,76 @@ class FakeClient:
response_template = f.read() response_template = f.read()
add_resource = getattr(self, '_add_%s' % res_type) add_resource = getattr(self, '_add_%s' % res_type)
args = [body] args = [body]
if s_uuid: if len(uuids):
args.append(s_uuid) args.append(uuids[0])
response = response_template % add_resource(*args) response = response_template % add_resource(*args)
return response return response
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, sec_uuid) = self._get_resource_type( (res_type, uuids) = self._get_resource_type(parsedurl.path)
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")
with open("%s/%s" % (self.fake_files_path, response_file)) as f: with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read() response_template = f.read()
# Manage attachment operations
is_attachment = False
if res_type.endswith('attachment'):
is_attachment = True
res_type = res_type[:res_type.index('attachment')]
res_dict = getattr(self, '_fake_%s_dict' % res_type) res_dict = getattr(self, '_fake_%s_dict' % res_type)
resource = res_dict[target_uuid] resource = res_dict[uuids[-1]]
if not is_attachment:
resource.update(json.loads(body)) resource.update(json.loads(body))
else:
relations = resource.get("_relations")
if not relations:
relations = {}
relations['LogicalPortAttachment'] = json.loads(body)
resource['_relations'] = relations
body_2 = json.loads(body)
if body_2['type'] == "PatchAttachment":
# We need to do a trick here
if self.LROUTER_RESOURCE in res_type:
res_type_2 = res_type.replace(self.LROUTER_RESOURCE,
self.LSWITCH_RESOURCE)
elif self.LSWITCH_RESOURCE in res_type:
res_type_2 = res_type.replace(self.LSWITCH_RESOURCE,
self.LROUTER_RESOURCE)
res_dict_2 = getattr(self, '_fake_%s_dict' % res_type_2)
body_2['peer_port_uuid'] = uuids[-1]
resource_2 = res_dict_2[json.loads(body)['peer_port_uuid']]
relations_2 = resource_2.get("_relations")
if not relations_2:
relations_2 = {}
relations_2['LogicalPortAttachment'] = body_2
resource_2['_relations'] = relations_2
elif body_2['type'] == "L3GatewayAttachment":
resource['attachment_gwsvc_uuid'] = (
body_2['l3_gateway_service_uuid'])
if not is_attachment:
response = response_template % resource response = response_template % resource
else:
if res_type == self.LROUTER_LPORT_RESOURCE:
lr_uuid = uuids[0]
ls_uuid = None
elif res_type == self.LSWITCH_LPORT_RESOURCE:
ls_uuid = uuids[0]
lr_uuid = None
lp_uuid = uuids[1]
response = response_template % self._fill_attachment(
json.loads(body), ls_uuid, lr_uuid, lp_uuid)
return response return response
def handle_delete(self, url): def handle_delete(self, url):
parsedurl = urlparse.urlparse(url) parsedurl = urlparse.urlparse(url)
(res_type, s_uuid, p_uuid, sec_uuid) = self._get_resource_type( (res_type, uuids) = self._get_resource_type(parsedurl.path)
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")
res_dict = getattr(self, '_fake_%s_dict' % res_type) res_dict = getattr(self, '_fake_%s_dict' % res_type)
del res_dict[target_uuid] del res_dict[uuids[-1]]
return "" return ""
def fake_request(self, *args, **kwargs): def fake_request(self, *args, **kwargs):
@ -267,5 +482,8 @@ class FakeClient:
def reset_all(self): def reset_all(self):
self._fake_lswitch_dict.clear() self._fake_lswitch_dict.clear()
self._fake_lport_dict.clear() self._fake_lrouter_dict.clear()
self._fake_lportstatus_dict.clear() self._fake_lswitch_lport_dict.clear()
self._fake_lrouter_lport_dict.clear()
self._fake_lswitch_lportstatus_dict.clear()
self._fake_lrouter_lportstatus_dict.clear()

View File

@ -0,0 +1,30 @@
# Copyright 2013 Nicira Networks, Inc.
#
# 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.
#
import unittest2 as unittest
from quantum.openstack.common import cfg
from quantum.plugins.nicira.nicira_nvp_plugin.common import config
class ConfigurationTest(unittest.TestCase):
def test_defaults(self):
self.assertEqual('sqlite://', cfg.CONF.DATABASE.sql_connection)
self.assertEqual(-1, cfg.CONF.DATABASE.sql_max_retries)
self.assertEqual(2, cfg.CONF.DATABASE.reconnect_interval)
self.assertEqual(64, cfg.CONF.NVP.max_lp_per_bridged_ls)
self.assertEqual(256, cfg.CONF.NVP.max_lp_per_overlay_ls)
self.assertEqual(5, cfg.CONF.NVP.concurrent_connections)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import contextlib
import logging import logging
import os import os
@ -30,6 +31,7 @@ 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 import quantum.tests.unit.test_extension_security_group as ext_sg
import quantum.tests.unit.test_l3_plugin as test_l3_plugin
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'
@ -174,6 +176,18 @@ class TestNiciraNetworksV2(test_plugin.TestNetworksV2,
self._test_create_bridge_network(vlan_id=5000) self._test_create_bridge_network(vlan_id=5000)
self.assertEquals(ctx_manager.exception.code, 400) self.assertEquals(ctx_manager.exception.code, 400)
def test_list_networks_filter_by_id(self):
# We add this unit test to cover some logic specific to the
# nvp plugin
with contextlib.nested(self.network(name='net1'),
self.network(name='net2')) as (net1, net2):
query_params = 'id=%s' % net1['network']['id']
self._test_list_resources('network', [net1],
query_params=query_params)
query_params += '&id=%s' % net2['network']['id']
self._test_list_resources('network', [net1, net2],
query_params=query_params)
class NiciraPortSecurityTestCase(psec.PortSecurityDBTestCase): class NiciraPortSecurityTestCase(psec.PortSecurityDBTestCase):
@ -235,3 +249,12 @@ class NiciraSecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase):
class TestNiciraSecurityGroup(ext_sg.TestSecurityGroups, class TestNiciraSecurityGroup(ext_sg.TestSecurityGroups,
NiciraSecurityGroupsTestCase): NiciraSecurityGroupsTestCase):
pass pass
class TestNiciraL3NatTestCase(test_l3_plugin.L3NatDBTestCase,
NiciraPluginV2TestCase):
def test_floatingip_with_assoc_fails(self):
self._test_floatingip_with_assoc_fails(
'quantum.plugins.nicira.nicira_nvp_plugin.'
'QuantumPlugin.NvpPluginV2')

View File

@ -324,12 +324,17 @@ class L3NatDBTestCase(test_db_plugin.QuantumDbPluginV2TestCase):
super(L3NatDBTestCase, self).tearDown() super(L3NatDBTestCase, self).tearDown()
def _create_router(self, fmt, tenant_id, name=None, def _create_router(self, fmt, tenant_id, name=None,
admin_state_up=None, set_context=False): admin_state_up=None, set_context=False,
arg_list=None, **kwargs):
data = {'router': {'tenant_id': tenant_id}} data = {'router': {'tenant_id': tenant_id}}
if name: if name:
data['router']['name'] = name data['router']['name'] = name
if admin_state_up: if admin_state_up:
data['router']['admin_state_up'] = admin_state_up data['router']['admin_state_up'] = admin_state_up
for arg in (('admin_state_up', 'tenant_id') + (arg_list or ())):
# Arg must be present and not empty
if arg in kwargs and kwargs[arg]:
data['router'][arg] = kwargs[arg]
router_req = self.new_create_request('routers', data, fmt) router_req = self.new_create_request('routers', data, fmt)
if set_context and tenant_id: if set_context and tenant_id:
# create a specific auth context for this request # create a specific auth context for this request
@ -1080,7 +1085,7 @@ class L3NatDBTestCase(test_db_plugin.QuantumDbPluginV2TestCase):
self._show('floatingips', fip['floatingip']['id'], self._show('floatingips', fip['floatingip']['id'],
expected_code=exc.HTTPNotFound.code) expected_code=exc.HTTPNotFound.code)
def test_floatingip_with_assoc_fails(self): def _test_floatingip_with_assoc_fails(self, plugin_class):
with self.subnet(cidr='200.0.0.1/24') as public_sub: with self.subnet(cidr='200.0.0.1/24') as public_sub:
self._set_net_external(public_sub['subnet']['network_id']) self._set_net_external(public_sub['subnet']['network_id'])
with self.port() as private_port: with self.port() as private_port:
@ -1093,9 +1098,8 @@ class L3NatDBTestCase(test_db_plugin.QuantumDbPluginV2TestCase):
self._router_interface_action('add', r['router']['id'], self._router_interface_action('add', r['router']['id'],
private_sub['subnet']['id'], private_sub['subnet']['id'],
None) None)
PLUGIN_CLASS = 'quantum.db.l3_db.L3_NAT_db_mixin' method = plugin_class + '._update_fip_assoc'
METHOD = PLUGIN_CLASS + '._update_fip_assoc' with mock.patch(method) as pl:
with mock.patch(METHOD) as pl:
pl.side_effect = q_exc.BadRequest( pl.side_effect = q_exc.BadRequest(
resource='floatingip', resource='floatingip',
msg='fake_error') msg='fake_error')
@ -1117,6 +1121,10 @@ class L3NatDBTestCase(test_db_plugin.QuantumDbPluginV2TestCase):
private_sub['subnet']['id'], private_sub['subnet']['id'],
None) None)
def test_floatingip_with_assoc_fails(self):
self._test_floatingip_with_assoc_fails(
'quantum.db.l3_db.L3_NAT_db_mixin')
def test_floatingip_update(self): def test_floatingip_update(self):
with self.port() as p: with self.port() as p:
private_sub = {'subnet': {'id': private_sub = {'subnet': {'id':