Initial commit: nvp plugin
blueprint: quantum-nvp-plugin Change-Id: I07c5d7b305928c341ef1b35a0d9b3281abcb03ae
This commit is contained in:
parent
a945d1a304
commit
2386a7e2d5
@ -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
|
||||||
|
36
etc/quantum/plugins/nicira/nvp.ini
Normal file
36
etc/quantum/plugins/nicira/nvp.ini
Normal 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>
|
0
quantum/plugins/nicira/__init__.py
Normal file
0
quantum/plugins/nicira/__init__.py
Normal file
204
quantum/plugins/nicira/nicira_nvp_plugin/NvpApiClient.py
Normal file
204
quantum/plugins/nicira/nicira_nvp_plugin/NvpApiClient.py
Normal 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."
|
593
quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py
Normal file
593
quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py
Normal 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)
|
24
quantum/plugins/nicira/nicira_nvp_plugin/README
Normal file
24
quantum/plugins/nicira/nicira_nvp_plugin/README
Normal 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
|
13
quantum/plugins/nicira/nicira_nvp_plugin/__init__.py
Normal file
13
quantum/plugins/nicira/nicira_nvp_plugin/__init__.py
Normal 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.
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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)
|
131
quantum/plugins/nicira/nicira_nvp_plugin/cli.py
Normal file
131
quantum/plugins/nicira/nicira_nvp_plugin/cli.py
Normal 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()
|
402
quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py
Normal file
402
quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py
Normal 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"
|
36
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_check.py
Normal file
36
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_check.py
Normal 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)
|
239
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_config.py
Normal file
239
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_config.py
Normal 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()
|
197
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_network.py
Normal file
197
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_network.py
Normal 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")
|
@ -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')
|
@ -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
|
@ -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)
|
509
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_port.py
Normal file
509
quantum/plugins/nicira/nicira_nvp_plugin/tests/test_port.py
Normal 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)
|
3
setup.py
3
setup.py
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user