# sql_idle_timeout = 3600
[NVP]
-# The number of logical ports to create per bridged logical switch
+# Maximum number of ports for each bridged logical switch
# max_lp_per_bridged_ls = 64
+# Maximum number of ports for each overlay (stt, gre) logical switch
+# 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.
# concurrent_connections = 3
+# Name of the default cluster where requests should be sent if a nova zone id
+# is not specified. If it is empty or reference a non-existent cluster
+# the first cluster specified in this configuration file will be used
+# default_cluster_name =
#[CLUSTER:example]
# This is uuid of the default NVP Transport zone that will be used for
PHYSICAL_NETWORK = 'provider:physical_network'
SEGMENTATION_ID = 'provider:segmentation_id'
-NETWORK_TYPE_VALUES = ['flat', 'gre', 'local', 'vlan']
+# TODO(salvatore-orlando): Devise a solution for allowing plugins
+# to alter the set of allowed values
+NETWORK_TYPE_VALUES = ['flat', 'gre', 'local', 'vlan', 'stt']
EXTENDED_ATTRIBUTES_2_0 = {
'networks': {
# @author: Aaron Rosen, Nicira Networks, Inc.
-import ConfigParser
-import json
import hashlib
import logging
-import netaddr
-import os
-import sys
-import traceback
-import urllib
-import uuid
+import webob.exc
+# FIXME(salvatore-orlando): get rid of relative imports
from common import config
from quantum.plugins.nicira.nicira_nvp_plugin.api_client import client_eventlet
-import NvpApiClient
-import nvplib
from nvp_plugin_version import PLUGIN_VERSION
+from quantum.plugins.nicira.nicira_nvp_plugin import nicira_models
+
+
from quantum.api.v2 import attributes
+from quantum.api.v2 import base
from quantum.common import constants
-from quantum.common import exceptions as exception
+from quantum.common import exceptions as q_exc
from quantum.common import rpc as q_rpc
from quantum.common import topics
from quantum.db import api as db
from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base
from quantum.db import models_v2
+from quantum.extensions import providernet as pnet
from quantum.openstack.common import cfg
from quantum.openstack.common import rpc
+from quantum import policy
+from quantum.plugins.nicira.nicira_nvp_plugin.common import (exceptions
+ as nvp_exc)
+from quantum.plugins.nicira.nicira_nvp_plugin import nicira_db
+from quantum.plugins.nicira.nicira_nvp_plugin import nvp_cluster
+
+import NvpApiClient
+import nvplib
-CONFIG_FILE = "nvp.ini"
-CONFIG_FILE_PATHS = []
-if os.environ.get('QUANTUM_HOME', None):
- CONFIG_FILE_PATHS.append('%s/etc' % os.environ['QUANTUM_HOME'])
-CONFIG_FILE_PATHS.append("/etc/quantum/plugins/nicira")
LOG = logging.getLogger("QuantumPlugin")
+# Provider network extension - allowed network types for the NVP Plugin
+class NetworkTypes:
+ """ Allowed provider network types for the NVP Plugin """
+ STT = 'stt'
+ GRE = 'gre'
+ FLAT = 'flat'
+ VLAN = 'vlan'
+
+
def parse_config():
"""Parse the supplied plugin configuration.
"sql_idle_timeout": cfg.CONF.DATABASE.sql_idle_timeout,
"sql_dbpool_enable": cfg.CONF.DATABASE.sql_dbpool_enable
}
- nvp_options = {'max_lp_per_bridged_ls': cfg.CONF.NVP.max_lp_per_bridged_ls}
- nvp_options.update({'failover_time': cfg.CONF.NVP.failover_time})
- nvp_options.update({'concurrent_connections':
- cfg.CONF.NVP.concurrent_connections})
-
+ nvp_options = cfg.CONF.NVP
nvp_conf = config.ClusterConfigOptions(cfg.CONF)
cluster_names = config.register_cluster_groups(nvp_conf)
nvp_conf.log_opt_values(LOG, logging.DEBUG)
return q_rpc.PluginRpcDispatcher([self])
-class NVPCluster(object):
- """Encapsulates controller connection and api_client for a cluster.
-
- Accessed within the NvpPluginV2 class.
-
- Each element in the self.controllers list is a dictionary that
- contains the following keys:
- ip, port, user, password, default_tz_uuid, uuid, zone
-
- There may be some redundancy here, but that has been done to provide
- future flexibility.
- """
- def __init__(self, name):
- self._name = name
- self.controllers = []
- self.api_client = None
-
- def __repr__(self):
- ss = ['{ "NVPCluster": [']
- ss.append('{ "name" : "%s" }' % self.name)
- ss.append(',')
- for c in self.controllers:
- ss.append(str(c))
- ss.append(',')
- ss.append('] }')
- return ''.join(ss)
-
- def add_controller(self, ip, port, user, password, request_timeout,
- http_timeout, retries, redirects,
- default_tz_uuid, uuid=None, zone=None):
- """Add a new set of controller parameters.
-
- :param ip: IP address of controller.
- :param port: port controller is listening on.
- :param user: user name.
- :param password: user password.
- :param request_timeout: timeout for an entire API request.
- :param http_timeout: timeout for a connect to a controller.
- :param retries: maximum number of request retries.
- :param redirects: maximum number of server redirect responses to
- follow.
- :param default_tz_uuid: default transport zone uuid.
- :param uuid: UUID of this cluster (used in MDI configs).
- :param zone: Zone of this cluster (used in MDI configs).
- """
-
- keys = [
- 'ip', 'user', 'password', 'default_tz_uuid', 'uuid', 'zone']
- controller_dict = dict([(k, locals()[k]) for k in keys])
-
- int_keys = [
- 'port', 'request_timeout', 'http_timeout', 'retries', 'redirects']
- for k in int_keys:
- controller_dict[k] = int(locals()[k])
-
- self.controllers.append(controller_dict)
-
- def get_controller(self, idx):
- return self.controllers[idx]
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, val=None):
- self._name = val
-
- @property
- def host(self):
- return self.controllers[0]['ip']
-
- @property
- def port(self):
- return self.controllers[0]['port']
-
- @property
- def user(self):
- return self.controllers[0]['user']
-
- @property
- def password(self):
- return self.controllers[0]['password']
-
- @property
- def request_timeout(self):
- return self.controllers[0]['request_timeout']
-
- @property
- def http_timeout(self):
- return self.controllers[0]['http_timeout']
-
- @property
- def retries(self):
- return self.controllers[0]['retries']
-
- @property
- def redirects(self):
- return self.controllers[0]['redirects']
-
- @property
- def default_tz_uuid(self):
- return self.controllers[0]['default_tz_uuid']
-
- @property
- def zone(self):
- return self.controllers[0]['zone']
-
- @property
- def uuid(self):
- return self.controllers[0]['uuid']
-
-
class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
"""
NvpPluginV2 is a Quantum plugin that provides L2 Virtual Network
functionality using NVP.
"""
+ supported_extension_aliases = ["provider"]
+ # Default controller cluster
+ default_cluster = None
+
def __init__(self, loglevel=None):
if loglevel:
logging.basicConfig(level=loglevel)
NvpApiClient.LOG.setLevel(loglevel)
self.db_opts, self.nvp_opts, self.clusters_opts = parse_config()
- self.clusters = []
+ self.clusters = {}
for c_opts in self.clusters_opts:
# Password is guaranteed to be the same across all controllers
# in the same NVP cluster.
- cluster = NVPCluster(c_opts['name'])
+ cluster = nvp_cluster.NVPCluster(c_opts['name'])
for controller_connection in c_opts['nvp_controller_connection']:
args = controller_connection.split(':')
try:
"controller %(conn)s in cluster %(name)s"),
{'conn': controller_connection,
'name': c_opts['name']})
- raise
+ raise nvp_exc.NvpInvalidConnection(
+ conn_params=controller_connection)
api_providers = [(x['ip'], x['port'], True)
for x in cluster.controllers]
http_timeout=cluster.http_timeout,
retries=cluster.retries,
redirects=cluster.redirects,
- failover_time=self.nvp_opts['failover_time'],
- concurrent_connections=self.nvp_opts['concurrent_connections'])
+ failover_time=self.nvp_opts.failover_time,
+ concurrent_connections=self.nvp_opts.concurrent_connections)
# TODO(salvatore-orlando): do login at first request,
- # not when plugin, is instantiated
+ # not when plugin is instantiated
cluster.api_client.login()
+ self.clusters[c_opts['name']] = cluster
- # TODO(pjb): What if the cluster isn't reachable this
- # instant? It isn't good to fall back to invalid cluster
- # strings.
- # Default for future-versions
- self.clusters.append(cluster)
-
- # Connect and configure ovs_quantum db
+ # Connect and configure nvp_quantum db
options = {
'sql_connection': self.db_opts['sql_connection'],
'sql_max_retries': self.db_opts['sql_max_retries'],
'reconnect_interval': self.db_opts['reconnect_interval'],
'base': models_v2.model_base.BASEV2,
}
+ def_cluster_name = self.nvp_opts.default_cluster_name
+ if def_cluster_name and def_cluster_name in self.clusters:
+ self.default_cluster = self.clusters[def_cluster_name]
+ else:
+ first_cluster_name = self.clusters.keys()[0]
+ if not def_cluster_name:
+ LOG.info(_("Default cluster name not specified. "
+ "Using first cluster:%s"), first_cluster_name)
+ elif not def_cluster_name in self.clusters:
+ LOG.warning(_("Default cluster name %(def_cluster_name)s. "
+ "Using first cluster:%(first_cluster_name)s")
+ % locals())
+ # otherwise set 1st cluster as default
+ self.default_cluster = self.clusters[first_cluster_name]
+
db.configure_db(options)
+ # Extend the fault map
+ self._extend_fault_map()
+ # Set up RPC interface for DHCP agent
self.setup_rpc()
+ def _extend_fault_map(self):
+ """ Extends the Quantum Fault Map
+
+ Exceptions specific to the NVP Plugin are mapped to standard
+ HTTP Exceptions
+ """
+ base.FAULT_MAP.update({nvp_exc.NvpInvalidNovaZone:
+ webob.exc.HTTPBadRequest,
+ nvp_exc.NvpNoMorePortsException:
+ webob.exc.HTTPBadRequest})
+
+ def _novazone_to_cluster(self, novazone_id):
+ if novazone_id in self.novazone_cluster_map:
+ return self.novazone_cluster_map[novazone_id]
+ LOG.debug(_("Looking for nova zone: %s") % novazone_id)
+ for x in self.clusters:
+ LOG.debug(_("Looking for nova zone %(novazone_id)s in "
+ "cluster: %(x)s") % locals())
+ if x.zone == str(novazone_id):
+ self.novazone_cluster_map[x.zone] = x
+ return x
+ LOG.error(_("Unable to find cluster config entry for nova zone: %s") %
+ novazone_id)
+ raise nvp_exc.NvpInvalidNovaZone(nova_zone=novazone_id)
+
+ def _find_target_cluster(self, resource):
+ """ Return cluster where configuration should be applied
+
+ If the resource being configured has a paremeter expressing
+ the zone id (nova_id), then select corresponding cluster,
+ otherwise return default cluster.
+
+ """
+ if 'nova_id' in resource:
+ return self._novazone_to_cluster(resource['nova_id'])
+ else:
+ return self.default_cluster
+
+ def _check_provider_view_auth(self, context, network):
+ return policy.check(context,
+ "extension:provider_network:view",
+ network)
+
+ def _enforce_provider_set_auth(self, context, network):
+ return policy.enforce(context,
+ "extension:provider_network:set",
+ network)
+
+ def _handle_provider_create(self, context, attrs):
+ # NOTE(salvatore-orlando): This method has been borrowed from
+ # the OpenvSwtich plugin, altough changed to match NVP specifics.
+ 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)
+ if not (network_type_set or physical_network_set or
+ segmentation_id_set):
+ return
+
+ # Authorize before exposing plugin details to client
+ self._enforce_provider_set_auth(context, attrs)
+ err_msg = None
+ if not network_type_set:
+ err_msg = _("%s required") % pnet.NETWORK_TYPE
+ elif network_type in (NetworkTypes.GRE, NetworkTypes.STT,
+ NetworkTypes.FLAT):
+ if segmentation_id_set:
+ err_msg = _("Segmentation ID cannot be specified with "
+ "flat network type")
+ elif network_type == NetworkTypes.VLAN:
+ if not segmentation_id_set:
+ err_msg = _("Segmentation ID must be specified with "
+ "vlan network type")
+ elif (segmentation_id_set and
+ (segmentation_id < 1 or segmentation_id > 4094)):
+ err_msg = _("%s out of range (1 to 4094)") % segmentation_id
+ else:
+ # Verify segment is not already allocated
+ binding = nicira_db.get_network_binding_by_vlanid(
+ context.session, segmentation_id)
+ if binding:
+ raise q_exc.VlanIdInUse(vlan_id=segmentation_id,
+ physical_network=physical_network)
+ else:
+ err_msg = _("%(net_type_param)s %(net_type_value)s not "
+ "supported") % {'net_type_param': pnet.NETWORK_TYPE,
+ 'net_type_value': network_type}
+ if err_msg:
+ raise q_exc.InvalidInput(error_message=err_msg)
+ # TODO(salvatore-orlando): Validate tranport zone uuid
+ # which should be specified in physical_network
+
+ def _extend_network_dict_provider(self, context, network, binding=None):
+ if self._check_provider_view_auth(context, network):
+ if not binding:
+ binding = nicira_db.get_network_binding(context.session,
+ network['id'])
+ # With NVP plugin 'normal' overlay networks will have no binding
+ # TODO(salvatore-orlando) make sure users can specify a distinct
+ # tz_uuid as 'provider network' for STT net type
+ if binding:
+ network[pnet.NETWORK_TYPE] = binding.binding_type
+ network[pnet.PHYSICAL_NETWORK] = binding.tz_uuid
+ network[pnet.SEGMENTATION_ID] = binding.vlan_id
+
+ def _handle_lswitch_selection(self, cluster, network,
+ network_binding, max_ports,
+ allow_extra_lswitches):
+ lswitches = nvplib.get_lswitches(cluster, network.id)
+ try:
+ # TODO find main_ls too!
+ return [ls for ls in lswitches
+ if (ls['_relations']['LogicalSwitchStatus']
+ ['lport_count'] < max_ports)].pop(0)
+ except IndexError:
+ # Too bad, no switch available
+ LOG.debug(_("No switch has available ports (%d checked)") %
+ len(lswitches))
+ if allow_extra_lswitches:
+ main_ls = [ls for ls in lswitches if ls['uuid'] == network.id]
+ tag_dict = dict((x['scope'], x['tag']) for x in main_ls[0]['tags'])
+ if not 'multi_lswitch' in tag_dict:
+ nvplib.update_lswitch(cluster,
+ main_ls[0]['uuid'],
+ main_ls[0]['display_name'],
+ network['tenant_id'],
+ tags=[{'tag': 'True',
+ 'scope': 'multi_lswitch'}])
+ selected_lswitch = nvplib.create_lswitch(
+ cluster, network.tenant_id,
+ "%s-ext-%s" % (network.name, len(lswitches)),
+ network_binding.binding_type,
+ network_binding.tz_uuid,
+ network_binding.vlan_id,
+ network.id)
+ return selected_lswitch
+ else:
+ LOG.error(_("Maximum number of logical ports reached for "
+ "logical network %s") % network.id)
+ raise nvp_exc.NvpNoMorePortsException(network=network.id)
+
def setup_rpc(self):
# RPC support for dhcp
self.topic = topics.PLUGIN
# Consume from all consumers in a thread
self.conn.consume_in_thread()
- @property
- def cluster(self):
- if len(self.clusters):
- return self.clusters[0]
- return None
-
- def clear_state(self):
- nvplib.clear_state(self.clusters[0])
-
def get_all_networks(self, tenant_id, **kwargs):
networks = []
for c in self.clusters:
}
:raises: exception.NoImplementedError
"""
+ net_data = network['network'].copy()
+ # 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 == attributes.ATTR_NOT_SPECIFIED:
+ net_data[attr] = None
# FIXME(arosen) implement admin_state_up = False in NVP
- if network['network']['admin_state_up'] is False:
+ 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"),
- network['network'].get('name', '<unknown>'))
-
- tenant_id = self._get_tenant_id_for_create(context, network)
- # TODO(salvatore-orlando): if the network is shared this should be
- # probably stored into the lswitch with a tag
- # TODO(salvatore-orlando): Important - provider networks support
- # (might require a bridged TZ)
- net = nvplib.create_network(network['network']['tenant_id'],
- network['network']['name'],
- clusters=self.clusters)
-
- network['network']['id'] = net['net-id']
- return super(NvpPluginV2, self).create_network(context, network)
+ "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)
+ lswitch = nvplib.create_lswitch(target_cluster,
+ tenant_id,
+ net_data.get('name'),
+ net_data.get(pnet.NETWORK_TYPE),
+ net_data.get(pnet.PHYSICAL_NETWORK),
+ net_data.get(pnet.SEGMENTATION_ID))
+ network['network']['id'] = lswitch['uuid']
+
+ with context.session.begin(subtransactions=True):
+ new_net = super(NvpPluginV2, self).create_network(context,
+ network)
+ if net_data.get(pnet.NETWORK_TYPE):
+ net_binding = nicira_db.add_network_binding(
+ context.session, new_net['id'],
+ net_data.get(pnet.NETWORK_TYPE),
+ net_data.get(pnet.PHYSICAL_NETWORK),
+ net_data.get(pnet.SEGMENTATION_ID))
+ self._extend_network_dict_provider(context, new_net,
+ net_binding)
+ return new_net
def delete_network(self, context, id):
"""
:raises: exception.NetworkInUse
:raises: exception.NetworkNotFound
"""
-
super(NvpPluginV2, self).delete_network(context, id)
+
+ # FIXME(salvatore-orlando): Failures here might lead NVP
+ # and quantum state to diverge
pairs = self._get_lswitch_cluster_pairs(id, context.tenant_id)
for (cluster, switches) in pairs:
nvplib.delete_networks(cluster, id, switches)
- LOG.debug(_("delete_network() completed for tenant: %s"),
+ LOG.debug(_("delete_network completed for tenant: %s"),
context.tenant_id)
def _get_lswitch_cluster_pairs(self, netw_id, tenant_id):
"""Figure out the set of lswitches on each cluster that maps to this
network id"""
pairs = []
- for c in self.clusters:
+ for c in self.clusters.itervalues():
lswitches = []
try:
- ls = nvplib.get_network(c, netw_id)
- lswitches.append(ls['uuid'])
- except exception.NetworkNotFound:
+ results = nvplib.get_lswitches(c, netw_id)
+ lswitches.extend([ls['uuid'] for ls in results])
+ except q_exc.NetworkNotFound:
continue
pairs.append((c, lswitches))
if len(pairs) == 0:
- raise exception.NetworkNotFound(net_id=netw_id)
+ raise q_exc.NetworkNotFound(net_id=netw_id)
LOG.debug(_("Returning pairs for network: %s"), pairs)
return pairs
:raises: exception.NetworkNotFound
:raises: exception.QuantumException
"""
- result = {}
- lswitch_query = "&uuid=%s" % id
- # always look for the tenant_id in the resource itself rather than
- # the context, as with shared networks context.tenant_id and
- # network['tenant_id'] might differ on GETs
# goto to the plugin DB and fecth the network
network = self._get_network(context, id)
- # TODO(salvatore-orlando): verify whether the query on os_tid is
- # redundant or not.
- if context.is_admin is False:
- tenant_query = ("&tag=%s&tag_scope=os_tid"
- % network['tenant_id'])
- else:
- tenant_query = ""
- # Then fetch the correspondiong logical switch in NVP as well
- # TODO(salvatore-orlando): verify whether the step on NVP
- # can be completely avoided
- lswitch_url_path = (
- "/ws.v1/lswitch?"
- "fields=uuid,display_name%s%s"
- % (tenant_query, lswitch_query))
+
+ # verify the fabric status of the corresponding
+ # logical switch(es) in nvp
try:
- for c in self.clusters:
- lswitch_results = nvplib.get_all_query_pages(
- lswitch_url_path, c)
- if lswitch_results:
- result['lswitch-display-name'] = (
- lswitch_results[0]['display_name'])
+ # FIXME(salvatore-orlando): This is not going to work unless
+ # nova_id is stored in db once multiple clusters are enabled
+ cluster = self._find_target_cluster(network)
+ lswitches = nvplib.get_lswitches(cluster, id)
+ net_op_status = constants.NET_STATUS_ACTIVE
+ quantum_status = network.status
+ for lswitch in lswitches:
+ lswitch_status = lswitch.get('LogicalSwitchStatus', None)
+ # FIXME(salvatore-orlando): Being unable to fetch the
+ # logical switch status should be an exception.
+ if (lswitch_status and
+ not lswitch_status.get('fabric_status', None)):
+ net_op_status = constants.NET_STATUS_DOWN
break
+ LOG.debug(_("Current network status:%(net_op_status)s; "
+ "Status in Quantum DB:%(quantum_status)s")
+ % locals())
+ if net_op_status != network.status:
+ # update the network status
+ with context.session.begin(subtransactions=True):
+ network.status = net_op_status
except Exception:
- LOG.error(_("Unable to get switches: %s"), traceback.format_exc())
- raise exception.QuantumException()
-
- if 'lswitch-display-name' not in result:
- raise exception.NetworkNotFound(net_id=id)
-
- # Fetch network in quantum
- quantum_db = super(NvpPluginV2, self).get_network(context, id, fields)
- d = {'id': id,
- 'name': result['lswitch-display-name'],
- 'tenant_id': network['tenant_id'],
- 'admin_state_up': True,
- 'status': constants.NET_STATUS_ACTIVE,
- 'shared': network['shared'],
- 'subnets': quantum_db.get('subnets', [])}
-
- LOG.debug(_("get_network() completed for tenant %(tenant_id)s: %(d)s"),
- {'tenant_id': context.tenant_id, 'd': d})
- return d
+ err_msg = _("Unable to get lswitches")
+ LOG.exception(err_msg)
+ raise nvp_exc.NvpPluginException(err_msg=err_msg)
+
+ # Don't do field selection here otherwise we won't be able
+ # to add provider networks fields
+ net_result = self._make_network_dict(network, None)
+ self._extend_network_dict_provider(context, net_result)
+ return self._fields(net_result, fields)
def get_networks(self, context, filters=None, fields=None):
"""
nvp_lswitches = []
quantum_lswitches = (
super(NvpPluginV2, self).get_networks(context, filters))
+ for net in quantum_lswitches:
+ self._extend_network_dict_provider(context, net)
if context.is_admin and not filters.get("tenant_id"):
tenant_filter = ""
else:
tenant_filter = "&tag=%s&tag_scope=os_tid" % context.tenant_id
- lswitch_filters = "uuid,display_name,fabric_status"
+ lswitch_filters = "uuid,display_name,fabric_status,tags"
lswitch_url_path = (
"/ws.v1/lswitch?fields=%s&relations=LogicalSwitchStatus%s"
% (lswitch_filters, tenant_filter))
try:
- for c in self.clusters:
+ for c in self.clusters.itervalues():
res = nvplib.get_all_query_pages(
lswitch_url_path, c)
nvp_lswitches.extend(res)
except Exception:
- LOG.error(_("Unable to get switches: %s"), traceback.format_exc())
- raise exception.QuantumException()
+ err_msg = _("Unable to get logical switches")
+ LOG.exception(err_msg)
+ raise nvp_exc.NvpPluginException(err_msg=err_msg)
# TODO (Aaron) This can be optimized
if filters.get("id"):
nvp_lswitches = filtered_lswitches
for quantum_lswitch in quantum_lswitches:
- Found = False
for nvp_lswitch in nvp_lswitches:
- if nvp_lswitch["uuid"] == quantum_lswitch["id"]:
+ # TODO(salvatore-orlando): watch out for "extended" lswitches
+ if nvp_lswitch['uuid'] == quantum_lswitch["id"]:
if (nvp_lswitch["_relations"]["LogicalSwitchStatus"]
["fabric_status"]):
quantum_lswitch["status"] = constants.NET_STATUS_ACTIVE
quantum_lswitch["status"] = constants.NET_STATUS_DOWN
quantum_lswitch["name"] = nvp_lswitch["display_name"]
nvp_lswitches.remove(nvp_lswitch)
- Found = True
break
-
- if not Found:
- raise Exception(_("Quantum and NVP Databases are out of "
- "Sync!"))
+ else:
+ raise nvp_exc.NvpOutOfSyncException()
# do not make the case in which switches are found in NVP
# but not in Quantum catastrophic.
if len(nvp_lswitches):
if network["network"].get("admin_state_up"):
if network['network']["admin_state_up"] is False:
- raise exception.NotImplementedError("admin_state_up=False "
- "networks are not "
- "supported.")
+ raise q_exc.NotImplementedError(_("admin_state_up=False "
+ "networks are not "
+ "supported."))
params = {}
params["network"] = network["network"]
pairs = self._get_lswitch_cluster_pairs(id, context.tenant_id)
if network['network'].get("name"):
for (cluster, switches) in pairs:
for switch in switches:
- result = nvplib.update_network(cluster, switch, **params)
+ nvplib.update_lswitch(cluster, switch,
+ network['network']['name'])
LOG.debug(_("update_network() completed for tenant: %s"),
context.tenant_id)
lport_fields_str = ("tags,admin_status_enabled,display_name,"
"fabric_status_up")
try:
- for c in self.clusters:
+ for c in self.clusters.itervalues():
lport_query_path = (
"/ws.v1/lswitch/%s/lport?fields=%s&%s%stag_scope=q_port_id"
"&relations=LogicalPortStatus" %
nvp_lports[tag["tag"]] = port
except Exception:
- LOG.error(_("Unable to get ports: %s"), traceback.format_exc())
- raise exception.QuantumException()
+ err_msg = _("Unable to get ports")
+ LOG.exception(err_msg)
+ raise nvp_exc.NvpPluginException(err_msg=err_msg)
lports = []
for quantum_lport in quantum_lports:
del nvp_lports[quantum_lport["id"]]
lports.append(quantum_lport)
except KeyError:
- raise Exception(_("Quantum and NVP Databases are out of "
- "Sync!"))
+
+ LOG.debug(_("Quantum logical port %s was not found on NVP"),
+ quantum_lport['id'])
+
# do not make the case in which ports are found in NVP
# but not in Quantum catastrophic.
if len(nvp_lports):
"admin_state_up": Sets admin state of port. if down, port
does not forward packets.
"status": dicates whether port is currently operational
- (limit values to "ACTIVE", "DOWN", "BUILD", and
- "ERROR"?)
+ (limit values to "ACTIVE", "DOWN", "BUILD", and "ERROR")
"fixed_ips": list of subnet ID's and IP addresses to be used on
this port
"device_id": identifies the device (e.g., virtual server) using
:raises: exception.NetworkNotFound
:raises: exception.StateInvalid
"""
-
+ tenant_id = self._get_tenant_id_for_create(context, port['port'])
# Set admin_state_up False since not created in NVP set
+ # TODO(salvatore-orlando) : verify whether subtransactions can help
+ # us avoiding multiple operations on the db. This might also allow
+ # us to use the same identifier for the NVP and the Quantum port
+ # Set admin_state_up False since not created in NVP yet
port["port"]["admin_state_up"] = False
# First we allocate port in quantum database
- try:
- quantum_db = super(NvpPluginV2, self).create_port(context, port)
- except Exception as e:
- raise e
+ quantum_db = super(NvpPluginV2, self).create_port(context, port)
- # Update fields obtained from quantum db
+ # Update fields obtained from quantum db (eg: MAC address)
port["port"].update(quantum_db)
-
# We want port to be up in NVP
port["port"]["admin_state_up"] = True
- params = {}
- params["max_lp_per_bridged_ls"] = \
- self.nvp_opts["max_lp_per_bridged_ls"]
- params["port"] = port["port"]
- params["clusters"] = self.clusters
- tenant_id = self._get_tenant_id_for_create(context, port["port"])
-
+ port_data = port['port']
+ # Fetch the network and network binding from Quantum db
+ network = self._get_network(context, port_data['network_id'])
+ network_binding = nicira_db.get_network_binding(
+ context.session, port_data['network_id'])
+ max_ports = self.nvp_opts.max_lp_per_overlay_ls
+ allow_extra_lswitches = False
+ if (network_binding and
+ network_binding.binding_type in (NetworkTypes.FLAT,
+ NetworkTypes.VLAN)):
+ max_ports = self.nvp_opts.max_lp_per_bridged_ls
+ allow_extra_lswitches = True
try:
- port["port"], nvp_port_id = nvplib.create_port(tenant_id,
- **params)
- nvplib.plug_interface(self.clusters, port["port"]["network_id"],
- nvp_port_id, "VifAttachment",
- port["port"]["id"])
- except Exception as e:
- # failed to create port in NVP delete port from quantum_db
+ q_net_id = port_data['network_id']
+ cluster = self._find_target_cluster(port_data)
+ selected_lswitch = self._handle_lswitch_selection(
+ cluster, network, network_binding, max_ports,
+ allow_extra_lswitches)
+ lswitch_uuid = selected_lswitch['uuid']
+ lport = nvplib.create_lport(cluster,
+ lswitch_uuid,
+ port_data['tenant_id'],
+ port_data['id'],
+ port_data['name'],
+ port_data['device_id'],
+ port_data['admin_state_up'],
+ port_data['mac_address'],
+ port_data['fixed_ips'])
+ # Get NVP ls uuid for quantum network
+ nvplib.plug_interface(cluster, selected_lswitch['uuid'],
+ lport['uuid'], "VifAttachment",
+ port_data['id'])
+ except nvp_exc.NvpNoMorePortsException as e:
+ LOG.error(_("Number of available ports for network %s exhausted"),
+ port_data['network_id'])
super(NvpPluginV2, self).delete_port(context, port["port"]["id"])
raise e
+ except Exception:
+ # failed to create port in NVP delete port from quantum_db
+ err_msg = _("An exception occured while plugging the interface "
+ "in NVP for port %s") % port_data['id']
+ LOG.exception(err_msg)
+ super(NvpPluginV2, self).delete_port(context, port["port"]["id"])
+ raise nvp_exc.NvpPluginException(err_desc=err_msg)
- d = {"port-id": port["port"]["id"],
- "port-op-status": port["port"]["status"]}
-
- LOG.debug(_("create_port() completed for tenant %(tenant_id)s: %(d)s"),
- locals())
+ LOG.debug(_("create_port completed on NVP for tenant %(tenant_id)s: "
+ "(%(id)s)") % port_data)
- # update port with admin_state_up True
+ # update port on Quantum DB with admin_state_up True
port_update = {"port": {"admin_state_up": True}}
return super(NvpPluginV2, self).update_port(context,
port["port"]["id"],
"""
params = {}
- quantum_db = super(NvpPluginV2, self).get_port(context, id)
+ port_quantum = super(NvpPluginV2, self).get_port(context, id)
port_nvp, cluster = (
- nvplib.get_port_by_quantum_tag(self.clusters,
- quantum_db["network_id"], id))
-
- LOG.debug(_("Update port request: %s"), params)
+ nvplib.get_port_by_quantum_tag(self.clusters.itervalues(),
+ port_quantum["network_id"], id))
params["cluster"] = cluster
- params["port"] = port["port"]
- params["port"]["id"] = quantum_db["id"]
- params["port"]["tenant_id"] = quantum_db["tenant_id"]
- result = nvplib.update_port(quantum_db["network_id"],
- port_nvp["uuid"], **params)
- LOG.debug(_("update_port() completed for tenant: %s"),
- context.tenant_id)
-
+ params["port"] = port_quantum
+ LOG.debug(_("Update port request: %s"), params)
+ nvplib.update_port(port_quantum['network_id'],
+ port_nvp['uuid'], **params)
return super(NvpPluginV2, self).update_port(context, id, port)
def delete_port(self, context, id):
:raises: exception.NetworkNotFound
"""
- port, cluster = nvplib.get_port_by_quantum_tag(self.clusters,
- '*', id)
+ # TODO(salvatore-orlando): pass only actual cluster
+ port, cluster = nvplib.get_port_by_quantum_tag(
+ self.clusters.itervalues(), '*', id)
if port is None:
- raise exception.PortNotFound(port_id=id)
+ raise q_exc.PortNotFound(port_id=id)
# TODO(bgh): if this is a bridged network and the lswitch we just got
# back will have zero ports after the delete we should garbage collect
# the lswitch.
quantum_db = super(NvpPluginV2, self).get_port(context, id, fields)
+ #TODO: pass only the appropriate cluster here
+ #Look for port in all lswitches
port, cluster = (
- nvplib.get_port_by_quantum_tag(self.clusters,
- quantum_db["network_id"], id))
+ nvplib.get_port_by_quantum_tag(self.clusters.itervalues(),
+ "*", id))
quantum_db["admin_state_up"] = port["admin_status_enabled"]
if port["_relations"]["LogicalPortStatus"]["fabric_status_up"]:
eventlet.monkey_patch()
logging.basicConfig(level=logging.INFO)
-lg = logging.getLogger('nvp_api_client')
+LOG = logging.getLogger(__name__)
# Default parameters.
DEFAULT_FAILOVER_TIME = 5
api_providers are configured.
'''
if not self._api_providers:
- lg.warn(_("[%d] no API providers currently available."), rid)
+ LOG.warn(_("[%d] no API providers currently available."), rid)
return None
# The sleep time is to give controllers time to become consistent after
# there has been a change in the controller used as the api_provider.
now = time.time()
if now < getattr(self, '_issue_conn_barrier', now):
- lg.warn(_("[%d] Waiting for failover timer to expire."), rid)
+ LOG.warn(_("[%d] Waiting for failover timer to expire."), rid)
time.sleep(self._issue_conn_barrier - now)
# Print out a warning if all connections are in use.
if self._conn_pool[self._active_conn_pool_idx].empty():
- lg.debug(_("[%d] Waiting to acquire client connection."), rid)
+ LOG.debug(_("[%d] Waiting to acquire client connection."), rid)
# Try to acquire a connection (block in get() until connection
# available or timeout occurs).
if active_conn_pool_idx != self._active_conn_pool_idx:
# active_conn_pool became inactive while we were waiting.
# Put connection back on old pool and try again.
- lg.warn(_("[%(rid)d] Active pool expired while waiting for "
- "connection: %(conn)s"),
- {'rid': rid, 'conn': _conn_str(conn)})
+ LOG.warn(_("[%(rid)d] Active pool expired while waiting for "
+ "connection: %(conn)s"),
+ {'rid': rid, 'conn': _conn_str(conn)})
self._conn_pool[active_conn_pool_idx].put(conn)
return self.acquire_connection(rid=rid)
# Check if the connection has been idle too long.
now = time.time()
if getattr(conn, 'last_used', now) < now - self.CONN_IDLE_TIMEOUT:
- lg.info(_("[%(rid)d] Connection %(conn)s idle for %(sec)0.2f "
- "seconds; reconnecting."),
- {'rid': rid, 'conn': _conn_str(conn),
- 'sec': now - conn.last_used})
+ LOG.info(_("[%(rid)d] Connection %(conn)s idle for %(sec)0.2f "
+ "seconds; reconnecting."),
+ {'rid': rid, 'conn': _conn_str(conn),
+ 'sec': now - conn.last_used})
conn = self._create_connection(*self._conn_params(conn))
# Stash conn pool so conn knows where to go when it releases.
conn.last_used = now
qsize = self._conn_pool[self._active_conn_pool_idx].qsize()
- lg.debug(_("[%(rid)d] Acquired connection %(conn)s. %(qsize)d "
- "connection(s) available."),
- {'rid': rid, 'conn': _conn_str(conn), 'qsize': qsize})
+ LOG.debug(_("[%(rid)d] Acquired connection %(conn)s. %(qsize)d "
+ "connection(s) available."),
+ {'rid': rid, 'conn': _conn_str(conn), 'qsize': qsize})
return conn
def release_connection(self, http_conn, bad_state=False, rid=-1):
:param rid: request id passed in from request eventlet.
'''
if self._conn_params(http_conn) not in self._api_providers:
- lg.warn(_("[%(rid)d] Released connection '%(conn)s' is not an "
- "API provider for the cluster"),
- {'rid': rid, 'conn': _conn_str(http_conn)})
+ LOG.warn(_("[%(rid)d] Released connection '%(conn)s' is not an "
+ "API provider for the cluster"),
+ {'rid': rid, 'conn': _conn_str(http_conn)})
return
# Retrieve "home" connection pool.
conn_pool = self._conn_pool[conn_pool_idx]
if bad_state:
# Reconnect to provider.
- lg.warn(_("[%(rid)d] Connection returned in bad state, "
- "reconnecting to %(conn)s"),
- {'rid': rid, 'conn': _conn_str(http_conn)})
+ LOG.warn(_("[%(rid)d] Connection returned in bad state, "
+ "reconnecting to %(conn)s"),
+ {'rid': rid, 'conn': _conn_str(http_conn)})
http_conn = self._create_connection(*self._conn_params(http_conn))
http_conn.idx = conn_pool_idx
# This pool is no longer in a good state. Switch to next pool.
self._active_conn_pool_idx += 1
self._active_conn_pool_idx %= len(self._conn_pool)
- lg.warn(_("[%(rid)d] Switched active_conn_pool from "
- "%(idx)d to %(pool_idx)d."),
- {'rid': rid, 'idx': http_conn.idx,
- 'pool_idx': self._active_conn_pool_idx})
+ LOG.warn(_("[%(rid)d] Switched active_conn_pool from "
+ "%(idx)d to %(pool_idx)d."),
+ {'rid': rid, 'idx': http_conn.idx,
+ 'pool_idx': self._active_conn_pool_idx})
# No connections to the new provider allowed until after this
# timer has expired (allow time for synchronization).
self._issue_conn_barrier = time.time() + self._failover_time
conn_pool.put(http_conn)
- lg.debug(_("[%(rid)d] Released connection %(conn)s. "
- "%(qsize)d connection(s) available."),
- {'rid': rid, 'conn': _conn_str(http_conn),
- 'qsize': conn_pool.qsize()})
+ LOG.debug(_("[%(rid)d] Released connection %(conn)s. "
+ "%(qsize)d connection(s) available."),
+ {'rid': rid, 'conn': _conn_str(http_conn),
+ 'qsize': conn_pool.qsize()})
@property
def need_login(self):
self.login()
self._doing_login_sem.release()
else:
- lg.debug(_("Waiting for auth to complete"))
+ LOG.debug(_("Waiting for auth to complete"))
self._doing_login_sem.acquire()
self._doing_login_sem.release()
return self._cookie
if ret:
if isinstance(ret, Exception):
- lg.error(_('NvpApiClient: login error "%s"'), ret)
+ LOG.error(_('NvpApiClient: login error "%s"'), ret)
raise ret
self._cookie = None
cookie = ret.getheader("Set-Cookie")
if cookie:
- lg.debug(_("Saving new authentication cookie '%s'"), cookie)
+ LOG.debug(_("Saving new authentication cookie '%s'"), cookie)
self._cookie = cookie
self._need_login = False
nvp_opts = [
cfg.IntOpt('max_lp_per_bridged_ls', default=64),
+ cfg.IntOpt('max_lp_per_overlay_ls', default=256),
cfg.IntOpt('concurrent_connections', default=5),
- cfg.IntOpt('failover_time', default=240)
+ cfg.IntOpt('failover_time', default=240),
+ cfg.StrOpt('default_cluster_name')
]
cluster_opts = [
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Nicira Networks, 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 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 Plugin exceptions """
+
+from quantum.common import exceptions as q_exc
+
+
+class NvpPluginException(q_exc.QuantumException):
+ message = _("An unexpected error occurred in the NVP Plugin:%(err_desc)s")
+
+
+class NvpInvalidConnection(NvpPluginException):
+ message = _("Invalid NVP connection parameters: %(conn_params)s")
+
+
+class NvpInvalidNovaZone(NvpPluginException):
+ message = _("Unable to find cluster config entry "
+ "for nova zone: %(nova_zone)s")
+
+
+class NvpNoMorePortsException(NvpPluginException):
+ message = _("Unable to create port on network %(network)s. "
+ "Maximum number of ports reached")
+
+
+class NvpOutOfSyncException(NvpPluginException):
+ message = _("Quantum state has diverged from the networking backend!")
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 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 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 logging
+
+from sqlalchemy.orm import exc
+
+import quantum.db.api as db
+from quantum.plugins.nicira.nicira_nvp_plugin import nicira_models
+
+LOG = logging.getLogger(__name__)
+
+
+def get_network_binding(session, network_id):
+ session = session or db.get_session()
+ try:
+ binding = (session.query(nicira_models.NvpNetworkBinding).
+ filter_by(network_id=network_id).
+ one())
+ return binding
+ except exc.NoResultFound:
+ return
+
+
+def get_network_binding_by_vlanid(session, vlan_id):
+ session = session or db.get_session()
+ try:
+ binding = (session.query(nicira_models.NvpNetworkBinding).
+ filter_by(vlan_id=vlan_id).
+ one())
+ return binding
+ except exc.NoResultFound:
+ return
+
+
+def add_network_binding(session, network_id, binding_type, tz_uuid, vlan_id):
+ with session.begin(subtransactions=True):
+ binding = nicira_models.NvpNetworkBinding(network_id, binding_type,
+ tz_uuid, vlan_id)
+ session.add(binding)
+ return binding
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 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 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.
+
+
+from sqlalchemy import Column, Enum, ForeignKey, Integer, String
+
+from quantum.db.models_v2 import model_base
+
+
+class NvpNetworkBinding(model_base.BASEV2):
+ """Represents a binding of a virtual network with a transport zone.
+
+ This model class associates a Quantum network with a transport zone;
+ optionally a vlan ID might be used if the binding type is 'bridge'
+ """
+ __tablename__ = 'nvp_network_bindings'
+
+ network_id = Column(String(36),
+ ForeignKey('networks.id', ondelete="CASCADE"),
+ primary_key=True)
+ # 'flat', 'vlan', stt' or 'gre'
+ binding_type = Column(Enum('flat', 'vlan', 'stt', 'gre'), nullable=False)
+ tz_uuid = Column(String(36))
+ vlan_id = Column(Integer)
+
+ def __init__(self, network_id, binding_type, tz_uuid, vlan_id):
+ self.network_id = network_id
+ self.binding_type = binding_type
+ self.tz_uuid = tz_uuid
+ self.vlan_id = vlan_id
+
+ def __repr__(self):
+ return "<NetworkBinding(%s,%s,%s,%s)>" % (self.network_id,
+ self.binding_type,
+ self.tz_uuid,
+ self.vlan_id)
--- /dev/null
+# Copyright 2012 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 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+
+
+class NVPCluster(object):
+ """Encapsulates controller connection and api_client for a cluster.
+
+ Accessed within the NvpPluginV2 class.
+
+ Each element in the self.controllers list is a dictionary that
+ contains the following keys:
+ ip, port, user, password, default_tz_uuid, uuid, zone
+
+ There may be some redundancy here, but that has been done to provide
+ future flexibility.
+ """
+
+ def __init__(self, name):
+ self._name = name
+ self.controllers = []
+ self.api_client = None
+
+ def __repr__(self):
+ ss = ['{ "NVPCluster": [']
+ ss.append('{ "name" : "%s" }' % self.name)
+ ss.append(',')
+ for c in self.controllers:
+ ss.append(str(c))
+ ss.append(',')
+ ss.append('] }')
+ return ''.join(ss)
+
+ def add_controller(self, ip, port, user, password, request_timeout,
+ http_timeout, retries, redirects,
+ default_tz_uuid, uuid=None, zone=None):
+ """Add a new set of controller parameters.
+
+ :param ip: IP address of controller.
+ :param port: port controller is listening on.
+ :param user: user name.
+ :param password: user password.
+ :param request_timeout: timeout for an entire API request.
+ :param http_timeout: timeout for a connect to a controller.
+ :param retries: maximum number of request retries.
+ :param redirects: maximum number of server redirect responses to
+ follow.
+ :param default_tz_uuid: default transport zone uuid.
+ :param uuid: UUID of this cluster (used in MDI configs).
+ :param zone: Zone of this cluster (used in MDI configs).
+ """
+
+ keys = [
+ 'ip', 'user', 'password', 'default_tz_uuid', 'uuid', 'zone']
+ controller_dict = dict([(k, locals()[k]) for k in keys])
+
+ int_keys = [
+ 'port', 'request_timeout', 'http_timeout', 'retries', 'redirects']
+ for k in int_keys:
+ controller_dict[k] = int(locals()[k])
+
+ self.controllers.append(controller_dict)
+
+ def get_controller(self, idx):
+ return self.controllers[idx]
+
+ @property
+ def name(self):
+ return self._name
+
+ @name.setter
+ def name(self, val=None):
+ self._name = val
+
+ @property
+ def host(self):
+ return self.controllers[0]['ip']
+
+ @property
+ def port(self):
+ return self.controllers[0]['port']
+
+ @property
+ def user(self):
+ return self.controllers[0]['user']
+
+ @property
+ def password(self):
+ return self.controllers[0]['password']
+
+ @property
+ def request_timeout(self):
+ return self.controllers[0]['request_timeout']
+
+ @property
+ def http_timeout(self):
+ return self.controllers[0]['http_timeout']
+
+ @property
+ def retries(self):
+ return self.controllers[0]['retries']
+
+ @property
+ def redirects(self):
+ return self.controllers[0]['redirects']
+
+ @property
+ def default_tz_uuid(self):
+ return self.controllers[0]['default_tz_uuid']
+
+ @property
+ def zone(self):
+ return self.controllers[0]['zone']
+
+ @property
+ def uuid(self):
+ return self.controllers[0]['uuid']
from copy import copy
import functools
+import itertools
import json
import hashlib
import logging
import uuid
from eventlet import semaphore
+
import NvpApiClient
#FIXME(danwent): I'd like this file to get to the point where it has
from quantum.common import constants
from quantum.common import exceptions as exception
+# HTTP METHODS CONSTANTS
+HTTP_GET = "GET"
+HTTP_POST = "POST"
+# Default transport type for logical switches
+DEF_TRANSPORT_TYPE = "stt"
+# Prefix to be used for all NVP API calls
+URI_PREFIX = "/ws.v1"
+# Resources exposed by NVP API
+LSWITCH_RESOURCE = "lswitch"
+LPORT_RESOURCE = "lport"
+
LOCAL_LOGGING = False
if LOCAL_LOGGING:
from logging.handlers import SysLogHandler
LOG.addHandler(syslog)
LOG.setLevel(logging.DEBUG)
else:
- LOG = logging.getLogger("nvplib")
- LOG.setLevel(logging.INFO)
+ LOG = logging.getLogger(__name__)
+ LOG.setLevel(logging.DEBUG)
# TODO(bgh): it would be more efficient to use a bitmap
taken_context_ids = []
_lqueue_cache = {}
+def _build_uri_path(resource,
+ resource_id=None,
+ parent_resource_id=None,
+ fields=None,
+ relations=None, filters=None):
+ # TODO(salvatore-orlando): This is ugly. do something more clever
+ # and aovid the if statement
+ if resource == LPORT_RESOURCE:
+ res_path = ("%s/%s/%s" % (LSWITCH_RESOURCE,
+ parent_resource_id,
+ resource) +
+ (resource_id and "/%s" % resource_id or ''))
+ else:
+ res_path = resource + (resource_id and
+ "/%s" % resource_id or '')
+
+ params = []
+ params.append(fields and "fields=%s" % fields)
+ params.append(relations and "relations=%s" % relations)
+ if filters:
+ params.extend(['%s=%s' % (k, v) for (k, v) in filters.iteritems()])
+ uri_path = "%s/%s" % (URI_PREFIX, res_path)
+ query_string = reduce(lambda x, y: "%s&%s" % (x, y),
+ itertools.ifilter(lambda x: x is not None, params),
+ "")
+ if query_string:
+ uri_path += "?%s" % query_string
+ return uri_path
+
+
def get_cluster_version(cluster):
"""Return major/minor version #"""
# Get control-cluster nodes
uri = "/ws.v1/control-cluster/node?_page_length=1&fields=uuid"
try:
- res = do_single_request("GET", uri, cluster=cluster)
+ res = do_single_request(HTTP_GET, uri, cluster=cluster)
res = json.loads(res)
except NvpApiClient.NvpApiException:
raise exception.QuantumException()
# running different version so we just need the first node version.
uri = "/ws.v1/control-cluster/node/%s/status" % node_uuid
try:
- res = do_single_request("GET", uri, cluster=cluster)
+ res = do_single_request(HTTP_GET, uri, cluster=cluster)
res = json.loads(res)
except NvpApiClient.NvpApiException:
raise exception.QuantumException()
while need_more_results:
page_cursor_str = (
"_page_cursor=%s" % page_cursor if page_cursor else "")
- res = do_single_request("GET", "%s%s%s" %
+ res = do_single_request(HTTP_GET, "%s%s%s" %
(path, query_marker, page_cursor_str),
cluster=c)
body = json.loads(res)
return (None, None)
-def get_network(cluster, net_id):
- path = "/ws.v1/lswitch/%s" % net_id
+def get_lswitches(cluster, quantum_net_id):
+ lswitch_uri_path = _build_uri_path(LSWITCH_RESOURCE, quantum_net_id,
+ relations="LogicalSwitchStatus")
+ results = []
try:
- resp_obj = do_single_request("GET", path, cluster=cluster)
- network = json.loads(resp_obj)
- LOG.warning(_("### nw:%s"), network)
- except NvpApiClient.ResourceNotFound:
- raise exception.NetworkNotFound(net_id=net_id)
+ resp_obj = do_single_request(HTTP_GET,
+ lswitch_uri_path,
+ cluster=cluster)
+ ls = json.loads(resp_obj)
+ results.append(ls)
+ for tag in ls['tags']:
+ if (tag['scope'] == "multi_lswitch" and
+ tag['tag'] == "True"):
+ # Fetch extra logical switches
+ extra_lswitch_uri_path = _build_uri_path(
+ LSWITCH_RESOURCE,
+ fields="uuid,display_name,tags,lport_count",
+ relations="LogicalSwitchStatus",
+ filters={'tag': quantum_net_id,
+ 'tag_scope': 'quantum_net_id'})
+ extra_switches = get_all_query_pages(extra_lswitch_uri_path,
+ cluster)
+ results.extend(extra_switches)
+ return results
except NvpApiClient.NvpApiException:
+ # TODO(salvatore-olrando): Do a better exception handling
+ # and re-raising
+ LOG.exception(_("An error occured while fetching logical switches "
+ "for Quantum network %s"), quantum_net_id)
raise exception.QuantumException()
- LOG.debug(_("Got network '%(net_id)s': %(network)s"), locals())
- return network
-
-
-def create_lswitch(cluster, lswitch_obj):
- LOG.info(_("Creating lswitch: %s"), lswitch_obj)
- # Warn if no tenant is specified
- found = "os_tid" in [x["scope"] for x in lswitch_obj["tags"]]
- if not found:
- LOG.warn(_("No tenant-id tag specified in logical switch: %s"),
- lswitch_obj)
- uri = "/ws.v1/lswitch"
+
+
+def create_lswitch(cluster, tenant_id, display_name,
+ transport_type=None,
+ transport_zone_uuid=None,
+ vlan_id=None,
+ quantum_net_id=None,
+ **kwargs):
+ nvp_binding_type = transport_type
+ if transport_type in ('flat', 'vlan'):
+ nvp_binding_type = 'bridge'
+ transport_zone_config = {"zone_uuid": (transport_zone_uuid or
+ cluster.default_tz_uuid),
+ "transport_type": (nvp_binding_type or
+ DEF_TRANSPORT_TYPE)}
+ lswitch_obj = {"display_name": display_name,
+ "transport_zones": [transport_zone_config],
+ "tags": [{"tag": tenant_id, "scope": "os_tid"}]}
+ if nvp_binding_type == 'bridge' and vlan_id:
+ transport_zone_config["binding_config"] = {"vlan_translation":
+ [{"transport": vlan_id}]}
+ if quantum_net_id:
+ lswitch_obj["tags"].append({"tag": quantum_net_id,
+ "scope": "quantum_net_id"})
+ if "tags" in kwargs:
+ lswitch_obj["tags"].extend(kwargs["tags"])
+ uri = _build_uri_path(LSWITCH_RESOURCE)
try:
- resp_obj = do_single_request("POST", uri,
- json.dumps(lswitch_obj),
- cluster=cluster)
+ lswitch_res = do_single_request(HTTP_POST, uri,
+ json.dumps(lswitch_obj),
+ cluster=cluster)
except NvpApiClient.NvpApiException:
raise exception.QuantumException()
-
- r = json.loads(resp_obj)
- d = {}
- d["net-id"] = r['uuid']
- d["net-name"] = r['display_name']
- LOG.debug(_("Created logical switch: %s"), d["net-id"])
- return d
+ lswitch = json.loads(lswitch_res)
+ LOG.debug(_("Created logical switch: %s") % lswitch['uuid'])
+ return lswitch
-def update_network(cluster, lswitch_id, **params):
- uri = "/ws.v1/lswitch/" + lswitch_id
- lswitch_obj = {}
- if params["network"]["name"]:
- lswitch_obj["display_name"] = params["network"]["name"]
+def update_lswitch(cluster, lswitch_id, display_name,
+ tenant_id=None, **kwargs):
+ uri = _build_uri_path(LSWITCH_RESOURCE, resource_id=lswitch_id)
+ # TODO(salvatore-orlando): Make sure this operation does not remove
+ # any other important tag set on the lswtich object
+ lswitch_obj = {"display_name": display_name,
+ "tags": [{"tag": tenant_id, "scope": "os_tid"}]}
+ if "tags" in kwargs:
+ lswitch_obj["tags"].extend(kwargs["tags"])
try:
resp_obj = do_single_request("PUT", uri, json.dumps(lswitch_obj),
cluster=cluster)
raise exception.QuantumException()
-def create_network(tenant_id, net_name, **kwargs):
- clusters = kwargs["clusters"]
- # Default to the primary cluster
- cluster = clusters[0]
-
- transport_zone = kwargs.get("transport_zone",
- cluster.default_tz_uuid)
- transport_type = kwargs.get("transport_type", "stt")
- lswitch_obj = {"display_name": net_name,
- "transport_zones": [
- {"zone_uuid": transport_zone,
- "transport_type": transport_type}
- ],
- "tags": [{"tag": tenant_id, "scope": "os_tid"}]}
-
- net = create_lswitch(cluster, lswitch_obj)
- net['net-op-status'] = constants.NET_STATUS_ACTIVE
- return net
-
-
def query_ports(cluster, network, relations=None, fields="*", filters=None):
uri = "/ws.v1/lswitch/" + network + "/lport?"
if relations:
if len(res["results"]) == 1:
return (res["results"][0], c)
- LOG.error(_("Port or Network not found, Error: %s"), str(e))
+ LOG.error(_("Port or Network not found"))
raise exception.PortNotFound(port_id=quantum_tag, net_id=lswitch)
return obj
-def create_port(tenant, **params):
- clusters = params["clusters"]
- dest_cluster = clusters[0] # primary cluster
-
- ls_uuid = params["port"]["network_id"]
+def create_lport(cluster, lswitch_uuid, tenant_id, quantum_port_id,
+ display_name, device_id, admin_status_enabled,
+ mac_address=None, fixed_ips=None):
+ """ Creates a logical port on the assigned logical switch """
# device_id can be longer than 40 so we rehash it
- device_id = hashlib.sha1(params["port"]["device_id"]).hexdigest()
+ hashed_device_id = hashlib.sha1(device_id).hexdigest()
lport_obj = dict(
- admin_status_enabled=params["port"]["admin_state_up"],
- display_name=params["port"]["name"],
- tags=[dict(scope='os_tid', tag=tenant),
- dict(scope='q_port_id', tag=params["port"]["id"]),
- dict(scope='vm_id', tag=device_id)]
+ 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),
+ dict(scope='vm_id', tag=hashed_device_id)],
)
- path = "/ws.v1/lswitch/" + ls_uuid + "/lport"
-
+ path = _build_uri_path(LPORT_RESOURCE, parent_resource_id=lswitch_uuid)
try:
- resp_obj = do_single_request("POST", path, json.dumps(lport_obj),
- cluster=dest_cluster)
+ resp_obj = do_single_request("POST", path,
+ json.dumps(lport_obj),
+ cluster=cluster)
except NvpApiClient.ResourceNotFound as e:
- LOG.error("Network not found, Error: %s" % str(e))
- raise exception.NetworkNotFound(net_id=params["port"]["network_id"])
- except NvpApiClient.NvpApiException as e:
- raise exception.QuantumException()
+ LOG.error("Logical switch not found, Error: %s" % str(e))
+ raise
result = json.loads(resp_obj)
- result['port-op-status'] = get_port_status(dest_cluster, ls_uuid,
- result['uuid'])
-
- params["port"].update({"admin_state_up": result["admin_status_enabled"],
- "status": result["port-op-status"]})
- return (params["port"], result['uuid'])
+ LOG.debug("Created logical port %s on logical swtich %s"
+ % (result['uuid'], lswitch_uuid))
+ return result
def get_port_status(cluster, lswitch_id, port_id):
return constants.PORT_STATUS_DOWN
-def plug_interface(clusters, lswitch_id, port, type, attachment=None):
- dest_cluster = clusters[0] # primary cluster
+def plug_interface(cluster, lswitch_id, port, type, attachment=None):
uri = "/ws.v1/lswitch/" + lswitch_id + "/lport/" + port + "/attachment"
-
lport_obj = {}
if attachment:
lport_obj["vif_uuid"] = attachment
lport_obj["type"] = type
try:
resp_obj = do_single_request("PUT", uri, json.dumps(lport_obj),
- cluster=dest_cluster)
+ cluster=cluster)
except NvpApiClient.ResourceNotFound as e:
LOG.error(_("Port or Network not found, Error: %s"), str(e))
raise exception.PortNotFound(port_id=port, net_id=lswitch_id)
"_relations": {"LogicalSwitchStatus":
{"fabric_status": true,
"type": "LogicalSwitchStatus",
+ "lport_count": %(lport_count)d,
"_href": "/ws.v1/lswitch/%(uuid)s/status",
"_schema": "/ws.v1/schema/LogicalSwitchStatus"}},
"type": "LogicalSwitchConfig",
+ "tags": %(tags_json)s,
"uuid": "%(uuid)s"}
zone_uuid = fake_lswitch['transport_zones'][0]['zone_uuid']
fake_lswitch['zone_uuid'] = zone_uuid
fake_lswitch['tenant_id'] = self._get_tag(fake_lswitch, 'os_tid')
+ fake_lswitch['lport_count'] = 0
return fake_lswitch
def _add_lport(self, body, ls_uuid):
self._fake_lport_dict[fake_lport['uuid']] = fake_lport
fake_lswitch = self._fake_lswitch_dict[ls_uuid]
+ fake_lswitch['lport_count'] += 1
fake_lport_status = fake_lport.copy()
fake_lport_status['ls_tenant_id'] = fake_lswitch['tenant_id']
fake_lport_status['ls_uuid'] = fake_lswitch['uuid']
def _list(self, resource_type, response_file,
switch_uuid=None, query=None):
(tag_filter, attr_filter) = self._get_filters(query)
-
with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read()
res_dict = getattr(self, '_fake_%s_dict' % resource_type)
res_dict[res_uuid].get('ls_uuid') == switch_uuid):
return True
return False
-
+ for item in res_dict.itervalues():
+ if 'tags' in item:
+ item['tags_json'] = json.dumps(item['tags'])
items = [json.loads(response_template % res_dict[res_uuid])
for res_uuid in res_dict
if (_lswitch_match(res_uuid) and
with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read()
res_dict = getattr(self, '_fake_%s_dict' % resource_type)
+ for item in res_dict.itervalues():
+ if 'tags' in item:
+ item['tags_json'] = json.dumps(item['tags'])
+
items = [json.loads(response_template % res_dict[res_uuid])
for res_uuid in res_dict if res_uuid == target_uuid]
if items:
# See the License for the specific language governing permissions and
# limitations under the License.
+import logging
import os
import mock
+import webob.exc
import quantum.common.test_lib as test_lib
+from quantum import context
+from quantum.extensions import providernet as pnet
+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
+LOG = logging.getLogger(__name__)
NICIRA_PKG_PATH = 'quantum.plugins.nicira.nicira_nvp_plugin'
_plugin_name = ('%s.QuantumPlugin.NvpPluginV2' % NICIRA_PKG_PATH)
+ def _create_network(self, fmt, name, admin_status_up,
+ arg_list=None, providernet_args=None, **kwargs):
+ data = {'network': {'name': name,
+ 'admin_state_up': admin_status_up,
+ 'tenant_id': self._tenant_id}}
+ attributes = kwargs
+ if providernet_args:
+ attributes.update(providernet_args)
+ for arg in (('admin_state_up', 'tenant_id', 'shared') +
+ (arg_list or ())):
+ # Arg must be present and not empty
+ if arg in kwargs and kwargs[arg]:
+ data['network'][arg] = kwargs[arg]
+ network_req = self.new_create_request('networks', data, fmt)
+ if (kwargs.get('set_context') and 'tenant_id' in kwargs):
+ # create a specific auth context for this request
+ network_req.environ['quantum.context'] = context.Context(
+ '', kwargs['tenant_id'])
+ return network_req.get_response(self.api)
+
def setUp(self):
etc_path = os.path.join(os.path.dirname(__file__), 'etc')
test_lib.test_config['config_files'] = [os.path.join(etc_path,
class TestNiciraPortsV2(test_plugin.TestPortsV2, NiciraPluginV2TestCase):
- pass
+
+ def test_exhaust_ports_overlay_network(self):
+ cfg.CONF.set_override('max_lp_per_overlay_ls', 1, group='NVP')
+ with self.network(name='testnet',
+ arg_list=(pnet.NETWORK_TYPE,
+ pnet.PHYSICAL_NETWORK,
+ pnet.SEGMENTATION_ID)) as net:
+ with self.subnet(network=net) as sub:
+ with self.port(subnet=sub):
+ # creating another port should see an exception
+ self._create_port('json', net['network']['id'], 400)
+
+ def test_exhaust_ports_bridged_network(self):
+ cfg.CONF.set_override('max_lp_per_bridged_ls', 1, group="NVP")
+ providernet_args = {pnet.NETWORK_TYPE: 'flat',
+ pnet.PHYSICAL_NETWORK: 'tzuuid'}
+ with self.network(name='testnet',
+ providernet_args=providernet_args,
+ arg_list=(pnet.NETWORK_TYPE,
+ pnet.PHYSICAL_NETWORK,
+ pnet.SEGMENTATION_ID)) as net:
+ with self.subnet(network=net) as sub:
+ with self.port(subnet=sub):
+ with self.port(subnet=sub):
+ plugin = manager.QuantumManager.get_plugin()
+ ls = nvplib.get_lswitches(plugin.default_cluster,
+ net['network']['id'])
+ self.assertEqual(len(ls), 2)
class TestNiciraNetworksV2(test_plugin.TestNetworksV2,
NiciraPluginV2TestCase):
- pass
+
+ def _test_create_bridge_network(self, vlan_id=None):
+ net_type = vlan_id and 'vlan' or 'flat'
+ name = 'bridge_net'
+ keys = [('subnets', []), ('name', name), ('admin_state_up', True),
+ ('status', 'ACTIVE'), ('shared', False),
+ (pnet.NETWORK_TYPE, net_type),
+ (pnet.PHYSICAL_NETWORK, 'tzuuid'),
+ (pnet.SEGMENTATION_ID, vlan_id)]
+ providernet_args = {pnet.NETWORK_TYPE: net_type,
+ pnet.PHYSICAL_NETWORK: 'tzuuid'}
+ if vlan_id:
+ providernet_args[pnet.SEGMENTATION_ID] = vlan_id
+ with self.network(name=name,
+ providernet_args=providernet_args,
+ arg_list=(pnet.NETWORK_TYPE,
+ pnet.PHYSICAL_NETWORK,
+ pnet.SEGMENTATION_ID)) as net:
+ for k, v in keys:
+ self.assertEquals(net['network'][k], v)
+
+ def test_create_bridge_network(self):
+ self._test_create_bridge_network()
+
+ def test_create_bridge_vlan_network(self):
+ self._test_create_bridge_network(vlan_id=123)
+
+ def test_create_bridge_vlan_network_outofrange_returns_400(self):
+ with self.assertRaises(webob.exc.HTTPClientError) as ctx_manager:
+ self._test_create_bridge_network(vlan_id=5000)
+ self.assertEquals(ctx_manager.exception.code, 400)