Browse Source

Refactor WSGI apps and utils to limit imports

The file nova/api/openstack/__init__.py had imported a lot of
modules, notably nova.utils. This means that any code which
runs within that package, notably the placement service, imports
all those modules, even if it is not going to use them. This
results in scripts/binaries that are heavier than they need
to be and in some cases including modules, like eventlet, that
it would feel safe to not have in the stack.

Unfortunately we cannot sinply rename nova/api/openstack/__init__.py
to another name because it contains FaultWrapper and FaultWrapper
is referred to, by package path, from the paste.ini file and that
file is out there in config land, and something we prefer not to
change. Therefore alternate methods of cleaning up were explored
and this has led to some useful changes:

Fault wrapper is the only consumer of walk_class_hierarchy so
there is no reason for it it to be in nova.utils.

nova.wsgi contains a mismash of WSGI middleware and applications,
which need only a small number of imports, and Server classes
which are more complex and not required by the WSGI wares.

Therefore nova.wsgi was split into nova.wsgi and nova.api.wsgi.
The name choices may not be ideal, but they were chosen to limit
the cascades of changes that are needed across code and tests.

Where utils.utf8 was used it has been replaced with the similar (but not
exactly equivalient) method from oslo_utils.encodeutils.

Change-Id: I297f30aa6eb01fe3b53fd8c9b7853949be31156d
Partial-Bug: #1743120
tags/18.0.0.0b1
Chris Dent 1 year ago
parent
commit
ef6f4e4c8e

+ 1
- 1
nova/api/auth.py View File

@@ -22,10 +22,10 @@ from oslo_serialization import jsonutils
22 22
 import webob.dec
23 23
 import webob.exc
24 24
 
25
+from nova.api import wsgi
25 26
 import nova.conf
26 27
 from nova import context
27 28
 from nova.i18n import _
28
-from nova import wsgi
29 29
 
30 30
 
31 31
 CONF = nova.conf.CONF

+ 1
- 1
nova/api/metadata/handler.py View File

@@ -27,13 +27,13 @@ import webob.dec
27 27
 import webob.exc
28 28
 
29 29
 from nova.api.metadata import base
30
+from nova.api import wsgi
30 31
 from nova import cache_utils
31 32
 import nova.conf
32 33
 from nova import context as nova_context
33 34
 from nova import exception
34 35
 from nova.i18n import _
35 36
 from nova.network.neutronv2 import api as neutronapi
36
-from nova import wsgi
37 37
 
38 38
 CONF = nova.conf.CONF
39 39
 LOG = logging.getLogger(__name__)

+ 15
- 3
nova/api/openstack/__init__.py View File

@@ -24,16 +24,28 @@ import webob.dec
24 24
 import webob.exc
25 25
 
26 26
 from nova.api.openstack import wsgi
27
+from nova.api import wsgi as base_wsgi
27 28
 import nova.conf
28 29
 from nova.i18n import translate
29
-from nova import utils
30
-from nova import wsgi as base_wsgi
31 30
 
32 31
 
33 32
 LOG = logging.getLogger(__name__)
34 33
 CONF = nova.conf.CONF
35 34
 
36 35
 
36
+def walk_class_hierarchy(clazz, encountered=None):
37
+    """Walk class hierarchy, yielding most derived classes first."""
38
+    if not encountered:
39
+        encountered = []
40
+    for subclass in clazz.__subclasses__():
41
+        if subclass not in encountered:
42
+            encountered.append(subclass)
43
+            # drill down to leaves first
44
+            for subsubclass in walk_class_hierarchy(subclass, encountered):
45
+                yield subsubclass
46
+            yield subclass
47
+
48
+
37 49
 class FaultWrapper(base_wsgi.Middleware):
38 50
     """Calls down the middleware stack, making exceptions into faults."""
39 51
 
@@ -42,7 +54,7 @@ class FaultWrapper(base_wsgi.Middleware):
42 54
     @staticmethod
43 55
     def status_to_type(status):
44 56
         if not FaultWrapper._status_to_type:
45
-            for clazz in utils.walk_class_hierarchy(webob.exc.HTTPError):
57
+            for clazz in walk_class_hierarchy(webob.exc.HTTPError):
46 58
                 FaultWrapper._status_to_type[clazz.code] = clazz
47 59
         return FaultWrapper._status_to_type.get(
48 60
                                   status, webob.exc.HTTPInternalServerError)()

+ 1
- 1
nova/api/openstack/auth.py View File

@@ -18,9 +18,9 @@ import webob.dec
18 18
 import webob.exc
19 19
 
20 20
 from nova.api.openstack import wsgi
21
+from nova.api import wsgi as base_wsgi
21 22
 import nova.conf
22 23
 from nova import context
23
-from nova import wsgi as base_wsgi
24 24
 
25 25
 CONF = nova.conf.CONF
26 26
 

+ 1
- 1
nova/api/openstack/compute/routes.py View File

@@ -93,8 +93,8 @@ from nova.api.openstack.compute import versionsV21
93 93
 from nova.api.openstack.compute import virtual_interfaces
94 94
 from nova.api.openstack.compute import volumes
95 95
 from nova.api.openstack import wsgi
96
+from nova.api import wsgi as base_wsgi
96 97
 import nova.conf
97
-from nova import wsgi as base_wsgi
98 98
 
99 99
 
100 100
 CONF = nova.conf.CONF

+ 1
- 1
nova/api/openstack/requestlog.py View File

@@ -20,7 +20,7 @@ import webob.dec
20 20
 import webob.exc
21 21
 
22 22
 from nova.api.openstack import wsgi
23
-from nova import wsgi as base_wsgi
23
+from nova.api import wsgi as base_wsgi
24 24
 
25 25
 # TODO(sdague) maybe we can use a better name here for the logger
26 26
 LOG = logging.getLogger(__name__)

+ 26
- 13
nova/api/openstack/wsgi.py View File

@@ -26,11 +26,10 @@ import webob
26 26
 
27 27
 from nova.api.openstack import api_version_request as api_version
28 28
 from nova.api.openstack import versioned_method
29
+from nova.api import wsgi
29 30
 from nova import exception
30 31
 from nova import i18n
31 32
 from nova.i18n import _
32
-from nova import utils
33
-from nova import wsgi
34 33
 
35 34
 
36 35
 LOG = logging.getLogger(__name__)
@@ -334,16 +333,28 @@ class ResponseObject(object):
334 333
         if self.obj is not None:
335 334
             body = serializer.serialize(self.obj)
336 335
         response = webob.Response(body=body)
337
-        if response.headers.get('Content-Length'):
338
-            # NOTE(andreykurilin): we need to encode 'Content-Length' header,
339
-            # since webob.Response auto sets it if "body" attr is presented.
340
-            # https://github.com/Pylons/webob/blob/1.5.0b0/webob/response.py#L147
341
-            response.headers['Content-Length'] = utils.utf8(
342
-                response.headers['Content-Length'])
343 336
         response.status_int = self.code
344
-        for hdr, value in self._headers.items():
345
-            response.headers[hdr] = utils.utf8(value)
346
-        response.headers['Content-Type'] = utils.utf8(content_type)
337
+        for hdr, val in self._headers.items():
338
+            if not isinstance(val, six.text_type):
339
+                val = six.text_type(val)
340
+            if six.PY2:
341
+                # In Py2.X Headers must be byte strings
342
+                response.headers[hdr] = encodeutils.safe_encode(val)
343
+            else:
344
+                # In Py3.X Headers must be utf-8 strings
345
+                response.headers[hdr] = encodeutils.safe_decode(
346
+                        encodeutils.safe_encode(val))
347
+        # Deal with content_type
348
+        if not isinstance(content_type, six.text_type):
349
+            content_type = six.text_type(content_type)
350
+        if six.PY2:
351
+            # In Py2.X Headers must be byte strings
352
+            response.headers['Content-Type'] = encodeutils.safe_encode(
353
+                content_type)
354
+        else:
355
+            # In Py3.X Headers must be utf-8 strings
356
+            response.headers['Content-Type'] = encodeutils.safe_decode(
357
+                    encodeutils.safe_encode(content_type))
347 358
         return response
348 359
 
349 360
     @property
@@ -658,13 +669,15 @@ class Resource(wsgi.Application):
658 669
 
659 670
         if hasattr(response, 'headers'):
660 671
             for hdr, val in list(response.headers.items()):
672
+                if not isinstance(val, six.text_type):
673
+                    val = six.text_type(val)
661 674
                 if six.PY2:
662 675
                     # In Py2.X Headers must be byte strings
663
-                    response.headers[hdr] = utils.utf8(val)
676
+                    response.headers[hdr] = encodeutils.safe_encode(val)
664 677
                 else:
665 678
                     # In Py3.X Headers must be utf-8 strings
666 679
                     response.headers[hdr] = encodeutils.safe_decode(
667
-                            utils.utf8(val))
680
+                            encodeutils.safe_encode(val))
668 681
 
669 682
             if not request.api_version_request.is_null():
670 683
                 response.headers[API_VERSION_REQUEST_HEADER] = \

+ 298
- 0
nova/api/wsgi.py View File

@@ -0,0 +1,298 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+"""WSGI primitives used throughout the nova WSGI apps."""
13
+
14
+import os
15
+import sys
16
+
17
+from oslo_log import log as logging
18
+from paste import deploy
19
+import routes.middleware
20
+import six
21
+import webob
22
+
23
+import nova.conf
24
+from nova import exception
25
+from nova.i18n import _, _LE
26
+
27
+
28
+CONF = nova.conf.CONF
29
+
30
+LOG = logging.getLogger(__name__)
31
+
32
+
33
+class Request(webob.Request):
34
+    def __init__(self, environ, *args, **kwargs):
35
+        if CONF.wsgi.secure_proxy_ssl_header:
36
+            scheme = environ.get(CONF.wsgi.secure_proxy_ssl_header)
37
+            if scheme:
38
+                environ['wsgi.url_scheme'] = scheme
39
+        super(Request, self).__init__(environ, *args, **kwargs)
40
+
41
+
42
+class Application(object):
43
+    """Base WSGI application wrapper. Subclasses need to implement __call__."""
44
+
45
+    @classmethod
46
+    def factory(cls, global_config, **local_config):
47
+        """Used for paste app factories in paste.deploy config files.
48
+
49
+        Any local configuration (that is, values under the [app:APPNAME]
50
+        section of the paste config) will be passed into the `__init__` method
51
+        as kwargs.
52
+
53
+        A hypothetical configuration would look like:
54
+
55
+            [app:wadl]
56
+            latest_version = 1.3
57
+            paste.app_factory = nova.api.fancy_api:Wadl.factory
58
+
59
+        which would result in a call to the `Wadl` class as
60
+
61
+            import nova.api.fancy_api
62
+            fancy_api.Wadl(latest_version='1.3')
63
+
64
+        You could of course re-implement the `factory` method in subclasses,
65
+        but using the kwarg passing it shouldn't be necessary.
66
+
67
+        """
68
+        return cls(**local_config)
69
+
70
+    def __call__(self, environ, start_response):
71
+        r"""Subclasses will probably want to implement __call__ like this:
72
+
73
+        @webob.dec.wsgify(RequestClass=Request)
74
+        def __call__(self, req):
75
+          # Any of the following objects work as responses:
76
+
77
+          # Option 1: simple string
78
+          res = 'message\n'
79
+
80
+          # Option 2: a nicely formatted HTTP exception page
81
+          res = exc.HTTPForbidden(explanation='Nice try')
82
+
83
+          # Option 3: a webob Response object (in case you need to play with
84
+          # headers, or you want to be treated like an iterable, or ...)
85
+          res = Response()
86
+          res.app_iter = open('somefile')
87
+
88
+          # Option 4: any wsgi app to be run next
89
+          res = self.application
90
+
91
+          # Option 5: you can get a Response object for a wsgi app, too, to
92
+          # play with headers etc
93
+          res = req.get_response(self.application)
94
+
95
+          # You can then just return your response...
96
+          return res
97
+          # ... or set req.response and return None.
98
+          req.response = res
99
+
100
+        See the end of http://pythonpaste.org/webob/modules/dec.html
101
+        for more info.
102
+
103
+        """
104
+        raise NotImplementedError(_('You must implement __call__'))
105
+
106
+
107
+class Middleware(Application):
108
+    """Base WSGI middleware.
109
+
110
+    These classes require an application to be
111
+    initialized that will be called next.  By default the middleware will
112
+    simply call its wrapped app, or you can override __call__ to customize its
113
+    behavior.
114
+
115
+    """
116
+
117
+    @classmethod
118
+    def factory(cls, global_config, **local_config):
119
+        """Used for paste app factories in paste.deploy config files.
120
+
121
+        Any local configuration (that is, values under the [filter:APPNAME]
122
+        section of the paste config) will be passed into the `__init__` method
123
+        as kwargs.
124
+
125
+        A hypothetical configuration would look like:
126
+
127
+            [filter:analytics]
128
+            redis_host = 127.0.0.1
129
+            paste.filter_factory = nova.api.analytics:Analytics.factory
130
+
131
+        which would result in a call to the `Analytics` class as
132
+
133
+            import nova.api.analytics
134
+            analytics.Analytics(app_from_paste, redis_host='127.0.0.1')
135
+
136
+        You could of course re-implement the `factory` method in subclasses,
137
+        but using the kwarg passing it shouldn't be necessary.
138
+
139
+        """
140
+        def _factory(app):
141
+            return cls(app, **local_config)
142
+        return _factory
143
+
144
+    def __init__(self, application):
145
+        self.application = application
146
+
147
+    def process_request(self, req):
148
+        """Called on each request.
149
+
150
+        If this returns None, the next application down the stack will be
151
+        executed. If it returns a response then that response will be returned
152
+        and execution will stop here.
153
+
154
+        """
155
+        return None
156
+
157
+    def process_response(self, response):
158
+        """Do whatever you'd like to the response."""
159
+        return response
160
+
161
+    @webob.dec.wsgify(RequestClass=Request)
162
+    def __call__(self, req):
163
+        response = self.process_request(req)
164
+        if response:
165
+            return response
166
+        response = req.get_response(self.application)
167
+        return self.process_response(response)
168
+
169
+
170
+class Debug(Middleware):
171
+    """Helper class for debugging a WSGI application.
172
+
173
+    Can be inserted into any WSGI application chain to get information
174
+    about the request and response.
175
+
176
+    """
177
+
178
+    @webob.dec.wsgify(RequestClass=Request)
179
+    def __call__(self, req):
180
+        print(('*' * 40) + ' REQUEST ENVIRON')
181
+        for key, value in req.environ.items():
182
+            print(key, '=', value)
183
+        print()
184
+        resp = req.get_response(self.application)
185
+
186
+        print(('*' * 40) + ' RESPONSE HEADERS')
187
+        for (key, value) in resp.headers.items():
188
+            print(key, '=', value)
189
+        print()
190
+
191
+        resp.app_iter = self.print_generator(resp.app_iter)
192
+
193
+        return resp
194
+
195
+    @staticmethod
196
+    def print_generator(app_iter):
197
+        """Iterator that prints the contents of a wrapper string."""
198
+        print(('*' * 40) + ' BODY')
199
+        for part in app_iter:
200
+            sys.stdout.write(six.text_type(part))
201
+            sys.stdout.flush()
202
+            yield part
203
+        print()
204
+
205
+
206
+class Router(object):
207
+    """WSGI middleware that maps incoming requests to WSGI apps."""
208
+
209
+    def __init__(self, mapper):
210
+        """Create a router for the given routes.Mapper.
211
+
212
+        Each route in `mapper` must specify a 'controller', which is a
213
+        WSGI app to call.  You'll probably want to specify an 'action' as
214
+        well and have your controller be an object that can route
215
+        the request to the action-specific method.
216
+
217
+        Examples:
218
+          mapper = routes.Mapper()
219
+          sc = ServerController()
220
+
221
+          # Explicit mapping of one route to a controller+action
222
+          mapper.connect(None, '/svrlist', controller=sc, action='list')
223
+
224
+          # Actions are all implicitly defined
225
+          mapper.resource('server', 'servers', controller=sc)
226
+
227
+          # Pointing to an arbitrary WSGI app.  You can specify the
228
+          # {path_info:.*} parameter so the target app can be handed just that
229
+          # section of the URL.
230
+          mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
231
+
232
+        """
233
+        self.map = mapper
234
+        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
235
+                                                          self.map)
236
+
237
+    @webob.dec.wsgify(RequestClass=Request)
238
+    def __call__(self, req):
239
+        """Route the incoming request to a controller based on self.map.
240
+
241
+        If no match, return a 404.
242
+
243
+        """
244
+        return self._router
245
+
246
+    @staticmethod
247
+    @webob.dec.wsgify(RequestClass=Request)
248
+    def _dispatch(req):
249
+        """Dispatch the request to the appropriate controller.
250
+
251
+        Called by self._router after matching the incoming request to a route
252
+        and putting the information into req.environ.  Either returns 404
253
+        or the routed WSGI app's response.
254
+
255
+        """
256
+        match = req.environ['wsgiorg.routing_args'][1]
257
+        if not match:
258
+            return webob.exc.HTTPNotFound()
259
+        app = match['controller']
260
+        return app
261
+
262
+
263
+class Loader(object):
264
+    """Used to load WSGI applications from paste configurations."""
265
+
266
+    def __init__(self, config_path=None):
267
+        """Initialize the loader, and attempt to find the config.
268
+
269
+        :param config_path: Full or relative path to the paste config.
270
+        :returns: None
271
+
272
+        """
273
+        self.config_path = None
274
+
275
+        config_path = config_path or CONF.wsgi.api_paste_config
276
+        if not os.path.isabs(config_path):
277
+            self.config_path = CONF.find_file(config_path)
278
+        elif os.path.exists(config_path):
279
+            self.config_path = config_path
280
+
281
+        if not self.config_path:
282
+            raise exception.ConfigNotFound(path=config_path)
283
+
284
+    def load_app(self, name):
285
+        """Return the paste URLMap wrapped WSGI application.
286
+
287
+        :param name: Name of the application to load.
288
+        :returns: Paste URLMap object wrapping the requested application.
289
+        :raises: `nova.exception.PasteAppNotFound`
290
+
291
+        """
292
+        try:
293
+            LOG.debug("Loading app %(name)s from %(path)s",
294
+                      {'name': name, 'path': self.config_path})
295
+            return deploy.loadapp("config:%s" % self.config_path, name=name)
296
+        except LookupError:
297
+            LOG.exception(_LE("Couldn't lookup app: %s"), name)
298
+            raise exception.PasteAppNotFound(name=name, path=self.config_path)

+ 2
- 1
nova/service.py View File

@@ -27,6 +27,7 @@ import oslo_messaging as messaging
27 27
 from oslo_service import service
28 28
 from oslo_utils import importutils
29 29
 
30
+from nova.api import wsgi as api_wsgi
30 31
 from nova import baserpc
31 32
 from nova import conductor
32 33
 import nova.conf
@@ -326,7 +327,7 @@ class WSGIService(service.Service):
326 327
         self.binary = 'nova-%s' % name
327 328
         self.topic = None
328 329
         self.manager = self._get_manager()
329
-        self.loader = loader or wsgi.Loader()
330
+        self.loader = loader or api_wsgi.Loader()
330 331
         self.app = self.loader.load_app(name)
331 332
         # inherit all compute_api worker counts from osapi_compute
332 333
         if name.startswith('openstack_compute_api'):

+ 1
- 1
nova/tests/fixtures.py View File

@@ -39,6 +39,7 @@ from wsgi_intercept import interceptor
39 39
 from nova.api.openstack.compute import tenant_networks
40 40
 from nova.api.openstack.placement import deploy as placement_deploy
41 41
 from nova.api.openstack import wsgi_app
42
+from nova.api import wsgi
42 43
 from nova.compute import rpcapi as compute_rpcapi
43 44
 from nova import context
44 45
 from nova.db import migration
@@ -53,7 +54,6 @@ from nova import rpc
53 54
 from nova import service
54 55
 from nova.tests.functional.api import client
55 56
 from nova.tests import uuidsentinel
56
-from nova import wsgi
57 57
 
58 58
 _TRUE_VALUES = ('True', 'true', '1', 'yes')
59 59
 

+ 1
- 1
nova/tests/unit/api/openstack/compute/test_versions.py View File

@@ -19,11 +19,11 @@ from oslo_serialization import jsonutils
19 19
 
20 20
 from nova.api.openstack import api_version_request as avr
21 21
 from nova.api.openstack.compute import views
22
+from nova.api import wsgi
22 23
 from nova import test
23 24
 from nova.tests.unit.api.openstack import fakes
24 25
 from nova.tests.unit import matchers
25 26
 from nova.tests import uuidsentinel as uuids
26
-from nova import wsgi
27 27
 
28 28
 
29 29
 NS = {

+ 1
- 1
nova/tests/unit/api/openstack/fakes.py View File

@@ -30,6 +30,7 @@ from nova.api.openstack import compute
30 30
 from nova.api.openstack.compute import versions
31 31
 from nova.api.openstack import urlmap
32 32
 from nova.api.openstack import wsgi as os_wsgi
33
+from nova.api import wsgi
33 34
 from nova.compute import flavors
34 35
 from nova.compute import vm_states
35 36
 import nova.conf
@@ -44,7 +45,6 @@ from nova.tests.unit import fake_block_device
44 45
 from nova.tests.unit import fake_network
45 46
 from nova.tests.unit.objects import test_keypair
46 47
 from nova import utils
47
-from nova import wsgi
48 48
 
49 49
 
50 50
 CONF = nova.conf.CONF

+ 1
- 1
nova/tests/unit/api/test_wsgi.py View File

@@ -24,8 +24,8 @@ import routes
24 24
 from six.moves import StringIO
25 25
 import webob
26 26
 
27
+from nova.api import wsgi
27 28
 from nova import test
28
-from nova import wsgi
29 29
 
30 30
 
31 31
 class Test(test.NoDBTestCase):

+ 1
- 1
nova/tests/unit/test_service.py View File

@@ -263,7 +263,7 @@ class TestWSGIService(test.NoDBTestCase):
263 263
 
264 264
     def setUp(self):
265 265
         super(TestWSGIService, self).setUp()
266
-        self.stub_out('nova.wsgi.Loader.load_app',
266
+        self.stub_out('nova.api.wsgi.Loader.load_app',
267 267
                       lambda *a, **kw: mock.MagicMock())
268 268
 
269 269
     @mock.patch('nova.objects.Service.get_by_host_and_binary')

+ 4
- 3
nova/tests/unit/test_wsgi.py View File

@@ -29,6 +29,7 @@ import six
29 29
 import testtools
30 30
 import webob
31 31
 
32
+import nova.api.wsgi
32 33
 import nova.exception
33 34
 from nova import test
34 35
 from nova.tests.unit import utils
@@ -51,14 +52,14 @@ class TestLoaderNothingExists(test.NoDBTestCase):
51 52
         self.flags(api_paste_config='api-paste.ini', group='wsgi')
52 53
         self.assertRaises(
53 54
             nova.exception.ConfigNotFound,
54
-            nova.wsgi.Loader,
55
+            nova.api.wsgi.Loader,
55 56
         )
56 57
 
57 58
     def test_asbpath_config_not_found(self):
58 59
         self.flags(api_paste_config='/etc/nova/api-paste.ini', group='wsgi')
59 60
         self.assertRaises(
60 61
             nova.exception.ConfigNotFound,
61
-            nova.wsgi.Loader,
62
+            nova.api.wsgi.Loader,
62 63
         )
63 64
 
64 65
 
@@ -77,7 +78,7 @@ document_root = /tmp
77 78
         self.config.write(self._paste_config.lstrip())
78 79
         self.config.seek(0)
79 80
         self.config.flush()
80
-        self.loader = nova.wsgi.Loader(self.config.name)
81
+        self.loader = nova.api.wsgi.Loader(self.config.name)
81 82
 
82 83
     def test_config_found(self):
83 84
         self.assertEqual(self.config.name, self.loader.config_path)

+ 0
- 13
nova/utils.py View File

@@ -683,19 +683,6 @@ def tempdir(**kwargs):
683 683
             LOG.error(_LE('Could not remove tmpdir: %s'), e)
684 684
 
685 685
 
686
-def walk_class_hierarchy(clazz, encountered=None):
687
-    """Walk class hierarchy, yielding most derived classes first."""
688
-    if not encountered:
689
-        encountered = []
690
-    for subclass in clazz.__subclasses__():
691
-        if subclass not in encountered:
692
-            encountered.append(subclass)
693
-            # drill down to leaves first
694
-            for subsubclass in walk_class_hierarchy(subclass, encountered):
695
-                yield subsubclass
696
-            yield subclass
697
-
698
-
699 686
 class UndoManager(object):
700 687
     """Provides a mechanism to facilitate rolling back a series of actions
701 688
     when an exception is raised.

+ 0
- 274
nova/wsgi.py View File

@@ -22,7 +22,6 @@ from __future__ import print_function
22 22
 import os.path
23 23
 import socket
24 24
 import ssl
25
-import sys
26 25
 
27 26
 import eventlet
28 27
 import eventlet.wsgi
@@ -30,11 +29,6 @@ import greenlet
30 29
 from oslo_log import log as logging
31 30
 from oslo_service import service
32 31
 from oslo_utils import excutils
33
-from paste import deploy
34
-import routes.middleware
35
-import six
36
-import webob.dec
37
-import webob.exc
38 32
 
39 33
 import nova.conf
40 34
 from nova import exception
@@ -230,271 +224,3 @@ class Server(service.ServiceBase):
230 224
                 self._server.wait()
231 225
         except greenlet.GreenletExit:
232 226
             LOG.info(_LI("WSGI server has stopped."))
233
-
234
-
235
-class Request(webob.Request):
236
-    def __init__(self, environ, *args, **kwargs):
237
-        if CONF.wsgi.secure_proxy_ssl_header:
238
-            scheme = environ.get(CONF.wsgi.secure_proxy_ssl_header)
239
-            if scheme:
240
-                environ['wsgi.url_scheme'] = scheme
241
-        super(Request, self).__init__(environ, *args, **kwargs)
242
-
243
-
244
-class Application(object):
245
-    """Base WSGI application wrapper. Subclasses need to implement __call__."""
246
-
247
-    @classmethod
248
-    def factory(cls, global_config, **local_config):
249
-        """Used for paste app factories in paste.deploy config files.
250
-
251
-        Any local configuration (that is, values under the [app:APPNAME]
252
-        section of the paste config) will be passed into the `__init__` method
253
-        as kwargs.
254
-
255
-        A hypothetical configuration would look like:
256
-
257
-            [app:wadl]
258
-            latest_version = 1.3
259
-            paste.app_factory = nova.api.fancy_api:Wadl.factory
260
-
261
-        which would result in a call to the `Wadl` class as
262
-
263
-            import nova.api.fancy_api
264
-            fancy_api.Wadl(latest_version='1.3')
265
-
266
-        You could of course re-implement the `factory` method in subclasses,
267
-        but using the kwarg passing it shouldn't be necessary.
268
-
269
-        """
270
-        return cls(**local_config)
271
-
272
-    def __call__(self, environ, start_response):
273
-        r"""Subclasses will probably want to implement __call__ like this:
274
-
275
-        @webob.dec.wsgify(RequestClass=Request)
276
-        def __call__(self, req):
277
-          # Any of the following objects work as responses:
278
-
279
-          # Option 1: simple string
280
-          res = 'message\n'
281
-
282
-          # Option 2: a nicely formatted HTTP exception page
283
-          res = exc.HTTPForbidden(explanation='Nice try')
284
-
285
-          # Option 3: a webob Response object (in case you need to play with
286
-          # headers, or you want to be treated like an iterable, or ...)
287
-          res = Response()
288
-          res.app_iter = open('somefile')
289
-
290
-          # Option 4: any wsgi app to be run next
291
-          res = self.application
292
-
293
-          # Option 5: you can get a Response object for a wsgi app, too, to
294
-          # play with headers etc
295
-          res = req.get_response(self.application)
296
-
297
-          # You can then just return your response...
298
-          return res
299
-          # ... or set req.response and return None.
300
-          req.response = res
301
-
302
-        See the end of http://pythonpaste.org/webob/modules/dec.html
303
-        for more info.
304
-
305
-        """
306
-        raise NotImplementedError(_('You must implement __call__'))
307
-
308
-
309
-class Middleware(Application):
310
-    """Base WSGI middleware.
311
-
312
-    These classes require an application to be
313
-    initialized that will be called next.  By default the middleware will
314
-    simply call its wrapped app, or you can override __call__ to customize its
315
-    behavior.
316
-
317
-    """
318
-
319
-    @classmethod
320
-    def factory(cls, global_config, **local_config):
321
-        """Used for paste app factories in paste.deploy config files.
322
-
323
-        Any local configuration (that is, values under the [filter:APPNAME]
324
-        section of the paste config) will be passed into the `__init__` method
325
-        as kwargs.
326
-
327
-        A hypothetical configuration would look like:
328
-
329
-            [filter:analytics]
330
-            redis_host = 127.0.0.1
331
-            paste.filter_factory = nova.api.analytics:Analytics.factory
332
-
333
-        which would result in a call to the `Analytics` class as
334
-
335
-            import nova.api.analytics
336
-            analytics.Analytics(app_from_paste, redis_host='127.0.0.1')
337
-
338
-        You could of course re-implement the `factory` method in subclasses,
339
-        but using the kwarg passing it shouldn't be necessary.
340
-
341
-        """
342
-        def _factory(app):
343
-            return cls(app, **local_config)
344
-        return _factory
345
-
346
-    def __init__(self, application):
347
-        self.application = application
348
-
349
-    def process_request(self, req):
350
-        """Called on each request.
351
-
352
-        If this returns None, the next application down the stack will be
353
-        executed. If it returns a response then that response will be returned
354
-        and execution will stop here.
355
-
356
-        """
357
-        return None
358
-
359
-    def process_response(self, response):
360
-        """Do whatever you'd like to the response."""
361
-        return response
362
-
363
-    @webob.dec.wsgify(RequestClass=Request)
364
-    def __call__(self, req):
365
-        response = self.process_request(req)
366
-        if response:
367
-            return response
368
-        response = req.get_response(self.application)
369
-        return self.process_response(response)
370
-
371
-
372
-class Debug(Middleware):
373
-    """Helper class for debugging a WSGI application.
374
-
375
-    Can be inserted into any WSGI application chain to get information
376
-    about the request and response.
377
-
378
-    """
379
-
380
-    @webob.dec.wsgify(RequestClass=Request)
381
-    def __call__(self, req):
382
-        print(('*' * 40) + ' REQUEST ENVIRON')
383
-        for key, value in req.environ.items():
384
-            print(key, '=', value)
385
-        print()
386
-        resp = req.get_response(self.application)
387
-
388
-        print(('*' * 40) + ' RESPONSE HEADERS')
389
-        for (key, value) in resp.headers.items():
390
-            print(key, '=', value)
391
-        print()
392
-
393
-        resp.app_iter = self.print_generator(resp.app_iter)
394
-
395
-        return resp
396
-
397
-    @staticmethod
398
-    def print_generator(app_iter):
399
-        """Iterator that prints the contents of a wrapper string."""
400
-        print(('*' * 40) + ' BODY')
401
-        for part in app_iter:
402
-            sys.stdout.write(six.text_type(part))
403
-            sys.stdout.flush()
404
-            yield part
405
-        print()
406
-
407
-
408
-class Router(object):
409
-    """WSGI middleware that maps incoming requests to WSGI apps."""
410
-
411
-    def __init__(self, mapper):
412
-        """Create a router for the given routes.Mapper.
413
-
414
-        Each route in `mapper` must specify a 'controller', which is a
415
-        WSGI app to call.  You'll probably want to specify an 'action' as
416
-        well and have your controller be an object that can route
417
-        the request to the action-specific method.
418
-
419
-        Examples:
420
-          mapper = routes.Mapper()
421
-          sc = ServerController()
422
-
423
-          # Explicit mapping of one route to a controller+action
424
-          mapper.connect(None, '/svrlist', controller=sc, action='list')
425
-
426
-          # Actions are all implicitly defined
427
-          mapper.resource('server', 'servers', controller=sc)
428
-
429
-          # Pointing to an arbitrary WSGI app.  You can specify the
430
-          # {path_info:.*} parameter so the target app can be handed just that
431
-          # section of the URL.
432
-          mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
433
-
434
-        """
435
-        self.map = mapper
436
-        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
437
-                                                          self.map)
438
-
439
-    @webob.dec.wsgify(RequestClass=Request)
440
-    def __call__(self, req):
441
-        """Route the incoming request to a controller based on self.map.
442
-
443
-        If no match, return a 404.
444
-
445
-        """
446
-        return self._router
447
-
448
-    @staticmethod
449
-    @webob.dec.wsgify(RequestClass=Request)
450
-    def _dispatch(req):
451
-        """Dispatch the request to the appropriate controller.
452
-
453
-        Called by self._router after matching the incoming request to a route
454
-        and putting the information into req.environ.  Either returns 404
455
-        or the routed WSGI app's response.
456
-
457
-        """
458
-        match = req.environ['wsgiorg.routing_args'][1]
459
-        if not match:
460
-            return webob.exc.HTTPNotFound()
461
-        app = match['controller']
462
-        return app
463
-
464
-
465
-class Loader(object):
466
-    """Used to load WSGI applications from paste configurations."""
467
-
468
-    def __init__(self, config_path=None):
469
-        """Initialize the loader, and attempt to find the config.
470
-
471
-        :param config_path: Full or relative path to the paste config.
472
-        :returns: None
473
-
474
-        """
475
-        self.config_path = None
476
-
477
-        config_path = config_path or CONF.wsgi.api_paste_config
478
-        if not os.path.isabs(config_path):
479
-            self.config_path = CONF.find_file(config_path)
480
-        elif os.path.exists(config_path):
481
-            self.config_path = config_path
482
-
483
-        if not self.config_path:
484
-            raise exception.ConfigNotFound(path=config_path)
485
-
486
-    def load_app(self, name):
487
-        """Return the paste URLMap wrapped WSGI application.
488
-
489
-        :param name: Name of the application to load.
490
-        :returns: Paste URLMap object wrapping the requested application.
491
-        :raises: `nova.exception.PasteAppNotFound`
492
-
493
-        """
494
-        try:
495
-            LOG.debug("Loading app %(name)s from %(path)s",
496
-                      {'name': name, 'path': self.config_path})
497
-            return deploy.loadapp("config:%s" % self.config_path, name=name)
498
-        except LookupError:
499
-            LOG.exception(_LE("Couldn't lookup app: %s"), name)
500
-            raise exception.PasteAppNotFound(name=name, path=self.config_path)

Loading…
Cancel
Save