Merge "Initial commit: nvp plugin"

This commit is contained in:
Jenkins 2012-02-25 17:43:44 +00:00 committed by Gerrit Code Review
commit ae6689d280
23 changed files with 3548 additions and 0 deletions

View File

@ -8,6 +8,7 @@ include etc/quantum/plugins/openvswitch/*.ini
include etc/quantum/plugins/cisco/*.ini include etc/quantum/plugins/cisco/*.ini
include etc/quantum/plugins/cisco/quantum.conf.ciscoext include etc/quantum/plugins/cisco/quantum.conf.ciscoext
include etc/quantum/plugins/linuxbridge/*.ini include etc/quantum/plugins/linuxbridge/*.ini
include etc/quantum/plugins/nicira/*
include quantum/plugins/*/README include quantum/plugins/*/README
include quantum/plugins/openvswitch/Makefile include quantum/plugins/openvswitch/Makefile
include quantum/plugins/openvswitch/agent/xenserver_install.sh include quantum/plugins/openvswitch/agent/xenserver_install.sh

View File

@ -0,0 +1,36 @@
# 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>

View File

View File

@ -0,0 +1,204 @@
'''
# 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."

View File

@ -0,0 +1,593 @@
# 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)

View File

@ -0,0 +1,24 @@
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

View File

@ -0,0 +1,13 @@
# 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.

View File

@ -0,0 +1,69 @@
# 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

View File

@ -0,0 +1,226 @@
# 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)

View File

@ -0,0 +1,30 @@
# 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)

View File

@ -0,0 +1,44 @@
# 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

View File

@ -0,0 +1,362 @@
# 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)

View File

@ -0,0 +1,131 @@
# 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()

View File

@ -0,0 +1,402 @@
# 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"

View File

@ -0,0 +1,36 @@
# 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)

View File

@ -0,0 +1,239 @@
# 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()

View File

@ -0,0 +1,197 @@
# 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")

View File

@ -0,0 +1,40 @@
# 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')

View File

@ -0,0 +1,36 @@
# 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

View File

@ -0,0 +1,353 @@
# 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)

View File

@ -0,0 +1,509 @@
# 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)

View File

@ -71,6 +71,7 @@ init_path = 'etc/init.d'
ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch' ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
cisco_plugin_config_path = 'etc/quantum/plugins/cisco' cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge' linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
DataFiles = [ DataFiles = [
(config_path, (config_path,
@ -87,6 +88,8 @@ DataFiles = [
'etc/quantum/plugins/cisco/db_conn.ini']), 'etc/quantum/plugins/cisco/db_conn.ini']),
(linuxbridge_plugin_config_path, (linuxbridge_plugin_config_path,
['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']), ['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
(nvp_plugin_config_path,
['etc/quantum/plugins/nicira/nvp.ini']),
] ]
setup( setup(