~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to nova/wsgi.py

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2011-01-21 11:48:06 UTC
  • mto: This revision was merged to the branch mainline in revision 9.
  • Revision ID: james.westby@ubuntu.com-20110121114806-v8fvnnl6az4m4ohv
Tags: upstream-2011.1~bzr597
ImportĀ upstreamĀ versionĀ 2011.1~bzr597

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
Utility methods for working with WSGI servers
22
22
"""
23
23
 
24
 
import json
25
 
import logging
 
24
import os
26
25
import sys
27
26
from xml.dom import minidom
28
27
 
35
34
import webob.dec
36
35
import webob.exc
37
36
 
38
 
 
39
 
logging.getLogger("routes.middleware").addHandler(logging.StreamHandler())
 
37
from paste import deploy
 
38
 
 
39
from nova import flags
 
40
from nova import log as logging
 
41
from nova import utils
 
42
 
 
43
 
 
44
FLAGS = flags.FLAGS
 
45
 
 
46
 
 
47
class WritableLogger(object):
 
48
    """A thin wrapper that responds to `write` and logs."""
 
49
 
 
50
    def __init__(self, logger, level=logging.DEBUG):
 
51
        self.logger = logger
 
52
        self.level = level
 
53
 
 
54
    def write(self, msg):
 
55
        self.logger.log(self.level, msg)
40
56
 
41
57
 
42
58
class Server(object):
43
59
    """Server class to manage multiple WSGI sockets and applications."""
44
60
 
45
61
    def __init__(self, threads=1000):
 
62
        logging.basicConfig()
46
63
        self.pool = eventlet.GreenPool(threads)
47
64
 
48
65
    def start(self, application, port, host='0.0.0.0', backlog=128):
49
66
        """Run a WSGI server with the given application."""
 
67
        logging.audit(_("Starting %s on %s:%s"), sys.argv[0], host, port)
50
68
        socket = eventlet.listen((host, port), backlog=backlog)
51
69
        self.pool.spawn_n(self._run, application, socket)
52
70
 
59
77
 
60
78
    def _run(self, application, socket):
61
79
        """Start a WSGI server in a new green thread."""
62
 
        eventlet.wsgi.server(socket, application, custom_pool=self.pool)
 
80
        logger = logging.getLogger('eventlet.wsgi.server')
 
81
        eventlet.wsgi.server(socket, application, custom_pool=self.pool,
 
82
                             log=WritableLogger(logger))
63
83
 
64
84
 
65
85
class Application(object):
66
 
# TODO(gundlach): I think we should toss this class, now that it has no
67
 
# purpose.
68
86
    """Base WSGI application wrapper. Subclasses need to implement __call__."""
69
87
 
 
88
    @classmethod
 
89
    def factory(cls, global_config, **local_config):
 
90
        """Used for paste app factories in paste.deploy config fles.
 
91
 
 
92
        Any local configuration (that is, values under the [app:APPNAME]
 
93
        section of the paste config) will be passed into the `__init__` method
 
94
        as kwargs.
 
95
 
 
96
        A hypothetical configuration would look like:
 
97
 
 
98
            [app:wadl]
 
99
            latest_version = 1.3
 
100
            paste.app_factory = nova.api.fancy_api:Wadl.factory
 
101
 
 
102
        which would result in a call to the `Wadl` class as
 
103
 
 
104
            import nova.api.fancy_api
 
105
            fancy_api.Wadl(latest_version='1.3')
 
106
 
 
107
        You could of course re-implement the `factory` method in subclasses,
 
108
        but using the kwarg passing it shouldn't be necessary.
 
109
 
 
110
        """
 
111
        return cls(**local_config)
 
112
 
70
113
    def __call__(self, environ, start_response):
71
114
        r"""Subclasses will probably want to implement __call__ like this:
72
115
 
100
143
        See the end of http://pythonpaste.org/webob/modules/dec.html
101
144
        for more info.
102
145
        """
103
 
        raise NotImplementedError("You must implement __call__")
 
146
        raise NotImplementedError(_("You must implement __call__"))
104
147
 
105
148
 
106
149
class Middleware(Application):
107
 
    """
108
 
    Base WSGI middleware wrapper. These classes require an application to be
 
150
    """Base WSGI middleware.
 
151
 
 
152
    These classes require an application to be
109
153
    initialized that will be called next.  By default the middleware will
110
154
    simply call its wrapped app, or you can override __call__ to customize its
111
155
    behavior.
112
156
    """
113
157
 
114
 
    def __init__(self, application):  # pylint: disable-msg=W0231
 
158
    @classmethod
 
159
    def factory(cls, global_config, **local_config):
 
160
        """Used for paste app factories in paste.deploy config fles.
 
161
 
 
162
        Any local configuration (that is, values under the [filter:APPNAME]
 
163
        section of the paste config) will be passed into the `__init__` method
 
164
        as kwargs.
 
165
 
 
166
        A hypothetical configuration would look like:
 
167
 
 
168
            [filter:analytics]
 
169
            redis_host = 127.0.0.1
 
170
            paste.filter_factory = nova.api.analytics:Analytics.factory
 
171
 
 
172
        which would result in a call to the `Analytics` class as
 
173
 
 
174
            import nova.api.analytics
 
175
            analytics.Analytics(app_from_paste, redis_host='127.0.0.1')
 
176
 
 
177
        You could of course re-implement the `factory` method in subclasses,
 
178
        but using the kwarg passing it shouldn't be necessary.
 
179
 
 
180
        """
 
181
        def _factory(app):
 
182
            return cls(app, **local_config)
 
183
        return _factory
 
184
 
 
185
    def __init__(self, application):
115
186
        self.application = application
116
187
 
 
188
    def process_request(self, req):
 
189
        """Called on each request.
 
190
 
 
191
        If this returns None, the next application down the stack will be
 
192
        executed. If it returns a response then that response will be returned
 
193
        and execution will stop here.
 
194
 
 
195
        """
 
196
        return None
 
197
 
 
198
    def process_response(self, response):
 
199
        """Do whatever you'd like to the response."""
 
200
        return response
 
201
 
117
202
    @webob.dec.wsgify
118
 
    def __call__(self, req):  # pylint: disable-msg=W0221
119
 
        """Override to implement middleware behavior."""
120
 
        return self.application
 
203
    def __call__(self, req):
 
204
        response = self.process_request(req)
 
205
        if response:
 
206
            return response
 
207
        response = req.get_response(self.application)
 
208
        return self.process_response(response)
121
209
 
122
210
 
123
211
class Debug(Middleware):
270
358
        needed to serialize a dictionary to that type.
271
359
        """
272
360
        self.metadata = metadata or {}
273
 
        req = webob.Request(environ)
 
361
        req = webob.Request.blank('', environ)
274
362
        suffix = req.path_info.split('.')[-1].lower()
275
363
        if suffix == 'json':
276
364
            self.handler = self._to_json
303
391
        try:
304
392
            is_xml = (datastring[0] == '<')
305
393
            if not is_xml:
306
 
                return json.loads(datastring)
 
394
                return utils.loads(datastring)
307
395
            return self._from_xml(datastring)
308
396
        except:
309
397
            return None
336
424
            return result
337
425
 
338
426
    def _to_json(self, data):
339
 
        return json.dumps(data)
 
427
        return utils.dumps(data)
340
428
 
341
429
    def _to_xml(self, data):
342
430
        metadata = self.metadata.get('application/xml', {})
372
460
            node = doc.createTextNode(str(data))
373
461
            result.appendChild(node)
374
462
        return result
 
463
 
 
464
 
 
465
def paste_config_file(basename):
 
466
    """Find the best location in the system for a paste config file.
 
467
 
 
468
    Search Order
 
469
    ------------
 
470
 
 
471
    The search for a paste config file honors `FLAGS.state_path`, which in a
 
472
    version checked out from bzr will be the `nova` directory in the top level
 
473
    of the checkout, and in an installation for a package for your distribution
 
474
    will likely point to someplace like /etc/nova.
 
475
 
 
476
    This method tries to load places likely to be used in development or
 
477
    experimentation before falling back to the system-wide configuration
 
478
    in `/etc/nova/`.
 
479
 
 
480
    * Current working directory
 
481
    * the `etc` directory under state_path, because when working on a checkout
 
482
      from bzr this will point to the default
 
483
    * top level of FLAGS.state_path, for distributions
 
484
    * /etc/nova, which may not be diffrerent from state_path on your distro
 
485
 
 
486
    """
 
487
 
 
488
    configfiles = [basename,
 
489
                   os.path.join(FLAGS.state_path, 'etc', basename),
 
490
                   os.path.join(FLAGS.state_path, basename),
 
491
                   '/etc/nova/%s' % basename]
 
492
    for configfile in configfiles:
 
493
        if os.path.exists(configfile):
 
494
            return configfile
 
495
 
 
496
 
 
497
def load_paste_configuration(filename, appname):
 
498
    """Returns a paste configuration dict, or None."""
 
499
    filename = os.path.abspath(filename)
 
500
    config = None
 
501
    try:
 
502
        config = deploy.appconfig("config:%s" % filename, name=appname)
 
503
    except LookupError:
 
504
        pass
 
505
    return config
 
506
 
 
507
 
 
508
def load_paste_app(filename, appname):
 
509
    """Builds a wsgi app from a paste config, None if app not configured."""
 
510
    filename = os.path.abspath(filename)
 
511
    app = None
 
512
    try:
 
513
        app = deploy.loadapp("config:%s" % filename, name=appname)
 
514
    except LookupError:
 
515
        pass
 
516
    return app
 
517
 
 
518
 
 
519
def paste_config_to_flags(config, mixins):
 
520
    for k, v in mixins.iteritems():
 
521
        value = config.get(k, v)
 
522
        converted_value = FLAGS[k].parser.Parse(value)
 
523
        setattr(FLAGS, k, converted_value)