Merge lp:~anso/nova/paste into lp:~hudson-openstack/nova/trunk

Proposed by Todd Willey
Status: Merged
Approved by: Todd Willey
Approved revision: 480
Merged at revision: 513
Proposed branch: lp:~anso/nova/paste
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 359 lines (+265/-5)
8 files modified
bin/nova-api-paste (+109/-0)
etc/nova-api.conf (+63/-0)
nova/api/ec2/__init__.py (+53/-2)
nova/api/ec2/metadatarequesthandler.py (+4/-0)
nova/api/openstack/__init__.py (+21/-2)
nova/api/openstack/auth.py (+6/-0)
nova/api/openstack/ratelimiting/__init__.py (+6/-0)
tools/pip-requires (+3/-1)
To merge this branch: bzr merge lp:~anso/nova/paste
Reviewer Review Type Date Requested Status
Devin Carlen (community) Approve
Jay Pipes (community) Approve
Review via email: mp+44813@code.launchpad.net

Description of the change

Uses paste.deploy to make application running configurable. This includes the ability to swap out middlewares, define new endpoints, and generally move away from having code to build wsgi routers and middleware chains into a configurable, extensible method for running wsgi servers.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

Hi Todd!

Good direction and starting point :) Here are some notes...

1) pep8 will fail

I can see a number of things that will fail pep8 checks, so before going further, you may want to do:

pep8 --repeat bin/* nova

and fix all the warnings/errors reported

2) i18n'ing

You will need to do:

gettext.install('nova', unicode=1)

*before* importing wsgi/flags from nova. The reason is because gettext.install() places the _() method into the builtins namespace. If nova.wsgi or nova.flags has any text strings that are surrounded by _(), then this will result in an error.

In addition, please wrap all text strings that could be output to a user with the _() function.

For instance, here:

71 + LOG.debug("Paste config at %s has no secion for known apis", paste_config)
72 + print "Paste config at %s has no secion for any known apis" % paste_config

the strings should be wrapped in _()...

3)

This code could be simplified/made clearer:

86 + ip = config.get('host', None)
...
91 + host = config.get("%s_host" % api, None) or ip or '0.0.0.0'

to:

ip = config.get('host', '0.0.0.0')
...
host = config.get("%s_host" % api, ip)

Cheers!
jay

review: Needs Fixing
lp:~anso/nova/paste updated
475. By Todd Willey

Merge trunk.

476. By Todd Willey

Pep-8 cleanup.

477. By Todd Willey

i18n

478. By Todd Willey

Clean up how we determine IP to bind to.

479. By Todd Willey

remove cloudpipe from paste config

480. By Todd Willey

pep 8

Revision history for this message
Todd Willey (xtoddx) wrote :

Thanks! I started this branch before i18n landed, and apparently missed a few things. It should all be fixed up now, as well as the pep-8 line spacing.

Revision history for this message
Jay Pipes (jaypipes) wrote :

sweetness.

Suggestion: get with annegentle on IRC to assist her in documenting how to spin up nova-api-paste.

Nice work, Todd :)

review: Approve
Revision history for this message
Devin Carlen (devcamcar) wrote :

sweet

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'bin/nova-api-paste'
2--- bin/nova-api-paste 1970-01-01 00:00:00 +0000
3+++ bin/nova-api-paste 2010-12-30 07:23:08 +0000
4@@ -0,0 +1,109 @@
5+#!/usr/bin/env python
6+# pylint: disable-msg=C0103
7+# vim: tabstop=4 shiftwidth=4 softtabstop=4
8+
9+# Copyright 2010 United States Government as represented by the
10+# Administrator of the National Aeronautics and Space Administration.
11+# All Rights Reserved.
12+#
13+# Licensed under the Apache License, Version 2.0 (the "License");
14+# you may not use this file except in compliance with the License.
15+# You may obtain a copy of the License at
16+#
17+# http://www.apache.org/licenses/LICENSE-2.0
18+#
19+# Unless required by applicable law or agreed to in writing, software
20+# distributed under the License is distributed on an "AS IS" BASIS,
21+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+# See the License for the specific language governing permissions and
23+# limitations under the License.
24+
25+"""Starter script for Nova API."""
26+
27+import gettext
28+import logging
29+import os
30+import sys
31+
32+from paste import deploy
33+
34+# If ../nova/__init__.py exists, add ../ to Python search path, so that
35+# it will override what happens to be installed in /usr/(local/)lib/python...
36+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
37+ os.pardir,
38+ os.pardir))
39+if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
40+ sys.path.insert(0, possible_topdir)
41+
42+gettext.install('nova', unicode=1)
43+
44+from nova import flags
45+from nova import wsgi
46+
47+LOG = logging.getLogger('nova.api')
48+LOG.setLevel(logging.DEBUG)
49+LOG.addHandler(logging.StreamHandler())
50+
51+FLAGS = flags.FLAGS
52+
53+API_ENDPOINTS = ['ec2', 'openstack']
54+
55+
56+def load_configuration(paste_config):
57+ """Load the paste configuration from the config file and return it."""
58+ config = None
59+ # Try each known name to get the global DEFAULTS, which will give ports
60+ for name in API_ENDPOINTS:
61+ try:
62+ config = deploy.appconfig("config:%s" % paste_config, name=name)
63+ except LookupError:
64+ pass
65+ if config:
66+ verbose = config.get('verbose', None)
67+ if verbose:
68+ FLAGS.verbose = int(verbose) == 1
69+ if FLAGS.verbose:
70+ logging.getLogger().setLevel(logging.DEBUG)
71+ return config
72+ LOG.debug(_("Paste config at %s has no secion for known apis"),
73+ paste_config)
74+ print _("Paste config at %s has no secion for any known apis") % \
75+ paste_config
76+ os.exit(1)
77+
78+
79+def launch_api(paste_config_file, section, server, port, host):
80+ """Launch an api server from the specified port and IP."""
81+ LOG.debug(_("Launching %s api on %s:%s"), section, host, port)
82+ app = deploy.loadapp('config:%s' % paste_config_file, name=section)
83+ server.start(app, int(port), host)
84+
85+
86+def run_app(paste_config_file):
87+ LOG.debug(_("Using paste.deploy config at: %s"), configfile)
88+ config = load_configuration(paste_config_file)
89+ LOG.debug(_("Configuration: %r"), config)
90+ server = wsgi.Server()
91+ ip = config.get('host', '0.0.0.0')
92+ for api in API_ENDPOINTS:
93+ port = config.get("%s_port" % api, None)
94+ if not port:
95+ continue
96+ host = config.get("%s_host" % api, ip)
97+ launch_api(configfile, api, server, port, host)
98+ LOG.debug(_("All api servers launched, now waiting"))
99+ server.wait()
100+
101+
102+if __name__ == '__main__':
103+ FLAGS(sys.argv)
104+ configfiles = ['/etc/nova/nova-api.conf']
105+ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
106+ configfiles.insert(0,
107+ os.path.join(possible_topdir, 'etc', 'nova-api.conf'))
108+ for configfile in configfiles:
109+ if os.path.exists(configfile):
110+ run_app(configfile)
111+ break
112+ else:
113+ LOG.debug(_("Skipping missing configuration: %s"), configfile)
114
115=== added directory 'etc'
116=== added file 'etc/nova-api.conf'
117--- etc/nova-api.conf 1970-01-01 00:00:00 +0000
118+++ etc/nova-api.conf 2010-12-30 07:23:08 +0000
119@@ -0,0 +1,63 @@
120+[DEFAULT]
121+verbose = 1
122+ec2_port = 8773
123+ec2_address = 0.0.0.0
124+openstack_port = 8774
125+openstack_address = 0.0.0.0
126+
127+#######
128+# EC2 #
129+#######
130+
131+[composite:ec2]
132+use = egg:Paste#urlmap
133+/: ec2versions
134+/services: ec2api
135+/latest: ec2metadata
136+/200: ec2metadata
137+/1.0: ec2metadata
138+
139+[pipeline:ec2api]
140+pipeline = authenticate router authorizer ec2executor
141+
142+[filter:authenticate]
143+paste.filter_factory = nova.api.ec2:authenticate_factory
144+
145+[filter:router]
146+paste.filter_factory = nova.api.ec2:router_factory
147+
148+[filter:authorizer]
149+paste.filter_factory = nova.api.ec2:authorizer_factory
150+
151+[app:ec2executor]
152+paste.app_factory = nova.api.ec2:executor_factory
153+
154+[app:ec2versions]
155+paste.app_factory = nova.api.ec2:versions_factory
156+
157+[app:ec2metadata]
158+paste.app_factory = nova.api.ec2.metadatarequesthandler:metadata_factory
159+
160+#############
161+# Openstack #
162+#############
163+
164+[composite:openstack]
165+use = egg:Paste#urlmap
166+/: osversions
167+/v1.0: openstackapi
168+
169+[pipeline:openstackapi]
170+pipeline = auth ratelimit osapi
171+
172+[filter:auth]
173+paste.filter_factory = nova.api.openstack.auth:auth_factory
174+
175+[filter:ratelimit]
176+paste.filter_factory = nova.api.openstack.ratelimiting:ratelimit_factory
177+
178+[app:osapi]
179+paste.app_factory = nova.api.openstack:router_factory
180+
181+[app:osversions]
182+paste.app_factory = nova.api.openstack:versions_factory
183
184=== modified file 'nova/api/ec2/__init__.py'
185--- nova/api/ec2/__init__.py 2010-12-22 21:50:58 +0000
186+++ nova/api/ec2/__init__.py 2010-12-30 07:23:08 +0000
187@@ -294,10 +294,9 @@
188 args = req.environ['ec2.action_args']
189
190 api_request = apirequest.APIRequest(controller, action)
191+ result = None
192 try:
193 result = api_request.send(context, **args)
194- req.headers['Content-Type'] = 'text/xml'
195- return result
196 except exception.ApiError as ex:
197
198 if ex.code:
199@@ -307,6 +306,12 @@
200 # TODO(vish): do something more useful with unknown exceptions
201 except Exception as ex:
202 return self._error(req, type(ex).__name__, str(ex))
203+ else:
204+ resp = webob.Response()
205+ resp.status = 200
206+ resp.headers['Content-Type'] = 'text/xml'
207+ resp.body = str(result)
208+ return resp
209
210 def _error(self, req, code, message):
211 logging.error("%s: %s", code, message)
212@@ -318,3 +323,49 @@
213 '<Message>%s</Message></Error></Errors>'
214 '<RequestID>?</RequestID></Response>' % (code, message))
215 return resp
216+
217+
218+class Versions(wsgi.Application):
219+
220+ @webob.dec.wsgify
221+ def __call__(self, req):
222+ """Respond to a request for all EC2 versions."""
223+ # available api versions
224+ versions = [
225+ '1.0',
226+ '2007-01-19',
227+ '2007-03-01',
228+ '2007-08-29',
229+ '2007-10-10',
230+ '2007-12-15',
231+ '2008-02-01',
232+ '2008-09-01',
233+ '2009-04-04',
234+ ]
235+ return ''.join('%s\n' % v for v in versions)
236+
237+
238+def authenticate_factory(global_args, **local_args):
239+ def authenticator(app):
240+ return Authenticate(app)
241+ return authenticator
242+
243+
244+def router_factory(global_args, **local_args):
245+ def router(app):
246+ return Router(app)
247+ return router
248+
249+
250+def authorizer_factory(global_args, **local_args):
251+ def authorizer(app):
252+ return Authorizer(app)
253+ return authorizer
254+
255+
256+def executor_factory(global_args, **local_args):
257+ return Executor()
258+
259+
260+def versions_factory(global_args, **local_args):
261+ return Versions()
262
263=== modified file 'nova/api/ec2/metadatarequesthandler.py'
264--- nova/api/ec2/metadatarequesthandler.py 2010-12-22 21:50:58 +0000
265+++ nova/api/ec2/metadatarequesthandler.py 2010-12-30 07:23:08 +0000
266@@ -79,3 +79,7 @@
267 if data is None:
268 raise webob.exc.HTTPNotFound()
269 return self.print_data(data)
270+
271+
272+def metadata_factory(global_args, **local_args):
273+ return MetadataRequestHandler()
274
275=== modified file 'nova/api/openstack/__init__.py'
276--- nova/api/openstack/__init__.py 2010-12-28 21:49:36 +0000
277+++ nova/api/openstack/__init__.py 2010-12-30 07:23:08 +0000
278@@ -20,7 +20,6 @@
279 WSGI middleware for OpenStack API controllers.
280 """
281
282-import json
283 import time
284
285 import logging
286@@ -41,7 +40,6 @@
287 from nova.api.openstack import ratelimiting
288 from nova.api.openstack import servers
289 from nova.api.openstack import sharedipgroups
290-from nova.auth import manager
291
292
293 FLAGS = flags.FLAGS
294@@ -113,3 +111,24 @@
295 controller=sharedipgroups.Controller())
296
297 super(APIRouter, self).__init__(mapper)
298+
299+
300+class Versions(wsgi.Application):
301+ @webob.dec.wsgify
302+ def __call__(self, req):
303+ """Respond to a request for all OpenStack API versions."""
304+ response = {
305+ "versions": [
306+ dict(status="CURRENT", id="v1.0")]}
307+ metadata = {
308+ "application/xml": {
309+ "attributes": dict(version=["status", "id"])}}
310+ return wsgi.Serializer(req.environ, metadata).to_content_type(response)
311+
312+
313+def router_factory(global_cof, **local_conf):
314+ return APIRouter()
315+
316+
317+def versions_factory(global_conf, **local_conf):
318+ return Versions()
319
320=== modified file 'nova/api/openstack/auth.py'
321--- nova/api/openstack/auth.py 2010-12-23 19:17:53 +0000
322+++ nova/api/openstack/auth.py 2010-12-30 07:23:08 +0000
323@@ -133,3 +133,9 @@
324 token = self.db.auth_create_token(ctxt, token_dict)
325 return token, user
326 return None, None
327+
328+
329+def auth_factory(global_conf, **local_conf):
330+ def auth(app):
331+ return AuthMiddleware(app)
332+ return auth
333
334=== modified file 'nova/api/openstack/ratelimiting/__init__.py'
335--- nova/api/openstack/ratelimiting/__init__.py 2010-12-24 00:09:52 +0000
336+++ nova/api/openstack/ratelimiting/__init__.py 2010-12-30 07:23:08 +0000
337@@ -219,3 +219,9 @@
338 # No delay
339 return None
340 return float(resp.getheader('X-Wait-Seconds'))
341+
342+
343+def ratelimit_factory(global_conf, **local_conf):
344+ def rl(app):
345+ return RateLimitingMiddleware(app)
346+ return rl
347
348=== modified file 'tools/pip-requires'
349--- tools/pip-requires 2010-12-22 20:59:53 +0000
350+++ tools/pip-requires 2010-12-30 07:23:08 +0000
351@@ -22,4 +22,6 @@
352 greenlet==0.3.1
353 nose
354 bzr
355-Twisted>=10.1.0
356\ No newline at end of file
357+Twisted>=10.1.0
358+PasteDeploy
359+paste