conf.d support
Allow Swift daemons and servers to optionally accept a directory as the configuration parameter. Directory based configuration leverages ConfigParser's native multi-file support. Files ending in '.conf' in the given directory are parsed in lexicographical order. Filenames starting with '.' are ignored. A mixture of file and directory configuration paths is not supported - if the configuration path is a file behavior is unchanged. * update swift-init to search for conf.d paths when building servers (e.g. /etc/swift/proxy-server.conf.d/) * new script swift-config can be used to inspect the cumulative configuration * pull a little bit of code out of run_wsgi and test separately * fix example config bug for the proxy servers client_disconnect option * added section on directory based configuration to deployment guide DocImpact Implements: blueprint confd Change-Id: I89b0f48e538117f28590cf6698401f74ef58003b
This commit is contained in:
parent
52a6595033
commit
34f5085c3e
58
bin/swift-config
Executable file
58
bin/swift-config
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from swift.common.manager import Server
|
||||||
|
from swift.common.utils import readconf
|
||||||
|
from swift.common.wsgi import appconfig
|
||||||
|
|
||||||
|
parser = optparse.OptionParser('%prog [options] SERVER')
|
||||||
|
parser.add_option('-c', '--config-num', metavar="N", type="int",
|
||||||
|
dest="number", default=0,
|
||||||
|
help="parse config for the Nth server only")
|
||||||
|
parser.add_option('-s', '--section', help="only display matching sections")
|
||||||
|
parser.add_option('-w', '--wsgi', action='store_true',
|
||||||
|
help="use wsgi/paste parser instead of readconf")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
options, args = parser.parse_args()
|
||||||
|
options = dict(vars(options))
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
return 'ERROR: specify type of server or conf_path'
|
||||||
|
conf_files = []
|
||||||
|
for arg in args:
|
||||||
|
if os.path.exists(arg):
|
||||||
|
conf_files.append(arg)
|
||||||
|
else:
|
||||||
|
conf_files += Server(arg).conf_files(**options)
|
||||||
|
for conf_file in conf_files:
|
||||||
|
print '# %s' % conf_file
|
||||||
|
if options['wsgi']:
|
||||||
|
app_config = appconfig(conf_file)
|
||||||
|
context = app_config.context
|
||||||
|
conf = dict([(c.name, c.config()) for c in getattr(
|
||||||
|
context, 'filter_contexts', [])])
|
||||||
|
conf[context.name] = app_config
|
||||||
|
else:
|
||||||
|
conf = readconf(conf_file)
|
||||||
|
flat_vars = {}
|
||||||
|
for k, v in conf.items():
|
||||||
|
if options['section'] and k != options['section']:
|
||||||
|
continue
|
||||||
|
if not isinstance(v, dict):
|
||||||
|
flat_vars[k] = v
|
||||||
|
continue
|
||||||
|
print '[%s]' % k
|
||||||
|
for opt, value in v.items():
|
||||||
|
print '%s = %s' % (opt, value)
|
||||||
|
print
|
||||||
|
for k, v in flat_vars.items():
|
||||||
|
print '# %s = %s' % (k, v)
|
||||||
|
print
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
@ -139,15 +139,102 @@ swift-ring-builder with no options will display help text with available
|
|||||||
commands and options. More information on how the ring works internally
|
commands and options. More information on how the ring works internally
|
||||||
can be found in the :doc:`Ring Overview <overview_ring>`.
|
can be found in the :doc:`Ring Overview <overview_ring>`.
|
||||||
|
|
||||||
|
.. _general-service-configuration:
|
||||||
|
|
||||||
|
-----------------------------
|
||||||
|
General Service Configuration
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Most Swift services fall into two categories. Swift's wsgi servers and
|
||||||
|
background daemons.
|
||||||
|
|
||||||
|
For more information specific to the configuration of Swift's wsgi servers
|
||||||
|
with paste deploy see :ref:`general-server-configuration`
|
||||||
|
|
||||||
|
Configuration for servers and daemons can be expressed together in the same
|
||||||
|
file for each type of server, or separately. If a required section for the
|
||||||
|
service trying to start is missing there will be an error. The sections not
|
||||||
|
used by the service are ignored.
|
||||||
|
|
||||||
|
Consider the example of an object storage node. By convention configuration
|
||||||
|
for the object-server, object-updater, object-replicator, and object-auditor
|
||||||
|
exist in a single file ``/etc/swift/object-server.conf``::
|
||||||
|
|
||||||
|
[DEFAULT]
|
||||||
|
|
||||||
|
[pipeline:main]
|
||||||
|
pipeline = object-server
|
||||||
|
|
||||||
|
[app:object-server]
|
||||||
|
use = egg:swift#object
|
||||||
|
|
||||||
|
[object-replicator]
|
||||||
|
reclaim_age = 259200
|
||||||
|
|
||||||
|
[object-updater]
|
||||||
|
|
||||||
|
[object-auditor]
|
||||||
|
|
||||||
|
Swift services expect a configuration path as the first argument::
|
||||||
|
|
||||||
|
$ swift-object-auditor
|
||||||
|
Usage: swift-object-auditor CONFIG [options]
|
||||||
|
|
||||||
|
Error: missing config path argument
|
||||||
|
|
||||||
|
If you omit the object-auditor section this file could not be used as the
|
||||||
|
configuration path when starting the ``swift-object-auditor`` daemon::
|
||||||
|
|
||||||
|
$ swift-object-auditor /etc/swift/object-server.conf
|
||||||
|
Unable to find object-auditor config section in /etc/swift/object-server.conf
|
||||||
|
|
||||||
|
If the configuration path is a directory instead of a file all of the files in
|
||||||
|
the directory with the file extension ".conf" will be combined to generate the
|
||||||
|
configuration object which is delivered to the Swift service. This is
|
||||||
|
referred to generally as "directory based configuration".
|
||||||
|
|
||||||
|
Directory based configuration leverages ConfigParser's native multi-file
|
||||||
|
support. Files ending in ".conf" in the given directory are parsed in
|
||||||
|
lexicographical order. Filenames starting with '.' are ignored. A mixture of
|
||||||
|
file and directory configuration paths is not supported - if the configuration
|
||||||
|
path is a file only that file will be parsed.
|
||||||
|
|
||||||
|
The swift service management tool ``swift-init`` has adopted the convention of
|
||||||
|
looking for ``/etc/swift/{type}-server.conf.d/`` if the file
|
||||||
|
``/etc/swift/{type}-server.conf`` file does not exist.
|
||||||
|
|
||||||
|
When using directory based configuration, if the same option under the same
|
||||||
|
section appears more than once in different files, the last value parsed is
|
||||||
|
said to override previous occurrences. You can ensure proper override
|
||||||
|
precedence by prefixing the files in the configuration directory with
|
||||||
|
numerical values.::
|
||||||
|
|
||||||
|
/etc/swift/
|
||||||
|
default.base
|
||||||
|
object-server.conf.d/
|
||||||
|
000_default.conf -> ../default.base
|
||||||
|
001_default-override.conf
|
||||||
|
010_server.conf
|
||||||
|
020_replicator.conf
|
||||||
|
030_updater.conf
|
||||||
|
040_auditor.conf
|
||||||
|
|
||||||
|
You can inspect the resulting combined configuration object using the
|
||||||
|
``swift-config`` command line tool
|
||||||
|
|
||||||
|
.. _general-server-configuration:
|
||||||
|
|
||||||
----------------------------
|
----------------------------
|
||||||
General Server Configuration
|
General Server Configuration
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
Swift uses paste.deploy (http://pythonpaste.org/deploy/) to manage server
|
Swift uses paste.deploy (http://pythonpaste.org/deploy/) to manage server
|
||||||
configurations. Default configuration options are set in the `[DEFAULT]`
|
configurations.
|
||||||
section, and any options specified there can be overridden in any of the other
|
|
||||||
sections BUT ONLY BY USING THE SYNTAX ``set option_name = value``. This is the
|
Default configuration options are set in the `[DEFAULT]` section, and any
|
||||||
unfortunate way paste.deploy works and I'll try to explain it in full.
|
options specified there can be overridden in any of the other sections BUT
|
||||||
|
ONLY BY USING THE SYNTAX ``set option_name = value``. This is the unfortunate
|
||||||
|
way paste.deploy works and I'll try to explain it in full.
|
||||||
|
|
||||||
First, here's an example paste.deploy configuration file::
|
First, here's an example paste.deploy configuration file::
|
||||||
|
|
||||||
@ -218,6 +305,7 @@ The main rule to remember when working with Swift configuration files is:
|
|||||||
configuration files.
|
configuration files.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---------------------------
|
---------------------------
|
||||||
Object Server Configuration
|
Object Server Configuration
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
# log_statsd_metric_prefix =
|
# log_statsd_metric_prefix =
|
||||||
# Use a comma separated list of full url (http://foo.bar:1234,https://foo.bar)
|
# Use a comma separated list of full url (http://foo.bar:1234,https://foo.bar)
|
||||||
# cors_allow_origin =
|
# cors_allow_origin =
|
||||||
|
# client_timeout = 60
|
||||||
# eventlet_debug = false
|
# eventlet_debug = false
|
||||||
# max_clients = 1024
|
# max_clients = 1024
|
||||||
|
|
||||||
@ -55,7 +56,6 @@ use = egg:swift#proxy
|
|||||||
# object_chunk_size = 8192
|
# object_chunk_size = 8192
|
||||||
# client_chunk_size = 8192
|
# client_chunk_size = 8192
|
||||||
# node_timeout = 10
|
# node_timeout = 10
|
||||||
# client_timeout = 60
|
|
||||||
# conn_timeout = 0.5
|
# conn_timeout = 0.5
|
||||||
# How long without an error before a node's error count is reset. This will
|
# How long without an error before a node's error count is reset. This will
|
||||||
# also be how long before a node is reenabled after suppression is triggered.
|
# also be how long before a node is reenabled after suppression is triggered.
|
||||||
|
1
setup.py
1
setup.py
@ -55,6 +55,7 @@ setup(
|
|||||||
'bin/swift-account-server',
|
'bin/swift-account-server',
|
||||||
'bin/swift-bench',
|
'bin/swift-bench',
|
||||||
'bin/swift-bench-client',
|
'bin/swift-bench-client',
|
||||||
|
'bin/swift-config',
|
||||||
'bin/swift-container-auditor',
|
'bin/swift-container-auditor',
|
||||||
'bin/swift-container-replicator',
|
'bin/swift-container-replicator',
|
||||||
'bin/swift-container-server',
|
'bin/swift-container-server',
|
||||||
|
@ -350,8 +350,8 @@ class Server():
|
|||||||
"""
|
"""
|
||||||
return conf_file.replace(
|
return conf_file.replace(
|
||||||
os.path.normpath(SWIFT_DIR), self.run_dir, 1).replace(
|
os.path.normpath(SWIFT_DIR), self.run_dir, 1).replace(
|
||||||
'%s-server' % self.type, self.server, 1).rsplit(
|
'%s-server' % self.type, self.server, 1).replace(
|
||||||
'.conf', 1)[0] + '.pid'
|
'.conf', '.pid', 1)
|
||||||
|
|
||||||
def get_conf_file_name(self, pid_file):
|
def get_conf_file_name(self, pid_file):
|
||||||
"""Translate pid_file to a corresponding conf_file
|
"""Translate pid_file to a corresponding conf_file
|
||||||
@ -363,13 +363,13 @@ class Server():
|
|||||||
"""
|
"""
|
||||||
if self.server in STANDALONE_SERVERS:
|
if self.server in STANDALONE_SERVERS:
|
||||||
return pid_file.replace(
|
return pid_file.replace(
|
||||||
os.path.normpath(self.run_dir), SWIFT_DIR, 1)\
|
os.path.normpath(self.run_dir), SWIFT_DIR, 1).replace(
|
||||||
.rsplit('.pid', 1)[0] + '.conf'
|
'.pid', '.conf', 1)
|
||||||
else:
|
else:
|
||||||
return pid_file.replace(
|
return pid_file.replace(
|
||||||
os.path.normpath(self.run_dir), SWIFT_DIR, 1).replace(
|
os.path.normpath(self.run_dir), SWIFT_DIR, 1).replace(
|
||||||
self.server, '%s-server' % self.type, 1).rsplit(
|
self.server, '%s-server' % self.type, 1).replace(
|
||||||
'.pid', 1)[0] + '.conf'
|
'.pid', '.conf', 1)
|
||||||
|
|
||||||
def conf_files(self, **kwargs):
|
def conf_files(self, **kwargs):
|
||||||
"""Get conf files for this server
|
"""Get conf files for this server
|
||||||
@ -380,10 +380,10 @@ class Server():
|
|||||||
"""
|
"""
|
||||||
if self.server in STANDALONE_SERVERS:
|
if self.server in STANDALONE_SERVERS:
|
||||||
found_conf_files = search_tree(SWIFT_DIR, self.server + '*',
|
found_conf_files = search_tree(SWIFT_DIR, self.server + '*',
|
||||||
'.conf')
|
'.conf', dir_ext='.conf.d')
|
||||||
else:
|
else:
|
||||||
found_conf_files = search_tree(SWIFT_DIR, '%s-server*' % self.type,
|
found_conf_files = search_tree(SWIFT_DIR, '%s-server*' % self.type,
|
||||||
'.conf')
|
'.conf', dir_ext='.conf.d')
|
||||||
number = kwargs.get('number')
|
number = kwargs.get('number')
|
||||||
if number:
|
if number:
|
||||||
try:
|
try:
|
||||||
@ -412,7 +412,7 @@ class Server():
|
|||||||
|
|
||||||
:returns: list of pid files
|
:returns: list of pid files
|
||||||
"""
|
"""
|
||||||
pid_files = search_tree(self.run_dir, '%s*' % self.server, '.pid')
|
pid_files = search_tree(self.run_dir, '%s*' % self.server)
|
||||||
if kwargs.get('number', 0):
|
if kwargs.get('number', 0):
|
||||||
conf_files = self.conf_files(**kwargs)
|
conf_files = self.conf_files(**kwargs)
|
||||||
# filter pid_files to match the index of numbered conf_file
|
# filter pid_files to match the index of numbered conf_file
|
||||||
|
@ -941,7 +941,7 @@ def parse_options(parser=None, once=False, test_args=None):
|
|||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
parser.print_usage()
|
parser.print_usage()
|
||||||
print _("Error: missing config file argument")
|
print _("Error: missing config path argument")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
config = os.path.abspath(args.pop(0))
|
config = os.path.abspath(args.pop(0))
|
||||||
if not os.path.exists(config):
|
if not os.path.exists(config):
|
||||||
@ -1208,13 +1208,21 @@ def cache_from_env(env):
|
|||||||
return item_from_env(env, 'swift.cache')
|
return item_from_env(env, 'swift.cache')
|
||||||
|
|
||||||
|
|
||||||
def readconf(conffile, section_name=None, log_name=None, defaults=None,
|
def read_conf_dir(parser, conf_dir):
|
||||||
|
conf_files = []
|
||||||
|
for f in os.listdir(conf_dir):
|
||||||
|
if f.endswith('.conf') and not f.startswith('.'):
|
||||||
|
conf_files.append(os.path.join(conf_dir, f))
|
||||||
|
return parser.read(sorted(conf_files))
|
||||||
|
|
||||||
|
|
||||||
|
def readconf(conf_path, section_name=None, log_name=None, defaults=None,
|
||||||
raw=False):
|
raw=False):
|
||||||
"""
|
"""
|
||||||
Read config file and return config items as a dict
|
Read config file(s) and return config items as a dict
|
||||||
|
|
||||||
:param conffile: path to config file, or a file-like object (hasattr
|
:param conf_path: path to config file/directory, or a file-like object
|
||||||
readline)
|
(hasattr readline)
|
||||||
:param section_name: config section to read (will return all sections if
|
:param section_name: config section to read (will return all sections if
|
||||||
not defined)
|
not defined)
|
||||||
:param log_name: name to be used with logging (will use section_name if
|
:param log_name: name to be used with logging (will use section_name if
|
||||||
@ -1228,18 +1236,23 @@ def readconf(conffile, section_name=None, log_name=None, defaults=None,
|
|||||||
c = RawConfigParser(defaults)
|
c = RawConfigParser(defaults)
|
||||||
else:
|
else:
|
||||||
c = ConfigParser(defaults)
|
c = ConfigParser(defaults)
|
||||||
if hasattr(conffile, 'readline'):
|
if hasattr(conf_path, 'readline'):
|
||||||
c.readfp(conffile)
|
c.readfp(conf_path)
|
||||||
else:
|
else:
|
||||||
if not c.read(conffile):
|
if os.path.isdir(conf_path):
|
||||||
print _("Unable to read config file %s") % conffile
|
# read all configs in directory
|
||||||
|
success = read_conf_dir(c, conf_path)
|
||||||
|
else:
|
||||||
|
success = c.read(conf_path)
|
||||||
|
if not success:
|
||||||
|
print _("Unable to read config from %s") % conf_path
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if section_name:
|
if section_name:
|
||||||
if c.has_section(section_name):
|
if c.has_section(section_name):
|
||||||
conf = dict(c.items(section_name))
|
conf = dict(c.items(section_name))
|
||||||
else:
|
else:
|
||||||
print _("Unable to find %s config section in %s") % \
|
print _("Unable to find %s config section in %s") % \
|
||||||
(section_name, conffile)
|
(section_name, conf_path)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if "log_name" not in conf:
|
if "log_name" not in conf:
|
||||||
if log_name is not None:
|
if log_name is not None:
|
||||||
@ -1252,7 +1265,7 @@ def readconf(conffile, section_name=None, log_name=None, defaults=None,
|
|||||||
conf.update({s: dict(c.items(s))})
|
conf.update({s: dict(c.items(s))})
|
||||||
if 'log_name' not in conf:
|
if 'log_name' not in conf:
|
||||||
conf['log_name'] = log_name
|
conf['log_name'] = log_name
|
||||||
conf['__file__'] = conffile
|
conf['__file__'] = conf_path
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
|
|
||||||
@ -1277,27 +1290,44 @@ def write_pickle(obj, dest, tmp=None, pickle_protocol=0):
|
|||||||
renamer(tmppath, dest)
|
renamer(tmppath, dest)
|
||||||
|
|
||||||
|
|
||||||
def search_tree(root, glob_match, ext):
|
def search_tree(root, glob_match, ext='', dir_ext=None):
|
||||||
"""Look in root, for any files/dirs matching glob, recurively traversing
|
"""Look in root, for any files/dirs matching glob, recursively traversing
|
||||||
any found directories looking for files ending with ext
|
any found directories looking for files ending with ext
|
||||||
|
|
||||||
:param root: start of search path
|
:param root: start of search path
|
||||||
:param glob_match: glob to match in root, matching dirs are traversed with
|
:param glob_match: glob to match in root, matching dirs are traversed with
|
||||||
os.walk
|
os.walk
|
||||||
:param ext: only files that end in ext will be returned
|
:param ext: only files that end in ext will be returned
|
||||||
|
:param dir_ext: if present directories that end with dir_ext will not be
|
||||||
|
traversed and instead will be returned as a matched path
|
||||||
|
|
||||||
:returns: list of full paths to matching files, sorted
|
:returns: list of full paths to matching files, sorted
|
||||||
|
|
||||||
"""
|
"""
|
||||||
found_files = []
|
found_files = []
|
||||||
for path in glob.glob(os.path.join(root, glob_match)):
|
for path in glob.glob(os.path.join(root, glob_match)):
|
||||||
if path.endswith(ext):
|
if os.path.isdir(path):
|
||||||
found_files.append(path)
|
|
||||||
else:
|
|
||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
for file in files:
|
if dir_ext and root.endswith(dir_ext):
|
||||||
if file.endswith(ext):
|
found_files.append(root)
|
||||||
found_files.append(os.path.join(root, file))
|
# the root is a config dir, descend no further
|
||||||
|
break
|
||||||
|
for file_ in files:
|
||||||
|
if ext and not file_.endswith(ext):
|
||||||
|
continue
|
||||||
|
found_files.append(os.path.join(root, file_))
|
||||||
|
found_dir = False
|
||||||
|
for dir_ in dirs:
|
||||||
|
if dir_ext and dir_.endswith(dir_ext):
|
||||||
|
found_dir = True
|
||||||
|
found_files.append(os.path.join(root, dir_))
|
||||||
|
if found_dir:
|
||||||
|
# do not descend further into matching directories
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if ext and not path.endswith(ext):
|
||||||
|
continue
|
||||||
|
found_files.append(path)
|
||||||
return sorted(found_files)
|
return sorted(found_files)
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ from StringIO import StringIO
|
|||||||
import eventlet
|
import eventlet
|
||||||
import eventlet.debug
|
import eventlet.debug
|
||||||
from eventlet import greenio, GreenPool, sleep, wsgi, listen
|
from eventlet import greenio, GreenPool, sleep, wsgi, listen
|
||||||
from paste.deploy import loadapp, appconfig
|
from paste.deploy import loadwsgi
|
||||||
from eventlet.green import socket, ssl
|
from eventlet.green import socket, ssl
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
|
|
||||||
@ -37,6 +37,74 @@ from swift.common.utils import capture_stdio, disable_fallocate, \
|
|||||||
validate_configuration, get_hub
|
validate_configuration, get_hub
|
||||||
|
|
||||||
|
|
||||||
|
class NamedConfigLoader(loadwsgi.ConfigLoader):
|
||||||
|
"""
|
||||||
|
Patch paste.deploy's ConfigLoader so each context object will know what
|
||||||
|
config section it came from.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_context(self, object_type, name=None, global_conf=None):
|
||||||
|
context = super(NamedConfigLoader, self).get_context(
|
||||||
|
object_type, name=name, global_conf=global_conf)
|
||||||
|
context.name = name
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
loadwsgi.ConfigLoader = NamedConfigLoader
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigDirLoader(NamedConfigLoader):
|
||||||
|
"""
|
||||||
|
Read configuration from multiple files under the given path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, conf_dir):
|
||||||
|
# parent class uses filename attribute when building error messages
|
||||||
|
self.filename = conf_dir = conf_dir.strip()
|
||||||
|
defaults = {
|
||||||
|
'here': os.path.normpath(os.path.abspath(conf_dir)),
|
||||||
|
'__file__': os.path.abspath(conf_dir)
|
||||||
|
}
|
||||||
|
self.parser = loadwsgi.NicerConfigParser(conf_dir, defaults=defaults)
|
||||||
|
self.parser.optionxform = str # Don't lower-case keys
|
||||||
|
utils.read_conf_dir(self.parser, conf_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def _loadconfigdir(object_type, uri, path, name, relative_to, global_conf):
|
||||||
|
if relative_to:
|
||||||
|
path = os.path.normpath(os.path.join(relative_to, path))
|
||||||
|
loader = ConfigDirLoader(path)
|
||||||
|
if global_conf:
|
||||||
|
loader.update_defaults(global_conf, overwrite=False)
|
||||||
|
return loader.get_context(object_type, name, global_conf)
|
||||||
|
|
||||||
|
|
||||||
|
# add config_dir parsing to paste.deploy
|
||||||
|
loadwsgi._loaders['config_dir'] = _loadconfigdir
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_conf_type(f):
|
||||||
|
"""
|
||||||
|
Wrap a function whos first argument is a paste.deploy style config uri,
|
||||||
|
such that you can pass it an un-adorned raw filesystem path and the config
|
||||||
|
directive (either config: or config_dir:) will be added automatically
|
||||||
|
based on the type of filesystem entity at the given path (either a file or
|
||||||
|
directory) before passing it through to the paste.deploy function.
|
||||||
|
"""
|
||||||
|
def wrapper(conf_path, *args, **kwargs):
|
||||||
|
if os.path.isdir(conf_path):
|
||||||
|
conf_type = 'config_dir'
|
||||||
|
else:
|
||||||
|
conf_type = 'config'
|
||||||
|
conf_uri = '%s:%s' % (conf_type, conf_path)
|
||||||
|
return f(conf_uri, *args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
appconfig = wrap_conf_type(loadwsgi.appconfig)
|
||||||
|
loadapp = wrap_conf_type(loadwsgi.loadapp)
|
||||||
|
|
||||||
|
|
||||||
def monkey_patch_mimetools():
|
def monkey_patch_mimetools():
|
||||||
"""
|
"""
|
||||||
mimetools.Message defaults content-type to "text/plain"
|
mimetools.Message defaults content-type to "text/plain"
|
||||||
@ -121,18 +189,47 @@ class RestrictedGreenPool(GreenPool):
|
|||||||
self.waitall()
|
self.waitall()
|
||||||
|
|
||||||
|
|
||||||
# TODO: pull pieces of this out to test
|
def run_server(conf, logger, sock):
|
||||||
def run_wsgi(conf_file, app_section, *args, **kwargs):
|
wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
|
||||||
|
# Turn off logging requests by the underlying WSGI software.
|
||||||
|
wsgi.HttpProtocol.log_request = lambda *a: None
|
||||||
|
# Redirect logging other messages by the underlying WSGI software.
|
||||||
|
wsgi.HttpProtocol.log_message = \
|
||||||
|
lambda s, f, *a: logger.error('ERROR WSGI: ' + f % a)
|
||||||
|
wsgi.WRITE_TIMEOUT = int(conf.get('client_timeout') or 60)
|
||||||
|
|
||||||
|
eventlet.hubs.use_hub(get_hub())
|
||||||
|
eventlet.patcher.monkey_patch(all=False, socket=True)
|
||||||
|
eventlet_debug = config_true_value(conf.get('eventlet_debug', 'no'))
|
||||||
|
eventlet.debug.hub_exceptions(eventlet_debug)
|
||||||
|
# utils.LogAdapter stashes name in server; fallback on unadapted loggers
|
||||||
|
if hasattr(logger, 'server'):
|
||||||
|
log_name = logger.server
|
||||||
|
else:
|
||||||
|
log_name = logger.name
|
||||||
|
app = loadapp(conf['__file__'], global_conf={'log_name': log_name})
|
||||||
|
max_clients = int(conf.get('max_clients', '1024'))
|
||||||
|
pool = RestrictedGreenPool(size=max_clients)
|
||||||
|
try:
|
||||||
|
wsgi.server(sock, app, NullLogger(), custom_pool=pool)
|
||||||
|
except socket.error, err:
|
||||||
|
if err[0] != errno.EINVAL:
|
||||||
|
raise
|
||||||
|
pool.waitall()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: pull more pieces of this to test more
|
||||||
|
def run_wsgi(conf_path, app_section, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Runs the server using the specified number of workers.
|
Runs the server using the specified number of workers.
|
||||||
|
|
||||||
:param conf_file: Path to paste.deploy style configuration file
|
:param conf_path: Path to paste.deploy style configuration file/directory
|
||||||
:param app_section: App name from conf file to load config from
|
:param app_section: App name from conf file to load config from
|
||||||
"""
|
"""
|
||||||
# Load configuration, Set logger and Load request processor
|
# Load configuration, Set logger and Load request processor
|
||||||
try:
|
try:
|
||||||
(app, conf, logger, log_name) = \
|
(app, conf, logger, log_name) = \
|
||||||
init_request_processor(conf_file, app_section, *args, **kwargs)
|
init_request_processor(conf_path, app_section, *args, **kwargs)
|
||||||
except ConfigFileError, e:
|
except ConfigFileError, e:
|
||||||
print e
|
print e
|
||||||
return
|
return
|
||||||
@ -148,34 +245,10 @@ def run_wsgi(conf_file, app_section, *args, **kwargs):
|
|||||||
# redirect errors to logger and close stdio
|
# redirect errors to logger and close stdio
|
||||||
capture_stdio(logger)
|
capture_stdio(logger)
|
||||||
|
|
||||||
def run_server(max_clients):
|
|
||||||
wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
|
|
||||||
# Turn off logging requests by the underlying WSGI software.
|
|
||||||
wsgi.HttpProtocol.log_request = lambda *a: None
|
|
||||||
# Redirect logging other messages by the underlying WSGI software.
|
|
||||||
wsgi.HttpProtocol.log_message = \
|
|
||||||
lambda s, f, *a: logger.error('ERROR WSGI: ' + f % a)
|
|
||||||
wsgi.WRITE_TIMEOUT = int(conf.get('client_timeout') or 60)
|
|
||||||
|
|
||||||
eventlet.hubs.use_hub(get_hub())
|
|
||||||
eventlet.patcher.monkey_patch(all=False, socket=True)
|
|
||||||
eventlet_debug = config_true_value(conf.get('eventlet_debug', 'no'))
|
|
||||||
eventlet.debug.hub_exceptions(eventlet_debug)
|
|
||||||
app = loadapp('config:%s' % conf_file,
|
|
||||||
global_conf={'log_name': log_name})
|
|
||||||
pool = RestrictedGreenPool(size=max_clients)
|
|
||||||
try:
|
|
||||||
wsgi.server(sock, app, NullLogger(), custom_pool=pool)
|
|
||||||
except socket.error, err:
|
|
||||||
if err[0] != errno.EINVAL:
|
|
||||||
raise
|
|
||||||
pool.waitall()
|
|
||||||
|
|
||||||
max_clients = int(conf.get('max_clients', '1024'))
|
|
||||||
worker_count = int(conf.get('workers', '1'))
|
worker_count = int(conf.get('workers', '1'))
|
||||||
# Useful for profiling [no forks].
|
# Useful for profiling [no forks].
|
||||||
if worker_count == 0:
|
if worker_count == 0:
|
||||||
run_server(max_clients)
|
run_server(conf, logger, sock)
|
||||||
return
|
return
|
||||||
|
|
||||||
def kill_children(*args):
|
def kill_children(*args):
|
||||||
@ -201,7 +274,7 @@ def run_wsgi(conf_file, app_section, *args, **kwargs):
|
|||||||
if pid == 0:
|
if pid == 0:
|
||||||
signal.signal(signal.SIGHUP, signal.SIG_DFL)
|
signal.signal(signal.SIGHUP, signal.SIG_DFL)
|
||||||
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||||
run_server(max_clients)
|
run_server(conf, logger, sock)
|
||||||
logger.notice('Child %d exiting normally' % os.getpid())
|
logger.notice('Child %d exiting normally' % os.getpid())
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -227,22 +300,22 @@ class ConfigFileError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def init_request_processor(conf_file, app_section, *args, **kwargs):
|
def init_request_processor(conf_path, app_section, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Loads common settings from conf
|
Loads common settings from conf
|
||||||
Sets the logger
|
Sets the logger
|
||||||
Loads the request processor
|
Loads the request processor
|
||||||
|
|
||||||
:param conf_file: Path to paste.deploy style configuration file
|
:param conf_path: Path to paste.deploy style configuration file/directory
|
||||||
:param app_section: App name from conf file to load config from
|
:param app_section: App name from conf file to load config from
|
||||||
:returns: the loaded application entry point
|
:returns: the loaded application entry point
|
||||||
:raises ConfigFileError: Exception is raised for config file error
|
:raises ConfigFileError: Exception is raised for config file error
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
conf = appconfig('config:%s' % conf_file, name=app_section)
|
conf = appconfig(conf_path, name=app_section)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
raise ConfigFileError("Error trying to load config %s: %s" %
|
raise ConfigFileError("Error trying to load config from %s: %s" %
|
||||||
(conf_file, e))
|
(conf_path, e))
|
||||||
|
|
||||||
validate_configuration()
|
validate_configuration()
|
||||||
|
|
||||||
@ -260,7 +333,7 @@ def init_request_processor(conf_file, app_section, *args, **kwargs):
|
|||||||
disable_fallocate()
|
disable_fallocate()
|
||||||
|
|
||||||
monkey_patch_mimetools()
|
monkey_patch_mimetools()
|
||||||
app = loadapp('config:%s' % conf_file, global_conf={'log_name': log_name})
|
app = loadapp(conf_path, global_conf={'log_name': log_name})
|
||||||
return (app, conf, logger, log_name)
|
return (app, conf, logger, log_name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -453,6 +453,47 @@ class TestServer(unittest.TestCase):
|
|||||||
conf = self.join_swift_dir(server_name + '.conf')
|
conf = self.join_swift_dir(server_name + '.conf')
|
||||||
self.assertEquals(conf_file, conf)
|
self.assertEquals(conf_file, conf)
|
||||||
|
|
||||||
|
def test_proxy_conf_dir(self):
|
||||||
|
conf_files = (
|
||||||
|
'proxy-server.conf.d/00.conf',
|
||||||
|
'proxy-server.conf.d/01.conf',
|
||||||
|
)
|
||||||
|
with temptree(conf_files) as t:
|
||||||
|
manager.SWIFT_DIR = t
|
||||||
|
server = manager.Server('proxy')
|
||||||
|
conf_dirs = server.conf_files()
|
||||||
|
self.assertEquals(len(conf_dirs), 1)
|
||||||
|
conf_dir = conf_dirs[0]
|
||||||
|
proxy_conf_dir = self.join_swift_dir('proxy-server.conf.d')
|
||||||
|
self.assertEquals(proxy_conf_dir, conf_dir)
|
||||||
|
|
||||||
|
def test_conf_dir(self):
|
||||||
|
conf_files = (
|
||||||
|
'object-server/object-server.conf-base',
|
||||||
|
'object-server/1.conf.d/base.conf',
|
||||||
|
'object-server/1.conf.d/1.conf',
|
||||||
|
'object-server/2.conf.d/base.conf',
|
||||||
|
'object-server/2.conf.d/2.conf',
|
||||||
|
'object-server/3.conf.d/base.conf',
|
||||||
|
'object-server/3.conf.d/3.conf',
|
||||||
|
'object-server/4.conf.d/base.conf',
|
||||||
|
'object-server/4.conf.d/4.conf',
|
||||||
|
)
|
||||||
|
with temptree(conf_files) as t:
|
||||||
|
manager.SWIFT_DIR = t
|
||||||
|
server = manager.Server('object-replicator')
|
||||||
|
conf_dirs = server.conf_files()
|
||||||
|
self.assertEquals(len(conf_dirs), 4)
|
||||||
|
c1 = self.join_swift_dir('object-server/1.conf.d')
|
||||||
|
c2 = self.join_swift_dir('object-server/2.conf.d')
|
||||||
|
c3 = self.join_swift_dir('object-server/3.conf.d')
|
||||||
|
c4 = self.join_swift_dir('object-server/4.conf.d')
|
||||||
|
for c in [c1, c2, c3, c4]:
|
||||||
|
self.assert_(c in conf_dirs)
|
||||||
|
# test configs returned sorted
|
||||||
|
sorted_confs = sorted([c1, c2, c3, c4])
|
||||||
|
self.assertEquals(conf_dirs, sorted_confs)
|
||||||
|
|
||||||
def test_iter_pid_files(self):
|
def test_iter_pid_files(self):
|
||||||
"""
|
"""
|
||||||
Server.iter_pid_files is kinda boring, test the
|
Server.iter_pid_files is kinda boring, test the
|
||||||
|
@ -25,6 +25,7 @@ import random
|
|||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
from textwrap import dedent
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
@ -366,7 +367,7 @@ class TestUtils(unittest.TestCase):
|
|||||||
utils.sys.stderr = stde
|
utils.sys.stderr = stde
|
||||||
self.assertRaises(SystemExit, utils.parse_options, once=True,
|
self.assertRaises(SystemExit, utils.parse_options, once=True,
|
||||||
test_args=[])
|
test_args=[])
|
||||||
self.assert_('missing config file' in stdo.getvalue())
|
self.assert_('missing config' in stdo.getvalue())
|
||||||
|
|
||||||
# verify conf file must exist, context manager will delete temp file
|
# verify conf file must exist, context manager will delete temp file
|
||||||
with NamedTemporaryFile() as f:
|
with NamedTemporaryFile() as f:
|
||||||
@ -721,6 +722,84 @@ log_name = %(yarr)s'''
|
|||||||
os.unlink('/tmp/test')
|
os.unlink('/tmp/test')
|
||||||
self.assertRaises(SystemExit, utils.readconf, '/tmp/test')
|
self.assertRaises(SystemExit, utils.readconf, '/tmp/test')
|
||||||
|
|
||||||
|
def test_readconf_dir(self):
|
||||||
|
config_dir = {
|
||||||
|
'server.conf.d/01.conf': """
|
||||||
|
[DEFAULT]
|
||||||
|
port = 8080
|
||||||
|
foo = bar
|
||||||
|
|
||||||
|
[section1]
|
||||||
|
name=section1
|
||||||
|
""",
|
||||||
|
'server.conf.d/section2.conf': """
|
||||||
|
[DEFAULT]
|
||||||
|
port = 8081
|
||||||
|
bar = baz
|
||||||
|
|
||||||
|
[section2]
|
||||||
|
name=section2
|
||||||
|
""",
|
||||||
|
'other-server.conf.d/01.conf': """
|
||||||
|
[DEFAULT]
|
||||||
|
port = 8082
|
||||||
|
|
||||||
|
[section3]
|
||||||
|
name=section3
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
# strip indent from test config contents
|
||||||
|
config_dir = dict((f, dedent(c)) for (f, c) in config_dir.items())
|
||||||
|
with temptree(*zip(*config_dir.items())) as path:
|
||||||
|
conf_dir = os.path.join(path, 'server.conf.d')
|
||||||
|
conf = utils.readconf(conf_dir)
|
||||||
|
expected = {
|
||||||
|
'__file__': os.path.join(path, 'server.conf.d'),
|
||||||
|
'log_name': None,
|
||||||
|
'section1': {
|
||||||
|
'port': '8081',
|
||||||
|
'foo': 'bar',
|
||||||
|
'bar': 'baz',
|
||||||
|
'name': 'section1',
|
||||||
|
},
|
||||||
|
'section2': {
|
||||||
|
'port': '8081',
|
||||||
|
'foo': 'bar',
|
||||||
|
'bar': 'baz',
|
||||||
|
'name': 'section2',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.assertEquals(conf, expected)
|
||||||
|
|
||||||
|
def test_readconf_dir_ignores_hidden_and_nondotconf_files(self):
|
||||||
|
config_dir = {
|
||||||
|
'server.conf.d/01.conf': """
|
||||||
|
[section1]
|
||||||
|
port = 8080
|
||||||
|
""",
|
||||||
|
'server.conf.d/.01.conf.swp': """
|
||||||
|
[section]
|
||||||
|
port = 8081
|
||||||
|
""",
|
||||||
|
'server.conf.d/01.conf-bak': """
|
||||||
|
[section]
|
||||||
|
port = 8082
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
# strip indent from test config contents
|
||||||
|
config_dir = dict((f, dedent(c)) for (f, c) in config_dir.items())
|
||||||
|
with temptree(*zip(*config_dir.items())) as path:
|
||||||
|
conf_dir = os.path.join(path, 'server.conf.d')
|
||||||
|
conf = utils.readconf(conf_dir)
|
||||||
|
expected = {
|
||||||
|
'__file__': os.path.join(path, 'server.conf.d'),
|
||||||
|
'log_name': None,
|
||||||
|
'section1': {
|
||||||
|
'port': '8080',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.assertEquals(conf, expected)
|
||||||
|
|
||||||
def test_drop_privileges(self):
|
def test_drop_privileges(self):
|
||||||
user = getuser()
|
user = getuser()
|
||||||
# over-ride os with mock
|
# over-ride os with mock
|
||||||
@ -925,6 +1004,26 @@ log_name = %(yarr)s'''
|
|||||||
for f in [f1, f2, f3, f4]:
|
for f in [f1, f2, f3, f4]:
|
||||||
self.assert_(f in folder_texts)
|
self.assert_(f in folder_texts)
|
||||||
|
|
||||||
|
def test_search_tree_with_directory_ext_match(self):
|
||||||
|
files = (
|
||||||
|
'object-server/object-server.conf-base',
|
||||||
|
'object-server/1.conf.d/base.conf',
|
||||||
|
'object-server/1.conf.d/1.conf',
|
||||||
|
'object-server/2.conf.d/base.conf',
|
||||||
|
'object-server/2.conf.d/2.conf',
|
||||||
|
'object-server/3.conf.d/base.conf',
|
||||||
|
'object-server/3.conf.d/3.conf',
|
||||||
|
'object-server/4.conf.d/base.conf',
|
||||||
|
'object-server/4.conf.d/4.conf',
|
||||||
|
)
|
||||||
|
with temptree(files) as t:
|
||||||
|
conf_dirs = utils.search_tree(t, 'object-server', '.conf',
|
||||||
|
dir_ext='conf.d')
|
||||||
|
self.assertEquals(len(conf_dirs), 4)
|
||||||
|
for i in range(4):
|
||||||
|
conf_dir = os.path.join(t, 'object-server/%d.conf.d' % (i + 1))
|
||||||
|
self.assert_(conf_dir in conf_dirs)
|
||||||
|
|
||||||
def test_write_file(self):
|
def test_write_file(self):
|
||||||
with temptree([]) as t:
|
with temptree([]) as t:
|
||||||
file_name = os.path.join(t, 'test')
|
file_name = os.path.join(t, 'test')
|
||||||
|
@ -13,24 +13,64 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
""" Tests for swift.common.utils """
|
""" Tests for swift.common.wsgi """
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
import errno
|
import errno
|
||||||
|
import logging
|
||||||
import mimetools
|
import mimetools
|
||||||
import socket
|
import socket
|
||||||
import unittest
|
import unittest
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
from textwrap import dedent
|
||||||
|
from gzip import GzipFile
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from urllib import quote
|
from urllib import quote
|
||||||
|
|
||||||
|
from eventlet import listen
|
||||||
|
|
||||||
|
import swift
|
||||||
from swift.common.swob import Request
|
from swift.common.swob import Request
|
||||||
from swift.common import wsgi
|
from swift.common import wsgi, utils, ring
|
||||||
|
|
||||||
|
from test.unit import temptree
|
||||||
|
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
def _fake_rings(tmpdir):
|
||||||
|
pickle.dump(ring.RingData([[0, 1, 0, 1], [1, 0, 1, 0]],
|
||||||
|
[{'id': 0, 'zone': 0, 'device': 'sda1', 'ip': '127.0.0.1',
|
||||||
|
'port': 6012},
|
||||||
|
{'id': 1, 'zone': 1, 'device': 'sdb1', 'ip': '127.0.0.1',
|
||||||
|
'port': 6022}], 30),
|
||||||
|
GzipFile(os.path.join(tmpdir, 'account.ring.gz'), 'wb'))
|
||||||
|
pickle.dump(ring.RingData([[0, 1, 0, 1], [1, 0, 1, 0]],
|
||||||
|
[{'id': 0, 'zone': 0, 'device': 'sda1', 'ip': '127.0.0.1',
|
||||||
|
'port': 6011},
|
||||||
|
{'id': 1, 'zone': 1, 'device': 'sdb1', 'ip': '127.0.0.1',
|
||||||
|
'port': 6021}], 30),
|
||||||
|
GzipFile(os.path.join(tmpdir, 'container.ring.gz'), 'wb'))
|
||||||
|
pickle.dump(ring.RingData([[0, 1, 0, 1], [1, 0, 1, 0]],
|
||||||
|
[{'id': 0, 'zone': 0, 'device': 'sda1', 'ip': '127.0.0.1',
|
||||||
|
'port': 6010},
|
||||||
|
{'id': 1, 'zone': 1, 'device': 'sdb1', 'ip': '127.0.0.1',
|
||||||
|
'port': 6020}], 30),
|
||||||
|
GzipFile(os.path.join(tmpdir, 'object.ring.gz'), 'wb'))
|
||||||
|
|
||||||
|
|
||||||
class TestWSGI(unittest.TestCase):
|
class TestWSGI(unittest.TestCase):
|
||||||
""" Tests for swift.common.wsgi """
|
""" Tests for swift.common.wsgi """
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
utils.HASH_PATH_PREFIX = 'startcap'
|
||||||
|
self._orig_parsetype = mimetools.Message.parsetype
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
mimetools.Message.parsetype = self._orig_parsetype
|
||||||
|
|
||||||
def test_monkey_patch_mimetools(self):
|
def test_monkey_patch_mimetools(self):
|
||||||
sio = StringIO('blah')
|
sio = StringIO('blah')
|
||||||
self.assertEquals(mimetools.Message(sio).type, 'text/plain')
|
self.assertEquals(mimetools.Message(sio).type, 'text/plain')
|
||||||
@ -69,6 +109,90 @@ class TestWSGI(unittest.TestCase):
|
|||||||
sio = StringIO('Content-Type: text/html; charset=ISO-8859-4')
|
sio = StringIO('Content-Type: text/html; charset=ISO-8859-4')
|
||||||
self.assertEquals(mimetools.Message(sio).subtype, 'html')
|
self.assertEquals(mimetools.Message(sio).subtype, 'html')
|
||||||
|
|
||||||
|
def test_init_request_processor(self):
|
||||||
|
config = """
|
||||||
|
[DEFAULT]
|
||||||
|
swift_dir = TEMPDIR
|
||||||
|
|
||||||
|
[pipeline:main]
|
||||||
|
pipeline = catch_errors proxy-server
|
||||||
|
|
||||||
|
[app:proxy-server]
|
||||||
|
use = egg:swift#proxy
|
||||||
|
conn_timeout = 0.2
|
||||||
|
|
||||||
|
[filter:catch_errors]
|
||||||
|
use = egg:swift#catch_errors
|
||||||
|
"""
|
||||||
|
contents = dedent(config)
|
||||||
|
with temptree(['proxy-server.conf']) as t:
|
||||||
|
conf_file = os.path.join(t, 'proxy-server.conf')
|
||||||
|
with open(conf_file, 'w') as f:
|
||||||
|
f.write(contents.replace('TEMPDIR', t))
|
||||||
|
_fake_rings(t)
|
||||||
|
app, conf, logger, log_name = wsgi.init_request_processor(
|
||||||
|
conf_file, 'proxy-server')
|
||||||
|
# verify pipeline is catch_errors -> proxy-servery
|
||||||
|
expected = swift.common.middleware.catch_errors.CatchErrorMiddleware
|
||||||
|
self.assert_(isinstance(app, expected))
|
||||||
|
self.assert_(isinstance(app.app, swift.proxy.server.Application))
|
||||||
|
# config settings applied to app instance
|
||||||
|
self.assertEquals(0.2, app.app.conn_timeout)
|
||||||
|
# appconfig returns values from 'proxy-server' section
|
||||||
|
expected = {
|
||||||
|
'__file__': conf_file,
|
||||||
|
'here': os.path.dirname(conf_file),
|
||||||
|
'conn_timeout': '0.2',
|
||||||
|
'swift_dir': t,
|
||||||
|
}
|
||||||
|
self.assertEquals(expected, conf)
|
||||||
|
# logger works
|
||||||
|
logger.info('testing')
|
||||||
|
self.assertEquals('proxy-server', log_name)
|
||||||
|
|
||||||
|
def test_init_request_processor_from_conf_dir(self):
|
||||||
|
config_dir = {
|
||||||
|
'proxy-server.conf.d/pipeline.conf': """
|
||||||
|
[pipeline:main]
|
||||||
|
pipeline = catch_errors proxy-server
|
||||||
|
""",
|
||||||
|
'proxy-server.conf.d/app.conf': """
|
||||||
|
[app:proxy-server]
|
||||||
|
use = egg:swift#proxy
|
||||||
|
conn_timeout = 0.2
|
||||||
|
""",
|
||||||
|
'proxy-server.conf.d/catch-errors.conf': """
|
||||||
|
[filter:catch_errors]
|
||||||
|
use = egg:swift#catch_errors
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
# strip indent from test config contents
|
||||||
|
config_dir = dict((f, dedent(c)) for (f, c) in config_dir.items())
|
||||||
|
with temptree(*zip(*config_dir.items())) as conf_root:
|
||||||
|
conf_dir = os.path.join(conf_root, 'proxy-server.conf.d')
|
||||||
|
with open(os.path.join(conf_dir, 'swift.conf'), 'w') as f:
|
||||||
|
f.write('[DEFAULT]\nswift_dir = %s' % conf_root)
|
||||||
|
_fake_rings(conf_root)
|
||||||
|
app, conf, logger, log_name = wsgi.init_request_processor(
|
||||||
|
conf_dir, 'proxy-server')
|
||||||
|
# verify pipeline is catch_errors -> proxy-servery
|
||||||
|
expected = swift.common.middleware.catch_errors.CatchErrorMiddleware
|
||||||
|
self.assert_(isinstance(app, expected))
|
||||||
|
self.assert_(isinstance(app.app, swift.proxy.server.Application))
|
||||||
|
# config settings applied to app instance
|
||||||
|
self.assertEquals(0.2, app.app.conn_timeout)
|
||||||
|
# appconfig returns values from 'proxy-server' section
|
||||||
|
expected = {
|
||||||
|
'__file__': conf_dir,
|
||||||
|
'here': conf_dir,
|
||||||
|
'conn_timeout': '0.2',
|
||||||
|
'swift_dir': conf_root,
|
||||||
|
}
|
||||||
|
self.assertEquals(expected, conf)
|
||||||
|
# logger works
|
||||||
|
logger.info('testing')
|
||||||
|
self.assertEquals('proxy-server', log_name)
|
||||||
|
|
||||||
def test_get_socket(self):
|
def test_get_socket(self):
|
||||||
# stubs
|
# stubs
|
||||||
conf = {}
|
conf = {}
|
||||||
@ -170,6 +294,117 @@ class TestWSGI(unittest.TestCase):
|
|||||||
wsgi.sleep = old_sleep
|
wsgi.sleep = old_sleep
|
||||||
wsgi.time = old_time
|
wsgi.time = old_time
|
||||||
|
|
||||||
|
def test_run_server(self):
|
||||||
|
config = """
|
||||||
|
[DEFAULT]
|
||||||
|
eventlet_debug = yes
|
||||||
|
client_timeout = 30
|
||||||
|
swift_dir = TEMPDIR
|
||||||
|
|
||||||
|
[pipeline:main]
|
||||||
|
pipeline = proxy-server
|
||||||
|
|
||||||
|
[app:proxy-server]
|
||||||
|
use = egg:swift#proxy
|
||||||
|
"""
|
||||||
|
|
||||||
|
contents = dedent(config)
|
||||||
|
with temptree(['proxy-server.conf']) as t:
|
||||||
|
conf_file = os.path.join(t, 'proxy-server.conf')
|
||||||
|
with open(conf_file, 'w') as f:
|
||||||
|
f.write(contents.replace('TEMPDIR', t))
|
||||||
|
_fake_rings(t)
|
||||||
|
with patch('swift.common.wsgi.wsgi') as _wsgi:
|
||||||
|
with patch('swift.common.wsgi.eventlet') as _eventlet:
|
||||||
|
conf = wsgi.appconfig(conf_file)
|
||||||
|
logger = logging.getLogger('test')
|
||||||
|
sock = listen(('localhost', 0))
|
||||||
|
wsgi.run_server(conf, logger, sock)
|
||||||
|
self.assertEquals('HTTP/1.0',
|
||||||
|
_wsgi.HttpProtocol.default_request_version)
|
||||||
|
self.assertEquals(30, _wsgi.WRITE_TIMEOUT)
|
||||||
|
_eventlet.hubs.use_hub.assert_called_with(utils.get_hub())
|
||||||
|
_eventlet.patcher.monkey_patch.assert_called_with(all=False,
|
||||||
|
socket=True)
|
||||||
|
_eventlet.debug.hub_exceptions.assert_called_with(True)
|
||||||
|
_wsgi.server.assert_called()
|
||||||
|
args, kwargs = _wsgi.server.call_args
|
||||||
|
server_sock, server_app, server_logger = args
|
||||||
|
self.assertEquals(sock, server_sock)
|
||||||
|
self.assert_(isinstance(server_app, swift.proxy.server.Application))
|
||||||
|
self.assert_(isinstance(server_logger, wsgi.NullLogger))
|
||||||
|
self.assert_('custom_pool' in kwargs)
|
||||||
|
|
||||||
|
def test_run_server_conf_dir(self):
|
||||||
|
config_dir = {
|
||||||
|
'proxy-server.conf.d/pipeline.conf': """
|
||||||
|
[pipeline:main]
|
||||||
|
pipeline = proxy-server
|
||||||
|
""",
|
||||||
|
'proxy-server.conf.d/app.conf': """
|
||||||
|
[app:proxy-server]
|
||||||
|
use = egg:swift#proxy
|
||||||
|
""",
|
||||||
|
'proxy-server.conf.d/default.conf': """
|
||||||
|
[DEFAULT]
|
||||||
|
eventlet_debug = yes
|
||||||
|
client_timeout = 30
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
# strip indent from test config contents
|
||||||
|
config_dir = dict((f, dedent(c)) for (f, c) in config_dir.items())
|
||||||
|
with temptree(*zip(*config_dir.items())) as conf_root:
|
||||||
|
conf_dir = os.path.join(conf_root, 'proxy-server.conf.d')
|
||||||
|
with open(os.path.join(conf_dir, 'swift.conf'), 'w') as f:
|
||||||
|
f.write('[DEFAULT]\nswift_dir = %s' % conf_root)
|
||||||
|
_fake_rings(conf_root)
|
||||||
|
with patch('swift.common.wsgi.wsgi') as _wsgi:
|
||||||
|
with patch('swift.common.wsgi.eventlet') as _eventlet:
|
||||||
|
conf = wsgi.appconfig(conf_dir)
|
||||||
|
logger = logging.getLogger('test')
|
||||||
|
sock = listen(('localhost', 0))
|
||||||
|
wsgi.run_server(conf, logger, sock)
|
||||||
|
|
||||||
|
self.assertEquals('HTTP/1.0',
|
||||||
|
_wsgi.HttpProtocol.default_request_version)
|
||||||
|
self.assertEquals(30, _wsgi.WRITE_TIMEOUT)
|
||||||
|
_eventlet.hubs.use_hub.assert_called_with(utils.get_hub())
|
||||||
|
_eventlet.patcher.monkey_patch.assert_called_with(all=False,
|
||||||
|
socket=True)
|
||||||
|
_eventlet.debug.hub_exceptions.assert_called_with(True)
|
||||||
|
_wsgi.server.assert_called()
|
||||||
|
args, kwargs = _wsgi.server.call_args
|
||||||
|
server_sock, server_app, server_logger = args
|
||||||
|
self.assertEquals(sock, server_sock)
|
||||||
|
self.assert_(isinstance(server_app, swift.proxy.server.Application))
|
||||||
|
self.assert_(isinstance(server_logger, wsgi.NullLogger))
|
||||||
|
self.assert_('custom_pool' in kwargs)
|
||||||
|
|
||||||
|
def test_appconfig_dir_ignores_hidden_files(self):
|
||||||
|
config_dir = {
|
||||||
|
'server.conf.d/01.conf': """
|
||||||
|
[app:main]
|
||||||
|
use = egg:swift#proxy
|
||||||
|
port = 8080
|
||||||
|
""",
|
||||||
|
'server.conf.d/.01.conf.swp': """
|
||||||
|
[app:main]
|
||||||
|
use = egg:swift#proxy
|
||||||
|
port = 8081
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
# strip indent from test config contents
|
||||||
|
config_dir = dict((f, dedent(c)) for (f, c) in config_dir.items())
|
||||||
|
with temptree(*zip(*config_dir.items())) as path:
|
||||||
|
conf_dir = os.path.join(path, 'server.conf.d')
|
||||||
|
conf = wsgi.appconfig(conf_dir)
|
||||||
|
expected = {
|
||||||
|
'__file__': os.path.join(path, 'server.conf.d'),
|
||||||
|
'here': os.path.join(path, 'server.conf.d'),
|
||||||
|
'port': '8080',
|
||||||
|
}
|
||||||
|
self.assertEquals(conf, expected)
|
||||||
|
|
||||||
def test_pre_auth_wsgi_input(self):
|
def test_pre_auth_wsgi_input(self):
|
||||||
oldenv = {}
|
oldenv = {}
|
||||||
newenv = wsgi.make_pre_authed_env(oldenv)
|
newenv = wsgi.make_pre_authed_env(oldenv)
|
||||||
@ -246,6 +481,7 @@ class TestWSGI(unittest.TestCase):
|
|||||||
self.assertEquals(r.body, 'the body')
|
self.assertEquals(r.body, 'the body')
|
||||||
self.assertEquals(r.environ['swift.source'], 'UT')
|
self.assertEquals(r.environ['swift.source'], 'UT')
|
||||||
|
|
||||||
|
|
||||||
class TestWSGIContext(unittest.TestCase):
|
class TestWSGIContext(unittest.TestCase):
|
||||||
|
|
||||||
def test_app_call(self):
|
def test_app_call(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user