21
21
"""Utility methods for working with WSGI servers."""
27
26
import eventlet.wsgi
28
eventlet.patcher.monkey_patch(all=False, socket=True, time=True)
30
27
import routes.middleware
35
from keystone import exception
32
from keystone.common import config
36
33
from keystone.common import logging
37
34
from keystone.common import utils
35
from keystone import exception
36
from keystone.openstack.common import importutils
37
from keystone.openstack.common import jsonutils
40
41
LOG = logging.getLogger(__name__)
43
# Environment variable used to pass the request context
44
CONTEXT_ENV = 'openstack.context'
47
# Environment variable used to pass the request params
48
PARAMS_ENV = 'openstack.params'
43
51
class WritableLogger(object):
44
52
"""A thin wrapper that responds to `write` and logs."""
61
69
self.pool = eventlet.GreenPool(threads)
62
70
self.socket_info = {}
63
71
self.greenthread = None
73
self.cert_required = False
65
75
def start(self, key=None, backlog=128):
66
76
"""Run a WSGI server with the given application."""
67
LOG.debug('Starting %(arg0)s on %(host)s:%(port)s' %
71
socket = eventlet.listen((self.host, self.port), backlog=backlog)
72
self.greenthread = self.pool.spawn(self._run, self.application, socket)
77
LOG.debug(_('Starting %(arg0)s on %(host)s:%(port)s') %
82
# TODO(dims): eventlet's green dns/socket module does not actually
83
# support IPv6 in getaddrinfo(). We need to get around this in the
84
# future or monitor upstream for a fix
85
info = socket.getaddrinfo(self.host,
88
socket.SOCK_STREAM)[0]
89
_socket = eventlet.listen(info[-1],
74
self.socket_info[key] = socket.getsockname()
93
self.socket_info[key] = _socket.getsockname()
96
if self.cert_required:
97
cert_reqs = ssl.CERT_REQUIRED
99
cert_reqs = ssl.CERT_NONE
100
sslsocket = eventlet.wrap_ssl(_socket, certfile=self.certfile,
101
keyfile=self.keyfile,
104
ca_certs=self.ca_certs)
107
self.greenthread = self.pool.spawn(self._run,
111
def set_ssl(self, certfile, keyfile=None, ca_certs=None,
113
self.certfile = certfile
114
self.keyfile = keyfile
115
self.ca_certs = ca_certs
116
self.cert_required = cert_required
77
120
if self.greenthread:
87
130
def _run(self, application, socket):
88
131
"""Start a WSGI server in a new green thread."""
89
132
log = logging.getLogger('eventlet.wsgi.server')
90
eventlet.wsgi.server(socket, application, custom_pool=self.pool,
91
log=WritableLogger(log))
134
eventlet.wsgi.server(socket, application, custom_pool=self.pool,
135
log=WritableLogger(log))
137
LOG.exception(_('Server error'))
94
141
class Request(webob.Request):
166
213
arg_dict = req.environ['wsgiorg.routing_args'][1]
167
214
action = arg_dict.pop('action')
168
215
del arg_dict['controller']
169
LOG.debug('arg_dict: %s', arg_dict)
216
LOG.debug(_('arg_dict: %s'), arg_dict)
171
218
# allow middleware up the stack to provide context & params
172
context = req.environ.get('openstack.context', {})
219
context = req.environ.get(CONTEXT_ENV, {})
173
220
context['query_string'] = dict(req.params.iteritems())
174
params = req.environ.get('openstack.params', {})
221
context['path'] = req.environ['PATH_INFO']
222
params = req.environ.get(PARAMS_ENV, {})
223
if 'REMOTE_USER' in req.environ:
224
context['REMOTE_USER'] = req.environ['REMOTE_USER']
225
elif context.get('REMOTE_USER', None) is not None:
226
del context['REMOTE_USER']
175
227
params.update(arg_dict)
177
229
# TODO(termie): do some basic normalization on methods
184
236
result = method(context, **params)
237
except exception.Unauthorized as e:
238
LOG.warning(_("Authorization failed. %s from %s")
239
% (e, req.environ['REMOTE_ADDR']))
240
return render_exception(e)
185
241
except exception.Error as e:
187
243
return render_exception(e)
244
except TypeError as e:
246
return render_exception(exception.ValidationError(e))
188
247
except Exception as e:
189
248
logging.exception(e)
190
249
return render_exception(exception.UnexpectedError(exception=e))
198
257
elif isinstance(result, webob.exc.WSGIHTTPException):
200
return render_response(body=result)
260
response_code = self._get_response_code(req)
261
return render_response(body=result, status=response_code)
263
def _get_response_code(self, req):
264
req_method = req.environ['REQUEST_METHOD']
265
controller = importutils.import_class('keystone.common.controller')
267
if isinstance(self, controller.V3Controller) and req_method == 'POST':
268
code = (201, 'Created')
202
271
def _normalize_arg(self, arg):
203
272
return str(arg).replace(':', '_').replace('-', '_')
210
279
if not context['is_admin']:
212
281
user_token_ref = self.token_api.get_token(
213
context=context, token_id=context['token_id'])
214
except exception.TokenNotFound:
215
raise exception.Unauthorized()
282
context=context, token_id=context['token_id'])
283
except exception.TokenNotFound as e:
284
raise exception.Unauthorized(e)
217
286
creds = user_token_ref['metadata'].copy()
369
435
mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
438
# if we're only running in debug, bump routes' internal logging up a
439
# notch, as it's very spammy
441
logging.getLogger('routes.middleware').setLevel(logging.INFO)
372
443
self.map = mapper
373
444
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
473
def render_response(body=None, status=(200, 'OK'), headers=None):
545
def render_response(body=None, status=None, headers=None):
474
546
"""Forms a WSGI response."""
475
resp = webob.Response()
476
resp.status = '%s %s' % status
477
resp.headerlist = headers or [('Content-Type', 'application/json'),
478
('Vary', 'X-Auth-Token')]
481
resp.body = json.dumps(body, cls=utils.SmarterEncoder)
547
headers = headers or []
548
headers.append(('Vary', 'X-Auth-Token'))
552
status = status or (204, 'No Content')
554
body = jsonutils.dumps(body, cls=utils.SmarterEncoder)
555
headers.append(('Content-Type', 'application/json'))
556
status = status or (200, 'OK')
558
return webob.Response(body=body,
559
status='%s %s' % status,
486
563
def render_exception(error):
487
564
"""Forms a WSGI response based on the current error."""
488
return render_response(status=(error.code, error.title), body={
491
'title': error.title,
492
'message': str(error),
567
'title': error.title,
568
'message': str(error)
570
if isinstance(error, exception.AuthPluginException):
571
body['error']['identity'] = error.authentication
572
return render_response(status=(error.code, error.title), body=body)