diff options
Diffstat (limited to 'keystone-moon/keystone/common/environment')
-rw-r--r-- | keystone-moon/keystone/common/environment/__init__.py | 100 | ||||
-rw-r--r-- | keystone-moon/keystone/common/environment/eventlet_server.py | 194 |
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 |