Merge "Initial commit: nvp plugin"
This commit is contained in:
commit
c8c2929151
@ -8,6 +8,7 @@ include etc/quantum/plugins/openvswitch/*.ini
|
||||
include etc/quantum/plugins/cisco/*.ini
|
||||
include etc/quantum/plugins/cisco/quantum.conf.ciscoext
|
||||
include etc/quantum/plugins/linuxbridge/*.ini
|
||||
include etc/quantum/plugins/nicira/*
|
||||
include quantum/plugins/*/README
|
||||
include quantum/plugins/openvswitch/Makefile
|
||||
include quantum/plugins/openvswitch/agent/xenserver_install.sh
|
||||
|
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:
|
||||