summaryrefslogtreecommitdiffstats
path: root/nova/wsgi.py
diff options
context:
space:
mode:
Diffstat (limited to 'nova/wsgi.py')
-rw-r--r--nova/wsgi.py168
1 files changed, 89 insertions, 79 deletions
diff --git a/nova/wsgi.py b/nova/wsgi.py
index 72758e50e..ea9bb963d 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Utility methods for working with WSGI servers
-"""
+"""Utility methods for working with WSGI servers."""
import os
import sys
@@ -33,7 +31,6 @@ import routes.middleware
import webob
import webob.dec
import webob.exc
-
from paste import deploy
from nova import exception
@@ -43,6 +40,7 @@ from nova import utils
FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.wsgi')
class WritableLogger(object):
@@ -61,13 +59,16 @@ class Server(object):
def __init__(self, threads=1000):
self.pool = eventlet.GreenPool(threads)
+ self.socket_info = {}
- def start(self, application, port, host='0.0.0.0', backlog=128):
+ def start(self, application, port, host='0.0.0.0', key=None, backlog=128):
"""Run a WSGI server with the given application."""
arg0 = sys.argv[0]
- logging.audit(_("Starting %(arg0)s on %(host)s:%(port)s") % locals())
+ logging.audit(_('Starting %(arg0)s on %(host)s:%(port)s') % locals())
socket = eventlet.listen((host, port), backlog=backlog)
self.pool.spawn_n(self._run, application, socket)
+ if key:
+ self.socket_info[key] = socket.getsockname()
def wait(self):
"""Wait until all servers have completed running."""
@@ -86,30 +87,34 @@ class Server(object):
class Request(webob.Request):
def best_match_content_type(self):
- """
- Determine the most acceptable content-type based on the
- query extension then the Accept header
- """
+ """Determine the most acceptable content-type.
- parts = self.path.rsplit(".", 1)
+ Based on the query extension then the Accept header.
+
+ """
+ parts = self.path.rsplit('.', 1)
if len(parts) > 1:
format = parts[1]
- if format in ["json", "xml"]:
- return "application/{0}".format(parts[1])
+ if format in ['json', 'xml']:
+ return 'application/{0}'.format(parts[1])
- ctypes = ["application/json", "application/xml"]
+ ctypes = ['application/json', 'application/xml']
bm = self.accept.best_match(ctypes)
- return bm or "application/json"
+ return bm or 'application/json'
def get_content_type(self):
- try:
- ct = self.headers["Content-Type"]
- assert ct in ("application/xml", "application/json")
- return ct
- except Exception:
- raise webob.exc.HTTPBadRequest("Invalid content type")
+ allowed_types = ("application/xml", "application/json")
+ if not "Content-Type" in self.headers:
+ msg = _("Missing Content-Type")
+ LOG.debug(msg)
+ raise webob.exc.HTTPBadRequest(msg)
+ type = self.content_type
+ if type in allowed_types:
+ return type
+ LOG.debug(_("Wrong Content-Type: %s") % type)
+ raise webob.exc.HTTPBadRequest("Invalid content type")
class Application(object):
@@ -117,7 +122,7 @@ class Application(object):
@classmethod
def factory(cls, global_config, **local_config):
- """Used for paste app factories in paste.deploy config fles.
+ """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [app:APPNAME]
section of the paste config) will be passed into the `__init__` method
@@ -172,8 +177,9 @@ class Application(object):
See the end of http://pythonpaste.org/webob/modules/dec.html
for more info.
+
"""
- raise NotImplementedError(_("You must implement __call__"))
+ raise NotImplementedError(_('You must implement __call__'))
class Middleware(Application):
@@ -183,11 +189,12 @@ class Middleware(Application):
initialized that will be called next. By default the middleware will
simply call its wrapped app, or you can override __call__ to customize its
behavior.
+
"""
@classmethod
def factory(cls, global_config, **local_config):
- """Used for paste app factories in paste.deploy config fles.
+ """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [filter:APPNAME]
section of the paste config) will be passed into the `__init__` method
@@ -239,20 +246,24 @@ class Middleware(Application):
class Debug(Middleware):
- """Helper class that can be inserted into any WSGI application chain
- to get information about the request and response."""
+ """Helper class for debugging a WSGI application.
+
+ Can be inserted into any WSGI application chain to get information
+ about the request and response.
+
+ """
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- print ("*" * 40) + " REQUEST ENVIRON"
+ print ('*' * 40) + ' REQUEST ENVIRON'
for key, value in req.environ.items():
- print key, "=", value
+ print key, '=', value
print
resp = req.get_response(self.application)
- print ("*" * 40) + " RESPONSE HEADERS"
+ print ('*' * 40) + ' RESPONSE HEADERS'
for (key, value) in resp.headers.iteritems():
- print key, "=", value
+ print key, '=', value
print
resp.app_iter = self.print_generator(resp.app_iter)
@@ -261,11 +272,8 @@ class Debug(Middleware):
@staticmethod
def print_generator(app_iter):
- """
- Iterator that prints the contents of a wrapper string iterator
- when iterated.
- """
- print ("*" * 40) + " BODY"
+ """Iterator that prints the contents of a wrapper string."""
+ print ('*' * 40) + ' BODY'
for part in app_iter:
sys.stdout.write(part)
sys.stdout.flush()
@@ -274,13 +282,10 @@ class Debug(Middleware):
class Router(object):
- """
- WSGI middleware that maps incoming requests to WSGI apps.
- """
+ """WSGI middleware that maps incoming requests to WSGI apps."""
def __init__(self, mapper):
- """
- Create a router for the given routes.Mapper.
+ """Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a
WSGI app to call. You'll probably want to specify an 'action' as
@@ -292,15 +297,16 @@ class Router(object):
sc = ServerController()
# Explicit mapping of one route to a controller+action
- mapper.connect(None, "/svrlist", controller=sc, action="list")
+ mapper.connect(None, '/svrlist', controller=sc, action='list')
# Actions are all implicitly defined
- mapper.resource("server", "servers", controller=sc)
+ mapper.resource('server', 'servers', controller=sc)
# Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
- mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
+ mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
+
"""
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
@@ -308,19 +314,22 @@ class Router(object):
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- """
- Route the incoming request to a controller based on self.map.
+ """Route the incoming request to a controller based on self.map.
+
If no match, return a 404.
+
"""
return self._router
@staticmethod
@webob.dec.wsgify(RequestClass=Request)
def _dispatch(req):
- """
+ """Dispatch the request to the appropriate controller.
+
Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404
or the routed WSGI app's response.
+
"""
match = req.environ['wsgiorg.routing_args'][1]
if not match:
@@ -330,22 +339,23 @@ class Router(object):
class Controller(object):
- """
+ """WSGI app that dispatched to methods.
+
WSGI app that reads routing information supplied by RoutesMiddleware
and calls the requested action method upon itself. All action methods
must, in addition to their normal parameters, accept a 'req' argument
which is the incoming wsgi.Request. They raise a webob.exc exception,
or return a dict which will be serialized by requested content type.
+
"""
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- """
- Call the method specified in req.environ by RoutesMiddleware.
- """
+ """Call the method specified in req.environ by RoutesMiddleware."""
arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict['action']
method = getattr(self, action)
+ LOG.debug("%s %s" % (req.method, req.url))
del arg_dict['controller']
del arg_dict['action']
if 'format' in arg_dict:
@@ -359,19 +369,23 @@ class Controller(object):
body = self._serialize(result, content_type, default_xmlns)
response = webob.Response()
- response.headers["Content-Type"] = content_type
+ response.headers['Content-Type'] = content_type
response.body = body
+ msg_dict = dict(url=req.url, status=response.status_int)
+ msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
+ LOG.debug(msg)
return response
else:
return result
def _serialize(self, data, content_type, default_xmlns):
- """
- Serialize the given dict to the provided content_type.
+ """Serialize the given dict to the provided content_type.
+
Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type.
+
"""
- _metadata = getattr(type(self), "_serialization_metadata", {})
+ _metadata = getattr(type(self), '_serialization_metadata', {})
serializer = Serializer(_metadata, default_xmlns)
try:
@@ -380,12 +394,13 @@ class Controller(object):
raise webob.exc.HTTPNotAcceptable()
def _deserialize(self, data, content_type):
- """
- Deserialize the request body to the specefied content type.
+ """Deserialize the request body to the specefied content type.
+
Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type.
+
"""
- _metadata = getattr(type(self), "_serialization_metadata", {})
+ _metadata = getattr(type(self), '_serialization_metadata', {})
serializer = Serializer(_metadata)
return serializer.deserialize(data, content_type)
@@ -395,55 +410,51 @@ class Controller(object):
class Serializer(object):
- """
- Serializes and deserializes dictionaries to certain MIME types.
- """
+ """Serializes and deserializes dictionaries to certain MIME types."""
def __init__(self, metadata=None, default_xmlns=None):
- """
- Create a serializer based on the given WSGI environment.
+ """Create a serializer based on the given WSGI environment.
+
'metadata' is an optional dict mapping MIME types to information
needed to serialize a dictionary to that type.
+
"""
self.metadata = metadata or {}
self.default_xmlns = default_xmlns
def _get_serialize_handler(self, content_type):
handlers = {
- "application/json": self._to_json,
- "application/xml": self._to_xml,
+ 'application/json': self._to_json,
+ 'application/xml': self._to_xml,
}
try:
return handlers[content_type]
except Exception:
- raise exception.InvalidContentType()
+ raise exception.InvalidContentType(content_type=content_type)
def serialize(self, data, content_type):
- """
- Serialize a dictionary into a string of the specified content type.
- """
+ """Serialize a dictionary into the specified content type."""
return self._get_serialize_handler(content_type)(data)
def deserialize(self, datastring, content_type):
- """
- Deserialize a string to a dictionary.
+ """Deserialize a string to a dictionary.
The string must be in the format of a supported MIME type.
+
"""
return self.get_deserialize_handler(content_type)(datastring)
def get_deserialize_handler(self, content_type):
handlers = {
- "application/json": self._from_json,
- "application/xml": self._from_xml,
+ 'application/json': self._from_json,
+ 'application/xml': self._from_xml,
}
try:
return handlers[content_type]
except Exception:
- raise exception.InvalidContentType(_("Invalid content type %s"
- % content_type))
+ raise exception.InvalidContentType(content_type=content_type)
def _from_json(self, datastring):
return utils.loads(datastring)
@@ -455,11 +466,11 @@ class Serializer(object):
return {node.nodeName: self._from_xml_node(node, plurals)}
def _from_xml_node(self, node, listnames):
- """
- Convert a minidom node to a simple Python type.
+ """Convert a minidom node to a simple Python type.
listnames is a collection of names of XML nodes whose subnodes should
be considered list items.
+
"""
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
return node.childNodes[0].nodeValue
@@ -566,7 +577,6 @@ def paste_config_file(basename):
* /etc/nova, which may not be diffrerent from state_path on your distro
"""
-
configfiles = [basename,
os.path.join(FLAGS.state_path, 'etc', 'nova', basename),
os.path.join(FLAGS.state_path, 'etc', basename),
@@ -582,7 +592,7 @@ def load_paste_configuration(filename, appname):
filename = os.path.abspath(filename)
config = None
try:
- config = deploy.appconfig("config:%s" % filename, name=appname)
+ config = deploy.appconfig('config:%s' % filename, name=appname)
except LookupError:
pass
return config
@@ -593,7 +603,7 @@ def load_paste_app(filename, appname):
filename = os.path.abspath(filename)
app = None
try:
- app = deploy.loadapp("config:%s" % filename, name=appname)
+ app = deploy.loadapp('config:%s' % filename, name=appname)
except LookupError:
pass
return app