]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Provider network implementation for NVP plugin.
authorSalvatore Orlando <salv.orlando@gmail.com>
Tue, 18 Dec 2012 15:44:44 +0000 (07:44 -0800)
committerSalvatore Orlando <salv.orlando@gmail.com>
Wed, 2 Jan 2013 19:55:18 +0000 (11:55 -0800)
blueprint nvp-provider-net

Implements the provider network extension support. The list of valid network
types has been updated to reflect the types supported by the nvp plugin.
This was necessary otherwise validation would have always failed.
Multiple logical switches might be associated with a quantum network; the
first logical switch will always have the same id as the quantum network.
Also now raises exception when port limit on overlay network is reached.

This patch also adds a check for the maximum number of ports on 'standard'
overlay networks, and performs some code refactoring for improving
maintanability. For instance the NVPCluster class has been moved into its own
module.

Change-Id: Ib26d327daf748cfcba9ca74e8dc2e8e89c676c2e

13 files changed:
etc/quantum/plugins/nicira/nvp.ini
quantum/extensions/providernet.py
quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py
quantum/plugins/nicira/nicira_nvp_plugin/api_client/client_eventlet.py
quantum/plugins/nicira/nicira_nvp_plugin/common/config.py
quantum/plugins/nicira/nicira_nvp_plugin/common/exceptions.py [new file with mode: 0644]
quantum/plugins/nicira/nicira_nvp_plugin/nicira_db.py [new file with mode: 0644]
quantum/plugins/nicira/nicira_nvp_plugin/nicira_models.py [new file with mode: 0644]
quantum/plugins/nicira/nicira_nvp_plugin/nvp_cluster.py [new file with mode: 0644]
quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py
quantum/tests/unit/nicira/etc/fake_get_lswitch.json
quantum/tests/unit/nicira/fake_nvpapiclient.py
quantum/tests/unit/nicira/test_nicira_plugin.py

index 506f486f965abe8df591720cfa2f3e1e2e67ec25..904035df820dfb8f3e9f87b12375233f92f49850 100644 (file)
@@ -24,13 +24,19 @@ reconnect_interval = 2
 # 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
index 204b7164c80144ca35999daec5f26158433bcdce..c259d3be8c881d4ea1ce0442c815ee1e658ac410 100644 (file)
@@ -19,7 +19,9 @@ NETWORK_TYPE = 'provider:network_type'
 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': {
index 5dd85632560da8f93a44932ad8024844f145b363..2838d1f35af3d348bbc518cc4e70a9b662593cfb 100644 (file)
 # @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.
 
@@ -77,11 +86,7 @@ def parse_config():
         "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)
@@ -116,125 +121,16 @@ class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
         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)
@@ -242,11 +138,11 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
             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:
@@ -259,7 +155,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
                                     "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]
@@ -269,29 +166,185 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
                 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
@@ -302,15 +355,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         # 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:
@@ -337,24 +381,40 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
                    }
         :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):
         """
@@ -365,29 +425,31 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         :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
 
@@ -414,55 +476,43 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         :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):
         """
@@ -491,6 +541,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         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 = ""
@@ -501,19 +553,20 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         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"):
@@ -525,9 +578,9 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
             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
@@ -535,12 +588,9 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
                         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):
@@ -587,9 +637,9 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
 
         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)
@@ -598,7 +648,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         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)
@@ -653,7 +704,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         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" %
@@ -667,8 +718,9 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
                                 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:
@@ -690,8 +742,10 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
                 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):
@@ -721,8 +775,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
          "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
@@ -732,46 +785,70 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         :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"],
@@ -803,23 +880,17 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         """
         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):
@@ -835,10 +906,11 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         :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.
@@ -867,9 +939,11 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
 
         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"]:
index a094644fce45f9e0a58f9713d78afa2d260f1697..75d0347669928cf9737b4dae7e8e93a48ebfef2d 100644 (file)
@@ -28,7 +28,7 @@ from common import _conn_str
 eventlet.monkey_patch()
 
 logging.basicConfig(level=logging.INFO)
-lg = logging.getLogger('nvp_api_client')
+LOG = logging.getLogger(__name__)
 
 # Default parameters.
 DEFAULT_FAILOVER_TIME = 5
@@ -156,19 +156,19 @@ class NvpApiClientEventlet(object):
                  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).
@@ -178,19 +178,19 @@ class NvpApiClientEventlet(object):
         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.
@@ -198,9 +198,9 @@ class NvpApiClientEventlet(object):
 
         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):
@@ -213,9 +213,9 @@ class NvpApiClientEventlet(object):
         :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.
@@ -223,9 +223,9 @@ class NvpApiClientEventlet(object):
         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
 
@@ -233,20 +233,20 @@ class NvpApiClientEventlet(object):
                 # 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):
@@ -263,7 +263,7 @@ class NvpApiClientEventlet(object):
                 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
@@ -277,13 +277,13 @@ class NvpApiClientEventlet(object):
 
         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
 
index d4a88875a162794c1c8fa3fdf11df3aaf69b914d..f0cd6b608a379d639b813852a44f9cf2f6cffe22 100644 (file)
@@ -40,8 +40,10 @@ database_opts = [
 
 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 = [
diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/common/exceptions.py b/quantum/plugins/nicira/nicira_nvp_plugin/common/exceptions.py
new file mode 100644 (file)
index 0000000..ed10c9b
--- /dev/null
@@ -0,0 +1,42 @@
+# 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!")
diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/nicira_db.py b/quantum/plugins/nicira/nicira_nvp_plugin/nicira_db.py
new file mode 100644 (file)
index 0000000..570e0bc
--- /dev/null
@@ -0,0 +1,54 @@
+# 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
diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/nicira_models.py b/quantum/plugins/nicira/nicira_nvp_plugin/nicira_models.py
new file mode 100644 (file)
index 0000000..256bb53
--- /dev/null
@@ -0,0 +1,49 @@
+# 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)
diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/nvp_cluster.py b/quantum/plugins/nicira/nicira_nvp_plugin/nvp_cluster.py
new file mode 100644 (file)
index 0000000..a55f185
--- /dev/null
@@ -0,0 +1,131 @@
+# 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']
index edc56add301a3ae991ef2c5572b6daf24cb79b7a..5e7098088cf7d0141f3291d13959742c70d9b31c 100644 (file)
@@ -25,6 +25,7 @@
 
 from copy import copy
 import functools
+import itertools
 import json
 import hashlib
 import logging
@@ -33,6 +34,7 @@ import re
 import uuid
 
 from eventlet import semaphore
+
 import NvpApiClient
 
 #FIXME(danwent): I'd like this file to get to the point where it has
@@ -40,6 +42,17 @@ import NvpApiClient
 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
@@ -52,8 +65,8 @@ if LOCAL_LOGGING:
     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 = []
@@ -63,12 +76,42 @@ _net_type_cache = {}  # cache of {net_id: network_type}
 _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()
@@ -79,7 +122,7 @@ def get_cluster_version(cluster):
     # 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()
@@ -97,7 +140,7 @@ def get_all_query_pages(path, c):
     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)
@@ -155,48 +198,83 @@ def find_lswitch_by_portid(clusters, port_id):
     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)
@@ -262,26 +340,6 @@ def delete_networks(cluster, net_id, lswitch_ids):
             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:
@@ -328,7 +386,7 @@ def get_port_by_quantum_tag(clusters, lswitch, quantum_tag):
         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)
 
 
@@ -403,38 +461,32 @@ def update_port(network, port_id, **params):
     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):
@@ -455,10 +507,8 @@ 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
@@ -466,7 +516,7 @@ def plug_interface(clusters, lswitch_id, port, type, attachment=None):
     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)
index e53a15e6f12910c34e2fc3d68741d9f4776176c0..58b132b307cce167b2283baf516b3e135c6b649c 100644 (file)
@@ -4,7 +4,9 @@
  "_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"}
index 962949639fd86d0602859548614f07a1e3fbe0e0..b538ec140687d2a94d8f0a02fa3a96f642320025 100644 (file)
@@ -77,6 +77,7 @@ class FakeClient:
         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):
@@ -92,6 +93,7 @@ class FakeClient:
         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']
@@ -115,7 +117,6 @@ class FakeClient:
     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)
@@ -143,7 +144,9 @@ class FakeClient:
                         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
@@ -159,6 +162,10 @@ class FakeClient:
         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:
index c6e11881b2aafbeadff4d37296f52bf71368f083..72bb541d6966d7aef8e9c43525c3600806454587 100644 (file)
 # 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'
 
 
@@ -28,6 +36,26 @@ class NiciraPluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
 
     _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,
@@ -61,9 +89,66 @@ class TestNiciraV2HTTPResponse(test_plugin.TestV2HTTPResponse,
 
 
 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)