"Fossies" - the Fresh Open Source Software Archive

Member "glance-20.0.1/glance/common/wsgi.py" (12 Aug 2020, 49177 Bytes) of package /linux/misc/openstack/glance-20.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "wsgi.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 20.0.0_vs_20.0.1.

    1 # Copyright 2010 United States Government as represented by the
    2 # Administrator of the National Aeronautics and Space Administration.
    3 # Copyright 2010 OpenStack Foundation
    4 # Copyright 2014 IBM Corp.
    5 # All Rights Reserved.
    6 #
    7 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    8 #    not use this file except in compliance with the License. You may obtain
    9 #    a copy of the License at
   10 #
   11 #         http://www.apache.org/licenses/LICENSE-2.0
   12 #
   13 #    Unless required by applicable law or agreed to in writing, software
   14 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   15 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   16 #    License for the specific language governing permissions and limitations
   17 #    under the License.
   18 
   19 """
   20 Utility methods for working with WSGI servers
   21 """
   22 from __future__ import print_function
   23 
   24 import abc
   25 import errno
   26 import functools
   27 import os
   28 import re
   29 import signal
   30 import struct
   31 import subprocess
   32 import sys
   33 import threading
   34 import time
   35 
   36 from eventlet.green import socket
   37 import eventlet.greenio
   38 import eventlet.wsgi
   39 import glance_store
   40 from os_win import utilsfactory as os_win_utilsfactory
   41 from oslo_concurrency import processutils
   42 from oslo_config import cfg
   43 from oslo_log import log as logging
   44 from oslo_serialization import jsonutils
   45 from oslo_utils import encodeutils
   46 from oslo_utils import strutils
   47 from osprofiler import opts as profiler_opts
   48 import routes.middleware
   49 import six
   50 import webob.dec
   51 import webob.exc
   52 from webob import multidict
   53 
   54 from glance.common import config
   55 from glance.common import exception
   56 from glance.common import store_utils
   57 from glance.common import utils
   58 from glance import i18n
   59 from glance.i18n import _, _LE, _LI, _LW
   60 
   61 
   62 bind_opts = [
   63     cfg.HostAddressOpt('bind_host',
   64                        default='0.0.0.0',
   65                        help=_("""
   66 IP address to bind the glance servers to.
   67 
   68 Provide an IP address to bind the glance server to. The default
   69 value is ``0.0.0.0``.
   70 
   71 Edit this option to enable the server to listen on one particular
   72 IP address on the network card. This facilitates selection of a
   73 particular network interface for the server.
   74 
   75 Possible values:
   76     * A valid IPv4 address
   77     * A valid IPv6 address
   78 
   79 Related options:
   80     * None
   81 
   82 """)),
   83 
   84     cfg.PortOpt('bind_port',
   85                 help=_("""
   86 Port number on which the server will listen.
   87 
   88 Provide a valid port number to bind the server's socket to. This
   89 port is then set to identify processes and forward network messages
   90 that arrive at the server. The default bind_port value for the API
   91 server is 9292 and for the registry server is 9191.
   92 
   93 Possible values:
   94     * A valid port number (0 to 65535)
   95 
   96 Related options:
   97     * None
   98 
   99 """)),
  100 ]
  101 
  102 socket_opts = [
  103     cfg.IntOpt('backlog',
  104                default=4096,
  105                min=1,
  106                help=_("""
  107 Set the number of incoming connection requests.
  108 
  109 Provide a positive integer value to limit the number of requests in
  110 the backlog queue. The default queue size is 4096.
  111 
  112 An incoming connection to a TCP listener socket is queued before a
  113 connection can be established with the server. Setting the backlog
  114 for a TCP socket ensures a limited queue size for incoming traffic.
  115 
  116 Possible values:
  117     * Positive integer
  118 
  119 Related options:
  120     * None
  121 
  122 """)),
  123 
  124     cfg.IntOpt('tcp_keepidle',
  125                default=600,
  126                min=1,
  127                help=_("""
  128 Set the wait time before a connection recheck.
  129 
  130 Provide a positive integer value representing time in seconds which
  131 is set as the idle wait time before a TCP keep alive packet can be
  132 sent to the host. The default value is 600 seconds.
  133 
  134 Setting ``tcp_keepidle`` helps verify at regular intervals that a
  135 connection is intact and prevents frequent TCP connection
  136 reestablishment.
  137 
  138 Possible values:
  139     * Positive integer value representing time in seconds
  140 
  141 Related options:
  142     * None
  143 
  144 """)),
  145 ]
  146 
  147 eventlet_opts = [
  148     cfg.IntOpt('workers',
  149                min=0,
  150                help=_("""
  151 Number of Glance worker processes to start.
  152 
  153 Provide a non-negative integer value to set the number of child
  154 process workers to service requests. By default, the number of CPUs
  155 available is set as the value for ``workers`` limited to 8. For
  156 example if the processor count is 6, 6 workers will be used, if the
  157 processor count is 24 only 8 workers will be used. The limit will only
  158 apply to the default value, if 24 workers is configured, 24 is used.
  159 
  160 Each worker process is made to listen on the port set in the
  161 configuration file and contains a greenthread pool of size 1000.
  162 
  163 NOTE: Setting the number of workers to zero, triggers the creation
  164 of a single API process with a greenthread pool of size 1000.
  165 
  166 Possible values:
  167     * 0
  168     * Positive integer value (typically equal to the number of CPUs)
  169 
  170 Related options:
  171     * None
  172 
  173 """)),
  174 
  175     cfg.IntOpt('max_header_line',
  176                default=16384,
  177                min=0,
  178                help=_("""
  179 Maximum line size of message headers.
  180 
  181 Provide an integer value representing a length to limit the size of
  182 message headers. The default value is 16384.
  183 
  184 NOTE: ``max_header_line`` may need to be increased when using large
  185 tokens (typically those generated by the Keystone v3 API with big
  186 service catalogs). However, it is to be kept in mind that larger
  187 values for ``max_header_line`` would flood the logs.
  188 
  189 Setting ``max_header_line`` to 0 sets no limit for the line size of
  190 message headers.
  191 
  192 Possible values:
  193     * 0
  194     * Positive integer
  195 
  196 Related options:
  197     * None
  198 
  199 """)),
  200 
  201     cfg.BoolOpt('http_keepalive',
  202                 default=True,
  203                 help=_("""
  204 Set keep alive option for HTTP over TCP.
  205 
  206 Provide a boolean value to determine sending of keep alive packets.
  207 If set to ``False``, the server returns the header
  208 "Connection: close". If set to ``True``, the server returns a
  209 "Connection: Keep-Alive" in its responses. This enables retention of
  210 the same TCP connection for HTTP conversations instead of opening a
  211 new one with each new request.
  212 
  213 This option must be set to ``False`` if the client socket connection
  214 needs to be closed explicitly after the response is received and
  215 read successfully by the client.
  216 
  217 Possible values:
  218     * True
  219     * False
  220 
  221 Related options:
  222     * None
  223 
  224 """)),
  225 
  226     cfg.IntOpt('client_socket_timeout',
  227                default=900,
  228                min=0,
  229                help=_("""
  230 Timeout for client connections' socket operations.
  231 
  232 Provide a valid integer value representing time in seconds to set
  233 the period of wait before an incoming connection can be closed. The
  234 default value is 900 seconds.
  235 
  236 The value zero implies wait forever.
  237 
  238 Possible values:
  239     * Zero
  240     * Positive integer
  241 
  242 Related options:
  243     * None
  244 
  245 """)),
  246 ]
  247 
  248 wsgi_opts = [
  249     cfg.StrOpt('secure_proxy_ssl_header',
  250                deprecated_for_removal=True,
  251                deprecated_reason=_('Use the http_proxy_to_wsgi middleware '
  252                                    'instead.'),
  253                help=_('The HTTP header used to determine the scheme for the '
  254                       'original request, even if it was removed by an SSL '
  255                       'terminating proxy. Typical value is '
  256                       '"HTTP_X_FORWARDED_PROTO".')),
  257 ]
  258 
  259 store_opts = [
  260     cfg.DictOpt('enabled_backends',
  261                 help=_('Key:Value pair of store identifier and store type. '
  262                        'In case of multiple backends should be separated '
  263                        'using comma.')),
  264 ]
  265 
  266 cli_opts = [
  267     cfg.StrOpt('pipe-handle',
  268                help='This argument is used internally on Windows. Glance '
  269                     'passes a pipe handle to child processes, which is then '
  270                     'used for inter-process communication.'),
  271 ]
  272 
  273 cache_opts = [
  274     cfg.FloatOpt('cache_prefetcher_interval',
  275                  default=300,
  276                  help=_("""
  277 The interval in seconds to run periodic job cache_images.
  278 
  279 The cache_images method will fetch all images which are in queued state
  280 for caching in cache directory. The default value is 300.
  281 
  282 Possible values:
  283     * Positive integer
  284 
  285 Related options:
  286     * None
  287 """))
  288 ]
  289 
  290 LOG = logging.getLogger(__name__)
  291 
  292 CONF = cfg.CONF
  293 CONF.register_opts(bind_opts)
  294 CONF.register_opts(socket_opts)
  295 CONF.register_opts(eventlet_opts)
  296 CONF.register_opts(wsgi_opts)
  297 CONF.register_opts(store_opts)
  298 CONF.register_opts(cache_opts)
  299 profiler_opts.set_defaults(CONF)
  300 
  301 ASYNC_EVENTLET_THREAD_POOL_LIST = []
  302 
  303 # Detect if we're running under the uwsgi server
  304 try:
  305     import uwsgi
  306     LOG.debug('Detected running under uwsgi')
  307 except ImportError:
  308     LOG.debug('Detected not running under uwsgi')
  309     uwsgi = None
  310 
  311 # Reserved file stores for staging and tasks operations
  312 RESERVED_STORES = {
  313     'os_glance_staging_store': 'file',
  314     'os_glance_tasks_store': 'file'
  315 }
  316 
  317 
  318 def register_cli_opts():
  319     CONF.register_cli_opts(cli_opts)
  320 
  321 
  322 def get_num_workers():
  323     """Return the configured number of workers."""
  324 
  325     # Windows only: we're already running on the worker side.
  326     if os.name == 'nt' and getattr(CONF, 'pipe_handle', None):
  327         return 0
  328 
  329     if CONF.workers is None:
  330         # None implies the number of CPUs limited to 8
  331         # See Launchpad bug #1748916 and the config help text
  332         workers = processutils.get_worker_count()
  333         return workers if workers < 8 else 8
  334     return CONF.workers
  335 
  336 
  337 def get_bind_addr(default_port=None):
  338     """Return the host and port to bind to."""
  339     return (CONF.bind_host, CONF.bind_port or default_port)
  340 
  341 
  342 def get_socket(default_port):
  343     """
  344     Bind socket to bind ip:port in conf
  345 
  346     note: Mostly comes from Swift with a few small changes...
  347 
  348     :param default_port: port to bind to if none is specified in conf
  349 
  350     :returns: a socket object as returned from socket.listen
  351     """
  352     bind_addr = get_bind_addr(default_port)
  353 
  354     # TODO(jaypipes): eventlet's greened socket module does not actually
  355     # support IPv6 in getaddrinfo(). We need to get around this in the
  356     # future or monitor upstream for a fix
  357     address_family = [
  358         addr[0] for addr in socket.getaddrinfo(bind_addr[0],
  359                                                bind_addr[1],
  360                                                socket.AF_UNSPEC,
  361                                                socket.SOCK_STREAM)
  362         if addr[0] in (socket.AF_INET, socket.AF_INET6)
  363     ][0]
  364 
  365     sock = utils.get_test_suite_socket()
  366     retry_until = time.time() + 30
  367 
  368     while not sock and time.time() < retry_until:
  369         try:
  370             sock = eventlet.listen(bind_addr,
  371                                    backlog=CONF.backlog,
  372                                    family=address_family)
  373         except socket.error as err:
  374             if err.args[0] != errno.EADDRINUSE:
  375                 raise
  376             eventlet.sleep(0.1)
  377     if not sock:
  378         raise RuntimeError(_("Could not bind to %(host)s:%(port)s after"
  379                              " trying for 30 seconds") %
  380                            {'host': bind_addr[0],
  381                             'port': bind_addr[1]})
  382 
  383     return sock
  384 
  385 
  386 def set_eventlet_hub():
  387     try:
  388         eventlet.hubs.use_hub('poll')
  389     except Exception:
  390         try:
  391             eventlet.hubs.use_hub('selects')
  392         except Exception:
  393             msg = _("eventlet 'poll' nor 'selects' hubs are available "
  394                     "on this platform")
  395             raise exception.WorkerCreationFailure(
  396                 reason=msg)
  397 
  398 
  399 def initialize_glance_store():
  400     """Initialize glance store."""
  401     glance_store.register_opts(CONF)
  402     glance_store.create_stores(CONF)
  403     glance_store.verify_default_store()
  404 
  405 
  406 def initialize_multi_store():
  407     """Initialize glance multi store backends."""
  408     glance_store.register_store_opts(CONF, reserved_stores=RESERVED_STORES)
  409     glance_store.create_multi_stores(CONF, reserved_stores=RESERVED_STORES)
  410     glance_store.verify_store()
  411 
  412 
  413 def get_asynchronous_eventlet_pool(size=1000):
  414     """Return eventlet pool to caller.
  415 
  416     Also store pools created in global list, to wait on
  417     it after getting signal for graceful shutdown.
  418 
  419     :param size: eventlet pool size
  420     :returns: eventlet pool
  421     """
  422     global ASYNC_EVENTLET_THREAD_POOL_LIST
  423 
  424     pool = eventlet.GreenPool(size=size)
  425     # Add pool to global ASYNC_EVENTLET_THREAD_POOL_LIST
  426     ASYNC_EVENTLET_THREAD_POOL_LIST.append(pool)
  427 
  428     return pool
  429 
  430 
  431 @six.add_metaclass(abc.ABCMeta)
  432 class BaseServer(object):
  433     """Server class to manage multiple WSGI sockets and applications.
  434 
  435     This class requires initialize_glance_store set to True if
  436     glance store needs to be initialized.
  437     """
  438     def __init__(self, threads=1000, initialize_glance_store=False,
  439                  initialize_prefetcher=False):
  440         os.umask(0o27)  # ensure files are created with the correct privileges
  441         self._logger = logging.getLogger("eventlet.wsgi.server")
  442         self.threads = threads
  443         self.children = set()
  444         self.stale_children = set()
  445         self.running = True
  446         # NOTE(abhishek): Allows us to only re-initialize glance_store when
  447         # the API's configuration reloads.
  448         self.initialize_glance_store = initialize_glance_store
  449         self.initialize_prefetcher = initialize_prefetcher
  450         if self.initialize_prefetcher:
  451             # NOTE(abhishekk): Importing the prefetcher just in time to avoid
  452             # import loop during initialization
  453             from glance.image_cache import prefetcher # noqa
  454             self.prefetcher = prefetcher.Prefetcher()
  455 
  456     def cache_images(self):
  457         # After every 'cache_prefetcher_interval' this call will run and fetch
  458         # all queued images into cache if there are any
  459         cache_thread = threading.Timer(CONF.cache_prefetcher_interval,
  460                                        self.cache_images)
  461         cache_thread.daemon = True
  462         cache_thread.start()
  463         self.prefetcher.run()
  464 
  465     @staticmethod
  466     def set_signal_handler(signal_name, handler):
  467         # Some signals may not be available on this platform.
  468         sig = getattr(signal, signal_name, None)
  469         if sig is not None:
  470             signal.signal(sig, handler)
  471 
  472     def hup(self, *args):
  473         """
  474         Reloads configuration files with zero down time
  475         """
  476         self.set_signal_handler("SIGHUP", signal.SIG_IGN)
  477         raise exception.SIGHUPInterrupt
  478 
  479     @abc.abstractmethod
  480     def kill_children(self, *args):
  481         pass
  482 
  483     @abc.abstractmethod
  484     def wait_on_children(self):
  485         pass
  486 
  487     @abc.abstractmethod
  488     def run_child(self):
  489         pass
  490 
  491     def reload(self):
  492         raise NotImplementedError()
  493 
  494     def start(self, application, default_port):
  495         """
  496         Run a WSGI server with the given application.
  497 
  498         :param application: The application to be run in the WSGI server
  499         :param default_port: Port to bind to if none is specified in conf
  500         """
  501         self.application = application
  502         self.default_port = default_port
  503         self.configure()
  504         self.start_wsgi()
  505         if self.initialize_prefetcher:
  506             self.cache_images()
  507 
  508     def start_wsgi(self):
  509         workers = get_num_workers()
  510         if workers == 0:
  511             # Useful for profiling, test, debug etc.
  512             self.pool = self.create_pool()
  513             self.pool.spawn_n(self._single_run, self.application, self.sock)
  514             return
  515         else:
  516             LOG.info(_LI("Starting %d workers"), workers)
  517             self.set_signal_handler("SIGTERM", self.kill_children)
  518             self.set_signal_handler("SIGINT", self.kill_children)
  519             self.set_signal_handler("SIGHUP", self.hup)
  520             while len(self.children) < workers:
  521                 self.run_child()
  522 
  523     def create_pool(self):
  524         return get_asynchronous_eventlet_pool(size=self.threads)
  525 
  526     def configure(self, old_conf=None, has_changed=None):
  527         """
  528         Apply configuration settings
  529 
  530         :param old_conf: Cached old configuration settings (if any)
  531         :param has changed: callable to determine if a parameter has changed
  532         """
  533         eventlet.wsgi.MAX_HEADER_LINE = CONF.max_header_line
  534         self.client_socket_timeout = CONF.client_socket_timeout or None
  535         if self.initialize_glance_store:
  536             if CONF.enabled_backends:
  537                 if store_utils.check_reserved_stores(CONF.enabled_backends):
  538                     msg = _("'os_glance_' prefix should not be used in "
  539                             "enabled_backends config option. It is reserved "
  540                             "for internal use only.")
  541                     raise RuntimeError(msg)
  542                 initialize_multi_store()
  543             else:
  544                 initialize_glance_store()
  545         self.configure_socket(old_conf, has_changed)
  546 
  547     def wait(self):
  548         """Wait until all servers have completed running."""
  549         try:
  550             if self.children:
  551                 self.wait_on_children()
  552             else:
  553                 self.pool.waitall()
  554         except KeyboardInterrupt:
  555             pass
  556 
  557     def run_server(self):
  558         """Run a WSGI server."""
  559         if cfg.CONF.pydev_worker_debug_host:
  560             utils.setup_remote_pydev_debug(cfg.CONF.pydev_worker_debug_host,
  561                                            cfg.CONF.pydev_worker_debug_port)
  562 
  563         eventlet.wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
  564         self.pool = self.create_pool()
  565         try:
  566             eventlet.wsgi.server(self.sock,
  567                                  self.application,
  568                                  log=self._logger,
  569                                  custom_pool=self.pool,
  570                                  debug=False,
  571                                  keepalive=CONF.http_keepalive,
  572                                  socket_timeout=self.client_socket_timeout)
  573         except socket.error as err:
  574             if err[0] != errno.EINVAL:
  575                 raise
  576 
  577         # waiting on async pools
  578         if ASYNC_EVENTLET_THREAD_POOL_LIST:
  579             for pool in ASYNC_EVENTLET_THREAD_POOL_LIST:
  580                 pool.waitall()
  581 
  582     def _single_run(self, application, sock):
  583         """Start a WSGI server in a new green thread."""
  584         LOG.info(_LI("Starting single process server"))
  585         eventlet.wsgi.server(sock, application, custom_pool=self.pool,
  586                              log=self._logger,
  587                              debug=False,
  588                              keepalive=CONF.http_keepalive,
  589                              socket_timeout=self.client_socket_timeout)
  590 
  591     def configure_socket(self, old_conf=None, has_changed=None):
  592         """
  593         Ensure a socket exists and is appropriately configured.
  594 
  595         This function is called on start up, and can also be
  596         called in the event of a configuration reload.
  597 
  598         When called for the first time a new socket is created.
  599         If reloading and either bind_host or bind port have been
  600         changed the existing socket must be closed and a new
  601         socket opened (laws of physics).
  602 
  603         In all other cases (bind_host/bind_port have not changed)
  604         the existing socket is reused.
  605 
  606         :param old_conf: Cached old configuration settings (if any)
  607         :param has changed: callable to determine if a parameter has changed
  608         """
  609         # Do we need a fresh socket?
  610         new_sock = (old_conf is None or (
  611                     has_changed('bind_host') or
  612                     has_changed('bind_port')))
  613 
  614         if new_sock:
  615             self._sock = None
  616             if old_conf is not None:
  617                 self.sock.close()
  618             _sock = get_socket(self.default_port)
  619             _sock.setsockopt(socket.SOL_SOCKET,
  620                              socket.SO_REUSEADDR, 1)
  621             # sockets can hang around forever without keepalive
  622             _sock.setsockopt(socket.SOL_SOCKET,
  623                              socket.SO_KEEPALIVE, 1)
  624             self.sock = _sock
  625 
  626         if new_sock or (old_conf is not None and has_changed('tcp_keepidle')):
  627             # This option isn't available in the OS X version of eventlet
  628             if hasattr(socket, 'TCP_KEEPIDLE'):
  629                 self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
  630                                      CONF.tcp_keepidle)
  631 
  632         if old_conf is not None and has_changed('backlog'):
  633             self.sock.listen(CONF.backlog)
  634 
  635 
  636 class PosixServer(BaseServer):
  637     def __init__(self, *args, **kwargs):
  638         super(PosixServer, self).__init__(*args, **kwargs)
  639 
  640         self.pgid = os.getpid()
  641         try:
  642             # NOTE(flaper87): Make sure this process
  643             # runs in its own process group.
  644             os.setpgid(self.pgid, self.pgid)
  645         except OSError:
  646             # NOTE(flaper87): When running glance-control,
  647             # (glance's functional tests, for example)
  648             # setpgid fails with EPERM as glance-control
  649             # creates a fresh session, of which the newly
  650             # launched service becomes the leader (session
  651             # leaders may not change process groups)
  652             #
  653             # Running glance-(api|registry) is safe and
  654             # shouldn't raise any error here.
  655             self.pgid = 0
  656 
  657     def kill_children(self, *args):
  658         """Kills the entire process group."""
  659         self.set_signal_handler("SIGTERM", signal.SIG_IGN)
  660         self.set_signal_handler("SIGINT", signal.SIG_IGN)
  661         self.set_signal_handler("SIGCHLD", signal.SIG_IGN)
  662         self.running = False
  663         os.killpg(self.pgid, signal.SIGTERM)
  664 
  665     def _remove_children(self, pid):
  666         if pid in self.children:
  667             self.children.remove(pid)
  668             LOG.info(_LI('Removed dead child %s'), pid)
  669         elif pid in self.stale_children:
  670             self.stale_children.remove(pid)
  671             LOG.info(_LI('Removed stale child %s'), pid)
  672         else:
  673             LOG.warn(_LW('Unrecognised child %s') % pid)
  674 
  675     def _verify_and_respawn_children(self, pid, status):
  676         if len(self.stale_children) == 0:
  677             LOG.debug('No stale children')
  678         if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0:
  679             LOG.error(_LE('Not respawning child %d, cannot '
  680                           'recover from termination') % pid)
  681             if not self.children and not self.stale_children:
  682                 LOG.info(
  683                     _LI('All workers have terminated. Exiting'))
  684                 self.running = False
  685         else:
  686             if len(self.children) < get_num_workers():
  687                 self.run_child()
  688 
  689     def wait_on_children(self):
  690         while self.running:
  691             try:
  692                 pid, status = os.wait()
  693                 if os.WIFEXITED(status) or os.WIFSIGNALED(status):
  694                     self._remove_children(pid)
  695                     self._verify_and_respawn_children(pid, status)
  696             except OSError as err:
  697                 if err.errno not in (errno.EINTR, errno.ECHILD):
  698                     raise
  699             except KeyboardInterrupt:
  700                 LOG.info(_LI('Caught keyboard interrupt. Exiting.'))
  701                 break
  702             except exception.SIGHUPInterrupt:
  703                 self.reload()
  704                 continue
  705         eventlet.greenio.shutdown_safe(self.sock)
  706         self.sock.close()
  707         LOG.debug('Exited')
  708 
  709     def run_child(self):
  710         def child_hup(*args):
  711             """Shuts down child processes, existing requests are handled."""
  712             self.set_signal_handler("SIGHUP", signal.SIG_IGN)
  713             eventlet.wsgi.is_accepting = False
  714             self.sock.close()
  715 
  716         pid = os.fork()
  717         if pid == 0:
  718             self.set_signal_handler("SIGHUP", child_hup)
  719             self.set_signal_handler("SIGTERM", signal.SIG_DFL)
  720             # ignore the interrupt signal to avoid a race whereby
  721             # a child worker receives the signal before the parent
  722             # and is respawned unnecessarily as a result
  723             self.set_signal_handler("SIGINT", signal.SIG_IGN)
  724             # The child has no need to stash the unwrapped
  725             # socket, and the reference prevents a clean
  726             # exit on sighup
  727             self._sock = None
  728             self.run_server()
  729             LOG.info(_LI('Child %d exiting normally'), os.getpid())
  730             # self.pool.waitall() is now called in wsgi's server so
  731             # it's safe to exit here
  732             sys.exit(0)
  733         else:
  734             LOG.info(_LI('Started child %s'), pid)
  735             self.children.add(pid)
  736 
  737     def reload(self):
  738         """
  739         Reload and re-apply configuration settings
  740 
  741         Existing child processes are sent a SIGHUP signal
  742         and will exit after completing existing requests.
  743         New child processes, which will have the updated
  744         configuration, are spawned. This allows preventing
  745         interruption to the service.
  746         """
  747         def _has_changed(old, new, param):
  748             old = old.get(param)
  749             new = getattr(new, param)
  750             return (new != old)
  751 
  752         old_conf = utils.stash_conf_values()
  753         has_changed = functools.partial(_has_changed, old_conf, CONF)
  754         CONF.reload_config_files()
  755         os.killpg(self.pgid, signal.SIGHUP)
  756         self.stale_children = self.children
  757         self.children = set()
  758 
  759         # Ensure any logging config changes are picked up
  760         logging.setup(CONF, 'glance')
  761         config.set_config_defaults()
  762 
  763         self.configure(old_conf, has_changed)
  764         self.start_wsgi()
  765 
  766 
  767 class Win32ProcessLauncher(object):
  768     def __init__(self):
  769         self._processutils = os_win_utilsfactory.get_processutils()
  770 
  771         self._workers = []
  772         self._worker_job_handles = []
  773 
  774     def add_process(self, cmd):
  775         LOG.info("Starting subprocess: %s", cmd)
  776 
  777         worker = subprocess.Popen(cmd, close_fds=False)
  778         try:
  779             job_handle = self._processutils.kill_process_on_job_close(
  780                 worker.pid)
  781         except Exception:
  782             LOG.exception("Could not associate child process "
  783                           "with a job, killing it.")
  784             worker.kill()
  785             raise
  786 
  787         self._worker_job_handles.append(job_handle)
  788         self._workers.append(worker)
  789 
  790         return worker
  791 
  792     def wait(self):
  793         pids = [worker.pid for worker in self._workers]
  794         if pids:
  795             self._processutils.wait_for_multiple_processes(pids,
  796                                                            wait_all=True)
  797         # By sleeping here, we allow signal handlers to be executed.
  798         time.sleep(0)
  799 
  800 
  801 class Win32Server(BaseServer):
  802     _py_script_re = re.compile(r'.*\.py\w?$')
  803     _sock = None
  804 
  805     def __init__(self, *args, **kwargs):
  806         super(Win32Server, self).__init__(*args, **kwargs)
  807         self._launcher = Win32ProcessLauncher()
  808         self._ioutils = os_win_utilsfactory.get_ioutils()
  809 
  810     def run_child(self):
  811         # We're passing copies of the socket through pipes.
  812         rfd, wfd = self._ioutils.create_pipe(inherit_handle=True)
  813 
  814         cmd = sys.argv + ['--pipe-handle=%s' % int(rfd)]
  815         # Recent setuptools versions will trim '-script.py' and '.exe'
  816         # extensions from sys.argv[0].
  817         if self._py_script_re.match(sys.argv[0]):
  818             cmd = [sys.executable] + cmd
  819 
  820         worker = self._launcher.add_process(cmd)
  821         self._ioutils.close_handle(rfd)
  822 
  823         share_sock_buff = self._sock.share(worker.pid)
  824         self._ioutils.write_file(
  825             wfd,
  826             struct.pack('<I', len(share_sock_buff)),
  827             4)
  828         self._ioutils.write_file(
  829             wfd, share_sock_buff, len(share_sock_buff))
  830 
  831         self.children.add(worker.pid)
  832 
  833     def kill_children(self, *args):
  834         # We're using job objects, the children will exit along with the
  835         # main process.
  836         exit(0)
  837 
  838     def wait_on_children(self):
  839         self._launcher.wait()
  840 
  841     def _get_sock_from_parent(self):
  842         # This is supposed to be called exactly once in the child process.
  843         # We're passing a copy of the socket through a pipe.
  844         pipe_handle = int(getattr(CONF, 'pipe_handle', 0))
  845         if not pipe_handle:
  846             err_msg = _("Did not receive a pipe handle, which is used when "
  847                         "communicating with the parent process.")
  848             raise exception.GlanceException(err_msg)
  849 
  850         # Get the length of the data to be received.
  851         buff = self._ioutils.get_buffer(4)
  852         self._ioutils.read_file(pipe_handle, buff, 4)
  853         socket_buff_sz = struct.unpack('<I', buff)[0]
  854 
  855         # Get the serialized socket object.
  856         socket_buff = self._ioutils.get_buffer(socket_buff_sz)
  857         self._ioutils.read_file(pipe_handle, socket_buff, socket_buff_sz)
  858         self._ioutils.close_handle(pipe_handle)
  859 
  860         # Recreate the socket object. This will only work with
  861         # Python 3.6 or later.
  862         return socket.fromshare(bytes(socket_buff[:]))
  863 
  864     def configure_socket(self, old_conf=None, has_changed=None):
  865         fresh_start = not (old_conf or has_changed)
  866         pipe_handle = getattr(CONF, 'pipe_handle', None)
  867 
  868         if not (fresh_start and pipe_handle):
  869             return super(Win32Server, self).configure_socket(
  870                 old_conf, has_changed)
  871 
  872         self.sock = self._get_sock_from_parent()
  873 
  874         if hasattr(socket, 'TCP_KEEPIDLE'):
  875             # This was introduced in WS 2016 RS3
  876             self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
  877                                  CONF.tcp_keepidle)
  878 
  879 
  880 if os.name == 'nt':
  881     Server = Win32Server
  882 else:
  883     Server = PosixServer
  884 
  885 
  886 class Middleware(object):
  887     """
  888     Base WSGI middleware wrapper. These classes require an application to be
  889     initialized that will be called next.  By default the middleware will
  890     simply call its wrapped app, or you can override __call__ to customize its
  891     behavior.
  892     """
  893 
  894     def __init__(self, application):
  895         self.application = application
  896 
  897     @classmethod
  898     def factory(cls, global_conf, **local_conf):
  899         def filter(app):
  900             return cls(app)
  901         return filter
  902 
  903     def process_request(self, req):
  904         """
  905         Called on each request.
  906 
  907         If this returns None, the next application down the stack will be
  908         executed. If it returns a response then that response will be returned
  909         and execution will stop here.
  910 
  911         """
  912         return None
  913 
  914     def process_response(self, response):
  915         """Do whatever you'd like to the response."""
  916         return response
  917 
  918     @webob.dec.wsgify
  919     def __call__(self, req):
  920         response = self.process_request(req)
  921         if response:
  922             return response
  923         response = req.get_response(self.application)
  924         response.request = req
  925         try:
  926             return self.process_response(response)
  927         except webob.exc.HTTPException as e:
  928             return e
  929 
  930 
  931 class Debug(Middleware):
  932     """
  933     Helper class that can be inserted into any WSGI application chain
  934     to get information about the request and response.
  935     """
  936 
  937     @webob.dec.wsgify
  938     def __call__(self, req):
  939         print(("*" * 40) + " REQUEST ENVIRON")
  940         for key, value in req.environ.items():
  941             print(key, "=", value)
  942         print('')
  943         resp = req.get_response(self.application)
  944 
  945         print(("*" * 40) + " RESPONSE HEADERS")
  946         for (key, value) in six.iteritems(resp.headers):
  947             print(key, "=", value)
  948         print('')
  949 
  950         resp.app_iter = self.print_generator(resp.app_iter)
  951 
  952         return resp
  953 
  954     @staticmethod
  955     def print_generator(app_iter):
  956         """
  957         Iterator that prints the contents of a wrapper string iterator
  958         when iterated.
  959         """
  960         print(("*" * 40) + " BODY")
  961         for part in app_iter:
  962             sys.stdout.write(part)
  963             sys.stdout.flush()
  964             yield part
  965         print()
  966 
  967 
  968 class APIMapper(routes.Mapper):
  969     """
  970     Handle route matching when url is '' because routes.Mapper returns
  971     an error in this case.
  972     """
  973 
  974     def routematch(self, url=None, environ=None):
  975         if url == "":
  976             result = self._match("", environ)
  977             return result[0], result[1]
  978         return routes.Mapper.routematch(self, url, environ)
  979 
  980 
  981 class RejectMethodController(object):
  982     def reject(self, req, allowed_methods, *args, **kwargs):
  983         LOG.debug("The method %s is not allowed for this resource",
  984                   req.environ['REQUEST_METHOD'])
  985         raise webob.exc.HTTPMethodNotAllowed(
  986             headers=[('Allow', allowed_methods)])
  987 
  988 
  989 class Router(object):
  990     """
  991     WSGI middleware that maps incoming requests to WSGI apps.
  992     """
  993 
  994     def __init__(self, mapper):
  995         """
  996         Create a router for the given routes.Mapper.
  997 
  998         Each route in `mapper` must specify a 'controller', which is a
  999         WSGI app to call.  You'll probably want to specify an 'action' as
 1000         well and have your controller be a wsgi.Controller, who will route
 1001         the request to the action method.
 1002 
 1003         Examples:
 1004           mapper = routes.Mapper()
 1005           sc = ServerController()
 1006 
 1007           # Explicit mapping of one route to a controller+action
 1008           mapper.connect(None, "/svrlist", controller=sc, action="list")
 1009 
 1010           # Actions are all implicitly defined
 1011           mapper.resource("server", "servers", controller=sc)
 1012 
 1013           # Pointing to an arbitrary WSGI app.  You can specify the
 1014           # {path_info:.*} parameter so the target app can be handed just that
 1015           # section of the URL.
 1016           mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
 1017         """
 1018         mapper.redirect("", "/")
 1019         self.map = mapper
 1020         self._router = routes.middleware.RoutesMiddleware(self._dispatch,
 1021                                                           self.map)
 1022 
 1023     @classmethod
 1024     def factory(cls, global_conf, **local_conf):
 1025         return cls(APIMapper())
 1026 
 1027     @webob.dec.wsgify
 1028     def __call__(self, req):
 1029         """
 1030         Route the incoming request to a controller based on self.map.
 1031         If no match, return either a 404(Not Found) or 501(Not Implemented).
 1032         """
 1033         return self._router
 1034 
 1035     @staticmethod
 1036     @webob.dec.wsgify
 1037     def _dispatch(req):
 1038         """
 1039         Called by self._router after matching the incoming request to a route
 1040         and putting the information into req.environ.  Either returns 404,
 1041         501, or the routed WSGI app's response.
 1042         """
 1043         match = req.environ['wsgiorg.routing_args'][1]
 1044         if not match:
 1045             implemented_http_methods = ['GET', 'HEAD', 'POST', 'PUT',
 1046                                         'DELETE', 'PATCH']
 1047             if req.environ['REQUEST_METHOD'] not in implemented_http_methods:
 1048                 return webob.exc.HTTPNotImplemented()
 1049             else:
 1050                 return webob.exc.HTTPNotFound()
 1051         app = match['controller']
 1052         return app
 1053 
 1054 
 1055 class _UWSGIChunkFile(object):
 1056 
 1057     def read(self, length=None):
 1058         position = 0
 1059         if length == 0:
 1060             return b""
 1061 
 1062         if length and length < 0:
 1063             length = None
 1064 
 1065         response = []
 1066         while True:
 1067             data = uwsgi.chunked_read()
 1068             # Return everything if we reached the end of the file
 1069             if not data:
 1070                 break
 1071             response.append(data)
 1072             # Return the data if we've reached the length
 1073             if length is not None:
 1074                 position += len(data)
 1075                 if position >= length:
 1076                     break
 1077         return b''.join(response)
 1078 
 1079 
 1080 class Request(webob.Request):
 1081     """Add some OpenStack API-specific logic to the base webob.Request."""
 1082 
 1083     def __init__(self, environ, *args, **kwargs):
 1084         if CONF.secure_proxy_ssl_header:
 1085             scheme = environ.get(CONF.secure_proxy_ssl_header)
 1086             if scheme:
 1087                 environ['wsgi.url_scheme'] = scheme
 1088         super(Request, self).__init__(environ, *args, **kwargs)
 1089 
 1090     @property
 1091     def body_file(self):
 1092         if uwsgi:
 1093             if self.headers.get('transfer-encoding', '').lower() == 'chunked':
 1094                 return _UWSGIChunkFile()
 1095         return super(Request, self).body_file
 1096 
 1097     @body_file.setter
 1098     def body_file(self, value):
 1099         # NOTE(cdent): If you have a property setter in a superclass, it will
 1100         # not be inherited.
 1101         webob.Request.body_file.fset(self, value)
 1102 
 1103     @property
 1104     def params(self):
 1105         """Override params property of webob.request.BaseRequest.
 1106 
 1107         Added an 'encoded_params' attribute in case of PY2 to avoid
 1108         encoding values in next subsequent calls to the params property.
 1109         """
 1110         if six.PY2:
 1111             encoded_params = getattr(self, 'encoded_params', None)
 1112             if encoded_params is None:
 1113                 params = super(Request, self).params
 1114                 params_dict = multidict.MultiDict()
 1115                 for key, value in params.items():
 1116                     params_dict.add(key, encodeutils.safe_encode(value))
 1117 
 1118                 setattr(self, 'encoded_params',
 1119                         multidict.NestedMultiDict(params_dict))
 1120             return self.encoded_params
 1121         return super(Request, self).params
 1122 
 1123     def best_match_content_type(self):
 1124         """Determine the requested response content-type."""
 1125         supported = ('application/json',)
 1126         best_matches = self.accept.acceptable_offers(supported)
 1127         if not best_matches:
 1128             return 'application/json'
 1129         return best_matches[0][0]
 1130 
 1131     def get_content_type(self, allowed_content_types):
 1132         """Determine content type of the request body."""
 1133         if "Content-Type" not in self.headers:
 1134             raise exception.InvalidContentType(content_type=None)
 1135 
 1136         content_type = self.content_type
 1137 
 1138         if content_type not in allowed_content_types:
 1139             raise exception.InvalidContentType(content_type=content_type)
 1140         else:
 1141             return content_type
 1142 
 1143     def best_match_language(self):
 1144         """Determines best available locale from the Accept-Language header.
 1145 
 1146         :returns: the best language match or None if the 'Accept-Language'
 1147                   header was not available in the request.
 1148         """
 1149         if not self.accept_language:
 1150             return None
 1151         langs = i18n.get_available_languages('glance')
 1152         # NOTE(rosmaita): give the webob lookup() function a sentinal value
 1153         # for default so we can preserve the behavior of this function as
 1154         # indicated by the current unit tests.  See Launchpad bug #1765748.
 1155         best_match = self.accept_language.lookup(langs, default='fake_LANG')
 1156         if best_match == 'fake_LANG':
 1157             best_match = None
 1158         return best_match
 1159 
 1160     def get_range_from_request(self, image_size):
 1161         """Return the `Range` in a request."""
 1162 
 1163         range_str = self.headers.get('Range')
 1164         if range_str is not None:
 1165 
 1166             # NOTE(dharinic): We do not support multi range requests.
 1167             if ',' in range_str:
 1168                 msg = ("Requests with multiple ranges are not supported in "
 1169                        "Glance. You may make multiple single-range requests "
 1170                        "instead.")
 1171                 raise webob.exc.HTTPBadRequest(explanation=msg)
 1172 
 1173             range_ = webob.byterange.Range.parse(range_str)
 1174             if range_ is None:
 1175                 msg = ("Invalid Range header.")
 1176                 raise webob.exc.HTTPRequestRangeNotSatisfiable(msg)
 1177             # NOTE(dharinic): Ensure that a range like bytes=4- for an image
 1178             # size of 3 is invalidated as per rfc7233.
 1179             if range_.start >= image_size:
 1180                 msg = ("Invalid start position in Range header. "
 1181                        "Start position MUST be in the inclusive range [0, %s]."
 1182                        % (image_size - 1))
 1183                 raise webob.exc.HTTPRequestRangeNotSatisfiable(msg)
 1184             return range_
 1185 
 1186         # NOTE(dharinic): For backward compatibility reasons, we maintain
 1187         # support for 'Content-Range' in requests even though it's not
 1188         # correct to use it in requests..
 1189         c_range_str = self.headers.get('Content-Range')
 1190         if c_range_str is not None:
 1191             content_range = webob.byterange.ContentRange.parse(c_range_str)
 1192             # NOTE(dharinic): Ensure that a content range like 1-4/* for an
 1193             # image size of 3 is invalidated.
 1194             if content_range is None:
 1195                 msg = ("Invalid Content-Range header.")
 1196                 raise webob.exc.HTTPRequestRangeNotSatisfiable(msg)
 1197             if (content_range.length is None and
 1198                     content_range.stop > image_size):
 1199                 msg = ("Invalid stop position in Content-Range header. "
 1200                        "The stop position MUST be in the inclusive range "
 1201                        "[0, %s]." % (image_size - 1))
 1202                 raise webob.exc.HTTPRequestRangeNotSatisfiable(msg)
 1203             if content_range.start >= image_size:
 1204                 msg = ("Invalid start position in Content-Range header. "
 1205                        "Start position MUST be in the inclusive range [0, %s]."
 1206                        % (image_size - 1))
 1207                 raise webob.exc.HTTPRequestRangeNotSatisfiable(msg)
 1208             return content_range
 1209 
 1210 
 1211 class JSONRequestDeserializer(object):
 1212     valid_transfer_encoding = frozenset(['chunked', 'compress', 'deflate',
 1213                                          'gzip', 'identity'])
 1214     httpverb_may_have_body = frozenset({'POST', 'PUT', 'PATCH'})
 1215 
 1216     @classmethod
 1217     def is_valid_encoding(cls, request):
 1218         request_encoding = request.headers.get('transfer-encoding', '').lower()
 1219         return request_encoding in cls.valid_transfer_encoding
 1220 
 1221     @classmethod
 1222     def is_valid_method(cls, request):
 1223         return request.method.upper() in cls.httpverb_may_have_body
 1224 
 1225     def has_body(self, request):
 1226         """
 1227         Returns whether a Webob.Request object will possess an entity body.
 1228 
 1229         :param request:  Webob.Request object
 1230         """
 1231 
 1232         if self.is_valid_encoding(request) and self.is_valid_method(request):
 1233             request.is_body_readable = True
 1234             return True
 1235 
 1236         if request.content_length is not None and request.content_length > 0:
 1237             return True
 1238 
 1239         return False
 1240 
 1241     @staticmethod
 1242     def _sanitizer(obj):
 1243         """Sanitizer method that will be passed to jsonutils.loads."""
 1244         return obj
 1245 
 1246     def from_json(self, datastring):
 1247         try:
 1248             jsondata = jsonutils.loads(datastring, object_hook=self._sanitizer)
 1249             if not isinstance(jsondata, (dict, list)):
 1250                 msg = _('Unexpected body type. Expected list/dict.')
 1251                 raise webob.exc.HTTPBadRequest(explanation=msg)
 1252             return jsondata
 1253         except ValueError:
 1254             msg = _('Malformed JSON in request body.')
 1255             raise webob.exc.HTTPBadRequest(explanation=msg)
 1256 
 1257     def default(self, request):
 1258         if self.has_body(request):
 1259             return {'body': self.from_json(request.body)}
 1260         else:
 1261             return {}
 1262 
 1263 
 1264 class JSONResponseSerializer(object):
 1265 
 1266     def _sanitizer(self, obj):
 1267         """Sanitizer method that will be passed to jsonutils.dumps."""
 1268         if hasattr(obj, "to_dict"):
 1269             return obj.to_dict()
 1270         if isinstance(obj, multidict.MultiDict):
 1271             return obj.mixed()
 1272         return jsonutils.to_primitive(obj)
 1273 
 1274     def to_json(self, data):
 1275         return jsonutils.dump_as_bytes(data, default=self._sanitizer)
 1276 
 1277     def default(self, response, result):
 1278         response.content_type = 'application/json'
 1279         body = self.to_json(result)
 1280         body = encodeutils.to_utf8(body)
 1281         response.body = body
 1282 
 1283 
 1284 def translate_exception(req, e):
 1285     """Translates all translatable elements of the given exception."""
 1286 
 1287     # The RequestClass attribute in the webob.dec.wsgify decorator
 1288     # does not guarantee that the request object will be a particular
 1289     # type; this check is therefore necessary.
 1290     if not hasattr(req, "best_match_language"):
 1291         return e
 1292 
 1293     locale = req.best_match_language()
 1294 
 1295     if isinstance(e, webob.exc.HTTPError):
 1296         e.explanation = i18n.translate(e.explanation, locale)
 1297         e.detail = i18n.translate(e.detail, locale)
 1298         if getattr(e, 'body_template', None):
 1299             e.body_template = i18n.translate(e.body_template, locale)
 1300     return e
 1301 
 1302 
 1303 class Resource(object):
 1304     """
 1305     WSGI app that handles (de)serialization and controller dispatch.
 1306 
 1307     Reads routing information supplied by RoutesMiddleware and calls
 1308     the requested action method upon its deserializer, controller,
 1309     and serializer. Those three objects may implement any of the basic
 1310     controller action methods (create, update, show, index, delete)
 1311     along with any that may be specified in the api router. A 'default'
 1312     method may also be implemented to be used in place of any
 1313     non-implemented actions. Deserializer methods must accept a request
 1314     argument and return a dictionary. Controller methods must accept a
 1315     request argument. Additionally, they must also accept keyword
 1316     arguments that represent the keys returned by the Deserializer. They
 1317     may raise a webob.exc exception or return a dict, which will be
 1318     serialized by requested content type.
 1319     """
 1320 
 1321     def __init__(self, controller, deserializer=None, serializer=None):
 1322         """
 1323         :param controller: object that implement methods created by routes lib
 1324         :param deserializer: object that supports webob request deserialization
 1325                              through controller-like actions
 1326         :param serializer: object that supports webob response serialization
 1327                            through controller-like actions
 1328         """
 1329         self.controller = controller
 1330         self.serializer = serializer or JSONResponseSerializer()
 1331         self.deserializer = deserializer or JSONRequestDeserializer()
 1332 
 1333     @webob.dec.wsgify(RequestClass=Request)
 1334     def __call__(self, request):
 1335         """WSGI method that controls (de)serialization and method dispatch."""
 1336         action_args = self.get_action_args(request.environ)
 1337         action = action_args.pop('action', None)
 1338         body_reject = strutils.bool_from_string(
 1339             action_args.pop('body_reject', None))
 1340 
 1341         try:
 1342             if body_reject and self.deserializer.has_body(request):
 1343                 msg = _('A body is not expected with this request.')
 1344                 raise webob.exc.HTTPBadRequest(explanation=msg)
 1345             deserialized_request = self.dispatch(self.deserializer,
 1346                                                  action, request)
 1347             action_args.update(deserialized_request)
 1348             action_result = self.dispatch(self.controller, action,
 1349                                           request, **action_args)
 1350         except webob.exc.WSGIHTTPException as e:
 1351             exc_info = sys.exc_info()
 1352             e = translate_exception(request, e)
 1353             six.reraise(type(e), e, exc_info[2])
 1354         except UnicodeDecodeError:
 1355             msg = _("Error decoding your request. Either the URL or the "
 1356                     "request body contained characters that could not be "
 1357                     "decoded by Glance")
 1358             raise webob.exc.HTTPBadRequest(explanation=msg)
 1359         except Exception as e:
 1360             LOG.exception(_LE("Caught error: %s"),
 1361                           encodeutils.exception_to_unicode(e))
 1362             response = webob.exc.HTTPInternalServerError()
 1363             return response
 1364 
 1365         # We cannot serialize an Exception, so return the action_result
 1366         if isinstance(action_result, Exception):
 1367             return action_result
 1368 
 1369         try:
 1370             response = webob.Response(request=request)
 1371             self.dispatch(self.serializer, action, response, action_result)
 1372             # encode all headers in response to utf-8 to prevent unicode errors
 1373             for name, value in list(response.headers.items()):
 1374                 if six.PY2 and isinstance(value, six.text_type):
 1375                     response.headers[name] = encodeutils.safe_encode(value)
 1376             return response
 1377         except webob.exc.WSGIHTTPException as e:
 1378             return translate_exception(request, e)
 1379         except webob.exc.HTTPException as e:
 1380             return e
 1381         # return unserializable result (typically a webob exc)
 1382         except Exception:
 1383             return action_result
 1384 
 1385     def dispatch(self, obj, action, *args, **kwargs):
 1386         """Find action-specific method on self and call it."""
 1387         try:
 1388             method = getattr(obj, action)
 1389         except AttributeError:
 1390             method = getattr(obj, 'default')
 1391 
 1392         return method(*args, **kwargs)
 1393 
 1394     def get_action_args(self, request_environment):
 1395         """Parse dictionary created by routes library."""
 1396         try:
 1397             args = request_environment['wsgiorg.routing_args'][1].copy()
 1398         except Exception:
 1399             return {}
 1400 
 1401         try:
 1402             del args['controller']
 1403         except KeyError:
 1404             pass
 1405 
 1406         try:
 1407             del args['format']
 1408         except KeyError:
 1409             pass
 1410 
 1411         return args