vmware-nsx/neutron/plugins/vmware/api_client/client.py
Aaron Rosen 666ea107ac NSX: Correct default timeout params
Previously, req_timeout and http_timeout were set to the same value
which is not correct. req_timeout is the total time limit for a cluster
request and  http_timeout is the time allowed before aborting a request on
an unresponsive controller. Since the default configuration allows 2
retries req_timeout should be double that of http_timeout because of this
this patch goes ahead and removes req_timeout as this should just be
http_timeout * retries.

Because prevouly req_timeout and http_timeout were the same this exposed
a corner case that when the nsx controller returned a 307 we would issue
the request against the redirected controller but in the case where the
session cookie had expire when the request was issued we would get a 401
response back and never retry the request. Now that the default values are
corrected this issue should no longer occur as the next time time we issue
the request we'll fetch a new auth cookie for the redirected controller.

This patch also bumps the timeout values to be higher. We've seen
more and more timeouts occur in our CI system largely because our
cloud is overloaded so increasing the default timeouts will *hopefully*
help reduce test failures.

DocImpact

Closes-bug: 1340969
Closes-bug: 1338846

Change-Id: Id7244cd4d9316931f4f7df1c3b41b3a894f2909a
2014-08-01 15:32:04 -07:00

140 lines
5.6 KiB
Python

# Copyright 2012 VMware, 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
from neutron.openstack.common import log as logging
from neutron.plugins.vmware.api_client import base
from neutron.plugins.vmware.api_client import eventlet_client
from neutron.plugins.vmware.api_client import eventlet_request
from neutron.plugins.vmware.api_client import exception
from neutron.plugins.vmware.api_client import version
LOG = logging.getLogger(__name__)
class NsxApiClient(eventlet_client.EventletApiClient):
"""The Nsx API Client."""
def __init__(self, api_providers, user, password,
concurrent_connections=base.DEFAULT_CONCURRENT_CONNECTIONS,
gen_timeout=base.GENERATION_ID_TIMEOUT,
use_https=True,
connect_timeout=base.DEFAULT_CONNECT_TIMEOUT,
http_timeout=75, retries=2, redirects=2):
'''Constructor. Adds the following:
: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.
'''
super(NsxApiClient, self).__init__(
api_providers, user, password,
concurrent_connections=concurrent_connections,
gen_timeout=gen_timeout, use_https=use_https,
connect_timeout=connect_timeout)
self._request_timeout = http_timeout * retries
self._http_timeout = http_timeout
self._retries = retries
self._redirects = redirects
self._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 NSX controller.
Assumes same password is used for all controllers.
:param user: controller user (usually admin). Provided for
backwards compatibility. In the normal mode of operation
this should be None.
:param password: controller password. Provided for backwards
compatibility. In the normal mode of operation this should
be None.
'''
if user:
self._user = user
if password:
self._password = password
return self._login()
def request(self, method, url, body="", content_type="application/json"):
'''Issues request to controller.'''
g = eventlet_request.GenericRequestEventlet(
self, method, url, body, content_type, auto_login=True,
http_timeout=self._http_timeout,
retries=self._retries, redirects=self._redirects)
g.start()
response = g.join()
LOG.debug(_('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.ApiRequestEventlet has already called this
# method in order to extract the body and headers for processing.
# ApiRequestEventlet 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'),
{'method': method, 'url': url})
raise exception.RequestTimeout()
status = response.status
if status == httplib.UNAUTHORIZED:
raise exception.UnAuthorizedRequest()
# Fail-fast: Check for exception conditions and raise the
# appropriate exceptions for known error codes.
if status in exception.ERROR_MAPPINGS:
LOG.error(_("Received error code: %s"), status)
LOG.error(_("Server Error Message: %s"), response.body)
exception.ERROR_MAPPINGS[status](response)
# 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._version:
self._version = version.find_version(response.headers)
return response.body
def get_version(self):
if not self._version:
# Determine the controller version by querying the
# cluster nodes. Currently, the version will be the
# one of the server that responds.
self.request('GET', '/ws.v1/control-cluster/node')
if not self._version:
LOG.error(_('Unable to determine NSX version. '
'Plugin might not work as expected.'))
return self._version