General cleanup and refactor of a lot of the API/WSGI service code.
This commit is contained in:
		
							
								
								
									
										18
									
								
								bin/nova-api
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								bin/nova-api
									
									
									
									
									
								
							| @@ -24,6 +24,8 @@ import gettext | |||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
|  | import eventlet.pool | ||||||
|  |  | ||||||
| # If ../nova/__init__.py exists, add ../ to Python search path, so that | # If ../nova/__init__.py exists, add ../ to Python search path, so that | ||||||
| # it will override what happens to be installed in /usr/(local/)lib/python... | # it will override what happens to be installed in /usr/(local/)lib/python... | ||||||
| possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), | possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), | ||||||
| @@ -46,6 +48,13 @@ LOG = logging.getLogger('nova.api') | |||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def launch(service_name): | ||||||
|  |     _service = service.WSGIService(service_name) | ||||||
|  |     _service.start() | ||||||
|  |     _service.wait() | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     utils.default_flagfile() |     utils.default_flagfile() | ||||||
|     FLAGS(sys.argv) |     FLAGS(sys.argv) | ||||||
| @@ -57,5 +66,10 @@ if __name__ == '__main__': | |||||||
|         flag_get = FLAGS.get(flag, None) |         flag_get = FLAGS.get(flag, None) | ||||||
|         LOG.debug("%(flag)s : %(flag_get)s" % locals()) |         LOG.debug("%(flag)s : %(flag_get)s" % locals()) | ||||||
|  |  | ||||||
|     service = service.serve_wsgi(service.ApiService) |  | ||||||
|     service.wait() |     pool = eventlet.pool.Pool() | ||||||
|  |     pool.execute(launch, "ec2") | ||||||
|  |     pool.execute(launch, "osapi") | ||||||
|  |     pool.wait_all() | ||||||
|  |  | ||||||
|  |     print >>sys.stderr, "Exiting..." | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								nova/log.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								nova/log.py
									
									
									
									
									
								
							| @@ -314,3 +314,16 @@ logging.setLoggerClass(NovaLogger) | |||||||
| def audit(msg, *args, **kwargs): | def audit(msg, *args, **kwargs): | ||||||
|     """Shortcut for logging to root log with sevrity 'AUDIT'.""" |     """Shortcut for logging to root log with sevrity 'AUDIT'.""" | ||||||
|     logging.root.log(AUDIT, msg, *args, **kwargs) |     logging.root.log(AUDIT, msg, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WritableLogger(object): | ||||||
|  |     """A thin wrapper that responds to `write` and logs.""" | ||||||
|  |  | ||||||
|  |     def __init__(self, logger, level=logging.DEBUG): | ||||||
|  |         self.logger = logger | ||||||
|  |         self.level = level | ||||||
|  |  | ||||||
|  |     def write(self, msg): | ||||||
|  |         self.logger.log(self.level, msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -232,45 +232,45 @@ class Service(object): | |||||||
|                 logging.exception(_('model server went away')) |                 logging.exception(_('model server went away')) | ||||||
|  |  | ||||||
|  |  | ||||||
| class WsgiService(object): | class WSGIService(object): | ||||||
|     """Base class for WSGI based services. |     """Provides ability to launch API from a 'paste' configuration.""" | ||||||
|  |  | ||||||
|     For each api you define, you must also define these flags: |     def __init__(self, name, config_name=None): | ||||||
|     :<api>_listen: The address on which to listen |         """Initialize, but do not start, an API service.""" | ||||||
|     :<api>_listen_port: The port on which to listen |         self.name = name | ||||||
|  |         self._config_name = config_name or FLAGS.api_paste_config | ||||||
|  |         self._config_location = self._find_config() | ||||||
|  |         self._config = self._load_config() | ||||||
|  |         self.application = self._load_application() | ||||||
|  |         host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") | ||||||
|  |         port = getattr(FLAGS, '%s_listen_port' % name, 0) | ||||||
|  |         self.server = wsgi.Server(name, self.application, host, port) | ||||||
|  |  | ||||||
|     """ |     def _find_config(self): | ||||||
|  |         """Attempt to find 'paste' configuration file.""" | ||||||
|  |         location = wsgi.paste_config_file(self._config_name) | ||||||
|  |         logging.debug(_("Using paste.deploy config at: %s"), location) | ||||||
|  |         return location | ||||||
|  |  | ||||||
|     def __init__(self, conf, apis): |     def _load_config(self): | ||||||
|         self.conf = conf |         """Read and return the 'paste' configuration file.""" | ||||||
|         self.apis = apis |         return wsgi.load_paste_configuration(self._config_location, self.name) | ||||||
|         self.wsgi_app = None |  | ||||||
|  |     def _load_application(self): | ||||||
|  |         """Using the loaded configuration, return the WSGI application.""" | ||||||
|  |         return wsgi.load_paste_app(self._config_location, self.name) | ||||||
|  |  | ||||||
|     def start(self): |     def start(self): | ||||||
|         self.wsgi_app = _run_wsgi(self.conf, self.apis) |         """Start serving this API using loaded configuration.""" | ||||||
|  |         self.server.start() | ||||||
|  |  | ||||||
|  |     def stop(self): | ||||||
|  |         """Stop serving this API.""" | ||||||
|  |         self.server.stop() | ||||||
|  |  | ||||||
|     def wait(self): |     def wait(self): | ||||||
|         self.wsgi_app.wait() |         """Wait for the service to stop serving this API.""" | ||||||
|  |         self.server.wait() | ||||||
|     def get_socket_info(self, api_name): |  | ||||||
|         """Returns the (host, port) that an API was started on.""" |  | ||||||
|         return self.wsgi_app.socket_info[api_name] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ApiService(WsgiService): |  | ||||||
|     """Class for our nova-api service.""" |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def create(cls, conf=None): |  | ||||||
|         if not conf: |  | ||||||
|             conf = wsgi.paste_config_file(FLAGS.api_paste_config) |  | ||||||
|             if not conf: |  | ||||||
|                 message = (_('No paste configuration found for: %s'), |  | ||||||
|                            FLAGS.api_paste_config) |  | ||||||
|                 raise exception.Error(message) |  | ||||||
|         api_endpoints = ['ec2', 'osapi'] |  | ||||||
|         service = cls(conf, api_endpoints) |  | ||||||
|         return service |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def serve(*services): | def serve(*services): | ||||||
| @@ -321,29 +321,3 @@ def serve_wsgi(cls, conf=None): | |||||||
|     service.start() |     service.start() | ||||||
|  |  | ||||||
|     return service |     return service | ||||||
|  |  | ||||||
|  |  | ||||||
| def _run_wsgi(paste_config_file, apis): |  | ||||||
|     logging.debug(_('Using paste.deploy config at: %s'), paste_config_file) |  | ||||||
|     apps = [] |  | ||||||
|     for api in apis: |  | ||||||
|         config = wsgi.load_paste_configuration(paste_config_file, api) |  | ||||||
|         if config is None: |  | ||||||
|             logging.debug(_('No paste configuration for app: %s'), api) |  | ||||||
|             continue |  | ||||||
|         logging.debug(_('App Config: %(api)s\n%(config)r') % locals()) |  | ||||||
|         logging.info(_('Running %s API'), api) |  | ||||||
|         app = wsgi.load_paste_app(paste_config_file, api) |  | ||||||
|         apps.append((app, |  | ||||||
|                      getattr(FLAGS, '%s_listen_port' % api), |  | ||||||
|                      getattr(FLAGS, '%s_listen' % api), |  | ||||||
|                      api)) |  | ||||||
|     if len(apps) == 0: |  | ||||||
|         logging.error(_('No known API applications configured in %s.'), |  | ||||||
|                       paste_config_file) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     server = wsgi.Server() |  | ||||||
|     for app in apps: |  | ||||||
|         server.start(*app) |  | ||||||
|     return server |  | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								nova/test.py
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								nova/test.py
									
									
									
									
									
								
							| @@ -38,7 +38,6 @@ from nova import flags | |||||||
| from nova import rpc | from nova import rpc | ||||||
| from nova import utils | from nova import utils | ||||||
| from nova import service | from nova import service | ||||||
| from nova import wsgi |  | ||||||
| from nova.virt import fake | from nova.virt import fake | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -81,7 +80,6 @@ class TestCase(unittest.TestCase): | |||||||
|         self.injected = [] |         self.injected = [] | ||||||
|         self._services = [] |         self._services = [] | ||||||
|         self._monkey_patch_attach() |         self._monkey_patch_attach() | ||||||
|         self._monkey_patch_wsgi() |  | ||||||
|         self._original_flags = FLAGS.FlagValuesDict() |         self._original_flags = FLAGS.FlagValuesDict() | ||||||
|         rpc.ConnectionPool = rpc.Pool(max_size=FLAGS.rpc_conn_pool_size) |         rpc.ConnectionPool = rpc.Pool(max_size=FLAGS.rpc_conn_pool_size) | ||||||
|  |  | ||||||
| @@ -107,7 +105,6 @@ class TestCase(unittest.TestCase): | |||||||
|  |  | ||||||
|             # Reset our monkey-patches |             # Reset our monkey-patches | ||||||
|             rpc.Consumer.attach_to_eventlet = self.original_attach |             rpc.Consumer.attach_to_eventlet = self.original_attach | ||||||
|             wsgi.Server.start = self.original_start |  | ||||||
|  |  | ||||||
|             # Stop any timers |             # Stop any timers | ||||||
|             for x in self.injected: |             for x in self.injected: | ||||||
| @@ -163,26 +160,6 @@ class TestCase(unittest.TestCase): | |||||||
|         _wrapped.func_name = self.original_attach.func_name |         _wrapped.func_name = self.original_attach.func_name | ||||||
|         rpc.Consumer.attach_to_eventlet = _wrapped |         rpc.Consumer.attach_to_eventlet = _wrapped | ||||||
|  |  | ||||||
|     def _monkey_patch_wsgi(self): |  | ||||||
|         """Allow us to kill servers spawned by wsgi.Server.""" |  | ||||||
|         self.original_start = wsgi.Server.start |  | ||||||
|  |  | ||||||
|         @functools.wraps(self.original_start) |  | ||||||
|         def _wrapped_start(inner_self, *args, **kwargs): |  | ||||||
|             original_spawn_n = inner_self.pool.spawn_n |  | ||||||
|  |  | ||||||
|             @functools.wraps(original_spawn_n) |  | ||||||
|             def _wrapped_spawn_n(*args, **kwargs): |  | ||||||
|                 rv = greenthread.spawn(*args, **kwargs) |  | ||||||
|                 self._services.append(rv) |  | ||||||
|  |  | ||||||
|             inner_self.pool.spawn_n = _wrapped_spawn_n |  | ||||||
|             self.original_start(inner_self, *args, **kwargs) |  | ||||||
|             inner_self.pool.spawn_n = original_spawn_n |  | ||||||
|  |  | ||||||
|         _wrapped_start.func_name = self.original_start.func_name |  | ||||||
|         wsgi.Server.start = _wrapped_start |  | ||||||
|  |  | ||||||
|     # Useful assertions |     # Useful assertions | ||||||
|     def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001): |     def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001): | ||||||
|         """Assert two dicts are equivalent. |         """Assert two dicts are equivalent. | ||||||
|   | |||||||
							
								
								
									
										61
									
								
								nova/wsgi.py
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								nova/wsgi.py
									
									
									
									
									
								
							| @@ -43,46 +43,45 @@ FLAGS = flags.FLAGS | |||||||
| LOG = logging.getLogger('nova.wsgi') | LOG = logging.getLogger('nova.wsgi') | ||||||
|  |  | ||||||
|  |  | ||||||
| class WritableLogger(object): |  | ||||||
|     """A thin wrapper that responds to `write` and logs.""" |  | ||||||
|  |  | ||||||
|     def __init__(self, logger, level=logging.DEBUG): |  | ||||||
|         self.logger = logger |  | ||||||
|         self.level = level |  | ||||||
|  |  | ||||||
|     def write(self, msg): |  | ||||||
|         self.logger.log(self.level, msg) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Server(object): | class Server(object): | ||||||
|     """Server class to manage multiple WSGI sockets and applications.""" |     """Server class to manage multiple WSGI sockets and applications.""" | ||||||
|  |  | ||||||
|     def __init__(self, threads=1000): |     default_pool_size = 1000 | ||||||
|         self.pool = eventlet.GreenPool(threads) |     logger_name = "eventlet.wsgi.server" | ||||||
|         self.socket_info = {} |  | ||||||
|  |  | ||||||
|     def start(self, application, port, host='0.0.0.0', key=None, backlog=128): |     def __init__(self, name, app, host, port, pool_size=None): | ||||||
|         """Run a WSGI server with the given application.""" |         self.name = name | ||||||
|         arg0 = sys.argv[0] |         self.app = app | ||||||
|         logging.audit(_('Starting %(arg0)s on %(host)s:%(port)s') % locals()) |         self.host = host | ||||||
|         socket = eventlet.listen((host, port), backlog=backlog) |         self.port = port | ||||||
|         self.pool.spawn_n(self._run, application, socket) |         self._pool = eventlet.GreenPool(pool_size or self.default_pool_size) | ||||||
|         if key: |         self._log = logging.WritableLogger(logging.getLogger(self.logger_name)) | ||||||
|             self.socket_info[key] = socket.getsockname() |  | ||||||
|  |     def _start(self, socket): | ||||||
|  |         """Blocking eventlet WSGI server launched from the real 'start'.""" | ||||||
|  |         eventlet.wsgi.server(socket, | ||||||
|  |                              self.app, | ||||||
|  |                              custom_pool=self._pool, | ||||||
|  |                              log=self._log) | ||||||
|  |  | ||||||
|  |     def start(self, backlog=128): | ||||||
|  |         """Serve given WSGI application using the given parameters.""" | ||||||
|  |         socket = eventlet.listen((self.host, self.port), backlog=backlog) | ||||||
|  |         self._server = eventlet.spawn(self._start, socket) | ||||||
|  |         (self.host, self.port) = socket.getsockname() | ||||||
|  |         LOG.info(_('Starting %(app)s on %(host)s:%(port)s') % self.__dict__) | ||||||
|  |  | ||||||
|  |     def stop(self): | ||||||
|  |         """Stop this server by killing the greenthread running it.""" | ||||||
|  |         self._server.kill() | ||||||
|  |  | ||||||
|     def wait(self): |     def wait(self): | ||||||
|         """Wait until all servers have completed running.""" |         """Wait until server has been stopped.""" | ||||||
|         try: |         try: | ||||||
|             self.pool.waitall() |             self._server.wait() | ||||||
|         except KeyboardInterrupt: |         except KeyboardInterrupt: | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|     def _run(self, application, socket): |  | ||||||
|         """Start a WSGI server in a new green thread.""" |  | ||||||
|         logger = logging.getLogger('eventlet.wsgi.server') |  | ||||||
|         eventlet.wsgi.server(socket, application, custom_pool=self.pool, |  | ||||||
|                              log=WritableLogger(logger)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Request(webob.Request): | class Request(webob.Request): | ||||||
|     pass |     pass | ||||||
| @@ -340,6 +339,8 @@ def paste_config_file(basename): | |||||||
|         if os.path.exists(configfile): |         if os.path.exists(configfile): | ||||||
|             return configfile |             return configfile | ||||||
|  |  | ||||||
|  |     raise Exception(_("Unable to find paste.deploy config '%s'") % basename) | ||||||
|  |  | ||||||
|  |  | ||||||
| def load_paste_configuration(filename, appname): | def load_paste_configuration(filename, appname): | ||||||
|     """Returns a paste configuration dict, or None.""" |     """Returns a paste configuration dict, or None.""" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Brian Lamar
					Brian Lamar