summaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/common/environment
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/common/environment')
-rw-r--r--keystone-moon/keystone/common/environment/__init__.py100
-rw-r--r--keystone-moon/keystone/common/environment/eventlet_server.py194
2 files changed, 294 insertions, 0 deletions
diff --git a/keystone-moon/keystone/common/environment/__init__.py b/keystone-moon/keystone/common/environment/__init__.py
new file mode 100644
index 00000000..da1de890
--- /dev/null
+++ b/keystone-moon/keystone/common/environment/__init__.py
@@ -0,0 +1,100 @@
+# Copyright 2013 OpenStack Foundation
+#
+# 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 functools
+import os
+
+from oslo_log import log
+
+LOG = log.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.debug("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')
+
+ # Raise the default from 8192 to accommodate large tokens
+ eventlet.wsgi.MAX_HEADER_LINE = 16384
+
+ # NOTE(ldbragst): Explicitly declare what should be monkey patched and
+ # what shouldn't. Doing this allows for more readable code when
+ # understanding Eventlet in Keystone. The following is a complete list
+ # of what is monkey patched instead of passing all=False and then passing
+ # module=True to monkey patch a specific module.
+ eventlet.patcher.monkey_patch(os=False, select=True, socket=True,
+ thread=monkeypatch_thread, time=True,
+ psycopg=False, MySQLdb=False)
+
+ 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-moon/keystone/common/environment/eventlet_server.py b/keystone-moon/keystone/common/environment/eventlet_server.py
new file mode 100644
index 00000000..639e074a
--- /dev/null
+++ b/keystone-moon/keystone/common/environment/eventlet_server.py
@@ -0,0 +1,194 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2010 OpenStack Foundation
+# 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 errno
+import re
+import socket
+import ssl
+import sys
+
+import eventlet
+import eventlet.wsgi
+import greenlet
+from oslo_log import log
+from oslo_log import loggers
+
+from keystone.i18n import _LE, _LI
+
+
+LOG = log.getLogger(__name__)
+
+# The size of a pool that is used to spawn a single green thread in which
+# a wsgi server is then started. The size of one is enough, because in case
+# of several workers the parent process forks and each child gets a copy
+# of a pool, which does not include any greenthread object as the spawn is
+# done after the fork.
+POOL_SIZE = 1
+
+
+class EventletFilteringLogger(loggers.WritableLogger):
+ # NOTE(morganfainberg): This logger is designed to filter out specific
+ # Tracebacks to limit the amount of data that eventlet can log. In the
+ # case of broken sockets (EPIPE and ECONNRESET), we are seeing a huge
+ # volume of data being written to the logs due to ~14 lines+ per traceback.
+ # The traceback in these cases are, at best, useful for limited debugging
+ # cases.
+ def __init__(self, *args, **kwargs):
+ super(EventletFilteringLogger, self).__init__(*args, **kwargs)
+ self.regex = re.compile(r'errno (%d|%d)' %
+ (errno.EPIPE, errno.ECONNRESET), re.IGNORECASE)
+
+ def write(self, msg):
+ m = self.regex.search(msg)
+ if m:
+ self.logger.log(log.logging.DEBUG, 'Error(%s) writing to socket.',
+ m.group(1))
+ else:
+ self.logger.log(self.level, msg.rstrip())
+
+
+class Server(object):
+ """Server class to manage multiple WSGI sockets and applications."""
+
+ def __init__(self, application, host=None, port=None, keepalive=False,
+ keepidle=None):
+ self.application = application
+ self.host = host or '0.0.0.0'
+ self.port = port or 0
+ # Pool for a green thread in which wsgi server will be running
+ self.pool = eventlet.GreenPool(POOL_SIZE)
+ self.socket_info = {}
+ self.greenthread = None
+ self.do_ssl = False
+ self.cert_required = False
+ self.keepalive = keepalive
+ self.keepidle = keepidle
+ self.socket = None
+
+ def listen(self, key=None, backlog=128):
+ """Create and start listening on socket.
+
+ Call before forking worker processes.
+
+ Raises Exception if this has already been called.
+ """
+
+ # 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.
+ # Please refer below link
+ # (https://bitbucket.org/eventlet/eventlet/
+ # src/e0f578180d7d82d2ed3d8a96d520103503c524ec/eventlet/support/
+ # greendns.py?at=0.12#cl-163)
+ info = socket.getaddrinfo(self.host,
+ self.port,
+ socket.AF_UNSPEC,
+ socket.SOCK_STREAM)[0]
+
+ try:
+ self.socket = eventlet.listen(info[-1], family=info[0],
+ backlog=backlog)
+ except EnvironmentError:
+ LOG.error(_LE("Could not bind to %(host)s:%(port)s"),
+ {'host': self.host, 'port': self.port})
+ raise
+
+ LOG.info(_LI('Starting %(arg0)s on %(host)s:%(port)s'),
+ {'arg0': sys.argv[0],
+ 'host': self.host,
+ 'port': self.port})
+
+ def start(self, key=None, backlog=128):
+ """Run a WSGI server with the given application."""
+
+ if self.socket is None:
+ self.listen(key=key, backlog=backlog)
+
+ dup_socket = self.socket.dup()
+ if key:
+ self.socket_info[key] = self.socket.getsockname()
+ # SSL is enabled
+ if self.do_ssl:
+ if self.cert_required:
+ cert_reqs = ssl.CERT_REQUIRED
+ else:
+ cert_reqs = ssl.CERT_NONE
+
+ dup_socket = eventlet.wrap_ssl(dup_socket, certfile=self.certfile,
+ keyfile=self.keyfile,
+ server_side=True,
+ cert_reqs=cert_reqs,
+ ca_certs=self.ca_certs)
+
+ # Optionally enable keepalive on the wsgi socket.
+ if self.keepalive:
+ dup_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+
+ if self.keepidle is not None:
+ dup_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
+ self.keepidle)
+
+ self.greenthread = self.pool.spawn(self._run,
+ self.application,
+ dup_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 stop(self):
+ if self.greenthread is not None:
+ 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 reset(self):
+ """Required by the service interface.
+
+ The service interface is used by the launcher when receiving a
+ SIGHUP. The service interface is defined in
+ keystone.openstack.common.service.Service.
+
+ Keystone does not need to do anything here.
+ """
+ pass
+
+ def _run(self, application, socket):
+ """Start a WSGI server with a new green thread pool."""
+ logger = log.getLogger('eventlet.wsgi.server')
+ try:
+ eventlet.wsgi.server(socket, application,
+ log=EventletFilteringLogger(logger),
+ debug=False)
+ except greenlet.GreenletExit:
+ # Wait until all servers have completed running
+ pass
+ except Exception:
+ LOG.exception(_LE('Server error'))
+ raise