include etc/quantum/plugins/cisco/*.ini
include etc/quantum/plugins/cisco/quantum.conf.ciscoext
include etc/quantum/plugins/linuxbridge/*.ini
+include etc/quantum/plugins/nicira/*
include quantum/plugins/*/README
include quantum/plugins/openvswitch/Makefile
include quantum/plugins/openvswitch/agent/xenserver_install.sh
--- /dev/null
+# Example configuration:
+# [NVP]
+# DEFAULT_TZ_UUID = 1e8e52cf-fa7f-46b0-a14a-f99835a9cb53
+# NVP_CONTROLLER_CONNECTIONS = NVP_CONN_1 NVP_CONN_2 NVP_CONN_3
+# NVP_CONN_1=10.0.1.2:443:admin:password:30:10:2:2
+# NVP_CONN_2=10.0.1.3:443:admin:password:30:10:2:2
+# NVP_CONN_3=10.0.1.4:443:admin:password:30:10:2:2
+[DEFAULT]
+# No default config for now.
+[NVP]
+# This is the uuid of the default NVP Transport zone that will be used for
+# creating isolated "Quantum" networks. The transport zone needs to be
+# created in NVP before starting Quantum with the plugin.
+DEFAULT_TZ_UUID = <insert default tz uuid>
+# This parameter is a space separated list of NVP_CONTROLLER_CONNECTIONS.
+NVP_CONTROLLER_CONNECTIONS = <space separated names of controller connections>
+# This parameter describes a connection to a single NVP controller.
+# <ip> is the ip address of the controller
+# <port> is the port of the controller (default NVP port is 443)
+# <user> is the user name for this controller
+# <pass> is the user password.
+# <request_timeout>: The total time limit on all operations for a controller
+# request (including retries, redirects from unresponsive controllers).
+# Default is 30.
+# <http_timeout>: How long to wait before aborting an unresponsive controller
+# (and allow for retries to another controller).
+# Default is 10.
+# <retries>: the maximum number of times to retry a particular request
+# Default is 2.
+# <redirects>: the maximum number of times to follow a redirect response from a server.
+# Default is 2.
+# There must be at least one NVP_CONTROLLER_CONNECTION per system.
+#
+# Here is an example:
+# NVP_CONTROLLER_CONNECTION_1=10.0.0.1:443:admin:password:30:10:2:2
+<connection name>=<ip>:<port>:<user>:<pass>:<api_call_timeout>:<http_timeout>:<retries>:<redirects>
--- /dev/null
+'''
+# Copyright 2012 Nicira Networks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+@author: Somik Behera, Nicira Networks, Inc.
+'''
+
+import httplib # basic HTTP library for HTTPS connections
+import logging
+from api_client.client_eventlet import NvpApiClientEventlet
+from api_client.request_eventlet import NvpGenericRequestEventlet
+
+LOG = logging.getLogger("NVPApiHelper")
+LOG.setLevel(logging.INFO)
+
+
+class NVPApiHelper(NvpApiClientEventlet):
+ '''
+ Helper class to do basic login, cookie management, and provide base
+ method to send HTTP requests.
+
+ Implements new eventlet-based framework derived from the management
+ console nvp_gevent_client module.
+ '''
+
+ def __init__(self, api_providers, user, password, request_timeout,
+ http_timeout, retries, redirects, failover_time,
+ concurrent_connections=3):
+ '''Constructor.
+
+ :param api_providers: a list of tuples in the form:
+ (host, port, is_ssl=True). Passed on to NvpClientEventlet.
+ :param user: the login username.
+ :param password: the login password.
+ :param concurrent_connections: the number of concurrent connections.
+ :param request_timeout: all operations (including retries, redirects
+ from unresponsive controllers, etc) should finish within this
+ timeout.
+ :param http_timeout: how long to wait before aborting an
+ unresponsive controller
+ :param retries: the number of concurrent connections.
+ :param redirects: the number of concurrent connections.
+ :param failover_time: minimum time between controller failover and new
+ connections allowed.
+ '''
+ NvpApiClientEventlet.__init__(
+ self, api_providers, user, password, concurrent_connections,
+ failover_time=failover_time)
+
+ self._request_timeout = request_timeout
+ self._http_timeout = http_timeout
+ self._retries = retries
+ self._redirects = redirects
+
+ def login(self, user=None, password=None):
+ '''Login to NVP controller.
+
+ Assumes same password is used for all controllers.
+
+ :param user: NVP controller user (usually admin). Provided for
+ backwards compatability. In the normal mode of operation
+ this should be None.
+ :param password: NVP controller password. Provided for backwards
+ compatability. In the normal mode of operation this should
+ be None.
+
+ :returns: Does not return a value.
+ '''
+ if user:
+ self._user = user
+ if password:
+ self._password = password
+
+ return NvpApiClientEventlet.login(self)
+
+ def request(self, method, url, body="", content_type="application/json"):
+ '''Issues request to controller.'''
+
+ g = NvpGenericRequestEventlet(
+ self, method, url, body, content_type, auto_login=True,
+ request_timeout=self._request_timeout,
+ http_timeout=self._http_timeout,
+ retries=self._retries, redirects=self._redirects)
+ g.start()
+ response = g.join()
+ LOG.debug('NVPApiHelper.request() returns "%s"' % response)
+
+ # response is a modified HTTPResponse object or None.
+ # response.read() will not work on response as the underlying library
+ # request_eventlet.NvpApiRequestEventlet has already called this
+ # method in order to extract the body and headers for processing.
+ # NvpApiRequestEventlet derived classes call .read() and
+ # .getheaders() on the HTTPResponse objects and store the results in
+ # the response object's .body and .headers data members for future
+ # access.
+
+ if response is None:
+ # Timeout.
+ LOG.error('Request timed out: %s to %s' % (method, url))
+ raise RequestTimeout()
+
+ status = response.status
+ if status == httplib.UNAUTHORIZED:
+ raise UnAuthorizedRequest()
+
+ # Fail-fast: Check for exception conditions and raise the
+ # appropriate exceptions for known error codes.
+ if status in self.error_codes:
+ LOG.error("Received error code: %s" % status)
+ LOG.error("Server Error Message: %s" % response.body)
+ self.error_codes[status](self)
+
+ # Continue processing for non-error condition.
+ if (status != httplib.OK and status != httplib.CREATED
+ and status != httplib.NO_CONTENT):
+ LOG.error("%s to %s, unexpected response code: %d (content = '%s')"
+ % (method, url, response.status, response.body))
+ return None
+
+ return response.body
+
+ def fourZeroFour(self):
+ raise ResourceNotFound()
+
+ def fourZeroNine(self):
+ raise Conflict()
+
+ def fiveZeroThree(self):
+ raise ServiceUnavailable()
+
+ def fourZeroThree(self):
+ raise Forbidden()
+
+ def zero(self):
+ raise NvpApiException()
+
+ error_codes = {404: fourZeroFour,
+ 409: fourZeroNine,
+ 503: fiveZeroThree,
+ 403: fourZeroThree,
+ 301: zero,
+ 307: zero,
+ 400: zero,
+ 500: zero}
+
+
+class NvpApiException(Exception):
+ '''
+ Base NvpApiClient Exception
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+
+ '''
+ message = "An unknown exception occurred."
+
+ def __init__(self, **kwargs):
+ try:
+ self._error_string = self.message % kwargs
+
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+
+ def __str__(self):
+ return self._error_string
+
+
+class UnAuthorizedRequest(NvpApiException):
+ message = "Server denied session's authentication credentials."
+
+
+class ResourceNotFound(NvpApiException):
+ message = "An entity referenced in the request was not found."
+
+
+class Conflict(NvpApiException):
+ message = "Request conflicts with configuration on a different entity."
+
+
+class ServiceUnavailable(NvpApiException):
+ message = "Request could not completed because the associated " \
+ "resource could not be reached."
+
+
+class Forbidden(NvpApiException):
+ message = "The request is forbidden from accessing the " \
+ "referenced resource."
+
+
+class RequestTimeout(NvpApiException):
+ message = "The request has timed out."
--- /dev/null
+# Copyright 2012 Nicira Networks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Somik Behera, Nicira Networks, Inc.
+# @author: Brad Hall, Nicira Networks, Inc.
+
+import ConfigParser
+import logging
+import nvplib
+import NvpApiClient
+import os
+import sys
+
+from api_client.client_eventlet import DEFAULT_CONCURRENT_CONNECTIONS
+from api_client.client_eventlet import DEFAULT_FAILOVER_TIME
+from api_client.request_eventlet import DEFAULT_REQUEST_TIMEOUT
+from api_client.request_eventlet import DEFAULT_HTTP_TIMEOUT
+from api_client.request_eventlet import DEFAULT_RETRIES
+from api_client.request_eventlet import DEFAULT_REDIRECTS
+
+from quantum.common import exceptions as exception
+
+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")
+CONFIG_KEYS = ["DEFAULT_TZ_UUID", "NVP_CONTROLLER_IP", "PORT", "USER",
+ "PASSWORD"]
+LOG = logging.getLogger("QuantumPlugin")
+
+
+def initConfig(cfile=None):
+ config = ConfigParser.ConfigParser()
+ if cfile == None:
+ if os.path.exists(CONFIG_FILE):
+ cfile = CONFIG_FILE
+ else:
+ cfile = find_config(os.path.abspath(os.path.dirname(__file__)))
+
+ if cfile == None:
+ raise Exception("Configuration file \"%s\" doesn't exist" %
+ (cfile))
+ LOG.info("Using configuration file: %s" % cfile)
+ config.read(cfile)
+ LOG.debug("Config: %s" % config)
+ return config
+
+
+def find_config(basepath):
+ LOG.info("Looking for %s in %s" % (CONFIG_FILE, basepath))
+ for root, dirs, files in os.walk(basepath, followlinks=True):
+ if CONFIG_FILE in files:
+ return os.path.join(root, CONFIG_FILE)
+ for alternate_path in CONFIG_FILE_PATHS:
+ p = os.path.join(alternate_path, CONFIG_FILE)
+ if os.path.exists(p):
+ return p
+ return None
+
+
+def parse_config(config):
+ '''Backwards compatible parsing.
+
+ :param config: ConfigParser object initilized with nvp.ini.
+ :returns: A tuple consisting of a control cluster object and a
+ plugin_config variable.
+ raises: In general, system exceptions are not caught but are propagated
+ up to the user. Config parsing is still very lightweight.
+ At some point, error handling needs to be significantly
+ enhanced to provide user friendly error messages, clean program
+ exists, rather than exceptions propagated to the user.
+ '''
+ # Extract plugin config parameters.
+ try:
+ failover_time = config.get('NVP', 'failover_time')
+ except ConfigParser.NoOptionError, e:
+ failover_time = str(DEFAULT_FAILOVER_TIME)
+
+ try:
+ concurrent_connections = config.get('NVP', 'concurrent_connections')
+ except ConfigParser.NoOptionError, e:
+ concurrent_connections = str(DEFAULT_CONCURRENT_CONNECTIONS)
+
+ plugin_config = {
+ 'failover_time': failover_time,
+ 'concurrent_connections': concurrent_connections
+ }
+ LOG.info('parse_config(): plugin_config == "%s"' % plugin_config)
+
+ cluster = NVPCluster('cluster1')
+
+ # Extract connection information.
+ try:
+ defined_connections = config.get(
+ 'NVP', 'NVP_CONTROLLER_CONNECTIONS')
+
+ for conn_key in defined_connections.split():
+ args = [config.get('NVP', 'DEFAULT_TZ_UUID')]
+ args.extend(config.get('NVP', conn_key).split(':'))
+ try:
+ cluster.add_controller(*args)
+ except Exception, e:
+ LOG.fatal('Invalid connection parameters: %s' % str(e))
+ sys.exit(1)
+
+ return cluster, plugin_config
+ except Exception, e:
+ LOG.info('No new style connections defined: %s' % e)
+
+ # Old style controller specification.
+ args = [config.get('NVP', k) for k in CONFIG_KEYS]
+ try:
+ cluster.add_controller(*args)
+ except Exception, e:
+ LOG.fatal('Invalid connection parameters.')
+ sys.exit(1)
+
+ return cluster, plugin_config
+
+
+class NVPCluster(object):
+ '''Encapsulates controller connection and api_client.
+
+ Initialized within parse_config().
+ Accessed within the NvpPlugin class.
+
+ Each element in the self.controllers list is a dictionary that
+ contains the following keys:
+ ip, port, user, password, default_tz_uuid
+
+ 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, default_tz_uuid, ip, port, user, password,
+ request_timeout=DEFAULT_REQUEST_TIMEOUT,
+ http_timeout=DEFAULT_HTTP_TIMEOUT,
+ retries=DEFAULT_RETRIES, redirects=DEFAULT_REDIRECTS):
+ '''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.
+ '''
+
+ keys = [
+ 'ip', 'port', 'user', 'password', 'default_tz_uuid']
+ controller_dict = dict([(k, locals()[k]) for k in keys])
+
+ int_keys = [
+ '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']
+
+
+class NvpPlugin(object):
+ '''
+ NvpPlugin is a Quantum plugin that provides L2 Virtual Network
+ functionality using NVP.
+ '''
+ supported_extension_aliases = ["portstats"]
+
+ def __init__(self, configfile=None, loglevel=None, cli=False):
+ if loglevel:
+ logging.basicConfig(level=loglevel)
+ nvplib.LOG.setLevel(loglevel)
+ NvpApiClient.LOG.setLevel(loglevel)
+
+ config = initConfig(configfile)
+ self.controller, self.plugin_config = parse_config(config)
+ c = self.controller
+ api_providers = [
+ (x['ip'], x['port'], True) for x in c.controllers]
+
+ c.api_client = NvpApiClient.NVPApiHelper(
+ api_providers, c.user, c.password,
+ request_timeout=c.request_timeout, http_timeout=c.http_timeout,
+ retries=c.retries, redirects=c.redirects,
+ failover_time=int(self.plugin_config['failover_time']),
+ concurrent_connections=int(
+ self.plugin_config['concurrent_connections']))
+
+ c.api_client.login()
+
+ # For testing..
+ self.api_client = self.controller.api_client
+
+ def get_all_networks(self, tenant_id, **kwargs):
+ '''
+ Returns a dictionary containing all <network_uuid, network_name> for
+ the specified tenant.
+
+ :returns: a list of mapping sequences with the following signature:
+ [{'net-id': uuid that uniquely identifies
+ the particular quantum network,
+ 'net-name': a human-readable name associated
+ with network referenced by net-id
+ },
+ ....
+ {'net-id': uuid that uniquely identifies the
+ particular quantum network,
+ 'net-name': a human-readable name associated
+ with network referenced by net-id
+ }
+ ]
+ :raises: None
+ '''
+ networks = nvplib.get_all_networks(self.controller, tenant_id,
+ [])
+ LOG.debug("get_all_networks() completed for tenant %s: %s" % (
+ tenant_id, networks))
+ return networks
+
+ def create_network(self, tenant_id, net_name, **kwargs):
+ '''
+ Creates a new Virtual Network, and assigns it a symbolic name.
+ :returns: a sequence of mappings with the following signature:
+ {'net-id': uuid that uniquely identifies the
+ particular quantum network,
+ 'net-name': a human-readable name associated
+ with network referenced by net-id
+ }
+ :raises:
+ '''
+ kwargs["controller"] = self.controller
+ return nvplib.create_network(tenant_id, net_name, **kwargs)
+
+ def create_custom_network(self, tenant_id, net_name, transport_zone,
+ controller):
+ return self.create_network(tenant_id, net_name,
+ network_type="custom",
+ transport_zone=transport_zone,
+ controller=controller)
+
+ def delete_network(self, tenant_id, netw_id):
+ '''
+ Deletes the network with the specified network identifier
+ belonging to the specified tenant.
+
+ :returns: a sequence of mappings with the following signature:
+ {'net-id': uuid that uniquely identifies the
+ particular quantum network
+ }
+ :raises: exception.NetworkInUse
+ :raises: exception.NetworkNotFound
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ nvplib.delete_network(self.controller, netw_id)
+
+ LOG.debug("delete_network() completed for tenant: %s" % tenant_id)
+ return {'net-id': netw_id}
+
+ def get_network_details(self, tenant_id, netw_id):
+ '''
+ Retrieves a list of all the remote vifs that
+ are attached to the network.
+
+ :returns: a sequence of mappings with the following signature:
+ {'net-id': uuid that uniquely identifies the
+ particular quantum network
+ 'net-name': a human-readable name associated
+ with network referenced by net-id
+ 'net-ifaces': ['vif1_on_network_uuid',
+ 'vif2_on_network_uuid',...,'vifn_uuid']
+ }
+ :raises: exception.NetworkNotFound
+ :raises: exception.QuantumException
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ result = None
+ remote_vifs = []
+ switch = netw_id
+ lports = nvplib.query_ports(self.controller, switch,
+ relations="LogicalPortAttachment")
+
+ for port in lports:
+ relation = port["_relations"]
+ vic = relation["LogicalPortAttachment"]
+ if "vif_uuid" in vic:
+ remote_vifs.append(vic["vif_uuid"])
+
+ if not result:
+ result = nvplib.get_network(self.controller, switch)
+
+ d = {"net-id": netw_id,
+ "net-ifaces": remote_vifs,
+ "net-name": result["display_name"],
+ "net-op-status": "UP"}
+ LOG.debug("get_network_details() completed for tenant %s: %s" % (
+ tenant_id, d))
+ return d
+
+ def update_network(self, tenant_id, netw_id, **kwargs):
+ '''
+ Updates the properties of a particular Virtual Network.
+
+ :returns: a sequence of mappings representing the new network
+ attributes, with the following signature:
+ {'net-id': uuid that uniquely identifies the
+ particular quantum network
+ 'net-name': the new human-readable name
+ associated with network referenced by net-id
+ }
+ :raises: exception.NetworkNotFound
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ result = nvplib.update_network(self.controller, netw_id, **kwargs)
+ LOG.debug("update_network() completed for tenant: %s" % tenant_id)
+ return {'net-id': netw_id, 'net-name': result["display_name"],
+ 'net-op-status': "UP"}
+
+ def get_all_ports(self, tenant_id, netw_id, **kwargs):
+ '''
+ Retrieves all port identifiers belonging to the
+ specified Virtual Network.
+
+ :returns: a list of mapping sequences with the following signature:
+ [{'port-id': uuid representing a particular port
+ on the specified quantum network
+ },
+ ....
+ {'port-id': uuid representing a particular port
+ on the specified quantum network
+ }
+ ]
+ :raises: exception.NetworkNotFound
+ '''
+ ids = []
+ filters = kwargs.get("filter_opts") or {}
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ LOG.debug("Getting logical ports on lswitch: %s" % netw_id)
+ lports = nvplib.query_ports(self.controller, netw_id, fields="uuid",
+ filters=filters)
+ for port in lports:
+ ids.append({"port-id": port["uuid"]})
+
+ # Delete from the filter so that Quantum doesn't attempt to filter on
+ # this too
+ if filters and "attachment" in filters:
+ del filters["attachment"]
+
+ LOG.debug("get_all_ports() completed for tenant: %s" % tenant_id)
+ LOG.debug("returning port listing:")
+ LOG.debug(ids)
+ return ids
+
+ def create_port(self, tenant_id, netw_id, port_init_state=None,
+ **params):
+ '''
+ Creates a port on the specified Virtual Network.
+
+ :returns: a mapping sequence with the following signature:
+ {'port-id': uuid representing the created port
+ on specified quantum network
+ }
+ :raises: exception.NetworkNotFound
+ :raises: exception.StateInvalid
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ params["controller"] = self.controller
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ result = nvplib.create_port(tenant_id, netw_id, port_init_state,
+ **params)
+ d = {"port-id": result["uuid"],
+ "port-op-status": result["port-op-status"]}
+ LOG.debug("create_port() completed for tenant %s: %s" % (tenant_id, d))
+ return d
+
+ def update_port(self, tenant_id, netw_id, portw_id, **params):
+ '''
+ Updates the properties of a specific port on the
+ specified Virtual Network.
+
+ :returns: a mapping sequence with the following signature:
+ {'port-id': uuid representing the
+ updated port on specified quantum network
+ 'port-state': update port state (UP or DOWN)
+ }
+ :raises: exception.StateInvalid
+ :raises: exception.PortNotFound
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ LOG.debug("Update port request: %s" % (params))
+ params["controller"] = self.controller
+ result = nvplib.update_port(netw_id, portw_id, **params)
+ LOG.debug("update_port() completed for tenant: %s" % tenant_id)
+ port = {'port-id': portw_id,
+ 'port-state': result["admin_status_enabled"],
+ 'port-op-status': result["port-op-status"]}
+ LOG.debug("returning updated port %s: " % port)
+ return port
+
+ def delete_port(self, tenant_id, netw_id, portw_id):
+ '''
+ Deletes a port on a specified Virtual Network,
+ if the port contains a remote interface attachment,
+ the remote interface is first un-plugged and then the port
+ is deleted.
+
+ :returns: a mapping sequence with the following signature:
+ {'port-id': uuid representing the deleted port
+ on specified quantum network
+ }
+ :raises: exception.PortInUse
+ :raises: exception.PortNotFound
+ :raises: exception.NetworkNotFound
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ nvplib.delete_port(self.controller, netw_id, portw_id)
+ LOG.debug("delete_port() completed for tenant: %s" % tenant_id)
+ return {"port-id": portw_id}
+
+ def get_port_details(self, tenant_id, netw_id, portw_id):
+ '''
+ This method allows the user to retrieve a remote interface
+ that is attached to this particular port.
+
+ :returns: a mapping sequence with the following signature:
+ {'port-id': uuid representing the port on
+ specified quantum network
+ 'net-id': uuid representing the particular
+ quantum network
+ 'attachment': uuid of the virtual interface
+ bound to the port, None otherwise
+ }
+ :raises: exception.PortNotFound
+ :raises: exception.NetworkNotFound
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ port = nvplib.get_port(self.controller, netw_id, portw_id,
+ "LogicalPortAttachment")
+ state = "ACTIVE" if port["admin_status_enabled"] else "DOWN"
+ op_status = nvplib.get_port_status(self.controller, netw_id, portw_id)
+
+ relation = port["_relations"]
+ attach_type = relation["LogicalPortAttachment"]["type"]
+
+ vif_uuid = "None"
+ if attach_type == "VifAttachment":
+ vif_uuid = relation["LogicalPortAttachment"]["vif_uuid"]
+
+ d = {"port-id": portw_id, "attachment": vif_uuid,
+ "net-id": netw_id, "port-state": state,
+ "port-op-status": op_status}
+ LOG.debug("Port details for tenant %s: %s" % (tenant_id, d))
+ return d
+
+ def plug_interface(self, tenant_id, netw_id, portw_id,
+ remote_interface_id):
+ '''
+ Attaches a remote interface to the specified port on the
+ specified Virtual Network.
+
+ :returns: None
+ :raises: exception.NetworkNotFound
+ :raises: exception.PortNotFound
+ :raises: exception.AlreadyAttached
+ (? should the network automatically unplug/replug)
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ result = nvplib.plug_interface(self.controller, netw_id, portw_id,
+ "VifAttachment", attachment=remote_interface_id)
+ LOG.debug("plug_interface() completed for %s: %s" % (
+ tenant_id, result))
+
+ def unplug_interface(self, tenant_id, netw_id, portw_id):
+ '''
+ Detaches a remote interface from the specified port on the
+ specified Virtual Network.
+
+ :returns: None
+ :raises: exception.NetworkNotFound
+ :raises: exception.PortNotFound
+ '''
+ if not nvplib.check_tenant(self.controller, netw_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=netw_id)
+ result = nvplib.unplug_interface(self.controller, netw_id, portw_id)
+
+ LOG.debug("unplug_interface() completed for tenant %s: %s" %
+ (tenant_id, result))
+
+ def get_port_stats(self, tenant_id, network_id, port_id):
+ """
+ Returns port statistics for a given port.
+
+ {
+ "rx_packets": 0,
+ "rx_bytes": 0,
+ "tx_errors": 0,
+ "rx_errors": 0,
+ "tx_bytes": 0,
+ "tx_packets": 0
+ }
+
+ :returns: dict() of stats
+ :raises: exception.NetworkNotFound
+ :raises: exception.PortNotFound
+ """
+ if not nvplib.check_tenant(self.controller, network_id, tenant_id):
+ raise exception.NetworkNotFound(net_id=network_id)
+ return nvplib.get_port_stats(self.controller, network_id, port_id)
--- /dev/null
+nvp-plugin
+-----------------------------------------------------------------------------
+
+Overview and pre-requisites
+
+ This is a Quantum plugin that can talk to a set of NVP controllers and
+ implements the core Quantum L2 api. In order to use it you must have
+ Nicira NVP running and configured. You must also have Quantum installed
+ and configured.
+
+Installation and Configuration
+
+ Edit nvp.ini to match your controller configuration and then modify your
+ Quantum plugins.ini provider path:
+
+ provider = quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPlugin
+
+Testing
+
+ Edit etc/quantum/plugins/nicira/nvp.ini to match your nvp configuration
+ (nvp must be up and running). Then:
+
+ $ cd quantum/plugins/nicira
+ $ PYTHONPATH=../../../:. nosetests -v
--- /dev/null
+# Copyright (C) 2009-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.
--- /dev/null
+# Copyright (C) 2009-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.
+#
+# Author: David Lapsley <dlapsley@nicira.com>, Nicira Networks, Inc.
+
+from abc import ABCMeta
+from abc import abstractmethod
+from abc import abstractproperty
+
+
+class NvpApiClient(object):
+ '''An abstract baseclass for all NvpApiClient implementations.
+
+ This defines the interface and property structure for synchronous and
+ coroutine-based classes.
+ '''
+
+ __metaclass__ = ABCMeta
+
+ # Default connection timeout for a controller. After CONN_IDLE_TIMEOUT
+ # seconds the client attempt to reconnect.
+ CONN_IDLE_TIMEOUT = 60 * 15
+
+ @abstractmethod
+ def update_providers(self, api_providers):
+ pass
+
+ @abstractproperty
+ def user(self):
+ pass
+
+ @abstractproperty
+ def password(self):
+ pass
+
+ @abstractproperty
+ def auth_cookie(self):
+ pass
+
+ @abstractmethod
+ def acquire_connection(self):
+ pass
+
+ @abstractmethod
+ def release_connection(self, http_conn, bad_state=False):
+ pass
+
+ @abstractproperty
+ def need_login(self):
+ pass
+
+ @abstractmethod
+ def wait_for_login(self):
+ pass
+
+ @abstractmethod
+ def login(self):
+ pass
--- /dev/null
+# Copyright (C) 2009-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.
+
+
+import client
+import eventlet
+import httplib
+import logging
+import request_eventlet
+import time
+from common import _conn_str
+
+
+logging.basicConfig(level=logging.INFO)
+lg = logging.getLogger('nvp_api_client')
+
+# Default parameters.
+DEFAULT_FAILOVER_TIME = 5
+DEFAULT_CONCURRENT_CONNECTIONS = 3
+DEFAULT_CONNECT_TIMEOUT = 5
+
+
+class NvpApiClientEventlet(object):
+ '''Eventlet-based implementation of NvpApiClient ABC.'''
+
+ CONN_IDLE_TIMEOUT = 60 * 15
+
+ def __init__(self, api_providers, user, password,
+ concurrent_connections=DEFAULT_CONCURRENT_CONNECTIONS,
+ use_https=True,
+ connect_timeout=DEFAULT_CONNECT_TIMEOUT,
+ failover_time=DEFAULT_FAILOVER_TIME):
+ '''Constructor
+
+ Args:
+ api_providers: a list of tuples of the form: (host, port, is_ssl).
+ user: login username.
+ password: login password.
+ concurrent_connections: total number of concurrent connections.
+ use_https: whether or not to use https for requests.
+ connect_timeout: connection timeout in seconds.
+ '''
+ self._api_providers = set([tuple(p) for p in api_providers])
+ self._user = user
+ self._password = password
+ self._concurrent_connections = concurrent_connections
+ self._use_https = use_https
+ self._connect_timeout = connect_timeout
+ self._failover_time = failover_time
+
+ # Connection pool is a queue. Head of the queue is the
+ # connection pool with the highest priority.
+ self._conn_pool = eventlet.queue.Queue()
+ for host, port, is_ssl in self._api_providers:
+ provider_conn_pool = eventlet.queue.Queue()
+ for i in range(concurrent_connections):
+ # All connections in a provider_conn_poool have the
+ # same priority (they connect to the same server).
+ conn = self._create_connection(host, port, is_ssl)
+ conn.conn_pool = provider_conn_pool
+ provider_conn_pool.put(conn)
+
+ self._conn_pool.put(provider_conn_pool)
+
+ self._active_conn_pool = self._conn_pool.get()
+
+ self._cookie = None
+ self._need_login = True
+ self._doing_login_sem = eventlet.semaphore.Semaphore(1)
+
+ def _create_connection(self, host, port, is_ssl):
+ if is_ssl:
+ return httplib.HTTPSConnection(host, port,
+ timeout=self._connect_timeout)
+ return httplib.HTTPConnection(host, port,
+ timeout=self._connect_timeout)
+
+ @staticmethod
+ def _conn_params(http_conn):
+ is_ssl = isinstance(http_conn, httplib.HTTPSConnection)
+ return (http_conn.host, http_conn.port, is_ssl)
+
+ def update_providers(self, api_providers):
+ raise Exception('update_providers() not implemented.')
+
+ @property
+ def user(self):
+ return self._user
+
+ @property
+ def password(self):
+ return self._password
+
+ @property
+ def auth_cookie(self):
+ return self._cookie
+
+ def acquire_connection(self):
+ '''Check out an available HTTPConnection instance.
+
+ Blocks until a connection is available.
+
+ Returns: An available HTTPConnection instance or None if no
+ api_providers are configured.
+ '''
+ if not self._api_providers:
+ 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.info("acquire_connection() waiting for timer to expire.")
+ time.sleep(self._issue_conn_barrier - now)
+
+ if self._active_conn_pool.empty():
+ lg.debug("Waiting to acquire an API client connection")
+
+ # get() call is blocking.
+ conn = self._active_conn_pool.get()
+ now = time.time()
+ if getattr(conn, 'last_used', now) < now - self.CONN_IDLE_TIMEOUT:
+ lg.info("Connection %s idle for %0.2f seconds; reconnecting."
+ % (_conn_str(conn), 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.conn_pool = self._active_conn_pool
+
+ conn.last_used = now
+ lg.debug("API client connection %s acquired" % _conn_str(conn))
+ return conn
+
+ def release_connection(self, http_conn, bad_state=False):
+ '''Mark HTTPConnection instance as available for check-out.
+
+ Args:
+ http_conn: An HTTPConnection instance obtained from this
+ instance.
+ bad_state: True if http_conn is known to be in a bad state
+ (e.g. connection fault.)
+ '''
+ if self._conn_params(http_conn) not in self._api_providers:
+ lg.debug("Released connection '%s' is no longer an API provider "
+ "for the cluster" % _conn_str(http_conn))
+ return
+
+ # Retrieve "home" connection pool.
+ conn_pool = http_conn.conn_pool
+ if bad_state:
+ # reconnect
+ lg.info("API connection fault, reconnecting to %s"
+ % _conn_str(http_conn))
+ http_conn = self._create_connection(*self._conn_params(http_conn))
+ http_conn.conn_pool = conn_pool
+ conn_pool.put(http_conn)
+
+ if self._active_conn_pool == http_conn.conn_pool:
+ # Get next connection from the connection pool and make it
+ # active.
+ lg.info("API connection fault changing active_conn_pool.")
+ self._conn_pool.put(self._active_conn_pool)
+ self._active_conn_pool = self._conn_pool.get()
+ self._issue_conn_barrier = time.time() + self._failover_time
+ else:
+ conn_pool.put(http_conn)
+
+ lg.debug("API client connection %s released" % _conn_str(http_conn))
+
+ @property
+ def need_login(self):
+ return self._need_login
+
+ @need_login.setter
+ def need_login(self, val=True):
+ self._need_login = val
+
+ def wait_for_login(self):
+ if self._need_login:
+ if self._doing_login_sem.acquire(blocking=False):
+ self.login()
+ self._doing_login_sem.release()
+ else:
+ lg.debug("Waiting for auth to complete")
+ self._doing_login_sem.acquire()
+ self._doing_login_sem.release()
+ return self._cookie
+
+ def login(self):
+ '''Issue login request and update authentication cookie.'''
+ g = request_eventlet.NvpLoginRequestEventlet(
+ self, self._user, self._password)
+ g.start()
+ ret = g.join()
+
+ if ret:
+ if isinstance(ret, Exception):
+ lg.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)
+ self._cookie = cookie
+ self._need_login = False
+
+ if not ret:
+ return None
+
+ return self._cookie
+
+
+# Register as subclass.
+client.NvpApiClient.register(NvpApiClientEventlet)
--- /dev/null
+# Copyright (C) 2009-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.
+
+
+import httplib
+import mock
+
+
+def _conn_str(conn):
+ if isinstance(conn, httplib.HTTPSConnection):
+ proto = "https://"
+ elif isinstance(conn, httplib.HTTPConnection):
+ proto = "http://"
+ elif isinstance(conn, mock.Mock):
+ proto = "http://"
+ else:
+ raise TypeError('_conn_str() invalid connection type: %s' % type(conn))
+
+ return "%s%s:%s" % (proto, conn.host, conn.port)
--- /dev/null
+# Copyright (C) 2009-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.
+
+
+from abc import ABCMeta
+from abc import abstractmethod
+from abc import abstractproperty
+
+
+class NvpApiRequest:
+ '''An abstract baseclass for all ApiRequest implementations.
+
+ This defines the interface and property structure for both eventlet and
+ gevent-based ApiRequest classes.
+ '''
+
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def start(self):
+ pass
+
+ @abstractmethod
+ def join(self):
+ pass
+
+ @abstractmethod
+ def copy(self):
+ pass
+
+ @abstractproperty
+ def request_error(self):
+ pass
--- /dev/null
+# Copyright (C) 2009-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.
+
+
+import client_eventlet
+import eventlet
+import httplib
+import urllib
+import urlparse
+import logging
+import request
+import time
+import json
+from common import _conn_str
+from eventlet import timeout
+
+
+logging.basicConfig(level=logging.INFO)
+lg = logging.getLogger("nvp_api_request")
+USER_AGENT = "NVP gevent client/1.0"
+
+# Default parameters.
+DEFAULT_REQUEST_TIMEOUT = 30
+DEFAULT_HTTP_TIMEOUT = 10
+DEFAULT_RETRIES = 2
+DEFAULT_REDIRECTS = 2
+API_REQUEST_POOL_SIZE = 10000
+
+
+class NvpApiRequestEventlet:
+ '''Eventlet-based ApiRequest class.
+
+ This class will form the basis for eventlet-based ApiRequest classes
+ (e.g. those used by the Quantum NVP Plugin).
+ '''
+
+ ALLOWED_STATUS_CODES = [
+ httplib.OK,
+ httplib.CREATED,
+ httplib.NO_CONTENT,
+ httplib.MOVED_PERMANENTLY,
+ httplib.TEMPORARY_REDIRECT,
+ httplib.BAD_REQUEST,
+ httplib.UNAUTHORIZED,
+ httplib.FORBIDDEN,
+ httplib.NOT_FOUND,
+ httplib.CONFLICT,
+ httplib.INTERNAL_SERVER_ERROR,
+ httplib.SERVICE_UNAVAILABLE
+ ]
+
+ API_REQUEST_POOL = eventlet.GreenPool(API_REQUEST_POOL_SIZE)
+
+ def __init__(self, nvp_api_client, url, method="GET", body=None,
+ headers=None,
+ request_timeout=DEFAULT_REQUEST_TIMEOUT,
+ retries=DEFAULT_RETRIES,
+ auto_login=True,
+ redirects=DEFAULT_REDIRECTS,
+ http_timeout=DEFAULT_HTTP_TIMEOUT):
+
+ self._api_client = nvp_api_client
+ self._url = url
+ self._method = method
+ self._body = body
+ self._headers = headers or {}
+ self._request_timeout = request_timeout
+ self._retries = retries
+ self._auto_login = auto_login
+ self._redirects = redirects
+ self._http_timeout = http_timeout
+
+ self._request_error = None
+
+ if "User-Agent" not in self._headers:
+ self._headers["User-Agent"] = USER_AGENT
+
+ self._green_thread = None
+
+ @classmethod
+ def _spawn(cls, func, *args, **kwargs):
+ return cls.API_REQUEST_POOL.spawn(func, *args, **kwargs)
+
+ def spawn(self, func, *args, **kwargs):
+ return self.__class__._spawn(func, *args, **kwargs)
+
+ @classmethod
+ def joinall(cls):
+ return cls.API_REQUEST_POOL.waitall()
+
+ def join(self):
+ if self._green_thread is not None:
+ return self._green_thread.wait()
+ lg.error('Joining on invalid green thread')
+ return Exception('Joining an invalid green thread')
+
+ def start(self):
+ self._green_thread = self.spawn(self._run)
+
+ def copy(self):
+ return NvpApiRequestEventlet(
+ self._api_client, self._url, self._method, self._body,
+ self._headers, self._request_timeout, self._retries,
+ self._auto_login, self._redirects, self._http_timeout)
+
+ @property
+ def request_error(self):
+ return self._request_error
+
+ def _run(self):
+ if self._request_timeout:
+ # No timeout exception escapes the with block.
+ with timeout.Timeout(self._request_timeout, False):
+ return self._handle_request()
+
+ lg.info('Request timeout handling request.')
+ self._request_error = Exception('Request timeout')
+ return None
+ else:
+ return self._handle_request()
+
+ def _request_str(self, conn, url):
+ return "%s %s/%s" % (self._method, _conn_str(conn), url)
+
+ def _issue_request(self):
+ conn = self._api_client.acquire_connection()
+ if conn is None:
+ error = Exception("No API connections available")
+ self._request_error = error
+ return error
+
+ url = self._url
+ lg.info("Issuing request '%s'" % self._request_str(conn, url))
+ issued_time = time.time()
+ is_conn_error = False
+ try:
+ redirects = 0
+ while (redirects <= self._redirects):
+ # Update connection with user specified request timeout,
+ # the connect timeout is usually smaller so we only set
+ # the request timeout after a connection is established
+ if conn.sock is None:
+ conn.connect()
+ conn.sock.settimeout(self._http_timeout)
+ elif conn.sock.gettimeout() != self._http_timeout:
+ conn.sock.settimeout(self._http_timeout)
+
+ try:
+ conn.request(self._method, url, self._body, self._headers)
+ except Exception, e:
+ lg.info('_issue_request: conn.request() exception: %s' % e)
+ raise e
+
+ response = conn.getresponse()
+ response.body = response.read()
+ response.headers = response.getheaders()
+ lg.info("Request '%s' complete: %s (%0.2f seconds)"
+ % (self._request_str(conn, url), response.status,
+ time.time() - issued_time))
+ if response.status not in [httplib.MOVED_PERMANENTLY,
+ httplib.TEMPORARY_REDIRECT]:
+ break
+ elif redirects >= self._redirects:
+ lg.warn("Maximum redirects exceeded, aborting request")
+ break
+ redirects += 1
+ conn, url = self._redirect_params(conn, response.headers)
+ if url is None:
+ response.status = httplib.INTERNAL_SERVER_ERROR
+ break
+ lg.info("Redirecting request to: %s" % \
+ self._request_str(conn, url))
+
+ # If we receive any of these responses, then our server did not
+ # process our request and may be in an errored state. Raise an
+ # exception, which will cause the the conn to be released with
+ # is_conn_error == True which puts the conn on the back of the
+ # client's priority queue.
+ if response.status >= 500:
+ lg.warn("API Request '%s %s' received: %s"
+ % (self._method, self._url, response.status))
+ raise Exception('Server error return: %s' %
+ response.status)
+ return response
+ except Exception, e:
+ if isinstance(e, httplib.BadStatusLine):
+ msg = "Invalid server response"
+ else:
+ msg = unicode(e)
+ lg.warn("Request '%s' failed: %s (%0.2f seconds)"
+ % (self._request_str(conn, url), msg,
+ time.time() - issued_time))
+ self._request_error = e
+ is_conn_error = True
+ return e
+ finally:
+ self._api_client.release_connection(conn, is_conn_error)
+
+ def _redirect_params(self, conn, headers):
+ url = None
+ for name, value in headers:
+ if name.lower() == "location":
+ url = value
+ break
+ if not url:
+ lg.warn("Received redirect status without location header field")
+ return (conn, None)
+ # Accept location with the following format:
+ # 1. /path, redirect to same node
+ # 2. scheme://hostname:[port]/path where scheme is https or http
+ # Reject others
+ # 3. e.g. relative paths, unsupported scheme, unspecified host
+ result = urlparse.urlparse(url)
+ if not result.scheme and not result.hostname and result.path:
+ if result.path[0] == "/":
+ if result.query:
+ url = "%s?%s" % (result.path, result.query)
+ else:
+ url = result.path
+ return (conn, url) # case 1
+ else:
+ lg.warn("Received invalid redirect location: %s" % url)
+ return (conn, None) # case 3
+ elif result.scheme not in ["http", "https"] or not result.hostname:
+ lg.warn("Received malformed redirect location: %s" % url)
+ return (conn, None) # case 3
+ # case 2, redirect location includes a scheme
+ # so setup a new connection and authenticate
+ use_https = result.scheme == "https"
+ api_providers = [(result.hostname, result.port, use_https)]
+ api_client = client_eventlet.NvpApiClientEventlet(
+ api_providers, self._api_client.user, self._api_client.password,
+ use_https=use_https)
+ api_client.wait_for_login()
+ if api_client.auth_cookie:
+ self._headers["Cookie"] = api_client.auth_cookie
+ else:
+ self._headers["Cookie"] = ""
+ conn = api_client.acquire_connection()
+ if result.query:
+ url = "%s?%s" % (result.path, result.query)
+ else:
+ url = result.path
+ return (conn, url)
+
+ def _handle_request(self):
+ attempt = 0
+ response = None
+ while response is None and attempt <= self._retries:
+ attempt += 1
+
+ if self._auto_login and self._api_client.need_login:
+ self._api_client.wait_for_login()
+
+ if self._api_client.auth_cookie and "Cookie" not in self._headers:
+ self._headers["Cookie"] = self._api_client.auth_cookie
+
+ req = self.spawn(self._issue_request).wait()
+ # automatically raises any exceptions returned.
+ lg.debug('req: %s' % type(req))
+
+ if isinstance(req, httplib.HTTPResponse):
+ if (req.status == httplib.UNAUTHORIZED
+ or req.status == httplib.FORBIDDEN):
+ self._api_client.need_login = True
+ if attempt <= self._retries:
+ continue
+ # else fall through to return the error code
+
+ lg.debug("API Request '%s %s' complete: %s"
+ % (self._method, self._url, req.status))
+ self._request_error = None
+ response = req
+ else:
+ lg.info('_handle_request: caught an error - %s' % req)
+ self._request_error = req
+
+ lg.debug('_handle_request: response - %s' % response)
+ return response
+
+
+class NvpLoginRequestEventlet(NvpApiRequestEventlet):
+ def __init__(self, nvp_client, user, password):
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ body = urllib.urlencode({"username": user, "password": password})
+ NvpApiRequestEventlet.__init__(
+ self, nvp_client, "/ws.v1/login", "POST", body, headers,
+ auto_login=False)
+
+ def session_cookie(self):
+ if self.successful():
+ return self.value.getheader("Set-Cookie")
+ return None
+
+
+class NvpGetApiProvidersRequestEventlet(NvpApiRequestEventlet):
+ def __init__(self, nvp_client):
+ url = "/ws.v1/control-cluster/node?fields=roles"
+ NvpApiRequestEventlet.__init__(
+ self, nvp_client, url, "GET", auto_login=True)
+
+ def api_providers(self):
+ """Parse api_providers from response.
+
+ Returns: api_providers in [(host, port, is_ssl), ...] format
+ """
+ def _provider_from_listen_addr(addr):
+ # (pssl|ptcp):<ip>:<port> => (host, port, is_ssl)
+ parts = addr.split(':')
+ return (parts[1], int(parts[2]), parts[0] == 'pssl')
+
+ try:
+ if self.successful():
+ ret = []
+ body = json.loads(self.value.body)
+ for node in body.get('results', []):
+ for role in node.get('roles', []):
+ if role.get('role') == 'api_provider':
+ addr = role.get('listen_addr')
+ if addr:
+ ret.append(_provider_from_listen_addr(addr))
+ return ret
+ except Exception, e:
+ lg.warn("Failed to parse API provider: %s" % e)
+ # intentionally fall through
+ return None
+
+
+class NvpGenericRequestEventlet(NvpApiRequestEventlet):
+ def __init__(self, nvp_client, method, url, body, content_type,
+ auto_login=False,
+ request_timeout=DEFAULT_REQUEST_TIMEOUT,
+ http_timeout=DEFAULT_HTTP_TIMEOUT,
+ retries=DEFAULT_RETRIES,
+ redirects=DEFAULT_REDIRECTS):
+ headers = {"Content-Type": content_type}
+
+ NvpApiRequestEventlet.__init__(
+ self, nvp_client, url, method, body, headers,
+ request_timeout=request_timeout, retries=retries,
+ auto_login=auto_login, redirects=redirects,
+ http_timeout=http_timeout)
+
+ def session_cookie(self):
+ if self.successful():
+ return self.value.getheader("Set-Cookie")
+ return None
+
+
+# Register subclasses
+request.NvpApiRequest.register(NvpApiRequestEventlet)
--- /dev/null
+# Copyright (C) 2009-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.
+
+from optparse import OptionParser
+
+import gettext
+import logging
+import os
+import sys
+
+gettext.install('nvp-plugin-cli', unicode=1)
+
+from QuantumPlugin import NvpPlugin as QuantumManager
+import nvplib
+
+logging.basicConfig(level=logging.INFO)
+LOG = logging.getLogger('nvp-plugin-cli')
+
+
+def print_help():
+ """Help for CLI"""
+ print "\nNVP Plugin Commands:"
+ for key in COMMANDS.keys():
+ print " %s %s" % (key,
+ " ".join(["<%s>" % y for y in COMMANDS[key]["args"]]))
+
+
+def build_args(cmd, cmdargs, arglist):
+ """Building the list of args for a particular CLI"""
+ args = []
+ orig_arglist = arglist[:]
+ try:
+ for cmdarg in cmdargs:
+ args.append(arglist[0])
+ del arglist[0]
+ except:
+ LOG.error("Not enough arguments for \"%s\" (expected: %d, got: %d)" % (
+ cmd, len(cmdargs), len(orig_arglist)))
+ print "Usage:\n %s %s" % (cmd,
+ " ".join(["<%s>" % y for y in COMMANDS[cmd]["args"]]))
+ sys.exit()
+ if len(arglist) > 0:
+ LOG.error("Too many arguments for \"%s\" (expected: %d, got: %d)" % (
+ cmd, len(cmdargs), len(orig_arglist)))
+ print "Usage:\n %s %s" % (cmd,
+ " ".join(["<%s>" % y for y in COMMANDS[cmd]["args"]]))
+ sys.exit()
+ return args
+
+
+def check_config(manager):
+ """A series of checks to make sure the plugin is correctly configured."""
+ checks = [{"function": nvplib.check_default_transport_zone,
+ "desc": "Transport zone check:"}]
+ any_failed = False
+ for c in checks:
+ result, msg = "PASS", ""
+ try:
+ c["function"]()
+ except Exception, e:
+ any_failed = True
+ result = "FAIL"
+ msg = "(%s)" % str(e)
+ print "%s %s%s" % (c["desc"], result, msg)
+ sys.exit({False: 0, True: 1}[any_failed])
+
+
+COMMANDS = {
+ "check_config": {
+ "need_login": True,
+ "func": check_config,
+ "args": []},
+ }
+
+
+def main():
+ usagestr = "Usage: %prog [OPTIONS] <command> [args]"
+ PARSER = OptionParser(usage=usagestr)
+ PARSER.add_option("-v", "--verbose", dest="verbose",
+ action="store_true", default=False, help="turn on verbose logging")
+ PARSER.add_option("-c", "--configfile", dest="configfile",
+ type="string", default="/etc/quantum/plugins/nvp/nvp.ini",
+ help="nvp plugin config file path (nvp.ini)")
+ options, args = PARSER.parse_args()
+
+ loglevel = logging.INFO
+ if options.verbose:
+ loglevel = logging.DEBUG
+
+ LOG.setLevel(loglevel)
+
+ if len(args) < 1:
+ PARSER.print_help()
+ print_help()
+ sys.exit(1)
+
+ CMD = args[0]
+ if CMD not in COMMANDS.keys():
+ LOG.error("Unknown command: %s" % CMD)
+ print_help()
+ sys.exit(1)
+
+ args = build_args(CMD, COMMANDS[CMD]["args"], args[1:])
+
+ LOG.debug("Executing command \"%s\" with args: %s" % (CMD, args))
+
+ manager = None
+ if COMMANDS[CMD]["need_login"] == True:
+ if not os.path.exists(options.configfile):
+ LOG.error("NVP plugin configuration file \"%s\" doesn't exist!" %
+ options.configfile)
+ sys.exit(1)
+ manager = QuantumManager(options.configfile, loglevel, cli=True)
+
+ COMMANDS[CMD]["func"](manager, *args)
+
+ sys.exit(0)
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+# Copyright 2012 Nicira Networks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Brad Hall, Nicira Networks, Inc.
+
+from quantum.common import exceptions as exception
+import json
+import logging
+import NvpApiClient
+
+LOG = logging.getLogger("nvplib")
+LOG.setLevel(logging.INFO)
+
+
+def do_single_request(*args, **kwargs):
+ """Issue a request to a specified controller if specified via kwargs
+ (controller=<controller>)."""
+ controller = kwargs["controller"]
+ LOG.debug("Issuing request to controller: %s" % controller.name)
+ return controller.api_client.request(*args)
+
+
+def check_default_transport_zone(c):
+ """Make sure the default transport zone specified in the config exists"""
+ msg = []
+ # This will throw an exception on failure and that's ok since it will
+ # just propogate to the cli.
+ resp = do_single_request("GET",
+ "/ws.v1/transport-zone?uuid=%s" % c.default_tz_uuid,
+ controller=c)
+ result = json.loads(resp)
+ if int(result["result_count"]) == 0:
+ msg.append("Unable to find zone \"%s\" for controller \"%s\"" %
+ (c.default_tz_uuid, c.name))
+ if len(msg) > 0:
+ raise Exception(' '.join(msg))
+
+
+def check_tenant(controller, net_id, tenant_id):
+ """Return true if the tenant "owns" this network"""
+ net = get_network(controller, net_id)
+ for t in net["tags"]:
+ if t["scope"] == "os_tid" and t["tag"] == tenant_id:
+ return True
+ return False
+
+# -------------------------------------------------------------------
+# Network functions
+# -------------------------------------------------------------------
+
+
+def get_network(controller, net_id):
+ path = "/ws.v1/lswitch/%s" % net_id
+ try:
+ resp_obj = do_single_request("GET", path, controller=controller)
+ network = json.loads(resp_obj)
+ except NvpApiClient.ResourceNotFound as e:
+ raise exception.NetworkNotFound(net_id=net_id)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ LOG.debug("Got network \"%s\": %s" % (net_id, network))
+ return network
+
+
+def create_lswitch(controller, lswitch_obj):
+ LOG.debug("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"
+ try:
+ resp_obj = do_single_request("POST", uri,
+ json.dumps(lswitch_obj),
+ controller=controller)
+ except NvpApiClient.NvpApiException as e:
+ 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
+
+
+def update_network(controller, network, **kwargs):
+ uri = "/ws.v1/lswitch/" + network
+ lswitch_obj = {}
+ if "name" in kwargs:
+ lswitch_obj["display_name"] = kwargs["name"]
+ try:
+ resp_obj = do_single_request("PUT", uri,
+ json.dumps(lswitch_obj), controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Network not found, Error: %s" % str(e))
+ raise exception.NetworkNotFound(net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+
+ obj = json.loads(resp_obj)
+ return obj
+
+
+def get_all_networks(controller, tenant_id, networks):
+ """Append the quantum network uuids we can find in the given controller to
+ "networks"
+ """
+ uri = "/ws.v1/lswitch?fields=*&tag=%s&tag_scope=os_tid" % tenant_id
+ try:
+ resp_obj = do_single_request("GET", uri, controller=controller)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ if not resp_obj:
+ return []
+ lswitches = json.loads(resp_obj)["results"]
+ for lswitch in lswitches:
+ net_id = lswitch["uuid"]
+ if net_id not in [x["net-id"] for x in networks]:
+ networks.append({"net-id": net_id,
+ "net-name": lswitch["display_name"]})
+ return networks
+
+
+def query_networks(controller, tenant_id, fields="*", tags=None):
+ uri = "/ws.v1/lswitch?fields=%s" % fields
+ if tags:
+ for t in tags:
+ uri += "&tag=%s&tag_scope=%s" % (t[0], t[1])
+ try:
+ resp_obj = do_single_request("GET", uri, controller=controller)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ if not resp_obj:
+ return []
+ lswitches = json.loads(resp_obj)["results"]
+ nets = [{'net-id': lswitch["uuid"],
+ 'net-name': lswitch["display_name"]}
+ for lswitch in lswitches]
+ return nets
+
+
+def delete_network(controller, network):
+ delete_networks(controller, [network])
+
+
+def delete_networks(controller, networks):
+ for network in networks:
+ path = "/ws.v1/lswitch/%s" % network
+
+ try:
+ do_single_request("DELETE", path, controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Network not found, Error: %s" % str(e))
+ raise exception.NetworkNotFound(net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+
+
+def create_network(tenant_id, net_name, **kwargs):
+ controller = kwargs["controller"]
+
+ transport_zone = kwargs.get("transport_zone",
+ controller.default_tz_uuid)
+ transport_type = kwargs.get("transport_type", "gre")
+ 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(controller, lswitch_obj)
+ net['net-op-status'] = "UP"
+ return net
+
+#---------------------------------------------------------------------
+# Port functions
+#---------------------------------------------------------------------
+
+
+def get_port_stats(controller, network_id, port_id):
+ try:
+ do_single_request("GET", "/ws.v1/lswitch/%s" % (network_id),
+ controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Network not found, Error: %s" % str(e))
+ raise exception.NetworkNotFound(net_id=network_id)
+ try:
+ path = "/ws.v1/lswitch/%s/lport/%s/statistic" % (network_id, port_id)
+ resp = do_single_request("GET", path, controller=controller)
+ stats = json.loads(resp)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Port not found, Error: %s" % str(e))
+ raise exception.PortNotFound(port_id=port_id, net_id=network_id)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ LOG.debug("Returning stats for port \"%s\" on \"%s\": %s" % (port_id,
+ network_id,
+ stats))
+ return stats
+
+
+def check_port_state(state):
+ if state not in ["ACTIVE", "DOWN"]:
+ LOG.error("Invalid port state (ACTIVE and " \
+ "DOWN are valid states): %s" % state)
+ raise exception.StateInvalid(port_state=state)
+
+
+def query_ports(controller, network, relations=None, fields="*", filters=None):
+ uri = "/ws.v1/lswitch/" + network + "/lport?"
+ if relations:
+ uri += "relations=%s" % relations
+ uri += "&fields=%s" % fields
+ if filters and "attachment" in filters:
+ uri += "&attachment_vif_uuid=%s" % filters["attachment"]
+ try:
+ resp_obj = do_single_request("GET", uri,
+ controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Network not found, Error: %s" % str(e))
+ raise exception.NetworkNotFound(net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ return json.loads(resp_obj)["results"]
+
+
+def delete_port(controller, network, port):
+ uri = "/ws.v1/lswitch/" + network + "/lport/" + port
+ try:
+ do_single_request("DELETE", uri, controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Port or Network not found, Error: %s" % str(e))
+ raise exception.PortNotFound(port_id=port, net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+
+
+def delete_all_ports(controller, ls_uuid):
+ res = do_single_request("GET",
+ "/ws.v1/lswitch/%s/lport?fields=uuid" % ls_uuid,
+ controller=controller)
+ res = json.loads(res)
+ for r in res["results"]:
+ do_single_request("DELETE",
+ "/ws.v1/lswitch/%s/lport/%s" % (ls_uuid, r["uuid"]),
+ controller=controller)
+
+
+def get_port(controller, network, port, relations=None):
+ uri = "/ws.v1/lswitch/" + network + "/lport/" + port + "?"
+ if relations:
+ uri += "relations=%s" % relations
+ try:
+ resp_obj = do_single_request("GET", uri, controller=controller)
+ port = json.loads(resp_obj)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Port or Network not found, Error: %s" % str(e))
+ raise exception.PortNotFound(port_id=port, net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ return port
+
+
+def plug_interface(controller, network, port, type, attachment=None):
+ uri = "/ws.v1/lswitch/" + network + "/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), controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Port or Network not found, Error: %s" % str(e))
+ raise exception.PortNotFound(port_id=port, net_id=network)
+ except NvpApiClient.Conflict as e:
+ LOG.error("Conflict while making attachment to port, " \
+ "Error: %s" % str(e))
+ raise exception.AlreadyAttached(att_id=attachment,
+ port_id=port,
+ net_id=network,
+ att_port_id="UNKNOWN")
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+
+ result = json.dumps(resp_obj)
+ return result
+
+
+def unplug_interface(controller, network, port):
+ uri = "/ws.v1/lswitch/" + network + "/lport/" + port + "/attachment"
+ lport_obj = {"type": "NoAttachment"}
+ try:
+ resp_obj = do_single_request("PUT",
+ uri, json.dumps(lport_obj), controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Port or Network not found, Error: %s" % str(e))
+ raise exception.PortNotFound(port_id=port, net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ return json.loads(resp_obj)
+
+
+def update_port(network, port_id, **params):
+ controller = params["controller"]
+ lport_obj = {}
+
+ if "state" in params:
+ state = params["state"]
+ check_port_state(state)
+ admin_status = True
+ if state == "DOWN":
+ admin_status = False
+ lport_obj["admin_status_enabled"] = admin_status
+
+ uri = "/ws.v1/lswitch/" + network + "/lport/" + port_id
+ try:
+ resp_obj = do_single_request("PUT", uri,
+ json.dumps(lport_obj), controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Port or Network not found, Error: %s" % str(e))
+ raise exception.PortNotFound(port_id=port_id, net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+
+ obj = json.loads(resp_obj)
+ obj["port-op-status"] = get_port_status(controller, network, obj["uuid"])
+ return obj
+
+
+def create_port(tenant, network, port_init_state, **params):
+ # Check initial state -- this throws an exception if the port state is
+ # invalid
+ check_port_state(port_init_state)
+
+ controller = params["controller"]
+
+ ls_uuid = network
+
+ admin_status = True
+ if port_init_state == "DOWN":
+ admin_status = False
+ lport_obj = {"admin_status_enabled": admin_status}
+
+ path = "/ws.v1/lswitch/" + ls_uuid + "/lport"
+ try:
+ resp_obj = do_single_request("POST", path,
+ json.dumps(lport_obj), controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Network not found, Error: %s" % str(e))
+ raise exception.NetworkNotFound(net_id=network)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+
+ result = json.loads(resp_obj)
+ result['port-op-status'] = get_port_status(controller, ls_uuid,
+ result['uuid'])
+ return result
+
+
+def get_port_status(controller, lswitch_id, port_id):
+ """Retrieve the operational status of the port"""
+ # Make sure the network exists first
+ try:
+ do_single_request("GET", "/ws.v1/lswitch/%s" % (lswitch_id),
+ controller=controller)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Network not found, Error: %s" % str(e))
+ raise exception.NetworkNotFound(net_id=lswitch_id)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ try:
+ r = do_single_request("GET",
+ "/ws.v1/lswitch/%s/lport/%s/status" % (lswitch_id, port_id),
+ controller=controller)
+ r = json.loads(r)
+ except NvpApiClient.ResourceNotFound as e:
+ LOG.error("Port not found, Error: %s" % str(e))
+ raise exception.PortNotFound(port_id=port_id, net_id=lswitch_id)
+ except NvpApiClient.NvpApiException as e:
+ raise exception.QuantumException()
+ if r['link_status_up'] is True:
+ return "UP"
+ else:
+ return "DOWN"
--- /dev/null
+# Copyright 2012 Nicira Networks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Brad Hall, Nicira Networks, Inc.
+
+import logging
+import unittest
+
+from nicira_nvp_plugin.QuantumPlugin import NvpPlugin
+from nicira_nvp_plugin import nvplib
+
+logging.basicConfig(level=logging.DEBUG)
+LOG = logging.getLogger("test_check")
+
+
+class NvpTests(unittest.TestCase):
+ def setUp(self):
+ self.quantum = NvpPlugin()
+
+ def tearDown(self):
+ pass
+
+ # These nvplib functions will throw an exception if the check fails
+ def test_check_default_transport_zone(self):
+ nvplib.check_default_transport_zone(self.quantum.controller)
--- /dev/null
+# Copyright 2012 Nicira Networks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import unittest
+import StringIO
+import ConfigParser
+from nicira_nvp_plugin.QuantumPlugin import parse_config
+from nicira_nvp_plugin.QuantumPlugin import NVPCluster
+
+
+class ConfigParserTest(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_nvp_config_000(self):
+ nvpc = NVPCluster('cluster1')
+ for f in [
+ (
+ 'default_tz_id1', 'ip1', 'port1', 'user1', 'passwd1', 42, 43,
+ 44, 45),
+ (
+ 'default_tz_id1', 'ip2', 'port2', 'user2', 'passwd2', 42, 43,
+ 44, 45),
+ (
+ 'default_tz_id1', 'ip3', 'port3', 'user3', 'passwd3', 42, 43,
+ 44, 45),
+ ]:
+ nvpc.add_controller(*f)
+
+ self.assertTrue(nvpc.name == 'cluster1')
+ self.assertTrue(len(nvpc.controllers) == 3)
+
+ def test_old_config_parser_old_style(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_IP = <controller ip>
+PORT = <port>
+USER = <user>
+PASSWORD = <pass>
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+
+ self.assertTrue(cluster1.name == 'cluster1')
+ self.assertTrue(
+ cluster1.controllers[0]['default_tz_uuid'] == '<default uuid>')
+ self.assertTrue(
+ cluster1.controllers[0]['port'] == '<port>')
+ self.assertTrue(
+ cluster1.controllers[0]['user'] == '<user>')
+ self.assertTrue(
+ cluster1.controllers[0]['password'] == '<pass>')
+ self.assertTrue(
+ cluster1.controllers[0]['request_timeout'] == 30)
+ self.assertTrue(
+ cluster1.controllers[0]['http_timeout'] == 10)
+ self.assertTrue(
+ cluster1.controllers[0]['retries'] == 2)
+ self.assertTrue(
+ cluster1.controllers[0]['redirects'] == 2)
+
+ def test_old_config_parser_new_style(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_CONNECTIONS = CONNECTION1
+CONNECTION1 = 10.0.0.1:4242:admin:admin:42:43:44:45
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+
+ self.assertTrue(cluster1.name == 'cluster1')
+ self.assertTrue(
+ cluster1.controllers[0]['default_tz_uuid'] == '<default uuid>')
+ self.assertTrue(
+ cluster1.controllers[0]['port'] == '4242')
+ self.assertTrue(
+ cluster1.controllers[0]['user'] == 'admin')
+ self.assertTrue(
+ cluster1.controllers[0]['password'] == 'admin')
+ self.assertTrue(
+ cluster1.controllers[0]['request_timeout'] == 42)
+ self.assertTrue(
+ cluster1.controllers[0]['http_timeout'] == 43)
+ self.assertTrue(
+ cluster1.controllers[0]['retries'] == 44)
+ self.assertTrue(
+ cluster1.controllers[0]['redirects'] == 45)
+
+ def test_old_config_parser_both_styles(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+NVP_CONTROLLER_IP = <controller ip>
+PORT = <port>
+USER = <user>
+PASSWORD = <pass>
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_CONNECTIONS = CONNECTION1
+CONNECTION1 = 10.0.0.1:4242:admin:admin:42:43:44:45
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+
+ self.assertTrue(cluster1.name == 'cluster1')
+ self.assertTrue(
+ cluster1.controllers[0]['default_tz_uuid'] == '<default uuid>')
+ self.assertTrue(
+ cluster1.controllers[0]['port'] == '4242')
+ self.assertTrue(
+ cluster1.controllers[0]['user'] == 'admin')
+ self.assertTrue(
+ cluster1.controllers[0]['password'] == 'admin')
+ self.assertTrue(
+ cluster1.controllers[0]['request_timeout'] == 42)
+ self.assertTrue(
+ cluster1.controllers[0]['http_timeout'] == 43)
+ self.assertTrue(
+ cluster1.controllers[0]['retries'] == 44)
+ self.assertTrue(
+ cluster1.controllers[0]['redirects'] == 45)
+
+ def test_old_config_parser_both_styles(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+NVP_CONTROLLER_IP = <controller ip>
+PORT = <port>
+USER = <user>
+PASSWORD = <pass>
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_CONNECTIONS = CONNECTION1
+CONNECTION1 = 10.0.0.1:4242:admin:admin:42:43:44:45
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+
+ self.assertTrue(cluster1.name == 'cluster1')
+ self.assertTrue(
+ cluster1.controllers[0]['default_tz_uuid'] == '<default uuid>')
+ self.assertTrue(
+ cluster1.controllers[0]['port'] == '4242')
+ self.assertTrue(
+ cluster1.controllers[0]['user'] == 'admin')
+ self.assertTrue(
+ cluster1.controllers[0]['password'] == 'admin')
+ self.assertTrue(
+ cluster1.controllers[0]['request_timeout'] == 42)
+ self.assertTrue(
+ cluster1.controllers[0]['http_timeout'] == 43)
+ self.assertTrue(
+ cluster1.controllers[0]['retries'] == 44)
+ self.assertTrue(
+ cluster1.controllers[0]['redirects'] == 45)
+
+ def test_failover_time(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_IP = <controller ip>
+PORT = 443
+USER = admin
+PASSWORD = admin
+FAILOVER_TIME = 10
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+ self.assertTrue(plugin_config['failover_time'] == '10')
+
+ def test_failover_time_new_style(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_CONNECTIONS = CONNECTION1
+CONNECTION1 = 10.0.0.1:4242:admin:admin:42:43:44:45
+FAILOVER_TIME = 10
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+ self.assertTrue(plugin_config['failover_time'] == '10')
+
+ def test_concurrent_connections_time(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_IP = <controller ip>
+PORT = 443
+USER = admin
+PASSWORD = admin
+CONCURRENT_CONNECTIONS = 5
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+ self.assertTrue(plugin_config['concurrent_connections'] == '5')
+
+ def test_concurrent_connections_time_new_style(self):
+ config = StringIO.StringIO('''
+[DEFAULT]
+[NVP]
+DEFAULT_TZ_UUID = <default uuid>
+NVP_CONTROLLER_CONNECTIONS = CONNECTION1
+CONNECTION1 = 10.0.0.1:4242:admin:admin:42:43:44:45
+CONCURRENT_CONNECTIONS = 5
+''')
+ cp = ConfigParser.ConfigParser()
+ cp.readfp(config)
+ cluster1, plugin_config = parse_config(cp)
+ self.assertTrue(plugin_config['concurrent_connections'] == '5')
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+# Copyright 2012 Nicira Networks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Somik Behera, Nicira Networks, Inc.
+# @author: Brad Hall, Nicira Networks, Inc.
+
+import json
+import logging
+import os
+import unittest
+from quantum.common import exceptions as exception
+from nicira_nvp_plugin.QuantumPlugin import NvpPlugin
+from nicira_nvp_plugin import NvpApiClient
+from nicira_nvp_plugin import nvplib
+
+logging.basicConfig(level=logging.DEBUG)
+LOG = logging.getLogger("test_network")
+
+
+class NvpTests(unittest.TestCase):
+ def setUp(self):
+ self.quantum = NvpPlugin()
+ self.BRIDGE_TZ_UUID = self._create_tz("bridge")
+ self.DEFAULT_TZ_UUID = self._create_tz("default")
+
+ self.nets = []
+ self.ports = []
+
+ def tearDown(self):
+ self._delete_tz(self.BRIDGE_TZ_UUID)
+ self._delete_tz(self.DEFAULT_TZ_UUID)
+
+ for tenant, net, port in self.ports:
+ self.quantum.delete_port(tenant, net, port)
+ for tenant, net in self.nets:
+ self.quantum.delete_network(tenant, net)
+
+ def _create_tz(self, name):
+ post_uri = "/ws.v1/transport-zone"
+ body = {"display_name": name,
+ "tags": [{"tag": "plugin-test"}]}
+ try:
+ resp_obj = self.quantum.api_client.request("POST",
+ post_uri, json.dumps(body))
+ except NvpApiClient.NvpApiException as e:
+ print("Unknown API Error: %s" % str(e))
+ raise exception.QuantumException()
+ return json.loads(resp_obj)["uuid"]
+
+ def _delete_tz(self, uuid):
+ post_uri = "/ws.v1/transport-zone/%s" % uuid
+ try:
+ resp_obj = self.quantum.api_client.request("DELETE", post_uri)
+ except NvpApiClient.NvpApiException as e:
+ LOG.error("Unknown API Error: %s" % str(e))
+ raise exception.QuantumException()
+
+ def test_create_multi_networks(self):
+
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ resp2 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantC")
+ resp3 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantD")
+ net_id = resp["net-id"]
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id1 = resp["port-id"]
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id1)
+ old_vic = resp["attachment"]
+ self.assertTrue(old_vic == "None")
+
+ self.quantum.plug_interface("quantum-test-tenant", net_id, port_id1,
+ "nova-instance-test-%s" % os.getpid())
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id1)
+ new_vic = resp["attachment"]
+ self.assertTrue(old_vic != new_vic)
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id2 = resp["port-id"]
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id2)
+ old_vic2 = resp["attachment"]
+ self.assertTrue(old_vic2 == "None")
+
+ self.quantum.plug_interface("quantum-test-tenant", net_id, port_id2,
+ "nova-instance-test2-%s" % os.getpid())
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id2)
+ new_vic = resp["attachment"]
+ self.assertTrue(old_vic2 != new_vic)
+
+ resp = self.quantum.get_all_ports("quantum-test-tenant", net_id)
+
+ resp = self.quantum.get_network_details("quantum-test-tenant", net_id)
+
+ resp = self.quantum.get_all_networks("quantum-test-tenant")
+
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id,
+ port_id1)
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id,
+ port_id2)
+ self.quantum.delete_network("quantum-test-tenant", net_id)
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ self.quantum.delete_network("quantum-test-tenant", resp2["net-id"])
+ self.quantum.delete_network("quantum-test-tenant", resp3["net-id"])
+
+ def test_update_network(self):
+ resp = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantA")
+ net_id = resp["net-id"]
+ try:
+ resp = self.quantum.update_network("quantum-test-tenant", net_id,
+ name="new-name")
+ except exception.NetworkNotFound:
+ self.assertTrue(False)
+
+ self.assertTrue(resp["net-name"] == "new-name")
+
+ def test_negative_delete_networks(self):
+ try:
+ self.quantum.delete_network("quantum-test-tenant", "xxx-no-net-id")
+ except exception.NetworkNotFound:
+ self.assertTrue(True)
+
+ def test_negative_get_network_details(self):
+ try:
+ self.quantum.get_network_details("quantum-test-tenant",
+ "xxx-no-net-id")
+ except exception.NetworkNotFound:
+ self.assertTrue(True)
+
+ def test_negative_update_network(self):
+ try:
+ self.quantum.update_network("quantum-test-tenant", "xxx-no-net-id",
+ name="new-name")
+ except exception.NetworkNotFound:
+ self.assertTrue(True)
+
+ def test_get_all_networks(self):
+ networks = self.quantum.get_all_networks("quantum-test-tenant")
+ num_nets = len(networks)
+
+ # Make sure we only get back networks with the specified tenant_id
+ unique_tid = "tenant-%s" % os.getpid()
+ # Add a network that we shouldn't get back
+ resp = self.quantum.create_custom_network(
+ "another_tid", "another_tid_network",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+ self.nets.append(("another_tid", net_id))
+ # Add 3 networks that we should get back
+ for i in [1, 2, 3]:
+ resp = self.quantum.create_custom_network(
+ unique_tid, "net-%s" % str(i),
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+ self.nets.append((unique_tid, net_id))
+ networks = self.quantum.get_all_networks(unique_tid)
+ self.assertTrue(len(networks) == 3)
+
+ def test_delete_nonexistent_network(self):
+ try:
+ nvplib.delete_network(self.quantum.controller,
+ "my-non-existent-network")
+ except exception.NetworkNotFound:
+ return
+ # shouldn't be reached
+ self.assertTrue(False)
+
+ def test_query_networks(self):
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+ self.nets.append(("quantum-test-tenant", net_id))
+ nets = nvplib.query_networks(self.quantum.controller,
+ "quantum-test-tenant")
--- /dev/null
+# Copyright (C) 2009-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.
+
+
+import httplib
+import unittest
+
+import nicira_nvp_plugin.api_client.common as naco
+
+
+class NvpApiCommonTest(unittest.TestCase):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_conn_str(self):
+ conn = httplib.HTTPSConnection('localhost', 4242, timeout=0)
+ self.assertTrue(
+ naco._conn_str(conn) == 'https://localhost:4242')
+
+ conn = httplib.HTTPConnection('localhost', 4242, timeout=0)
+ self.assertTrue(
+ naco._conn_str(conn) == 'http://localhost:4242')
+
+ with self.assertRaises(TypeError):
+ naco._conn_str('not an httplib.HTTPSConnection')
--- /dev/null
+# Copyright (C) 2009-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.
+
+
+import logging
+import unittest
+from eventlet.green import urllib2
+
+logging.basicConfig(level=logging.DEBUG)
+lg = logging.getLogger("test_nvp_api_request")
+
+REQUEST_TIMEOUT = 1
+
+
+def fetch(url):
+ return urllib2.urlopen(url).read()
+
+
+class NvpApiRequestTest(unittest.TestCase):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
--- /dev/null
+# Copyright (C) 2009-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.
+
+
+# System
+import httplib
+import logging
+import new
+import random
+import unittest
+
+# Third party
+import eventlet
+from eventlet.green import urllib2
+from mock import Mock
+from mock import patch
+
+# Local
+import nicira_nvp_plugin.api_client.client_eventlet as nace
+import nicira_nvp_plugin.api_client.request_eventlet as nare
+
+logging.basicConfig(level=logging.DEBUG)
+lg = logging.getLogger("test_nvp_api_request_eventlet")
+
+REQUEST_TIMEOUT = 1
+
+
+def fetch(url):
+ return urllib2.urlopen(url).read()
+
+
+class NvpApiRequestEventletTest(unittest.TestCase):
+
+ def setUp(self):
+
+ self.client = nace.NvpApiClientEventlet(
+ [("127.0.0.1", 4401, True)], "admin", "admin")
+ self.url = "/ws.v1/_debug"
+ self.req = nare.NvpApiRequestEventlet(
+ self.client, self.url)
+
+ def tearDown(self):
+ self.client = None
+ self.req = None
+
+ def test_construct_eventlet_api_request(self):
+ e = nare.NvpApiRequestEventlet(self.client, self.url)
+ self.assertTrue(e is not None)
+
+ def test_apirequest_spawn(self):
+ def x(id):
+ eventlet.greenthread.sleep(random.random())
+ lg.info('spawned: %d' % id)
+
+ for i in range(10):
+ nare.NvpApiRequestEventlet._spawn(x, i)
+
+ def test_apirequest_start(self):
+ for i in range(10):
+ a = nare.NvpApiRequestEventlet(
+ self.client, self.url, request_timeout=0.1)
+ a._handle_request = Mock()
+ a.start()
+ eventlet.greenthread.sleep(0.1)
+ logging.info('_handle_request called: %s' %
+ a._handle_request.called)
+ nare.NvpApiRequestEventlet.joinall()
+
+ def test_join_with_handle_request(self):
+ self.req._handle_request = Mock()
+ self.req.start()
+ self.req.join()
+ self.assertTrue(self.req._handle_request.called)
+
+ def test_join_without_handle_request(self):
+ self.req._handle_request = Mock()
+ self.req.join()
+ self.assertFalse(self.req._handle_request.called)
+
+ def test_copy(self):
+ req = self.req.copy()
+ for att in [
+ '_api_client', '_url', '_method', '_body', '_headers',
+ '_http_timeout', '_request_timeout', '_retries',
+ '_redirects', '_auto_login']:
+ self.assertTrue(getattr(req, att) is getattr(self.req, att))
+
+ def test_request_error(self):
+ self.assertTrue(self.req.request_error is None)
+
+ def test_run_and_handle_request(self):
+ self.req._request_timeout = None
+ self.req._handle_request = Mock()
+ self.req.start()
+ self.req.join()
+ self.assertTrue(self.req._handle_request.called)
+
+ def test_run_and_timeout(self):
+ def my_handle_request(self):
+ lg.info('my_handle_request() self: %s' % self)
+ lg.info('my_handle_request() dir(self): %s' % dir(self))
+ eventlet.greenthread.sleep(REQUEST_TIMEOUT * 2)
+
+ self.req._request_timeout = REQUEST_TIMEOUT
+ self.req._handle_request = new.instancemethod(
+ my_handle_request, self.req, nare.NvpApiRequestEventlet)
+ self.req.start()
+ self.assertTrue(self.req.join() is None)
+
+ def prep_issue_request(self):
+ mysock = Mock()
+ mysock.gettimeout.return_value = 4242
+
+ myresponse = Mock()
+ myresponse.read.return_value = 'body'
+ myresponse.getheaders.return_value = 'headers'
+ myresponse.status = httplib.MOVED_PERMANENTLY
+
+ myconn = Mock()
+ myconn.request.return_value = None
+ myconn.sock = mysock
+ myconn.getresponse.return_value = myresponse
+ myconn.__str__ = Mock()
+ myconn.__str__.return_value = 'myconn string'
+
+ req = self.req
+ req._request_timeout = REQUEST_TIMEOUT = 1
+ req._redirect_params = Mock()
+ req._redirect_params.return_value = (myconn, 'url')
+ req._request_str = Mock()
+ req._request_str.return_value = 'http://cool/cool'
+
+ client = self.client
+ client.need_login = False
+ client._auto_login = False
+ client._auth_cookie = False
+ client.acquire_connection = Mock()
+ client.acquire_connection.return_value = myconn
+ client.release_connection = Mock()
+
+ return (mysock, myresponse, myconn)
+
+ def test_issue_request_trigger_exception(self):
+ (mysock, myresponse, myconn) = self.prep_issue_request()
+ self.client.acquire_connection.return_value = None
+
+ self.req._issue_request()
+ lg.info('request_error: %s' % self.req._request_error)
+ self.assertTrue(isinstance(self.req._request_error, Exception))
+ self.assertTrue(self.client.acquire_connection.called)
+
+ def test_issue_request_handle_none_sock(self):
+ (mysock, myresponse, myconn) = self.prep_issue_request()
+ myconn.sock = None
+ self.req.start()
+ self.assertTrue(self.req.join() is None)
+ self.assertTrue(self.client.acquire_connection.called)
+
+ def test_issue_request_exceed_maximum_retries(self):
+ (mysock, myresponse, myconn) = self.prep_issue_request()
+ self.req.start()
+ self.assertTrue(self.req.join() is None)
+ self.assertTrue(self.client.acquire_connection.called)
+
+ def test_issue_request_trigger_non_redirect(self):
+ (mysock, myresponse, myconn) = self.prep_issue_request()
+ myresponse.status = httplib.OK
+ self.req.start()
+ self.assertTrue(self.req.join() is None)
+ self.assertTrue(self.client.acquire_connection.called)
+
+ def test_issue_request_trigger_internal_server_error(self):
+ (mysock, myresponse, myconn) = self.prep_issue_request()
+ self.req._redirect_params.return_value = (myconn, None)
+ self.req.start()
+ self.assertTrue(self.req.join() is None)
+ self.assertTrue(self.client.acquire_connection.called)
+
+ def test_redirect_params_break_on_location(self):
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(
+ myconn, [('location', None)])
+ self.assertTrue(retval is None)
+
+ def test_redirect_params_parse_a_url(self):
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(
+ myconn, [('location', '/path/a/b/c')])
+ self.assertTrue(retval is not None)
+
+ def test_redirect_params_invalid_redirect_location(self):
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(
+ myconn, [('location', '+path/a/b/c')])
+ self.assertTrue(retval is None)
+
+ def test_redirect_params_invalid_scheme(self):
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(
+ myconn, [('location', 'invalidscheme://hostname:1/path')])
+ self.assertTrue(retval is None)
+
+ def test_redirect_params_setup_https_with_cooki(self):
+ with patch('nicira_nvp_plugin.api_client.client_eventlet'
+ '.NvpApiClientEventlet') as mock:
+ api_client = mock.return_value
+ api_client.wait_for_login.return_value = None
+ api_client.auth_cookie = 'mycookie'
+ api_client.acquire_connection.return_value = True
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(
+ myconn, [('location', 'https://host:1/path')])
+
+ self.assertTrue(retval is not None)
+ self.assertTrue(api_client.wait_for_login.called)
+ self.assertTrue(api_client.acquire_connection.called)
+
+ def test_redirect_params_setup_htttps_and_query(self):
+ with patch('nicira_nvp_plugin.api_client.client_eventlet'
+ '.NvpApiClientEventlet') as mock:
+ api_client = mock.return_value
+ api_client.wait_for_login.return_value = None
+ api_client.auth_cookie = 'mycookie'
+ api_client.acquire_connection.return_value = True
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(myconn, [
+ ('location', 'https://host:1/path?q=1')])
+
+ self.assertTrue(retval is not None)
+ self.assertTrue(api_client.wait_for_login.called)
+ self.assertTrue(api_client.acquire_connection.called)
+
+ def test_redirect_params_setup_https_connection_no_cookie(self):
+ with patch('nicira_nvp_plugin.api_client.client_eventlet'
+ '.NvpApiClientEventlet') as mock:
+ api_client = mock.return_value
+ api_client.wait_for_login.return_value = None
+ api_client.auth_cookie = None
+ api_client.acquire_connection.return_value = True
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(myconn, [
+ ('location', 'https://host:1/path')])
+
+ self.assertTrue(retval is not None)
+ self.assertTrue(api_client.wait_for_login.called)
+ self.assertTrue(api_client.acquire_connection.called)
+
+ def test_redirect_params_setup_https_and_query_no_cookie(self):
+ with patch('nicira_nvp_plugin.api_client.client_eventlet'
+ '.NvpApiClientEventlet') as mock:
+ api_client = mock.return_value
+ api_client.wait_for_login.return_value = None
+ api_client.auth_cookie = None
+ api_client.acquire_connection.return_value = True
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(
+ myconn, [('location', 'https://host:1/path?q=1')])
+ self.assertTrue(retval is not None)
+ self.assertTrue(api_client.wait_for_login.called)
+ self.assertTrue(api_client.acquire_connection.called)
+
+ def test_redirect_params_path_only_with_query(self):
+ with patch('nicira_nvp_plugin.api_client.client_eventlet'
+ '.NvpApiClientEventlet') as mock:
+ api_client = mock.return_value
+ api_client.wait_for_login.return_value = None
+ api_client.auth_cookie = None
+ api_client.acquire_connection.return_value = True
+ myconn = Mock()
+ (conn, retval) = self.req._redirect_params(myconn, [
+ ('location', '/path?q=1')])
+ self.assertTrue(retval is not None)
+
+ def test_handle_request_auto_login(self):
+ self.req._auto_login = True
+ self.req._api_client = Mock()
+ self.req._api_client.need_login = True
+ self.req._request_str = Mock()
+ self.req._request_str.return_value = 'http://cool/cool'
+ self.req.spawn = Mock()
+ self.req._handle_request()
+
+ def test_handle_request_auto_login_unauth(self):
+ self.req._auto_login = True
+ self.req._api_client = Mock()
+ self.req._api_client.need_login = True
+ self.req._request_str = Mock()
+ self.req._request_str.return_value = 'http://cool/cool'
+
+ import socket
+ resp = httplib.HTTPResponse(socket.socket())
+ resp.status = httplib.UNAUTHORIZED
+ mywaiter = Mock()
+ mywaiter.wait = Mock()
+ mywaiter.wait.return_value = resp
+ self.req.spawn = Mock(return_value=mywaiter)
+ self.req._handle_request()
+
+ # NvpLoginRequestEventlet tests.
+ def test_construct_eventlet_login_request(self):
+ r = nare.NvpLoginRequestEventlet(self.client, 'user', 'password')
+ self.assertTrue(r is not None)
+
+ def test_session_cookie_session_cookie_retrieval(self):
+ r = nare.NvpLoginRequestEventlet(self.client, 'user', 'password')
+ r.successful = Mock()
+ r.successful.return_value = True
+ r.value = Mock()
+ r.value.get_header = Mock()
+ r.value.get_header.return_value = 'cool'
+ self.assertTrue(r.session_cookie() is not None)
+
+ def test_session_cookie_not_retrieved(self):
+ r = nare.NvpLoginRequestEventlet(self.client, 'user', 'password')
+ r.successful = Mock()
+ r.successful.return_value = False
+ r.value = Mock()
+ r.value.get_header = Mock()
+ r.value.get_header.return_value = 'cool'
+ self.assertTrue(r.session_cookie() is None)
+
+ # NvpGetApiProvidersRequestEventlet tests.
+ def test_construct_eventlet_get_api_providers_request(self):
+ r = nare.NvpGetApiProvidersRequestEventlet(self.client)
+ self.assertTrue(r is not None)
+
+ def test_api_providers_none_api_providers(self):
+ r = nare.NvpGetApiProvidersRequestEventlet(self.client)
+ r.successful = Mock(return_value=False)
+ self.assertTrue(r.api_providers() is None)
+
+ def test_api_providers_non_none_api_providers(self):
+ r = nare.NvpGetApiProvidersRequestEventlet(self.client)
+ r.value = Mock()
+ r.value.body = '''{
+ "results": [
+ { "roles": [
+ { "role": "api_provider",
+ "listen_addr": "pssl:1.1.1.1:1" }]}]}'''
+ r.successful = Mock(return_value=True)
+ lg.info('%s' % r.api_providers())
+ self.assertTrue(r.api_providers() is not None)
--- /dev/null
+# Copyright 2012 Nicira Networks, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Somik Behera, Nicira Networks, Inc.
+
+import json
+import logging
+import os
+import unittest
+
+from quantum.common import exceptions as exception
+from nicira_nvp_plugin.QuantumPlugin import NvpPlugin
+from nicira_nvp_plugin import NvpApiClient
+from nicira_nvp_plugin import nvplib
+
+logging.basicConfig(level=logging.DEBUG)
+LOG = logging.getLogger("test_port")
+
+
+class NvpTests(unittest.TestCase):
+ def setUp(self):
+ self.quantum = NvpPlugin()
+ self.BRIDGE_TZ_UUID = self._create_tz("bridge")
+ self.networks = []
+ self.ports = []
+ self.transport_nodes = []
+ self.cis_uuids = []
+
+ def tearDown(self):
+ self._delete_tz(self.BRIDGE_TZ_UUID)
+
+ for (net_id, p) in self.ports:
+ self.quantum.unplug_interface("quantum-test-tenant", net_id, p)
+ self.quantum.delete_port("quantum-test-tenant", net_id, p)
+ for n in self.networks:
+ self.quantum.delete_network("quantum-test-tenant", n)
+ for t in self.transport_nodes:
+ nvplib.do_single_request("DELETE", "/ws.v1/transport-node/%s" % t,
+ controller=self.quantum.controller)
+ for c in self.cis_uuids:
+ nvplib.do_single_request("DELETE",
+ "/ws.v1/cluster-interconnect-service/%s" % c,
+ controller=self.quantum.controller)
+
+ def _create_tz(self, name):
+ post_uri = "/ws.v1/transport-zone"
+ body = {"display_name": name,
+ "tags": [{"tag": "plugin-test"}]}
+ try:
+ resp_obj = self.quantum.api_client.request("POST",
+ post_uri, json.dumps(body))
+ except NvpApiClient.NvpApiException as e:
+ LOG.error("Unknown API Error: %s" % str(e))
+ raise exception.QuantumException()
+ return json.loads(resp_obj)["uuid"]
+
+ def _delete_tz(self, uuid):
+ post_uri = "/ws.v1/transport-zone/%s" % uuid
+ try:
+ resp_obj = self.quantum.api_client.request("DELETE", post_uri)
+ except NvpApiClient.NvpApiException as e:
+ LOG.error("Unknown API Error: %s" % str(e))
+ raise exception.QuantumException()
+
+ def test_create_and_delete_lots_of_ports(self):
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+
+ nports = 250
+
+ ids = []
+ for i in xrange(0, nports):
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ ids.append(port_id)
+
+ # Test that we get the correct number of ports back
+ ports = self.quantum.get_all_ports("quantum-test-tenant", net_id)
+ self.assertTrue(len(ports) == nports)
+
+ # Verify that each lswitch has matching tags
+ net = nvplib.get_network(self.quantum.controller, net_id)
+ tags = []
+ net_tags = [t["tag"] for t in net["tags"]]
+ if len(tags) == 0:
+ tags = net_tags
+ else:
+ for t in net_tags:
+ self.assertTrue(t in tags)
+
+ for port_id in ids:
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id,
+ port_id)
+ try:
+ self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id)
+ except exception.PortNotFound:
+ continue
+ # Shouldn't be reached
+ self.assertFalse(True)
+
+ self.quantum.delete_network("quantum-test-tenant", net_id)
+
+ def test_create_and_delete_port(self):
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id, port_id)
+ self.quantum.delete_network("quantum-test-tenant", net_id)
+
+ def test_create_and_delete_port_with_portsec(self):
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+
+ params = {}
+ params["NICIRA:allowed_address_pairs"] = [
+ {
+ "ip_address": "172.168.17.5",
+ "mac_address": "10:9a:dd:61:4e:89"
+ },
+ {
+ "ip_address": "172.168.17.6",
+ "mac_address": "10:9a:dd:61:4e:88"
+ }
+ ]
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE", **params)
+ port_id = resp["port-id"]
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id, port_id)
+ self.quantum.delete_network("quantum-test-tenant", net_id)
+ self.assertTrue(True)
+
+ def test_create_update_and_delete_port(self):
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id)
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id,
+ port_id)
+ self.quantum.delete_network("quantum-test-tenant",
+ net_id)
+ self.assertTrue(True)
+
+ def test_create_plug_unplug_iface(self):
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id)
+ old_vic = resp["attachment"]
+ self.assertTrue(old_vic == "None")
+ self.quantum.plug_interface("quantum-test-tenant", net_id, port_id,
+ "nova-instance-test-%s" % os.getpid())
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id)
+ new_vic = resp["attachment"]
+
+ self.assertTrue(old_vic != new_vic)
+ self.quantum.unplug_interface("quantum-test-tenant", net_id, port_id)
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id)
+ new_vic = resp["attachment"]
+ self.assertTrue(old_vic == new_vic)
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id, port_id)
+ self.quantum.delete_network("quantum-test-tenant", net_id)
+ self.assertTrue(True)
+
+ def test_create_multi_port_attachment(self):
+ resp = self.quantum.create_custom_network(
+ "quantum-test-tenant", "quantum-Private-TenantA",
+ self.BRIDGE_TZ_UUID, self.quantum.controller)
+ net_id = resp["net-id"]
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id1 = resp["port-id"]
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id1)
+ old_vic = resp["attachment"]
+ self.assertTrue(old_vic == "None")
+
+ self.quantum.plug_interface("quantum-test-tenant", net_id, port_id1,
+ "nova-instance-test-%s" % os.getpid())
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id1)
+ new_vic = resp["attachment"]
+ self.assertTrue(old_vic != new_vic)
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id2 = resp["port-id"]
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id2)
+ old_vic2 = resp["attachment"]
+ self.assertTrue(old_vic2 == "None")
+
+ self.quantum.plug_interface("quantum-test-tenant", net_id, port_id2,
+ "nova-instance-test2-%s" % os.getpid())
+ resp = self.quantum.get_port_details("quantum-test-tenant", net_id,
+ port_id2)
+ new_vic = resp["attachment"]
+ self.assertTrue(old_vic2 != new_vic)
+
+ resp = self.quantum.get_all_ports("quantum-test-tenant", net_id)
+
+ resp = self.quantum.get_network_details("quantum-test-tenant", net_id)
+
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id,
+ port_id1)
+ resp = self.quantum.delete_port("quantum-test-tenant", net_id,
+ port_id2)
+ self.quantum.delete_network("quantum-test-tenant", net_id)
+ self.assertTrue(True)
+
+ def test_negative_get_all_ports(self):
+ try:
+ self.quantum.get_all_ports("quantum-test-tenant", "xxx-no-net-id")
+ except exception.NetworkNotFound:
+ self.assertTrue(True)
+ return
+
+ self.assertTrue(False)
+
+ def test_negative_create_port1(self):
+ try:
+ self.quantum.create_port("quantum-test-tenant", "xxx-no-net-id",
+ "ACTIVE")
+ except exception.NetworkNotFound:
+ self.assertTrue(True)
+ return
+
+ self.assertTrue(False)
+
+ def test_negative_create_port2(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.create_port("quantum-test-tenant", resp1["net-id"],
+ "INVALID")
+ except exception.StateInvalid:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ return
+
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ self.assertTrue(False)
+
+ def test_negative_update_port1(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.update_port("quantum-test-tenant", resp1["net-id"],
+ "port_id_fake", state="ACTIVE")
+ except exception.PortNotFound:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ return
+
+ self.assertTrue(False)
+
+ def test_negative_update_port2(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.update_port("quantum-test-tenant", resp1["net-id"],
+ "port_id_fake", state="INVALID")
+ except exception.StateInvalid:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ return
+
+ self.assertTrue(False)
+
+ def test_negative_update_port3(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.update_port("quantum-test-tenant", resp1["net-id"],
+ "port_id_fake", state="ACTIVE")
+ except exception.PortNotFound:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ return
+
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ self.assertTrue(False)
+
+ def test_negative_delete_port1(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.delete_port("quantum-test-tenant", resp1["net-id"],
+ "port_id_fake")
+ except exception.PortNotFound:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ return
+
+ self.assertTrue(False)
+
+ def test_negative_delete_port2(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.delete_port("quantum-test-tenant", resp1["net-id"],
+ "port_id_fake")
+ except exception.PortNotFound:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ return
+
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ self.assertTrue(False)
+
+ def test_negative_get_port_details(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.get_port_details("quantum-test-tenant",
+ resp1["net-id"],
+ "port_id_fake")
+ except exception.PortNotFound:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant",
+ resp1["net-id"])
+ return
+
+ self.quantum.delete_network("quantum-test-tenant", resp1["net-id"])
+ self.assertTrue(False)
+
+ def test_negative_plug_interface(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.plug_interface("quantum-test-tenant",
+ resp1["net-id"],
+ "port_id_fake", "iface_id_fake")
+ except exception.PortNotFound:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant",
+ resp1["net-id"])
+ return
+
+ self.assertTrue(False)
+
+ def test_negative_unplug_interface(self):
+ resp1 = self.quantum.create_network("quantum-test-tenant",
+ "quantum-Private-TenantB")
+ try:
+ self.quantum.unplug_interface("quantum-test-tenant",
+ resp1["net-id"], "port_id_fake")
+ except exception.PortNotFound:
+ self.assertTrue(True)
+ self.quantum.delete_network("quantum-test-tenant",
+ resp1["net-id"])
+ return
+
+ self.assertTrue(False)
+
+ def test_get_port_status_invalid_lswitch(self):
+ try:
+ nvplib.get_port_status(self.quantum.controller,
+ "invalid-lswitch",
+ "invalid-port")
+ except exception.NetworkNotFound:
+ return
+ # Shouldn't be reached
+ self.assertTrue(False)
+
+ def test_get_port_status_invalid_port(self):
+ resp = self.quantum.create_custom_network("quantum-test-tenant",
+ "quantum-Private-TenantA", self.BRIDGE_TZ_UUID,
+ self.quantum.controller)
+ net_id = resp["net-id"]
+ self.networks.append(net_id)
+
+ try:
+ nvplib.get_port_status(self.quantum.controller, net_id,
+ "invalid-port")
+ except exception.PortNotFound:
+ return
+ # Shouldn't be reached
+ self.assertTrue(False)
+
+ def test_get_port_status_returns_the_right_stuff(self):
+ resp = self.quantum.create_custom_network("quantum-test-tenant",
+ "quantum-Private-TenantA", self.BRIDGE_TZ_UUID,
+ self.quantum.controller)
+ net_id = resp["net-id"]
+ self.networks.append(net_id)
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ self.ports.append((net_id, port_id))
+ res = nvplib.get_port_status(self.quantum.controller, net_id, port_id)
+ self.assertTrue(res in ['UP', 'DOWN', 'PROVISIONING'])
+
+ def test_get_port_stats_invalid_lswitch(self):
+ try:
+ nvplib.get_port_stats(self.quantum.controller,
+ "invalid-lswitch",
+ "invalid-port")
+ except exception.NetworkNotFound:
+ return
+ # Shouldn't be reached
+ self.assertTrue(False)
+
+ def test_get_port_stats_invalid_port(self):
+ resp = self.quantum.create_custom_network("quantum-test-tenant",
+ "quantum-Private-TenantA", self.BRIDGE_TZ_UUID,
+ self.quantum.controller)
+ net_id = resp["net-id"]
+ self.networks.append(net_id)
+
+ try:
+ nvplib.get_port_stats(self.quantum.controller, net_id,
+ "invalid-port")
+ except exception.PortNotFound:
+ return
+ # Shouldn't be reached
+ self.assertTrue(False)
+
+ def test_get_port_stats_returns_the_right_stuff(self):
+ resp = self.quantum.create_custom_network("quantum-test-tenant",
+ "quantum-Private-TenantA", self.BRIDGE_TZ_UUID,
+ self.quantum.controller)
+ net_id = resp["net-id"]
+ self.networks.append(net_id)
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ self.ports.append((net_id, port_id))
+ res = nvplib.get_port_stats(self.quantum.controller, net_id, port_id)
+ self.assertTrue("tx_errors" in res)
+ self.assertTrue("tx_bytes" in res)
+ self.assertTrue("tx_packets" in res)
+ self.assertTrue("rx_errors" in res)
+ self.assertTrue("rx_bytes" in res)
+ self.assertTrue("rx_packets" in res)
+
+ def test_port_filters_by_attachment(self):
+ resp = self.quantum.create_custom_network("quantum-test-tenant",
+ "quantum-Private-TenantA", self.BRIDGE_TZ_UUID,
+ self.quantum.controller)
+ net_id = resp["net-id"]
+ self.networks.append(net_id)
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ port_id1 = port_id
+ self.ports.append((net_id, port_id))
+ self.quantum.plug_interface("quantum-test-tenant", net_id, port_id,
+ "attachment1")
+
+ resp = self.quantum.create_port("quantum-test-tenant", net_id,
+ "ACTIVE")
+ port_id = resp["port-id"]
+ port_id2 = port_id
+ self.ports.append((net_id, port_id))
+ self.quantum.plug_interface("quantum-test-tenant", net_id, port_id,
+ "attachment2")
+
+ # Make sure we get all the ports that we created back
+ ports = self.quantum.get_all_ports("quantum-test-tenant", net_id)
+ self.assertTrue(len(ports) == 2)
+
+ # Make sure we only get the filtered ones back
+ ports = self.quantum.get_all_ports("quantum-test-tenant", net_id,
+ filter_opts={"attachment": "attachment2"})
+ self.assertTrue(len(ports) == 1)
+ self.assertTrue(ports[0]["port-id"] == port_id2)
+
+ # Make sure we don't get any back with an invalid filter
+ ports = self.quantum.get_all_ports("quantum-test-tenant", net_id,
+ filter_opts={"attachment": "invalidattachment"})
+ self.assertTrue(len(ports) == 0)
ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
+nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
DataFiles = [
(config_path,
'etc/quantum/plugins/cisco/db_conn.ini']),
(linuxbridge_plugin_config_path,
['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
+ (nvp_plugin_config_path,
+ ['etc/quantum/plugins/nicira/nvp.ini']),
]
setup(