summaryrefslogtreecommitdiffstats
path: root/keystone/common/environment
diff options
context:
space:
mode:
authorJamie Lennox <jlennox@redhat.com>2013-05-30 17:48:04 +1000
committerAdam Young <ayoung@redhat.com>2013-06-18 14:10:36 -0400
commit3afd9791ef3e2472987cdabb54ef5f27a062469c (patch)
treee4bb40f0431bd3061a9fc6ea66f8df5aa6be067e /keystone/common/environment
parenta012186bb68c5514dda87f1e045983a3c2e5b236 (diff)
downloadkeystone-3afd9791ef3e2472987cdabb54ef5f27a062469c.tar.gz
keystone-3afd9791ef3e2472987cdabb54ef5f27a062469c.tar.xz
keystone-3afd9791ef3e2472987cdabb54ef5f27a062469c.zip
Isolate eventlet code into environment.
The environment module will be configured once, during code initialization. Subsequently all other possibly-evented modules will retrieve from environment and transparently obtain either the eventlet or standard library modules. If eventlet, httplib, subprocess or other environment dependant module is referenced outside of the environment module it should be considered a bug. The changes to tests are required to ensure that test is imported first to setup the environment. Hopefully these can all be replaced with an __init__.py in a post-nose keystone. Implements: blueprint extract-eventlet Change-Id: Icacd6f2ee0906ac5d303777c1f87a184f38283bf
Diffstat (limited to 'keystone/common/environment')
-rw-r--r--keystone/common/environment/__init__.py78
-rw-r--r--keystone/common/environment/eventlet_server.py114
2 files changed, 192 insertions, 0 deletions
diff --git a/keystone/common/environment/__init__.py b/keystone/common/environment/__init__.py
new file mode 100644
index 00000000..ac93e24f
--- /dev/null
+++ b/keystone/common/environment/__init__.py
@@ -0,0 +1,78 @@
+import functools
+import os
+
+from keystone.common import config
+from keystone.common import logging
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+__all__ = ['Server', 'httplib', 'subprocess']
+
+_configured = False
+
+Server = None
+httplib = None
+subprocess = None
+
+
+def configure_once(name):
+ """Ensure that environment configuration is only run once.
+
+ If environment is reconfigured in the same way then it is ignored.
+ It is an error to attempt to reconfigure environment in a different way.
+ """
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ global _configured
+ if _configured:
+ if _configured == name:
+ return
+ else:
+ raise SystemError("Environment has already been "
+ "configured as %s" % _configured)
+
+ LOG.info(_("Environment configured as: %s"), name)
+ _configured = name
+ return func(*args, **kwargs)
+
+ return wrapper
+ return decorator
+
+
+@configure_once('eventlet')
+def use_eventlet(monkeypatch_thread=None):
+ global httplib, subprocess, Server
+
+ # This must be set before the initial import of eventlet because if
+ # dnspython is present in your environment then eventlet monkeypatches
+ # socket.getaddrinfo() with an implementation which doesn't work for IPv6.
+ os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
+
+ import eventlet
+ from eventlet.green import httplib as _httplib
+ from eventlet.green import subprocess as _subprocess
+ from keystone.common.environment import eventlet_server
+
+ if monkeypatch_thread is None:
+ monkeypatch_thread = not os.getenv('STANDARD_THREADS')
+
+ eventlet.patcher.monkey_patch(all=False, socket=True, time=True,
+ thread=monkeypatch_thread)
+
+ Server = eventlet_server.Server
+ httplib = _httplib
+ subprocess = _subprocess
+
+
+@configure_once('stdlib')
+def use_stdlib():
+ global httplib, subprocess
+
+ import httplib as _httplib
+ import subprocess as _subprocess
+
+ httplib = _httplib
+ subprocess = _subprocess
diff --git a/keystone/common/environment/eventlet_server.py b/keystone/common/environment/eventlet_server.py
new file mode 100644
index 00000000..18987d26
--- /dev/null
+++ b/keystone/common/environment/eventlet_server.py
@@ -0,0 +1,114 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2010 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import socket
+import ssl
+import sys
+
+import eventlet
+import eventlet.wsgi
+import greenlet
+
+from keystone.common import logging
+from keystone.common import wsgi
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Server(object):
+ """Server class to manage multiple WSGI sockets and applications."""
+
+ def __init__(self, application, host=None, port=None, threads=1000):
+ self.application = application
+ self.host = host or '0.0.0.0'
+ self.port = port or 0
+ self.pool = eventlet.GreenPool(threads)
+ self.socket_info = {}
+ self.greenthread = None
+ self.do_ssl = False
+ self.cert_required = False
+
+ def start(self, key=None, backlog=128):
+ """Run a WSGI server with the given application."""
+ LOG.debug(_('Starting %(arg0)s on %(host)s:%(port)s') %
+ {'arg0': sys.argv[0],
+ 'host': self.host,
+ 'port': self.port})
+
+ # TODO(dims): eventlet's green dns/socket module does not actually
+ # support IPv6 in getaddrinfo(). We need to get around this in the
+ # future or monitor upstream for a fix
+ info = socket.getaddrinfo(self.host,
+ self.port,
+ socket.AF_UNSPEC,
+ socket.SOCK_STREAM)[0]
+ _socket = eventlet.listen(info[-1],
+ family=info[0],
+ backlog=backlog)
+ if key:
+ self.socket_info[key] = _socket.getsockname()
+ # SSL is enabled
+ if self.do_ssl:
+ if self.cert_required:
+ cert_reqs = ssl.CERT_REQUIRED
+ else:
+ cert_reqs = ssl.CERT_NONE
+ sslsocket = eventlet.wrap_ssl(_socket, certfile=self.certfile,
+ keyfile=self.keyfile,
+ server_side=True,
+ cert_reqs=cert_reqs,
+ ca_certs=self.ca_certs)
+ _socket = sslsocket
+
+ self.greenthread = self.pool.spawn(self._run,
+ self.application,
+ _socket)
+
+ def set_ssl(self, certfile, keyfile=None, ca_certs=None,
+ cert_required=True):
+ self.certfile = certfile
+ self.keyfile = keyfile
+ self.ca_certs = ca_certs
+ self.cert_required = cert_required
+ self.do_ssl = True
+
+ def kill(self):
+ if self.greenthread:
+ self.greenthread.kill()
+
+ def wait(self):
+ """Wait until all servers have completed running."""
+ try:
+ self.pool.waitall()
+ except KeyboardInterrupt:
+ pass
+ except greenlet.GreenletExit:
+ pass
+
+ def _run(self, application, socket):
+ """Start a WSGI server in a new green thread."""
+ log = logging.getLogger('eventlet.wsgi.server')
+ try:
+ eventlet.wsgi.server(socket, application, custom_pool=self.pool,
+ log=wsgi.WritableLogger(log))
+ except Exception:
+ LOG.exception(_('Server error'))
+ raise