ddfe418f3d
Bug #1126352 This patch introduces a simple framework for enabling nvlib to call the appropriate routine according to the current version. To this aim, we leverage the 'server' header which is returned by every NVP API calls (except login/logout). The patch also accounts for the changes introduced in NVP 3.0 Change-Id: Ief3a6f2b5b99ea33548353ce3bee3fa23e42752f
236 lines
8.3 KiB
Python
236 lines
8.3 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 Nicira, Inc.
|
|
# All Rights Reserved
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
#
|
|
# @author: Somik Behera, Nicira Networks, Inc.
|
|
|
|
import httplib # basic HTTP library for HTTPS connections
|
|
import logging
|
|
from quantum.plugins.nicira.nicira_nvp_plugin.api_client import (
|
|
client_eventlet, request_eventlet)
|
|
|
|
LOG = logging.getLogger("NVPApiHelper")
|
|
LOG.setLevel(logging.INFO)
|
|
|
|
|
|
def _find_nvp_version_in_headers(headers):
|
|
# be safe if headers is None - do not cause a failure
|
|
for (header_name, header_value) in (headers or ()):
|
|
try:
|
|
if header_name == 'server':
|
|
return header_value.split('/')[1]
|
|
except IndexError:
|
|
LOG.warning(_("Unable to fetch NVP version from response "
|
|
"headers:%s"), headers)
|
|
|
|
|
|
class NVPApiHelper(client_eventlet.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,
|
|
concurrent_connections=3, nvp_gen_timeout=-1):
|
|
'''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 (and allow for retries to another
|
|
controller in the cluster)
|
|
:param retries: the number of concurrent connections.
|
|
:param redirects: the number of concurrent connections.
|
|
'''
|
|
client_eventlet.NvpApiClientEventlet.__init__(
|
|
self, api_providers, user, password, concurrent_connections,
|
|
nvp_gen_timeout)
|
|
|
|
self._request_timeout = request_timeout
|
|
self._http_timeout = http_timeout
|
|
self._retries = retries
|
|
self._redirects = redirects
|
|
self._nvp_version = None
|
|
|
|
# NOTE(salvatore-orlando): This method is not used anymore. Login is now
|
|
# performed automatically inside the request eventlet if necessary.
|
|
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 client_eventlet.NvpApiClientEventlet._login(self)
|
|
|
|
def request(self, method, url, body="", content_type="application/json"):
|
|
'''Issues request to controller.'''
|
|
|
|
g = request_eventlet.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: %(method)s to %(url)s'), locals())
|
|
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(_("%(method)s to %(url)s, unexpected response code: "
|
|
"%(status)d (content = '%(body)s')"),
|
|
{'method': method, 'url': url,
|
|
'status': response.status, 'body': response.body})
|
|
return None
|
|
|
|
if not self._nvp_version:
|
|
self._nvp_version = _find_nvp_version_in_headers(response.headers)
|
|
|
|
return response.body
|
|
|
|
def get_nvp_version(self):
|
|
if not self._nvp_version:
|
|
# generate a simple request (/ws.v1/log)
|
|
# this will cause nvp_version to be fetched
|
|
# don't bother about response
|
|
self.request('GET', '/ws.v1/log')
|
|
return self._nvp_version
|
|
|
|
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()
|
|
|
|
# TODO(del): ensure error_codes are handled/raised appropriately
|
|
# in api_client.
|
|
error_codes = {404: fourZeroFour,
|
|
409: fourZeroNine,
|
|
503: fiveZeroThree,
|
|
403: fourZeroThree,
|
|
301: zero,
|
|
307: zero,
|
|
400: zero,
|
|
500: zero,
|
|
503: 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.")
|