1
# Copyright 2012 VMware, Inc.
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
21
from oslo.serialization import jsonutils
23
from neutron.i18n import _LI, _LW
24
from neutron.openstack.common import log as logging
25
from neutron.plugins.vmware.api_client import request
27
LOG = logging.getLogger(__name__)
28
USER_AGENT = "Neutron eventlet client/2.0"
31
class EventletApiRequest(request.ApiRequest):
32
'''Eventlet-based ApiRequest class.
34
This class will form the basis for eventlet-based ApiRequest classes
37
# Maximum number of green threads present in the system at one time.
38
API_REQUEST_POOL_SIZE = request.DEFAULT_API_REQUEST_POOL_SIZE
40
# Pool of green threads. One green thread is allocated per incoming
41
# request. Incoming requests will block when the pool is empty.
42
API_REQUEST_POOL = eventlet.GreenPool(API_REQUEST_POOL_SIZE)
44
# A unique id is assigned to each incoming request. When the current
45
# request id reaches MAXIMUM_REQUEST_ID it wraps around back to 0.
46
MAXIMUM_REQUEST_ID = request.DEFAULT_MAXIMUM_REQUEST_ID
48
# The request id for the next incoming request.
49
CURRENT_REQUEST_ID = 0
51
def __init__(self, client_obj, url, method="GET", body=None,
53
retries=request.DEFAULT_RETRIES,
55
redirects=request.DEFAULT_REDIRECTS,
56
http_timeout=request.DEFAULT_HTTP_TIMEOUT, client_conn=None):
58
self._api_client = client_obj
62
self._headers = headers or {}
63
self._request_timeout = http_timeout * retries
64
self._retries = retries
65
self._auto_login = auto_login
66
self._redirects = redirects
67
self._http_timeout = http_timeout
68
self._client_conn = client_conn
71
self._request_error = None
73
if "User-Agent" not in self._headers:
74
self._headers["User-Agent"] = USER_AGENT
76
self._green_thread = None
77
# Retrieve and store this instance's unique request id.
78
self._request_id = EventletApiRequest.CURRENT_REQUEST_ID
79
# Update the class variable that tracks request id.
80
# Request IDs wrap around at MAXIMUM_REQUEST_ID
81
next_request_id = self._request_id + 1
82
next_request_id %= self.MAXIMUM_REQUEST_ID
83
EventletApiRequest.CURRENT_REQUEST_ID = next_request_id
86
def _spawn(cls, func, *args, **kwargs):
87
'''Allocate a green thread from the class pool.'''
88
return cls.API_REQUEST_POOL.spawn(func, *args, **kwargs)
90
def spawn(self, func, *args, **kwargs):
91
'''Spawn a new green thread with the supplied function and args.'''
92
return self.__class__._spawn(func, *args, **kwargs)
96
'''Wait for all outstanding requests to complete.'''
97
return cls.API_REQUEST_POOL.waitall()
100
'''Wait for instance green thread to complete.'''
101
if self._green_thread is not None:
102
return self._green_thread.wait()
103
return Exception(_('Joining an invalid green thread'))
106
'''Start request processing.'''
107
self._green_thread = self.spawn(self._run)
110
'''Return a copy of this request instance.'''
111
return EventletApiRequest(
112
self._api_client, self._url, self._method, self._body,
113
self._headers, self._retries,
114
self._auto_login, self._redirects, self._http_timeout)
117
'''Method executed within green thread.'''
118
if self._request_timeout:
119
# No timeout exception escapes the with block.
120
with eventlet.timeout.Timeout(self._request_timeout, False):
121
return self._handle_request()
123
LOG.info(_LI('[%d] Request timeout.'), self._rid())
124
self._request_error = Exception(_('Request timeout'))
127
return self._handle_request()
129
def _handle_request(self):
130
'''First level request handling.'''
134
while response is None and attempt <= self._retries:
135
eventlet.greenthread.sleep(timeout)
138
req = self._issue_request()
139
# automatically raises any exceptions returned.
140
if isinstance(req, httplib.HTTPResponse):
142
if attempt <= self._retries and not self._abort:
143
if req.status in (httplib.UNAUTHORIZED, httplib.FORBIDDEN):
145
elif req.status == httplib.SERVICE_UNAVAILABLE:
148
# else fall through to return the error code
150
LOG.debug("[%(rid)d] Completed request '%(method)s %(url)s'"
152
{'rid': self._rid(), 'method': self._method,
153
'url': self._url, 'status': req.status})
154
self._request_error = None
157
LOG.info(_LI('[%(rid)d] Error while handling request: '
159
{'rid': self._rid(), 'req': req})
160
self._request_error = req
165
class LoginRequestEventlet(EventletApiRequest):
166
'''Process a login request.'''
168
def __init__(self, client_obj, user, password, client_conn=None,
172
headers.update({"Content-Type": "application/x-www-form-urlencoded"})
173
body = urllib.urlencode({"username": user, "password": password})
174
super(LoginRequestEventlet, self).__init__(
175
client_obj, "/ws.v1/login", "POST", body, headers,
176
auto_login=False, client_conn=client_conn)
178
def session_cookie(self):
179
if self.successful():
180
return self.value.getheader("Set-Cookie")
184
class GetApiProvidersRequestEventlet(EventletApiRequest):
185
'''Get a list of API providers.'''
187
def __init__(self, client_obj):
188
url = "/ws.v1/control-cluster/node?fields=roles"
189
super(GetApiProvidersRequestEventlet, self).__init__(
190
client_obj, url, "GET", auto_login=True)
192
def api_providers(self):
193
"""Parse api_providers from response.
195
Returns: api_providers in [(host, port, is_ssl), ...] format
197
def _provider_from_listen_addr(addr):
198
# (pssl|ptcp):<ip>:<port> => (host, port, is_ssl)
199
parts = addr.split(':')
200
return (parts[1], int(parts[2]), parts[0] == 'pssl')
203
if self.successful():
205
body = jsonutils.loads(self.value.body)
206
for node in body.get('results', []):
207
for role in node.get('roles', []):
208
if role.get('role') == 'api_provider':
209
addr = role.get('listen_addr')
211
ret.append(_provider_from_listen_addr(addr))
213
except Exception as e:
214
LOG.warn(_LW("[%(rid)d] Failed to parse API provider: %(e)s"),
215
{'rid': self._rid(), 'e': e})
216
# intentionally fall through
220
class GenericRequestEventlet(EventletApiRequest):
221
'''Handle a generic request.'''
223
def __init__(self, client_obj, method, url, body, content_type,
225
http_timeout=request.DEFAULT_HTTP_TIMEOUT,
226
retries=request.DEFAULT_RETRIES,
227
redirects=request.DEFAULT_REDIRECTS):
228
headers = {"Content-Type": content_type}
229
super(GenericRequestEventlet, self).__init__(
230
client_obj, url, method, body, headers,
232
auto_login=auto_login, redirects=redirects,
233
http_timeout=http_timeout)
235
def session_cookie(self):
236
if self.successful():
237
return self.value.getheader("Set-Cookie")
241
request.ApiRequest.register(EventletApiRequest)