Merge branch 'master' into feature/hummingbird

Change-Id: I29d820e434e6e03bb11f07d88f686c3db99286ed
This commit is contained in:
Michael Barton 2016-01-07 02:19:13 +00:00
commit 93ddaffaeb
162 changed files with 10409 additions and 4302 deletions

View File

@ -1,9 +1,11 @@
#!/bin/bash #!/bin/bash
SRC_DIR=$(python -c "import os; print os.path.dirname(os.path.realpath('$0'))") SRC_DIR=$(python -c "import os; print os.path.dirname(os.path.realpath('$0'))")
set -e
cd ${SRC_DIR}/test/functional cd ${SRC_DIR}
nosetests --exe $@ export TESTS_DIR=${SRC_DIR}/test/functional
ostestr --serial --pretty $@
rvalue=$? rvalue=$?
cd - cd -

2
.gitignore vendored
View File

@ -16,4 +16,6 @@ pycscope.*
.idea .idea
MANIFEST MANIFEST
.testrepository/*
subunit.log
test/probe/.noseids test/probe/.noseids

View File

@ -58,7 +58,7 @@ Madhuri Kumari <madhuri.rai07@gmail.com> madhuri <madhuri@madhuri-VirtualBox.(no
Morgan Fainberg <morgan.fainberg@gmail.com> <m@metacloud.com> Morgan Fainberg <morgan.fainberg@gmail.com> <m@metacloud.com>
Hua Zhang <zhuadl@cn.ibm.com> <zhuadl@cn.ibm.com> Hua Zhang <zhuadl@cn.ibm.com> <zhuadl@cn.ibm.com>
Yummy Bian <yummy.bian@gmail.com> <yummy.bian@gmail.com> Yummy Bian <yummy.bian@gmail.com> <yummy.bian@gmail.com>
Alistair Coles <alistair.coles@hp.com> <alistair.coles@hp.com> Alistair Coles <alistair.coles@hpe.com> <alistair.coles@hp.com>
Tong Li <litong01@us.ibm.com> <litong01@us.ibm.com> Tong Li <litong01@us.ibm.com> <litong01@us.ibm.com>
Paul Luse <paul.e.luse@intel.com> <paul.e.luse@intel.com> Paul Luse <paul.e.luse@intel.com> <paul.e.luse@intel.com>
Yuan Zhou <yuan.zhou@intel.com> <yuan.zhou@intel.com> Yuan Zhou <yuan.zhou@intel.com> <yuan.zhou@intel.com>
@ -66,9 +66,9 @@ Jola Mirecka <jola.mirecka@hp.com> <jola.mirecka@hp.com>
Ning Zhang <ning@zmanda.com> <ning@zmanda.com> Ning Zhang <ning@zmanda.com> <ning@zmanda.com>
Mauro Stettler <mauro.stettler@gmail.com> <mauro.stettler@gmail.com> Mauro Stettler <mauro.stettler@gmail.com> <mauro.stettler@gmail.com>
Pawel Palucki <pawel.palucki@gmail.com> <pawel.palucki@gmail.com> Pawel Palucki <pawel.palucki@gmail.com> <pawel.palucki@gmail.com>
Guang Yee <guang.yee@hp.com> <guang.yee@hp.com> Guang Yee <guang.yee@hpe.com> <guang.yee@hp.com>
Jing Liuqing <jing.liuqing@99cloud.net> <jing.liuqing@99cloud.net> Jing Liuqing <jing.liuqing@99cloud.net> <jing.liuqing@99cloud.net>
Lorcan Browne <lorcan.browne@hp.com> <lorcan.browne@hp.com> Lorcan Browne <lorcan.browne@hpe.com> <lorcan.browne@hp.com>
Eohyung Lee <liquidnuker@gmail.com> <liquid@kt.com> Eohyung Lee <liquidnuker@gmail.com> <liquid@kt.com>
Harshit Chitalia <harshit@acelio.com> <harshit@acelio.com> Harshit Chitalia <harshit@acelio.com> <harshit@acelio.com>
Richard Hawkins <richard.hawkins@rackspace.com> Richard Hawkins <richard.hawkins@rackspace.com>
@ -83,3 +83,7 @@ Atsushi Sakai <sakaia@jp.fujitsu.com>
Takashi Natsume <natsume.takashi@lab.ntt.co.jp> Takashi Natsume <natsume.takashi@lab.ntt.co.jp>
Nakagawa Masaaki <nakagawamsa@nttdata.co.jp> nakagawamsa Nakagawa Masaaki <nakagawamsa@nttdata.co.jp> nakagawamsa
Romain Le Disez <romain.ledisez@ovh.net> Romain LE DISEZ Romain Le Disez <romain.ledisez@ovh.net> Romain LE DISEZ
Donagh McCabe <donagh.mccabe@hpe.com> <donagh.mccabe@hp.com>
Eamonn O'Toole <eamonn.otoole@hpe.com> <eamonn.otoole@hp.com>
Gerry Drudy <gerry.drudy@hpe.com> <gerry.drudy@hp.com>
Mark Seger <mark.seger@hpe.com> <mark.seger@hp.com>

4
.testr.conf Normal file
View File

@ -0,0 +1,4 @@
[DEFAULT]
test_command=SWIFT_TEST_DEBUG_LOGS=${SWIFT_TEST_DEBUG_LOGS} ${PYTHON:-python} -m subunit.run discover -t ./ ${TESTS_DIR:-./test/functional/} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -20,7 +20,7 @@ from hashlib import md5
import getopt import getopt
from itertools import chain from itertools import chain
import simplejson import json
from eventlet.greenpool import GreenPool from eventlet.greenpool import GreenPool
from eventlet.event import Event from eventlet.event import Event
from six.moves.urllib.parse import quote from six.moves.urllib.parse import quote
@ -176,7 +176,7 @@ class Auditor(object):
break break
if node['id'] not in responses: if node['id'] not in responses:
responses[node['id']] = dict(resp.getheaders()) responses[node['id']] = dict(resp.getheaders())
results = simplejson.loads(resp.read()) results = json.loads(resp.read())
except Exception: except Exception:
self.container_exceptions += 1 self.container_exceptions += 1
consistent = False consistent = False
@ -249,7 +249,7 @@ class Auditor(object):
" from %ss:%ss" % " from %ss:%ss" %
(account, node['ip'], node['device'])) (account, node['ip'], node['device']))
break break
results = simplejson.loads(resp.read()) results = json.loads(resp.read())
except Exception: except Exception:
self.account_exceptions += 1 self.account_exceptions += 1
consistent = False consistent = False

View File

@ -14,15 +14,12 @@
# 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.
import json
from collections import defaultdict from collections import defaultdict
from six.moves.configparser import ConfigParser from six.moves.configparser import ConfigParser
from optparse import OptionParser from optparse import OptionParser
from sys import exit, stdout, stderr from sys import exit, stdout, stderr
from time import time from time import time
try:
import simplejson as json
except ImportError:
import json
from eventlet import GreenPool, hubs, patcher, Timeout from eventlet import GreenPool, hubs, patcher, Timeout
from eventlet.pools import Pool from eventlet.pools import Pool

View File

@ -20,7 +20,21 @@ from optparse import OptionParser
from swift.common.manager import Manager, UnknownCommandError, \ from swift.common.manager import Manager, UnknownCommandError, \
KILL_WAIT, RUN_DIR KILL_WAIT, RUN_DIR
USAGE = """%prog <server>[.config] [<server>[.config] ...] <command> [options] USAGE = \
"""%prog <server>[.<config>] [<server>[.<config>] ...] <command> [options]
where:
<server> is the name of a swift service e.g. proxy-server.
The '-server' part of the name may be omitted.
<config> is an explicit configuration filename without the
.conf extension. If <config> is specified then <server> should
refer to a directory containing the configuration file, e.g.:
swift-init object.1 start
will start an object-server using the configuration file
/etc/swift/object-server/1.conf
<command> is a command from the list below.
Commands: Commands:
""" + '\n'.join(["%16s: %s" % x for x in Manager.list_commands()]) """ + '\n'.join(["%16s: %s" % x for x in Manager.list_commands()])
@ -50,6 +64,16 @@ def main():
dest="run_dir", default=RUN_DIR, dest="run_dir", default=RUN_DIR,
help="alternative directory to store running pid files " help="alternative directory to store running pid files "
"default: %s" % RUN_DIR) "default: %s" % RUN_DIR)
# Changing behaviour if missing config
parser.add_option('--strict', dest='strict', action='store_true',
help="Return non-zero status code if some config is "
"missing. Default mode if all servers are "
"explicitly named.")
# a negative option for strict
parser.add_option('--non-strict', dest='strict', action='store_false',
help="Return zero status code even if some config is "
"missing. Default mode if any server is a glob or "
"one of aliases `all`, `main` or `rest`.")
options, args = parser.parse_args() options, args = parser.parse_args()

View File

@ -57,6 +57,8 @@ IP address the account server should bind to. The default is 0.0.0.0 which will
it bind to all available addresses. it bind to all available addresses.
.IP "\fBbind_port\fR" .IP "\fBbind_port\fR"
TCP port the account server should bind to. The default is 6002. TCP port the account server should bind to. The default is 6002.
.IP "\fBbind_timeout\fR"
Timeout to bind socket. The default is 30.
.IP \fBbacklog\fR .IP \fBbacklog\fR
TCP backlog. Maximum number of allowed pending connections. The default value is 4096. TCP backlog. Maximum number of allowed pending connections. The default value is 4096.
.IP \fBworkers\fR .IP \fBworkers\fR
@ -79,12 +81,46 @@ Parent directory or where devices are mounted. Default is /srv/node.
.IP \fBmount_check\fR .IP \fBmount_check\fR
Whether or not check if the devices are mounted to prevent accidentally writing to Whether or not check if the devices are mounted to prevent accidentally writing to
the root device. The default is set to true. the root device. The default is set to true.
.IP \fBdisable_fallocate\fR
Disable pre-allocate disk space for a file. The default is false.
.IP \fBlog_name\fR .IP \fBlog_name\fR
Label used when logging. The default is swift. Label used when logging. The default is swift.
.IP \fBlog_facility\fR .IP \fBlog_facility\fR
Syslog log facility. The default is LOG_LOCAL0. Syslog log facility. The default is LOG_LOCAL0.
.IP \fBlog_level\fR .IP \fBlog_level\fR
Logging level. The default is INFO. Logging level. The default is INFO.
.IP "\fBlog_address\fR
Logging address. The default is /dev/log.
.IP \fBlog_max_line_length\fR
The following caps the length of log lines to the value given; no limit if
set to 0, the default.
.IP \fBlog_custom_handlers\fR
Comma separated list of functions to call to setup custom log handlers.
functions get passed: conf, name, log_to_console, log_route, fmt, logger,
adapted_logger. The default is empty.
.IP \fBlog_udp_host\fR
If set, log_udp_host will override log_address.
.IP "\fBlog_udp_port\fR
UDP log port, the default is 514.
.IP \fBlog_statsd_host\fR = localhost
log_statsd_* enable StatsD logging.
.IP \fBlog_statsd_port\fR
The default is 8125.
.IP \fBlog_statsd_default_sample_rate\fR
The default is 1.
.IP \fBlog_statsd_sample_rate_factor\fR
The default is 1.
.IP \fBlog_statsd_metric_prefix\fR
The default is empty.
.IP \fBdb_preallocation\fR
If you don't mind the extra disk space usage in overhead, you can turn this
on to preallocate disk space with SQLite databases to decrease fragmentation.
The default is false.
.IP \fBeventlet_debug\fR
Debug mode for eventlet library. The default is false.
.IP \fBfallocate_reserve\fR
You can set fallocate_reserve to the number of bytes you'd like fallocate to
reserve, whether there is space for the given file size or not. The default is 0.
.RE .RE
.PD .PD
@ -117,12 +153,21 @@ This is normally \fBegg:swift#account\fR.
Label used when logging. The default is account-server. Label used when logging. The default is account-server.
.IP "\fBset log_facility\fR .IP "\fBset log_facility\fR
Syslog log facility. The default is LOG_LOCAL0. Syslog log facility. The default is LOG_LOCAL0.
.IP "\fB set log_level\fR .IP "\fBset log_level\fR
Logging level. The default is INFO. Logging level. The default is INFO.
.IP "\fB set log_requests\fR .IP "\fBset log_requests\fR
Enables request logging. The default is True. Enables request logging. The default is True.
.IP "\fB set log_address\fR .IP "\fBset log_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP "\fBauto_create_account_prefix\fR
The default is ".".
.IP "\fBreplication_server\fR
Configure parameter for creating specific server.
To handle all verbs, including replication verbs, do not specify
"replication_server" (this is the default). To only handle replication,
set to a true value (e.g. "true" or "1"). To handle only non-replication
verbs, set to "false". Unless you have a separate replication network, you
should not specify any value for "replication_server". The default is empty.
.RE .RE
.PD .PD
@ -158,6 +203,36 @@ and ensure that swift has read/write. The default is /var/cache/swift.
.RE .RE
.PD .PD
.RS 0
.IP "\fB[filter:xprofile]\fR"
.RS 3
.IP "\fBuse\fR"
Entry point for paste.deploy for the xprofile middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#xprofile\fR.
.IP "\fBprofile_module\fR"
This option enable you to switch profilers which should inherit from python
standard profiler. Currently the supported value can be 'cProfile', 'eventlet.green.profile' etc.
.IP "\fBlog_filename_prefix\fR"
This prefix will be used to combine process ID and timestamp to name the
profile data file. Make sure the executing user has permission to write
into this path (missing path segments will be created, if necessary).
If you enable profiling in more than one type of daemon, you must override
it with an unique value like, the default is /var/log/swift/profile/account.profile.
.IP "\fBdump_interval\fR"
The profile data will be dumped to local disk based on above naming rule
in this interval. The default is 5.0.
.IP "\fBdump_timestamp\fR"
Be careful, this option will enable profiler to dump data into the file with
time stamp which means there will be lots of files piled up in the directory.
The default is false
.IP "\fBpath\fR"
This is the path of the URL to access the mini web UI. The default is __profile__.
.IP "\fBflush_at_shutdown\fR"
Clear the data when the wsgi server shutdown. The default is false.
.IP "\fBunwind\fR"
Unwind the iterator of applications. Default is false.
.RE
.PD
.SH ADDITIONAL SECTIONS .SH ADDITIONAL SECTIONS
@ -177,7 +252,7 @@ Logging level. The default is INFO.
.IP \fBlog_address\fR .IP \fBlog_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBper_diff\fR .IP \fBper_diff\fR
The default is 1000. Maximum number of database rows that will be sync'd in a single HTTP replication request. The default is 1000.
.IP \fBmax_diffs\fR .IP \fBmax_diffs\fR
This caps how long the replicator will spend trying to sync a given database per pass so the other databases don't get starved. The default is 100. This caps how long the replicator will spend trying to sync a given database per pass so the other databases don't get starved. The default is 100.
.IP \fBconcurrency\fR .IP \fBconcurrency\fR
@ -193,6 +268,15 @@ Connection timeout to external services. The default is 0.5 seconds.
.IP \fBreclaim_age\fR .IP \fBreclaim_age\fR
Time elapsed in seconds before an account can be reclaimed. The default is Time elapsed in seconds before an account can be reclaimed. The default is
604800 seconds. 604800 seconds.
.IP \fBrsync_compress\fR
Allow rsync to compress data which is transmitted to destination node
during sync. However, this is applicable only when destination node is in
a different region than the local one. The default is false.
.IP \fBrsync_module\fR
Format of the rysnc module where the replicator will send data. See
etc/rsyncd.conf-sample for some usage examples.
.IP \fBrecon_cache_path\fR
Path to recon cache directory. The default is /var/cache/swift.
.RE .RE
@ -213,6 +297,8 @@ Logging address. The default is /dev/log.
Will audit, at most, 1 account per device per interval. The default is 1800 seconds. Will audit, at most, 1 account per device per interval. The default is 1800 seconds.
.IP \fBaccounts_per_second\fR .IP \fBaccounts_per_second\fR
Maximum accounts audited per second. Should be tuned according to individual system specs. 0 is unlimited. The default is 200. Maximum accounts audited per second. Should be tuned according to individual system specs. 0 is unlimited. The default is 200.
.IP \fBrecon_cache_path\fR
Path to recon cache directory. The default is /var/cache/swift.
.RE .RE
@ -237,6 +323,18 @@ Minimum time for a pass to take. The default is 3600 seconds.
Request timeout to external services. The default is 10 seconds. Request timeout to external services. The default is 10 seconds.
.IP \fBconn_timeout\fR .IP \fBconn_timeout\fR
Connection timeout to external services. The default is 0.5 seconds. Connection timeout to external services. The default is 0.5 seconds.
.IP \fBdelay_reaping\fR
Normally, the reaper begins deleting account information for deleted accounts
immediately; you can set this to delay its work however. The value is in
seconds. The default is 0.
.IP \fBreap_warn_after\fR
If the account fails to be be reaped due to a persistent error, the
account reaper will log a message such as:
Account <name> has not been reaped since <date>
You can search logs for this message if space is not being reclaimed
after you delete account(s).
Default is 2592000 seconds (30 days). This is in addition to any time
requested by delay_reaping.
.RE .RE
.PD .PD

View File

@ -57,6 +57,8 @@ IP address the container server should bind to. The default is 0.0.0.0 which wil
it bind to all available addresses. it bind to all available addresses.
.IP "\fBbind_port\fR" .IP "\fBbind_port\fR"
TCP port the container server should bind to. The default is 6001. TCP port the container server should bind to. The default is 6001.
.IP "\fBbind_timeout\fR"
Timeout to bind socket. The default is 30.
.IP \fBbacklog\fR .IP \fBbacklog\fR
TCP backlog. Maximum number of allowed pending connections. The default value is 4096. TCP backlog. Maximum number of allowed pending connections. The default value is 4096.
.IP \fBworkers\fR .IP \fBworkers\fR
@ -70,6 +72,12 @@ concurrent requests.
Maximum number of clients one worker can process simultaneously (it will Maximum number of clients one worker can process simultaneously (it will
actually accept(2) N + 1). Setting this to one (1) will only handle one request actually accept(2) N + 1). Setting this to one (1) will only handle one request
at a time, without accepting another request concurrently. The default is 1024. at a time, without accepting another request concurrently. The default is 1024.
.IP \fBallowed_sync_hosts\fR
This is a comma separated list of hosts allowed in the X-Container-Sync-To
field for containers. This is the old-style of using container sync. It is
strongly recommended to use the new style of a separate
container-sync-realms.conf -- see container-sync-realms.conf-sample
allowed_sync_hosts = 127.0.0.1
.IP \fBuser\fR .IP \fBuser\fR
The system user that the container server will run as. The default is swift. The system user that the container server will run as. The default is swift.
.IP \fBswift_dir\fR .IP \fBswift_dir\fR
@ -79,6 +87,8 @@ Parent directory or where devices are mounted. Default is /srv/node.
.IP \fBmount_check\fR .IP \fBmount_check\fR
Whether or not check if the devices are mounted to prevent accidentally writing to Whether or not check if the devices are mounted to prevent accidentally writing to
the root device. The default is set to true. the root device. The default is set to true.
.IP \fBdisable_fallocate\fR
Disable pre-allocate disk space for a file. The default is false.
.IP \fBlog_name\fR .IP \fBlog_name\fR
Label used when logging. The default is swift. Label used when logging. The default is swift.
.IP \fBlog_facility\fR .IP \fBlog_facility\fR
@ -87,6 +97,36 @@ Syslog log facility. The default is LOG_LOCAL0.
Logging level. The default is INFO. Logging level. The default is INFO.
.IP \fBlog_address\fR .IP \fBlog_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBlog_max_line_length\fR
The following caps the length of log lines to the value given; no limit if
set to 0, the default.
.IP \fBlog_custom_handlers\fR
Comma separated list of functions to call to setup custom log handlers.
functions get passed: conf, name, log_to_console, log_route, fmt, logger,
adapted_logger. The default is empty.
.IP \fBlog_udp_host\fR
If set, log_udp_host will override log_address.
.IP "\fBlog_udp_port\fR
UDP log port, the default is 514.
.IP \fBlog_statsd_host\fR = localhost
log_statsd_* enable StatsD logging.
.IP \fBlog_statsd_port\fR
The default is 8125.
.IP \fBlog_statsd_default_sample_rate\fR
The default is 1.
.IP \fBlog_statsd_sample_rate_factor\fR
The default is 1.
.IP \fBlog_statsd_metric_prefix\fR
The default is empty.
.IP \fBdb_preallocation\fR
If you don't mind the extra disk space usage in overhead, you can turn this
on to preallocate disk space with SQLite databases to decrease fragmentation.
The default is false.
.IP \fBeventlet_debug\fR
Debug mode for eventlet library. The default is false.
.IP \fBfallocate_reserve\fR
You can set fallocate_reserve to the number of bytes you'd like fallocate to
reserve, whether there is space for the given file size or not. The default is 0.
.RE .RE
.PD .PD
@ -129,6 +169,17 @@ Logging address. The default is /dev/log.
Request timeout to external services. The default is 3 seconds. Request timeout to external services. The default is 3 seconds.
.IP \fBconn_timeout\fR .IP \fBconn_timeout\fR
Connection timeout to external services. The default is 0.5 seconds. Connection timeout to external services. The default is 0.5 seconds.
.IP \fBallow_versions\fR
The default is false.
.IP \fBauto_create_account_prefix\fR
The default is '.'.
.IP \fBreplication_server\fR
Configure parameter for creating specific server.
To handle all verbs, including replication verbs, do not specify
"replication_server" (this is the default). To only handle replication,
set to a True value (e.g. "True" or "1"). To handle only non-replication
verbs, set to "False". Unless you have a separate replication network, you
should not specify any value for "replication_server".
.RE .RE
.PD .PD
@ -164,6 +215,36 @@ and ensure that swift has read/write. The default is /var/cache/swift.
.RE .RE
.PD .PD
.RS 0
.IP "\fB[filter:xprofile]\fR"
.RS 3
.IP "\fBuse\fR"
Entry point for paste.deploy for the xprofile middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#xprofile\fR.
.IP "\fBprofile_module\fR"
This option enable you to switch profilers which should inherit from python
standard profiler. Currently the supported value can be 'cProfile', 'eventlet.green.profile' etc.
.IP "\fBlog_filename_prefix\fR"
This prefix will be used to combine process ID and timestamp to name the
profile data file. Make sure the executing user has permission to write
into this path (missing path segments will be created, if necessary).
If you enable profiling in more than one type of daemon, you must override
it with an unique value like, the default is /var/log/swift/profile/account.profile.
.IP "\fBdump_interval\fR"
The profile data will be dumped to local disk based on above naming rule
in this interval. The default is 5.0.
.IP "\fBdump_timestamp\fR"
Be careful, this option will enable profiler to dump data into the file with
time stamp which means there will be lots of files piled up in the directory.
The default is false
.IP "\fBpath\fR"
This is the path of the URL to access the mini web UI. The default is __profile__.
.IP "\fBflush_at_shutdown\fR"
Clear the data when the wsgi server shutdown. The default is false.
.IP "\fBunwind\fR"
Unwind the iterator of applications. Default is false.
.RE
.PD
.SH ADDITIONAL SECTIONS .SH ADDITIONAL SECTIONS
@ -182,8 +263,8 @@ Syslog log facility. The default is LOG_LOCAL0.
Logging level. The default is INFO. Logging level. The default is INFO.
.IP \fBlog_address\fR .IP \fBlog_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBer_diff\fR .IP \fBper_diff\fR
The default is 1000. Maximum number of database rows that will be sync'd in a single HTTP replication request. The default is 1000.
.IP \fBmax_diffs\fR .IP \fBmax_diffs\fR
This caps how long the replicator will spend trying to sync a given database per pass so the other databases don't get starved. The default is 100. This caps how long the replicator will spend trying to sync a given database per pass so the other databases don't get starved. The default is 100.
.IP \fBconcurrency\fR .IP \fBconcurrency\fR
@ -199,6 +280,15 @@ Connection timeout to external services. The default is 0.5 seconds.
.IP \fBreclaim_age\fR .IP \fBreclaim_age\fR
Time elapsed in seconds before an container can be reclaimed. The default is Time elapsed in seconds before an container can be reclaimed. The default is
604800 seconds. 604800 seconds.
.IP \fBrsync_compress\fR
Allow rsync to compress data which is transmitted to destination node
during sync. However, this is applicable only when destination node is in
a different region than the local one. The default is false.
.IP \fBrsync_module\fR
Format of the rysnc module where the replicator will send data. See
etc/rsyncd.conf-sample for some usage examples.
.IP \fBrecon_cache_path\fR
Path to recon cache directory. The default is /var/cache/swift.
.RE .RE
@ -226,6 +316,8 @@ Connection timeout to external services. The default is 0.5 seconds.
Slowdown will sleep that amount between containers. The default is 0.01 seconds. Slowdown will sleep that amount between containers. The default is 0.01 seconds.
.IP \fBaccount_suppression_time\fR .IP \fBaccount_suppression_time\fR
Seconds to suppress updating an account that has generated an error. The default is 60 seconds. Seconds to suppress updating an account that has generated an error. The default is 60 seconds.
.IP \fBrecon_cache_path\fR
Path to recon cache directory. The default is /var/cache/swift.
.RE .RE
.PD .PD
@ -246,6 +338,8 @@ Logging address. The default is /dev/log.
Will audit, at most, 1 container per device per interval. The default is 1800 seconds. Will audit, at most, 1 container per device per interval. The default is 1800 seconds.
.IP \fBcontainers_per_second\fR .IP \fBcontainers_per_second\fR
Maximum containers audited per second. Should be tuned according to individual system specs. 0 is unlimited. The default is 200. Maximum containers audited per second. Should be tuned according to individual system specs. 0 is unlimited. The default is 200.
.IP \fBrecon_cache_path\fR
Path to recon cache directory. The default is /var/cache/swift.
.RE .RE
@ -268,8 +362,10 @@ If you need to use an HTTP Proxy, set it here; defaults to no proxy.
Will audit, at most, each container once per interval. The default is 300 seconds. Will audit, at most, each container once per interval. The default is 300 seconds.
.IP \fBcontainer_time\fR .IP \fBcontainer_time\fR
Maximum amount of time to spend syncing each container per pass. The default is 60 seconds. Maximum amount of time to spend syncing each container per pass. The default is 60 seconds.
.IP \fBrequest_retries\fR .IP \fBconn_timeout\fR
Server errors from requests will be retried by default. Connection timeout to external services. The default is 5 seconds.
.IP \fBrequest_tries\fR
Server errors from requests will be retried by default. The default is 3.
.IP \fBinternal_client_conf_path\fR .IP \fBinternal_client_conf_path\fR
Internal client config file path. Internal client config file path.
.RE .RE

View File

@ -50,14 +50,22 @@ Project name in case of keystone auth version 3
Project domain name in case of keystone auth version 3 Project domain name in case of keystone auth version 3
.IP "\fBuser_domain_name\fR" .IP "\fBuser_domain_name\fR"
User domain name in case of keystone auth version 3 User domain name in case of keystone auth version 3
.IP "\fBendpoint_type\fR"
The default is 'publicURL'.
.IP "\fBkeystone_api_insecure\fR"
The default is false.
.IP "\fBswift_dir\fR" .IP "\fBswift_dir\fR"
Location of openstack-swift configuration and ring files Location of openstack-swift configuration and ring files
.IP "\fBdispersion_coverage\fR" .IP "\fBdispersion_coverage\fR"
Percentage of partition coverage to use. The default is 1.0. Percentage of partition coverage to use. The default is 1.0.
.IP "\fBretries\fR" .IP "\fBretries\fR"
Maximum number of attempts Maximum number of attempts. The defaul is 5.
.IP "\fBconcurrency\fR" .IP "\fBconcurrency\fR"
Concurrency to use. The default is 25. Concurrency to use. The default is 25.
.IP "\fBcontainer_populate\fR"
The default is true.
.IP "\fBobject_populate\fR"
The default is true.
.IP "\fBdump_json\fR" .IP "\fBdump_json\fR"
Whether to output in json format. The default is no. Whether to output in json format. The default is no.
.IP "\fBcontainer_report\fR" .IP "\fBcontainer_report\fR"

View File

@ -65,6 +65,27 @@ Syslog log facility. The default is LOG_LOCAL0.
Logging level. The default is INFO. Logging level. The default is INFO.
.IP \fBlog_address\fR .IP \fBlog_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBlog_max_line_length\fR
The following caps the length of log lines to the value given; no limit if
set to 0, the default.
.IP \fBlog_custom_handlers\fR
Comma separated list of functions to call to setup custom log handlers.
functions get passed: conf, name, log_to_console, log_route, fmt, logger,
adapted_logger. The default is empty.
.IP \fBlog_udp_host\fR
If set, log_udp_host will override log_address.
.IP "\fBlog_udp_port\fR
UDP log port, the default is 514.
.IP \fBlog_statsd_host\fR = localhost
log_statsd_* enable StatsD logging.
.IP \fBlog_statsd_port\fR
The default is 8125.
.IP \fBlog_statsd_default_sample_rate\fR
The default is 1.
.IP \fBlog_statsd_sample_rate_factor\fR
The default is 1.
.IP \fBlog_statsd_metric_prefix\fR
The default is empty.
.RE .RE
.PD .PD
@ -126,9 +147,59 @@ Entry point for paste.deploy for the catch_errors middleware. This is the refere
The default is \fBegg:swift#catch_errors\fR. See proxy-server.conf-sample for options or See proxy-server.conf manpage. The default is \fBegg:swift#catch_errors\fR. See proxy-server.conf-sample for options or See proxy-server.conf manpage.
.RE .RE
.RS 0
.IP "\fB[filter:proxy-logging]\fR"
.RE
Logging for the proxy server now lives in this middleware.
If the access_* variables are not set, logging directives from [DEFAULT]
without "access_" will be used.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the proxy_logging middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#proxy_logging\fR. See proxy-server.conf-sample for options or See proxy-server.conf manpage.
.RE
.PD .PD
.SH ADDITIONAL SECTIONS
.PD 1
.RS 0
The following sections are used by other swift-account services, such as replicator,
auditor and reaper.
.IP "\fB[account-replicator]\fR"
.RE
.RS 3
.IP \fBinterval\fR
Replaces run_pause with the more standard "interval", which means the replicator won't pause unless it takes less than the interval set. The default is 300.
.IP "\fBauto_create_account_prefix\fR
The default is ".".
.IP \fBexpiring_objects_account_name\fR
The default is 'expiring_objects'.
.IP \fBreport_interval\fR
The default is 300 seconds.
.IP \fBconcurrency\fR
Number of replication workers to spawn. The default is 1.
.IP \fBprocesses\fR
Processes is how many parts to divide the work into, one part per process that will be doing the work.
Processes set 0 means that a single process will be doing all the work.
Processes can also be specified on the command line and will override the config value.
The default is 0.
.IP \fBprocess\fR
Process is which of the parts a particular process will work on process can also be specified
on the command line and will override the config value process is "zero based", if you want
to use 3 processes, you should run processes with process set to 0, 1, and 2. The default is 0.
.IP \fBreclaim_age\fR
The expirer will re-attempt expiring if the source object is not available
up to reclaim_age seconds before it gives up and deletes the entry in the
queue. The default is 604800 seconds.
.IP \fBrecon_cache_path\fR
Path to recon cache directory. The default is /var/cache/swift.
.RE
.PD
.SH DOCUMENTATION .SH DOCUMENTATION
.LP .LP

View File

@ -33,7 +33,7 @@
.SH DESCRIPTION .SH DESCRIPTION
.PP .PP
This is the configuration file used by the object server and other object This is the configuration file used by the object server and other object
background services, such as; replicator, updater and auditor. background services, such as; replicator, reconstructor, updater and auditor.
The configuration file follows the python-pastedeploy syntax. The file is divided The configuration file follows the python-pastedeploy syntax. The file is divided
into sections, which are enclosed by square brackets. Each section will contain a into sections, which are enclosed by square brackets. Each section will contain a
@ -57,6 +57,8 @@ IP address the object server should bind to. The default is 0.0.0.0 which will m
it bind to all available addresses. it bind to all available addresses.
.IP "\fBbind_port\fR" .IP "\fBbind_port\fR"
TCP port the object server should bind to. The default is 6000. TCP port the object server should bind to. The default is 6000.
.IP "\fBbind_timeout\fR"
Timeout to bind socket. The default is 30.
.IP \fBbacklog\fR .IP \fBbacklog\fR
TCP backlog. Maximum number of allowed pending connections. The default value is 4096. TCP backlog. Maximum number of allowed pending connections. The default value is 4096.
.IP \fBworkers\fR .IP \fBworkers\fR
@ -79,6 +81,17 @@ Parent directory or where devices are mounted. Default is /srv/node.
.IP \fBmount_check\fR .IP \fBmount_check\fR
Whether or not check if the devices are mounted to prevent accidentally writing to Whether or not check if the devices are mounted to prevent accidentally writing to
the root device. The default is set to true. the root device. The default is set to true.
.IP \fBdisable_fallocate\fR
Disable pre-allocate disk space for a file. The default is false.
.IP \fBexpiring_objects_container_divisor\fR
The default is 86400.
.IP \fBexpiring_objects_account_name\fR
The default is 'expiring_objects'.
.IP \fBservers_per_port\fR
Make object-server run this many worker processes per unique port of
"local" ring devices across all storage policies. This can help provide
the isolation of threads_per_disk without the severe overhead. The default
value of 0 disables this feature.
.IP \fBlog_name\fR .IP \fBlog_name\fR
Label used when logging. The default is swift. Label used when logging. The default is swift.
.IP \fBlog_facility\fR .IP \fBlog_facility\fR
@ -87,6 +100,45 @@ Syslog log facility. The default is LOG_LOCAL0.
Logging level. The default is INFO. Logging level. The default is INFO.
.IP \fBlog_address\fR .IP \fBlog_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBlog_max_line_length\fR
The following caps the length of log lines to the value given; no limit if
set to 0, the default.
.IP \fBlog_custom_handlers\fR
Comma separated list of functions to call to setup custom log handlers.
functions get passed: conf, name, log_to_console, log_route, fmt, logger,
adapted_logger. The default is empty.
.IP \fBlog_udp_host\fR
If set, log_udp_host will override log_address.
.IP "\fBlog_udp_port\fR
UDP log port, the default is 514.
.IP \fBlog_statsd_host\fR = localhost
log_statsd_* enable StatsD logging.
.IP \fBlog_statsd_port\fR
The default is 8125.
.IP \fBlog_statsd_default_sample_rate\fR
The default is 1.
.IP \fBlog_statsd_sample_rate_factor\fR
The default is 1.
.IP \fBlog_statsd_metric_prefix\fR
The default is empty.
.IP \fBeventlet_debug\fR
Debug mode for eventlet library. The default is false.
.IP \fBfallocate_reserve\fR
You can set fallocate_reserve to the number of bytes you'd like fallocate to
reserve, whether there is space for the given file size or not. The default is 0.
.IP \fBnode_timeout\fR
Request timeout to external services. The default is 3 seconds.
.IP \fBconn_timeout\fR
Connection timeout to external services. The default is 0.5 seconds.
.IP \fBcontainer_update_timeout\fR
Time to wait while sending a container update on object update. The default is 1 second.
.IP \fBclient_timeout\fR
Time to wait while receiving each chunk of data from a client or another
backend node. The default is 60.
.IP \fBnetwork_chunk_size\fR
The default is 65536.
.IP \fBdisk_chunk_size\fR
The default is 65536.
.RE .RE
.PD .PD
@ -115,22 +167,68 @@ that are acceptable within this section.
.IP "\fBuse\fR" .IP "\fBuse\fR"
Entry point for paste.deploy for the object server. This is the reference to the installed python egg. Entry point for paste.deploy for the object server. This is the reference to the installed python egg.
This is normally \fBegg:swift#object\fR. This is normally \fBegg:swift#object\fR.
.IP "\fBset log_name\fR .IP "\fBset log_name\fR"
Label used when logging. The default is object-server. Label used when logging. The default is object-server.
.IP "\fBset log_facility\fR .IP "\fBset log_facility\fR"
Syslog log facility. The default is LOG_LOCAL0. Syslog log facility. The default is LOG_LOCAL0.
.IP "\fB set log_level\fR .IP "\fBset log_level\fR"
Logging level. The default is INFO. Logging level. The default is INFO.
.IP "\fB set log_requests\fR .IP "\fBset log_requests\fR"
Enables request logging. The default is True. Enables request logging. The default is True.
.IP "\fB set log_address\fR .IP "\fBset log_address\fR"
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBnode_timeout\fR .IP "\fBmax_upload_time\fR"
Request timeout to external services. The default is 3 seconds. The default is 86400.
.IP \fBconn_timeout\fR .IP "\fBslow\fR"
Connection timeout to external services. The default is 0.5 seconds. The default is 0.
.IP \fBcontainer_update_timeout\fR .IP "\fBkeep_cache_size\fR"
Time to wait while sending a container update on object update. The default is 1 second. Objects smaller than this are not evicted from the buffercache once read. The default is 5242880.
.IP "\fBkeep_cache_private\fR"
If true, objects for authenticated GET requests may be kept in buffer cache
if small enough. The default is false.
.IP "\fBmb_per_sync\fR"
On PUTs, sync data every n MB. The default is 512.
.IP "\fBallowed_headers\fR"
Comma separated list of headers that can be set in metadata on an object.
This list is in addition to X-Object-Meta-* headers and cannot include Content-Type, etag, Content-Length, or deleted.
The default is 'Content-Disposition, Content-Encoding, X-Delete-At, X-Object-Manifest, X-Static-Large-Object'.
.IP "\fBauto_create_account_prefix\fR"
The default is '.'.
.IP "\fBthreads_per_disk\fR"
A value of 0 means "don't use thread pools". A reasonable starting point is
4. The default is 0.
.IP "\fBreplication_server\fR"
Configure parameter for creating specific server
To handle all verbs, including replication verbs, do not specify
"replication_server" (this is the default). To only handle replication,
set to a True value (e.g. "True" or "1"). To handle only non-replication
verbs, set to "False". Unless you have a separate replication network, you
should not specify any value for "replication_server".
.IP "\fBreplication_concurrency\fR"
Set to restrict the number of concurrent incoming REPLICATION requests
Set to 0 for unlimited (the default is 4). Note that REPLICATION is currently an ssync only item.
.IP "\fBreplication_one_per_device\fR"
Restricts incoming REPLICATION requests to one per device,
replication_currency above allowing. This can help control I/O to each
device, but you may wish to set this to False to allow multiple REPLICATION
requests (up to the above replication_concurrency setting) per device. The default is true.
.IP "\fBreplication_lock_timeout\fR"
Number of seconds to wait for an existing replication device lock before
giving up. The default is 15.
.IP "\fBreplication_failure_threshold\fR"
.IP "\fBreplication_failure_ratio\fR"
These two settings control when the REPLICATION subrequest handler will
abort an incoming REPLICATION attempt. An abort will occur if there are at
least threshold number of failures and the value of failures / successes
exceeds the ratio. The defaults of 100 and 1.0 means that at least 100
failures have to occur and there have to be more failures than successes for
an abort to occur.
.IP "\fBsplice\fR"
Use splice() for zero-copy object GETs. This requires Linux kernel
version 3.0 or greater. If you set "splice = yes" but the kernel
does not support it, error messages will appear in the object server
logs at startup, but your object servers should continue to function.
The default is false.
.RE .RE
.PD .PD
@ -164,9 +262,41 @@ This is normally \fBegg:swift#recon\fR.
The recon_cache_path simply sets the directory where stats for a few items will be stored. The recon_cache_path simply sets the directory where stats for a few items will be stored.
Depending on the method of deployment you may need to create this directory manually Depending on the method of deployment you may need to create this directory manually
and ensure that swift has read/write. The default is /var/cache/swift. and ensure that swift has read/write. The default is /var/cache/swift.
.IP "\fBrecon_lock_path\fR"
The default is /var/lock.
.RE .RE
.PD .PD
.RS 0
.IP "\fB[filter:xprofile]\fR"
.RS 3
.IP "\fBuse\fR"
Entry point for paste.deploy for the xprofile middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#xprofile\fR.
.IP "\fBprofile_module\fR"
This option enable you to switch profilers which should inherit from python
standard profiler. Currently the supported value can be 'cProfile', 'eventlet.green.profile' etc.
.IP "\fBlog_filename_prefix\fR"
This prefix will be used to combine process ID and timestamp to name the
profile data file. Make sure the executing user has permission to write
into this path (missing path segments will be created, if necessary).
If you enable profiling in more than one type of daemon, you must override
it with an unique value like, the default is /var/log/swift/profile/account.profile.
.IP "\fBdump_interval\fR"
The profile data will be dumped to local disk based on above naming rule
in this interval. The default is 5.0.
.IP "\fBdump_timestamp\fR"
Be careful, this option will enable profiler to dump data into the file with
time stamp which means there will be lots of files piled up in the directory.
The default is false
.IP "\fBpath\fR"
This is the path of the URL to access the mini web UI. The default is __profile__.
.IP "\fBflush_at_shutdown\fR"
Clear the data when the wsgi server shutdown. The default is false.
.IP "\fBunwind\fR"
Unwind the iterator of applications. Default is false.
.RE
.PD
.SH ADDITIONAL SECTIONS .SH ADDITIONAL SECTIONS
@ -195,10 +325,26 @@ Time in seconds to wait between replication passes. The default is 30.
Number of replication workers to spawn. The default is 1. Number of replication workers to spawn. The default is 1.
.IP \fBstats_interval\fR .IP \fBstats_interval\fR
Interval in seconds between logging replication statistics. The default is 300. Interval in seconds between logging replication statistics. The default is 300.
.IP \fBsync_method\fR
The sync method to use; default is rsync but you can use ssync to try the
EXPERIMENTAL all-swift-code-no-rsync-callouts method. Once ssync is verified
as having performance comparable to, or better than, rsync, we plan to
deprecate rsync so we can move on with more features for replication.
.IP \fBrsync_timeout\fR .IP \fBrsync_timeout\fR
Max duration of a partition rsync. The default is 900 seconds. Max duration of a partition rsync. The default is 900 seconds.
.IP \fBrsync_io_timeout\fR .IP \fBrsync_io_timeout\fR
Passed to rsync for I/O OP timeout. The default is 30 seconds. Passed to rsync for I/O OP timeout. The default is 30 seconds.
.IP \fBrsync_compress\fR
Allow rsync to compress data which is transmitted to destination node
during sync. However, this is applicable only when destination node is in
a different region than the local one.
NOTE: Objects that are already compressed (for example: .tar.gz, .mp3) might
slow down the syncing process. The default is false.
.IP \fBrsync_module\fR
Format of the rysnc module where the replicator will send data. See
etc/rsyncd.conf-sample for some usage examples. The default is empty.
.IP \fBnode_timeout\fR
Request timeout to external services. The default is 10 seconds.
.IP \fBrsync_bwlimit\fR .IP \fBrsync_bwlimit\fR
Passed to rsync for bandwidth limit in kB/s. The default is 0 (unlimited). Passed to rsync for bandwidth limit in kB/s. The default is 0 (unlimited).
.IP \fBhttp_timeout\fR .IP \fBhttp_timeout\fR
@ -206,18 +352,87 @@ Max duration of an HTTP request. The default is 60 seconds.
.IP \fBlockup_timeout\fR .IP \fBlockup_timeout\fR
Attempts to kill all workers if nothing replicates for lockup_timeout seconds. The Attempts to kill all workers if nothing replicates for lockup_timeout seconds. The
default is 1800 seconds. default is 1800 seconds.
.IP \fBring_check_interval\fR
The default is 15.
.IP \fBrsync_error_log_line_length\fR
Limits how long rsync error log lines are. 0 (default) means to log the entire line.
.IP \fBreclaim_age\fR .IP \fBreclaim_age\fR
Time elapsed in seconds before an object can be reclaimed. The default is Time elapsed in seconds before an object can be reclaimed. The default is
604800 seconds. 604800 seconds.
.IP \fBrecon_enable\fR
Enable logging of replication stats for recon. The default is on.
.IP "\fBrecon_cache_path\fR" .IP "\fBrecon_cache_path\fR"
The recon_cache_path simply sets the directory where stats for a few items will be stored. The recon_cache_path simply sets the directory where stats for a few items will be stored.
Depending on the method of deployment you may need to create this directory manually Depending on the method of deployment you may need to create this directory manually
and ensure that swift has read/write.The default is /var/cache/swift. and ensure that swift has read/write.The default is /var/cache/swift.
.IP "\fBhandoffs_first\fR"
The flag to replicate handoffs prior to canonical partitions.
It allows to force syncing and deleting handoffs quickly.
If set to a True value(e.g. "True" or "1"), partitions
that are not supposed to be on the node will be replicated first.
The default is false.
.IP "\fBhandoff_delete\fR"
The number of replicas which are ensured in swift.
If the number less than the number of replicas is set, object-replicator
could delete local handoffs even if all replicas are not ensured in the
cluster. Object-replicator would remove local handoff partition directories
after syncing partition when the number of successful responses is greater
than or equal to this number. By default(auto), handoff partitions will be
removed when it has successfully replicated to all the canonical nodes.
The handoffs_first and handoff_delete are options for a special case
such as disk full in the cluster. These two options SHOULD NOT BE
CHANGED, except for such an extreme situations. (e.g. disks filled up
or are about to fill up. Anyway, DO NOT let your drives fill up).
.RE .RE
.RS 0
.IP "\fB[object-reconstructor]\fR"
.RE
.RS 3
.IP \fBlog_name\fR
Label used when logging. The default is object-reconstructor.
.IP \fBlog_facility\fR
Syslog log facility. The default is LOG_LOCAL0.
.IP \fBlog_level\fR
Logging level. The default is INFO.
.IP \fBlog_address\fR
Logging address. The default is /dev/log.
.IP \fBdaemonize\fR
Whether or not to run replication as a daemon. The default is yes.
.IP "\fBrun_pause [deprecated]\fR"
Time in seconds to wait between replication passes. The default is 30.
.IP \fBinterval\fR
Time in seconds to wait between replication passes. The default is 30.
.IP \fBconcurrency\fR
Number of replication workers to spawn. The default is 1.
.IP \fBstats_interval\fR
Interval in seconds between logging replication statistics. The default is 300.
.IP \fBnode_timeout\fR
Request timeout to external services. The default is 10 seconds.
.IP \fBhttp_timeout\fR
Max duration of an HTTP request. The default is 60 seconds.
.IP \fBlockup_timeout\fR
Attempts to kill all workers if nothing replicates for lockup_timeout seconds. The
default is 1800 seconds.
.IP \fBring_check_interval\fR
The default is 15.
.IP \fBreclaim_age\fR
Time elapsed in seconds before an object can be reclaimed. The default is
604800 seconds.
.IP "\fBrecon_cache_path\fR"
The recon_cache_path simply sets the directory where stats for a few items will be stored.
Depending on the method of deployment you may need to create this directory manually
and ensure that swift has read/write.The default is /var/cache/swift.
.IP "\fBhandoffs_first\fR"
The flag to replicate handoffs prior to canonical partitions.
It allows to force syncing and deleting handoffs quickly.
If set to a True value(e.g. "True" or "1"), partitions
that are not supposed to be on the node will be replicated first.
The default is false.
.RE
.PD
.RS 0 .RS 0
.IP "\fB[object-updater]\fR" .IP "\fB[object-updater]\fR"
.RE .RE
@ -236,10 +451,12 @@ Minimum time for a pass to take. The default is 300 seconds.
Number of reaper workers to spawn. The default is 1. Number of reaper workers to spawn. The default is 1.
.IP \fBnode_timeout\fR .IP \fBnode_timeout\fR
Request timeout to external services. The default is 10 seconds. Request timeout to external services. The default is 10 seconds.
.IP \fBconn_timeout\fR
Connection timeout to external services. The default is 0.5 seconds.
.IP \fBslowdown\fR .IP \fBslowdown\fR
Slowdown will sleep that amount between objects. The default is 0.01 seconds. Slowdown will sleep that amount between objects. The default is 0.01 seconds.
.IP "\fBrecon_cache_path\fR"
The recon_cache_path simply sets the directory where stats for a few items will be stored.
Depending on the method of deployment you may need to create this directory manually
and ensure that swift has read/write. The default is /var/cache/swift.
.RE .RE
.PD .PD
@ -257,16 +474,28 @@ Logging level. The default is INFO.
.IP \fBlog_address\fR .IP \fBlog_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBdisk_chunk_size\fR
The default is 65536.
.IP \fBfiles_per_second\fR .IP \fBfiles_per_second\fR
Maximum files audited per second. Should be tuned according to individual Maximum files audited per second. Should be tuned according to individual
system specs. 0 is unlimited. The default is 20. system specs. 0 is unlimited. The default is 20.
.IP \fBbytes_per_second\fR .IP \fBbytes_per_second\fR
Maximum bytes audited per second. Should be tuned according to individual Maximum bytes audited per second. Should be tuned according to individual
system specs. 0 is unlimited. The default is 10000000. system specs. 0 is unlimited. The default is 10000000.
.IP \fBconcurrency\fR
Number of reaper workers to spawn. The default is 1.
.IP \fBlog_time\fR .IP \fBlog_time\fR
The default is 3600 seconds. The default is 3600 seconds.
.IP \fBzero_byte_files_per_second\fR .IP \fBzero_byte_files_per_second\fR
The default is 50. The default is 50.
.IP "\fBrecon_cache_path\fR"
The recon_cache_path simply sets the directory where stats for a few items will be stored.
Depending on the method of deployment you may need to create this directory manually
and ensure that swift has read/write. The default is /var/cache/swift.
.IP \fBobject_size_stats\fR
Takes a comma separated list of ints. If set, the object auditor will
increment a counter for every object whose size is <= to the given break
points and report the result after a full scan.
.RE .RE

View File

@ -56,8 +56,21 @@ IP address the proxy server should bind to. The default is 0.0.0.0 which will ma
it bind to all available addresses. it bind to all available addresses.
.IP "\fBbind_port\fR" .IP "\fBbind_port\fR"
TCP port the proxy server should bind to. The default is 80. TCP port the proxy server should bind to. The default is 80.
.IP "\fBbind_timeout\fR"
Timeout to bind socket. The default is 30.
.IP \fBbacklog\fR .IP \fBbacklog\fR
TCP backlog. Maximum number of allowed pending connections. The default value is 4096. TCP backlog. Maximum number of allowed pending connections. The default value is 4096.
.IP \fBadmin_key\fR
Key to use for admin calls that are HMAC signed. Default is empty,
which will disable admin calls to /info.
.IP \fBdisallowed_sections\fR
Allows the ability to withhold sections from showing up in the public calls
to /info. You can withhold subsections by separating the dict level with a
".". The following would cause the sections 'container_quotas' and 'tempurl'
to not be listed, and the key max_failed_deletes would be removed from
bulk_delete. Default value is 'swift.valid_api_versions' which allows all
registered features to be listed via HTTP GET /info except
swift.valid_api_versions information
.IP \fBworkers\fR .IP \fBworkers\fR
The number of pre-forked processes that will accept connections. Zero means The number of pre-forked processes that will accept connections. Zero means
no fork. The default is auto which will make the server try to match the no fork. The default is auto which will make the server try to match the
@ -71,6 +84,8 @@ actually accept(2) N + 1). Setting this to one (1) will only handle one request
at a time, without accepting another request concurrently. The default is 1024. at a time, without accepting another request concurrently. The default is 1024.
.IP \fBuser\fR .IP \fBuser\fR
The system user that the proxy server will run as. The default is swift. The system user that the proxy server will run as. The default is swift.
.IP \fBexpose_info\fR
Enables exposing configuration settings via HTTP GET /info. The default is true.
.IP \fBswift_dir\fR .IP \fBswift_dir\fR
Swift configuration directory. The default is /etc/swift. Swift configuration directory. The default is /etc/swift.
.IP \fBcert_file\fR .IP \fBcert_file\fR
@ -79,6 +94,10 @@ disabled by default.
.IP \fBkey_file\fR .IP \fBkey_file\fR
Location of the SSL certificate key file. The default path is /etc/swift/proxy.key. This is Location of the SSL certificate key file. The default path is /etc/swift/proxy.key. This is
disabled by default. disabled by default.
.IP \fBexpiring_objects_container_divisor\fR
The default is 86400.
.IP \fBexpiring_objects_account_name\fR
The default is 'expiring_objects'.
.IP \fBlog_name\fR .IP \fBlog_name\fR
Label used when logging. The default is swift. Label used when logging. The default is swift.
.IP \fBlog_facility\fR .IP \fBlog_facility\fR
@ -87,10 +106,41 @@ Syslog log facility. The default is LOG_LOCAL0.
Logging level. The default is INFO. Logging level. The default is INFO.
.IP \fBlog_address\fR .IP \fBlog_address\fR
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP \fBlog_max_line_length\fR
To cap the length of log lines to the value given. No limit if set to 0, the default.
.IP \fBlog_headers\fR
The default is false.
.IP \fBlog_custom_handlers\fR
Comma separated list of functions to call to setup custom log handlers.
functions get passed: conf, name, log_to_console, log_route, fmt, logger,
adapted_logger. The default is empty.
.IP \fBlog_udp_host\fR
If set, log_udp_host will override log_address.
.IP "\fBlog_udp_port\fR
UDP log port, the default is 514.
.IP \fBlog_statsd_host\fR = localhost
log_statsd_* enable StatsD logging.
.IP \fBlog_statsd_port\fR
The default is 8125.
.IP \fBlog_statsd_default_sample_rate\fR
The default is 1.
.IP \fBlog_statsd_sample_rate_factor\fR
The default is 1.
.IP \fBlog_statsd_metric_prefix\fR
The default is empty.
.IP \fBclient_timeout\fR
Time to wait while receiving each chunk of data from a client or another
backend node. The default is 60.
.IP \fBeventlet_debug\fR
Debug mode for eventlet library. The default is false.
.IP \fBtrans_id_suffix\fR .IP \fBtrans_id_suffix\fR
This optional suffix (default is empty) that would be appended to the swift transaction This optional suffix (default is empty) that would be appended to the swift transaction
id allows one to easily figure out from which cluster that X-Trans-Id belongs to. id allows one to easily figure out from which cluster that X-Trans-Id belongs to.
This is very useful when one is managing more than one swift cluster. This is very useful when one is managing more than one swift cluster.
.IP \fBcors_allow_origin\fR
Use a comma separated list of full url (http://foo.bar:1234,https://foo.bar)
.IP \fBstrict_cors_mode\fR
The default is true.
.RE .RE
.PD .PD
@ -104,8 +154,13 @@ are acceptable within this section.
.IP "\fBpipeline\fR" .IP "\fBpipeline\fR"
It is used when you need apply a number of filters. It is a list of filters It is used when you need apply a number of filters. It is a list of filters
ended by an application. The normal pipeline is "catch_errors healthcheck ended by an application. The normal pipeline is "catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit tempauth container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server".
cache ratelimit tempauth proxy-logging proxy-server".
Note: The double proxy-logging in the pipeline is not a mistake. The
left-most proxy-logging is there to log requests that were handled in
middleware and never made it through to the right-most middleware (and
proxy server). Double logging is prevented for normal requests. See
proxy-logging docs.
.RE .RE
.PD .PD
@ -127,6 +182,7 @@ This is normally \fBegg:swift#healthcheck\fR.
An optional filesystem path which, if present, will cause the healthcheck An optional filesystem path which, if present, will cause the healthcheck
URL to return "503 Service Unavailable" with a body of "DISABLED BY FILE". URL to return "503 Service Unavailable" with a body of "DISABLED BY FILE".
.RE .RE
.PD
.RS 0 .RS 0
@ -154,11 +210,28 @@ systems are in use for one Swift cluster. The default is AUTH.
.IP \fBauth_prefix\fR .IP \fBauth_prefix\fR
The auth prefix will cause requests beginning with this prefix to be routed The auth prefix will cause requests beginning with this prefix to be routed
to the auth subsystem, for granting tokens, etc. The default is /auth/. to the auth subsystem, for granting tokens, etc. The default is /auth/.
.IP \fBrequire_group\fR
The require_group parameter names a group that must be presented by
either X-Auth-Token or X-Service-Token. Usually this parameter is
used only with multiple reseller prefixes (e.g., SERVICE_require_group=blah).
By default, no group is needed. Do not use .admin.
.IP \fBtoken_life\fR .IP \fBtoken_life\fR
This is the time in seconds before the token expires. The default is 86400. This is the time in seconds before the token expires. The default is 86400.
.IP \fBallow_overrides\fR
This allows middleware higher in the WSGI pipeline to override auth
processing, useful for middleware such as tempurl and formpost. If you know
you're not going to use such middleware and you want a bit of extra security,
you can set this to false. The default is true.
.IP \fBstorage_url_scheme\fR
This specifies what scheme to return with storage urls:
http, https, or default (chooses based on what the server is running as)
This can be useful with an SSL load balancer in front of a non-SSL server.
.IP \fBuser_<account>_<user>\fR .IP \fBuser_<account>_<user>\fR
Lastly, you need to list all the accounts/users you want here. The format is: Lastly, you need to list all the accounts/users you want here. The format is:
user_<account>_<user> = <key> [group] [group] [...] [storage_url] user_<account>_<user> = <key> [group] [group] [...] [storage_url]
or if you want underscores in <account> or <user>, you can base64 encode them
(with no equal signs) and use this format:
user64_<account_b64>_<user_b64> = <key> [group] [group] [...] [storage_url]
There are special groups of: \fI.reseller_admin\fR who can do anything to any account for this auth There are special groups of: \fI.reseller_admin\fR who can do anything to any account for this auth
and also \fI.admin\fR who can do anything within the account. and also \fI.admin\fR who can do anything within the account.
@ -184,6 +257,107 @@ Here are example entries, required for running the tests:
.RE .RE
.PD .PD
.RS 0
.IP "\fB[filter:authtoken]\fR"
.RE
To enable Keystone authentication you need to have the auth token
middleware first to be configured. Here is an example below, please
refer to the keystone's documentation for details about the
different settings.
You'll need to have as well the keystoneauth middleware enabled
and have it in your main pipeline so instead of having tempauth in
there you can change it to: authtoken keystoneauth
.PD 0
.RS 10
.IP "paste.filter_factory = keystonemiddleware.auth_token:filter_factory"
.IP "identity_uri = http://keystonehost:35357/"
.IP "auth_uri = http://keystonehost:5000/"
.IP "admin_tenant_name = service"
.IP "admin_user = swift"
.IP "admin_password = password"
.IP ""
.IP "# delay_auth_decision defaults to False, but leaving it as false will"
.IP "# prevent other auth systems, staticweb, tempurl, formpost, and ACLs from"
.IP "# working. This value must be explicitly set to True."
.IP "delay_auth_decision = False"
.IP
.IP "cache = swift.cache"
.IP "include_service_catalog = False"
.RE
.PD
.RS 0
.IP "\fB[filter:keystoneauth]\fR"
.RE
Keystone authentication middleware.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the keystoneauth middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#keystoneauth\fR.
.IP \fBreseller_prefix\fR
The reseller_prefix option lists account namespaces that this middleware is
responsible for. The prefix is placed before the Keystone project id.
For example, for project 12345678, and prefix AUTH, the account is
named AUTH_12345678 (i.e., path is /v1/AUTH_12345678/...).
Several prefixes are allowed by specifying a comma-separated list
as in: "reseller_prefix = AUTH, SERVICE". The empty string indicates a
single blank/empty prefix. If an empty prefix is required in a list of
prefixes, a value of '' (two single quote characters) indicates a
blank/empty prefix. Except for the blank/empty prefix, an underscore ('_')
character is appended to the value unless already present.
.IP \fBoperator_roles\fR
The user must have at least one role named by operator_roles on a
project in order to create, delete and modify containers and objects
and to set and read privileged headers such as ACLs.
If there are several reseller prefix items, you can prefix the
parameter so it applies only to those accounts (for example
the parameter SERVICE_operator_roles applies to the /v1/SERVICE_<project>
path). If you omit the prefix, the option applies to all reseller
prefix items. For the blank/empty prefix, prefix with '' (do not put
underscore after the two single quote characters).
.IP \fBreseller_admin_role\fR
The reseller admin role has the ability to create and delete accounts.
.IP \fBallow_overrides\fR
This allows middleware higher in the WSGI pipeline to override auth
processing, useful for middleware such as tempurl and formpost. If you know
you're not going to use such middleware and you want a bit of extra security,
you can set this to false.
.IP \fBis_admin [DEPRECATED]\fR
If is_admin is true, a user whose username is the same as the project name
and who has any role on the project will have access rights elevated to be
the same as if the user had an operator role. Note that the condition
compares names rather than UUIDs. This option is deprecated.
.IP \fBservice_roles\fR
If the service_roles parameter is present, an X-Service-Token must be
present in the request that when validated, grants at least one role listed
in the parameter. The X-Service-Token may be scoped to any project.
If there are several reseller prefix items, you can prefix the
parameter so it applies only to those accounts (for example
the parameter SERVICE_service_roles applies to the /v1/SERVICE_<project>
path). If you omit the prefix, the option applies to all reseller
prefix items. For the blank/empty prefix, prefix with '' (do not put
underscore after the two single quote characters).
By default, no service_roles are required.
.IP \fBdefault_domain_id\fR
For backwards compatibility, keystoneauth will match names in cross-tenant
access control lists (ACLs) when both the requesting user and the tenant
are in the default domain i.e the domain to which existing tenants are
migrated. The default_domain_id value configured here should be the same as
the value used during migration of tenants to keystone domains.
.IP \fBallow_names_in_acls\fR
For a new installation, or an installation in which keystone projects may
move between domains, you should disable backwards compatible name matching
in ACLs by setting allow_names_in_acls to false:
.RE
.PD
.RS 0 .RS 0
.IP "\fB[filter:cache]\fR" .IP "\fB[filter:cache]\fR"
.RE .RE
@ -202,8 +376,10 @@ Syslog log facility. The default is LOG_LOCAL0.
Logging level. The default is INFO. Logging level. The default is INFO.
.IP "\fBset log_address\fR" .IP "\fBset log_address\fR"
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP "\fBset log_headers\fR " .IP "\fBset log_headers\fR"
Enables the ability to log request headers. The default is False. Enables the ability to log request headers. The default is False.
.IP \fBmemcache_max_connections\fR
Sets the maximum number of connections to each memcached server per worker.
.IP \fBmemcache_servers\fR .IP \fBmemcache_servers\fR
If not set in the configuration file, the value for memcache_servers will be If not set in the configuration file, the value for memcache_servers will be
read from /etc/swift/memcache.conf (see memcache.conf-sample) or lacking that read from /etc/swift/memcache.conf (see memcache.conf-sample) or lacking that
@ -225,7 +401,7 @@ To avoid an instant full cache flush, existing installations should upgrade with
If not set in the configuration file, the value for memcache_serialization_support will be read from /etc/swift/memcache.conf if it exists (see memcache.conf-sample). Otherwise, the default value as indicated above will be used. If not set in the configuration file, the value for memcache_serialization_support will be read from /etc/swift/memcache.conf if it exists (see memcache.conf-sample). Otherwise, the default value as indicated above will be used.
.RE .RE
.PD
.RS 0 .RS 0
@ -268,14 +444,20 @@ in requests per second. If set to 0 means disabled. The default is 0.
.IP \fBcontainer_ratelimit_size\fR .IP \fBcontainer_ratelimit_size\fR
When set with container_limit_x = r: for containers of size x, limit requests per second When set with container_limit_x = r: for containers of size x, limit requests per second
to r. Will limit PUT, DELETE, and POST requests to /a/c/o. The default is ''. to r. Will limit PUT, DELETE, and POST requests to /a/c/o. The default is ''.
.IP \fBcontainer_listing_ratelimit_size\fR
Similarly to the above container-level write limits, the following will limit
container GET (listing) requests.
.RE .RE
.PD
.RS 0 .RS 0
.IP "\fB[filter:domain_remap]\fR" .IP "\fB[filter:domain_remap]\fR"
.RE .RE
Middleware that translates container and account parts of a domain to path parameters that the proxy server understands. The container.account.storageurl/object gets translated to container.account.storageurl/path_root/account/container/object and account.storageurl/path_root/container/object gets translated to account.storageurl/path_root/account/container/object Middleware that translates container and account parts of a domain to path parameters that the proxy server understands.
The container.account.storageurl/object gets translated to container.account.storageurl/path_root/account/container/object and account.storageurl/path_root/container/object gets translated to account.storageurl/path_root/account/container/object
.RS 3 .RS 3
.IP \fBuse\fR .IP \fBuse\fR
@ -283,9 +465,13 @@ Entry point for paste.deploy for the domain_remap middleware. This is the refere
This is normally \fBegg:swift#domain_remap\fR. This is normally \fBegg:swift#domain_remap\fR.
.IP "\fBset log_name\fR" .IP "\fBset log_name\fR"
Label used when logging. The default is domain_remap. Label used when logging. The default is domain_remap.
.IP "\fBset log_facility\fR"
Syslog log facility. The default is LOG_LOCAL0.
.IP "\fBset log_level\fR "
Logging level. The default is INFO.
.IP "\fBset log_address\fR" .IP "\fBset log_address\fR"
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP "\fBset log_headers\fR" .IP "\fBset log_headers\fR "
Enables the ability to log request headers. The default is False. Enables the ability to log request headers. The default is False.
.IP \fBstorage_domain\fR .IP \fBstorage_domain\fR
The domain to be used by the middleware. The domain to be used by the middleware.
@ -304,7 +490,7 @@ Defaults to 'AUTH'.
The default reseller prefix. This is used when none of the configured The default reseller prefix. This is used when none of the configured
reseller_prefixes match. When not set, no reseller prefix is added. reseller_prefixes match. When not set, no reseller prefix is added.
.RE .RE
.PD
.RS 0 .RS 0
@ -325,7 +511,7 @@ Logging address. The default is /dev/log.
.IP "\fBset log_headers\fR" .IP "\fBset log_headers\fR"
Enables the ability to log request headers. The default is False. Enables the ability to log request headers. The default is False.
.RE .RE
.PD
.RS 0 .RS 0
@ -354,7 +540,7 @@ The domain to be used by the middleware.
How deep in the CNAME chain to look for something that matches the storage domain. How deep in the CNAME chain to look for something that matches the storage domain.
The default is 1. The default is 1.
.RE .RE
.PD
.RS 0 .RS 0
@ -367,8 +553,6 @@ Note: Put staticweb just after your auth filter(s) in the pipeline
.IP \fBuse\fR .IP \fBuse\fR
Entry point for paste.deploy for the staticweb middleware. This is the reference to the installed python egg. Entry point for paste.deploy for the staticweb middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#staticweb\fR. This is normally \fBegg:swift#staticweb\fR.
.IP \fBcache_timeout\fR
Seconds to cache container x-container-meta-web-* header values. The default is 300 seconds.
.IP "\fBset log_name\fR" .IP "\fBset log_name\fR"
Label used when logging. The default is staticweb. Label used when logging. The default is staticweb.
.IP "\fBset log_facility\fR" .IP "\fBset log_facility\fR"
@ -379,14 +563,8 @@ Logging level. The default is INFO.
Logging address. The default is /dev/log. Logging address. The default is /dev/log.
.IP "\fBset log_headers\fR" .IP "\fBset log_headers\fR"
Enables the ability to log request headers. The default is False. Enables the ability to log request headers. The default is False.
.IP "\fBset access_log_name\fR"
Label used when logging. The default is staticweb.
.IP "\fBset access_log_facility\fR"
Syslog log facility. The default is LOG_LOCAL0.
.IP "\fBset access_log_level\fR "
Logging level. The default is INFO.
.RE .RE
.PD
.RS 0 .RS 0
@ -396,6 +574,11 @@ Logging level. The default is INFO.
Note: Put tempurl before slo, dlo, and your auth filter(s) in the pipeline Note: Put tempurl before slo, dlo, and your auth filter(s) in the pipeline
.RS 3 .RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the tempurl middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#tempurl\fR.
.IP \fBmethods\fR
The methods allowed with Temp URLs. The default is 'GET HEAD PUT POST DELETE'.
.IP \fBincoming_remove_headers\fR .IP \fBincoming_remove_headers\fR
The headers to remove from incoming requests. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. incoming_allow_headers is a list of exceptions to these removals. The headers to remove from incoming requests. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. incoming_allow_headers is a list of exceptions to these removals.
.IP \fBincoming_allow_headers\fR .IP \fBincoming_allow_headers\fR
@ -404,9 +587,8 @@ The headers allowed as exceptions to incoming_remove_headers. Simply a whitespac
The headers to remove from outgoing responses. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. outgoing_allow_headers is a list of exceptions to these removals. The headers to remove from outgoing responses. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. outgoing_allow_headers is a list of exceptions to these removals.
.IP "\fBoutgoing_allow_headers\fR" .IP "\fBoutgoing_allow_headers\fR"
The headers allowed as exceptions to outgoing_remove_headers. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match. The headers allowed as exceptions to outgoing_remove_headers. Simply a whitespace delimited list of header names and names can optionally end with '*' to indicate a prefix match.
.IP "\fBset log_level\fR "
.RE .RE
.PD
.RS 0 .RS 0
@ -420,6 +602,7 @@ Note: Put formpost just before your auth filter(s) in the pipeline
Entry point for paste.deploy for the formpost middleware. This is the reference to the installed python egg. Entry point for paste.deploy for the formpost middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#formpost\fR. This is normally \fBegg:swift#formpost\fR.
.RE .RE
.PD
@ -434,12 +617,25 @@ Note: Just needs to be placed before the proxy-server in the pipeline.
Entry point for paste.deploy for the name_check middleware. This is the reference to the installed python egg. Entry point for paste.deploy for the name_check middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#name_check\fR. This is normally \fBegg:swift#name_check\fR.
.IP \fBforbidden_chars\fR .IP \fBforbidden_chars\fR
Characters that will not be allowed in a name. Characters that will not be allowed in a name. The default is '"`<>.
.IP \fBmaximum_length\fR .IP \fBmaximum_length\fR
Maximum number of characters that can be in the name. Maximum number of characters that can be in the name. The default is 255.
.IP \fBforbidden_regexp\fR .IP \fBforbidden_regexp\fR
Python regular expressions of substrings that will not be allowed in a name. Python regular expressions of substrings that will not be allowed in a name. The default is /\./|/\.\./|/\.$|/\.\.$.
.RE .RE
.PD
.RS 0
.IP "\fB[filter:list-endpoints]\fR"
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the list_endpoints middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#list_endpoints\fR.
.IP \fBlist_endpoints_path\fR
The default is '/endpoints/'.
.RE
.PD
.RS 0 .RS 0
@ -474,20 +670,249 @@ Default is localhost.
Default is 8125. Default is 8125.
.IP \fBaccess_log_statsd_default_sample_rate\fR .IP \fBaccess_log_statsd_default_sample_rate\fR
Default is 1. Default is 1.
.IP \fBaccess_log_statsd_sample_rate_factor\fR
The default is 1.
.IP \fBaccess_log_statsd_metric_prefix\fR .IP \fBaccess_log_statsd_metric_prefix\fR
Default is "" (empty-string) Default is "" (empty-string)
.IP \fBaccess_log_headers\fR .IP \fBaccess_log_headers\fR
Default is False. Default is False.
.IP \fBaccess_log_headers_only\fR
If access_log_headers is True and access_log_headers_only is set only
these headers are logged. Multiple headers can be defined as comma separated
list like this: access_log_headers_only = Host, X-Object-Meta-Mtime
.IP \fBreveal_sensitive_prefix\fR
By default, the X-Auth-Token is logged. To obscure the value,
set reveal_sensitive_prefix to the number of characters to log.
For example, if set to 12, only the first 12 characters of the
token appear in the log. An unauthorized access of the log file
won't allow unauthorized usage of the token. However, the first
12 or so characters is unique enough that you can trace/debug
token usage. Set to 0 to suppress the token completely (replaced
by '...' in the log). The default is 16 chars.
Note: reveal_sensitive_prefix will not affect the value logged with access_log_headers=True.
.IP \fBlog_statsd_valid_http_methods\fR .IP \fBlog_statsd_valid_http_methods\fR
What HTTP methods are allowed for StatsD logging (comma-sep); request methods What HTTP methods are allowed for StatsD logging (comma-sep); request methods
not in this list will have "BAD_METHOD" for the <verb> portion of the metric. not in this list will have "BAD_METHOD" for the <verb> portion of the metric.
Default is "GET,HEAD,POST,PUT,DELETE,COPY,OPTIONS". Default is "GET,HEAD,POST,PUT,DELETE,COPY,OPTIONS".
.RE .RE
.PD .PD
.RS 0
.IP "\fB[filter:bulk]\fR"
.RE
Note: Put before both ratelimit and auth in the pipeline.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the bulk middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#bulk\fR.
.IP \fBmax_containers_per_extraction\fR
The default is 10000.
.IP \fBmax_failed_extractions\fR
The default is 1000.
.IP \fBmax_deletes_per_request\fR
The default is 10000.
.IP \fBmax_failed_deletes\fR
The default is 1000.
In order to keep a connection active during a potentially long bulk request,
Swift may return whitespace prepended to the actual response body. This
whitespace will be yielded no more than every yield_frequency seconds.
The default is 10.
.IP \fByield_frequency\fR
.IP \fBdelete_container_retry_count\fR
Note: This parameter is used during a bulk delete of objects and
their container. This would frequently fail because it is very likely
that all replicated objects have not been deleted by the time the middleware got a
successful response. It can be configured the number of retries. And the
number of seconds to wait between each retry will be 1.5**retry
The default is 0.
.RE
.PD
.RS 0
.IP "\fB[filter:slo]\fR"
.RE
Note: Put after auth and staticweb in the pipeline.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the slo middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#slo\fR.
.IP \fBmax_manifest_segments\fR
The default is 1000.
.IP \fBmax_manifest_size\fR
The default is 2097152.
.IP \fBmin_segment_size\fR
The default is 1048576
.IP \fBrate_limit_after_segment\fR
Start rate-limiting object segments after the Nth segment of a segmented
object. The default is 10 segments.
.IP \fBrate_limit_segments_per_sec\fR
Once segment rate-limiting kicks in for an object, limit segments served to N
per second. The default is 1.
.IP \fBmax_get_time\fR
Time limit on GET requests (seconds). The default is 86400.
.RE
.PD
.RS 0
.IP "\fB[filter:dlo]\fR"
.RE
Note: Put after auth and staticweb in the pipeline.
If you don't put it in the pipeline, it will be inserted for you.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the dlo middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#dlo\fR.
.IP \fBrate_limit_after_segment\fR
Start rate-limiting object segments after the Nth segment of a segmented
object. The default is 10 segments.
.IP \fBrate_limit_segments_per_sec\fR
Once segment rate-limiting kicks in for an object, limit segments served to N
per second. The default is 1.
.IP \fBmax_get_time\fR
Time limit on GET requests (seconds). The default is 86400.
.RE
.PD
.RS 0
.IP "\fB[filter:container-quotas]\fR"
.RE
Note: Put after auth in the pipeline.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the container_quotas middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#container_quotas\fR.
.RE
.PD
.RS 0
.IP "\fB[filter:account-quotas]\fR"
.RE
Note: Put after auth in the pipeline.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the account_quotas middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#account_quotas\fR.
.RE
.PD
.RS 0
.IP "\fB[filter:gatekeeper]\fR"
.RE
Note: this middleware requires python-dnspython
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the gatekeeper middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#gatekeeper\fR.
.IP "\fBset log_name\fR"
Label used when logging. The default is gatekeeper.
.IP "\fBset log_facility\fR"
Syslog log facility. The default is LOG_LOCAL0.
.IP "\fBset log_level\fR "
Logging level. The default is INFO.
.IP "\fBset log_address\fR"
Logging address. The default is /dev/log.
.IP "\fBset log_headers\fR"
Enables the ability to log request headers. The default is False.
.RE
.PD
.RS 0
.IP "\fB[filter:container_sync]\fR"
.RE
Note: this middleware requires python-dnspython
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the container_sync middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#container_sync\fR.
.IP \fBallow_full_urls\fR
Set this to false if you want to disallow any full url values to be set for
any new X-Container-Sync-To headers. This will keep any new full urls from
coming in, but won't change any existing values already in the cluster.
Updating those will have to be done manually, as knowing what the true realm
endpoint should be cannot always be guessed. The default is true.
.IP \fBcurrent\fR
Set this to specify this clusters //realm/cluster as "current" in /info
.RE
.PD
.RS 0
.IP "\fB[filter:xprofile]\fR"
.RE
Note: Put it at the beginning of the pipeline to profile all middleware. But it is safer to put this after healthcheck.
.RS 3
.IP "\fBuse\fR"
Entry point for paste.deploy for the xprofile middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#xprofile\fR.
.IP "\fBprofile_module\fR"
This option enable you to switch profilers which should inherit from python
standard profiler. Currently the supported value can be 'cProfile', 'eventlet.green.profile' etc.
.IP "\fBlog_filename_prefix\fR"
This prefix will be used to combine process ID and timestamp to name the
profile data file. Make sure the executing user has permission to write
into this path (missing path segments will be created, if necessary).
If you enable profiling in more than one type of daemon, you must override
it with an unique value like, the default is /var/log/swift/profile/account.profile.
.IP "\fBdump_interval\fR"
The profile data will be dumped to local disk based on above naming rule
in this interval. The default is 5.0.
.IP "\fBdump_timestamp\fR"
Be careful, this option will enable profiler to dump data into the file with
time stamp which means there will be lots of files piled up in the directory.
The default is false
.IP "\fBpath\fR"
This is the path of the URL to access the mini web UI. The default is __profile__.
.IP "\fBflush_at_shutdown\fR"
Clear the data when the wsgi server shutdown. The default is false.
.IP "\fBunwind\fR"
Unwind the iterator of applications. Default is false.
.RE
.PD
.RS 0
.IP "\fB[filter:versioned_writes]\fR"
.RE
Note: Put after slo, dlo in the pipeline.
If you don't put it in the pipeline, it will be inserted automatically.
.RS 3
.IP \fBuse\fR
Entry point for paste.deploy for the versioned_writes middleware. This is the reference to the installed python egg.
This is normally \fBegg:swift#versioned_writes\fR.
.IP \fBallow_versioned_writes\fR
Enables using versioned writes middleware and exposing configuration settings via HTTP GET /info.
WARNING: Setting this option bypasses the "allow_versions" option
in the container configuration file, which will be eventually
deprecated. See documentation for more details.
.RE
.PD
.SH APP SECTION .SH APP SECTION
@ -518,10 +943,19 @@ Chunk size to read from object servers. The default is 8192.
Chunk size to read from clients. The default is 8192. Chunk size to read from clients. The default is 8192.
.IP \fBnode_timeout\fR .IP \fBnode_timeout\fR
Request timeout to external services. The default is 10 seconds. Request timeout to external services. The default is 10 seconds.
.IP \fBclient_timeout\fR .IP \fBrecoverable_node_timeout\fR
Timeout to read one chunk from a client. The default is 60 seconds. How long the proxy server will wait for an initial response and to read a
chunk of data from the object servers while serving GET / HEAD requests.
Timeouts from these requests can be recovered from so setting this to
something lower than node_timeout would provide quicker error recovery
while allowing for a longer timeout for non-recoverable requests (PUTs).
Defaults to node_timeout, should be overriden if node_timeout is set to a
high number to prevent client timeouts from firing before the proxy server
has a chance to retry.
.IP \fBconn_timeout\fR .IP \fBconn_timeout\fR
Connection timeout to external services. The default is 0.5 seconds. Connection timeout to external services. The default is 0.5 seconds.
.IP \fBpost_quorum_timeout\fR
How long to wait for requests to finish after a quorum has been established. The default is 0.5 seconds.
.IP \fBerror_suppression_interval\fR .IP \fBerror_suppression_interval\fR
Time in seconds that must elapse since the last error for a node to Time in seconds that must elapse since the last error for a node to
be considered no longer error limited. The default is 60 seconds. be considered no longer error limited. The default is 60 seconds.
@ -539,12 +973,63 @@ container sync won't be able to sync posts. The default is True.
.IP \fBaccount_autocreate\fR .IP \fBaccount_autocreate\fR
If set to 'true' authorized accounts that do not yet exist within the Swift cluster If set to 'true' authorized accounts that do not yet exist within the Swift cluster
will be automatically created. The default is set to false. will be automatically created. The default is set to false.
.IP \fBrate_limit_after_segment\fR .IP \fBauto_create_account_prefix\fR
Start rate-limiting object segments after the Nth segment of a segmented Prefix used when automatically creating accounts. The default is '.'.
object. The default is 10 segments. .IP \fBmax_containers_per_account\fR
.IP \fBrate_limit_segments_per_sec\fR If set to a positive value, trying to create a container when the account
Once segment rate-limiting kicks in for an object, limit segments served to N already has at least this maximum containers will result in a 403 Forbidden.
per second. The default is 1. Note: This is a soft limit, meaning a user might exceed the cap for
recheck_account_existence before the 403s kick in.
.IP \fBmax_containers_whitelist\fR
This is a comma separated list of account hashes that ignore the max_containers_per_account cap.
.IP \fBdeny_host_headers\fR
Comma separated list of Host headers to which the proxy will deny requests. The default is empty.
.IP \fBput_queue_depth\fR
Depth of the proxy put queue. The default is 10.
.IP \fBsorting_method\fR
Storage nodes can be chosen at random (shuffle - default), by using timing
measurements (timing), or by using an explicit match (affinity).
Using timing measurements may allow for lower overall latency, while
using affinity allows for finer control. In both the timing and
affinity cases, equally-sorting nodes are still randomly chosen to
spread load.
The valid values for sorting_method are "affinity", "shuffle", and "timing".
.IP \fBtiming_expiry\fR
If the "timing" sorting_method is used, the timings will only be valid for
the number of seconds configured by timing_expiry. The default is 300.
.IP \fBmax_large_object_get_time\fR
The maximum time (seconds) that a large object connection is allowed to last. The default is 86400.
.IP \fBrequest_node_count\fR
Set to the number of nodes to contact for a normal request. You can use
'* replicas' at the end to have it use the number given times the number of
replicas for the ring being used for the request. The default is '2 * replicas'.
.IP \fBread_affinity\fR
Which backend servers to prefer on reads. Format is r<N> for region
N or r<N>z<M> for region N, zone M. The value after the equals is
the priority; lower numbers are higher priority.
Default is empty, meaning no preference.
Example: first read from region 1 zone 1, then region 1 zone 2, then anything in region 2, then everything else:
read_affinity = r1z1=100, r1z2=200, r2=300
.IP \fBwrite_affinity\fR
Which backend servers to prefer on writes. Format is r<N> for region
N or r<N>z<M> for region N, zone M. If this is set, then when
handling an object PUT request, some number (see setting
write_affinity_node_count) of local backend servers will be tried
before any nonlocal ones. Default is empty, meaning no preference.
Example: try to write to regions 1 and 2 before writing to any other
nodes:
write_affinity = r1, r2
.IP \fBwrite_affinity_node_count\fR
The number of local (as governed by the write_affinity setting)
nodes to attempt to contact first, before any non-local ones. You
can use '* replicas' at the end to have it use the number given
times the number of replicas for the ring being used for the
request. The default is '2 * replicas'.
.IP \fBswift_owner_headers\fR
These are the headers whose values will only be shown to swift_owners. The
exact definition of a swift_owner is up to the auth system in use, but
usually indicates administrative responsibilities.
The default is 'x-container-read, x-container-write, x-container-sync-key, x-container-sync-to, x-account-meta-temp-url-key, x-account-meta-temp-url-key-2, x-container-meta-temp-url-key, x-container-meta-temp-url-key-2, x-account-access-control'.
.RE .RE
.PD .PD

View File

@ -109,6 +109,8 @@ allows one to use the keywords such as "all", "main" and "rest" for the <server>
.IP "-c N, --config-num=N \t send command to the Nth server only .IP "-c N, --config-num=N \t send command to the Nth server only
.IP "-k N, --kill-wait=N \t wait N seconds for processes to die (default 15) .IP "-k N, --kill-wait=N \t wait N seconds for processes to die (default 15)
.IP "-r RUN_DIR, --run-dir=RUN_DIR directory where the pids will be stored (default /var/run/swift) .IP "-r RUN_DIR, --run-dir=RUN_DIR directory where the pids will be stored (default /var/run/swift)
.IP "--strict return non-zero status code if some config is missing. Default mode if server is explicitly named."
.IP "--non-strict return zero status code even if some config is missing. Default mode if server is one of aliases `all`, `main` or `rest`."
.PD .PD
.RE .RE

View File

@ -83,7 +83,9 @@ Get drive audit error stats
.IP "\fB-T, --time\fR" .IP "\fB-T, --time\fR"
Check time synchronization Check time synchronization
.IP "\fB--all\fR" .IP "\fB--all\fR"
Perform all checks. Equivalent to \-arudlqT \-\-md5 Perform all checks. Equivalent to \-arudlqT
\-\-md5 \-\-sockstat \-\-auditor \-\-updater \-\-expirer
\-\-driveaudit \-\-validate\-servers
.IP "\fB--region=REGION\fR" .IP "\fB--region=REGION\fR"
Only query servers in specified region Only query servers in specified region
.IP "\fB-z ZONE, --zone=ZONE\fR" .IP "\fB-z ZONE, --zone=ZONE\fR"

View File

@ -110,8 +110,8 @@ You can create scripts to create the account and container rings and rebalance.
cd /etc/swift cd /etc/swift
rm -f account.builder account.ring.gz backups/account.builder backups/account.ring.gz rm -f account.builder account.ring.gz backups/account.builder backups/account.ring.gz
swift-ring-builder account.builder create 18 3 1 swift-ring-builder account.builder create 18 3 1
swift-ring-builder account.builder add z1-<account-server-1>:6002/sdb1 1 swift-ring-builder account.builder add r1z1-<account-server-1>:6002/sdb1 1
swift-ring-builder account.builder add z2-<account-server-2>:6002/sdb1 1 swift-ring-builder account.builder add r1z2-<account-server-2>:6002/sdb1 1
swift-ring-builder account.builder rebalance swift-ring-builder account.builder rebalance
You need to replace the values of <account-server-1>, You need to replace the values of <account-server-1>,
@ -121,7 +121,8 @@ You can create scripts to create the account and container rings and rebalance.
6002, and have a storage device called "sdb1" (this is a directory 6002, and have a storage device called "sdb1" (this is a directory
name created under /drives when we setup the account server). The name created under /drives when we setup the account server). The
"z1", "z2", etc. designate zones, and you can choose whether you "z1", "z2", etc. designate zones, and you can choose whether you
put devices in the same or different zones. put devices in the same or different zones. The "r1" designates
the region, with different regions specified as "r1", "r2", etc.
2. Make the script file executable and run it to create the account ring file:: 2. Make the script file executable and run it to create the account ring file::
@ -588,7 +589,9 @@ This information can also be queried via the swift-recon command line utility::
--md5 Get md5sum of servers ring and compare to local copy --md5 Get md5sum of servers ring and compare to local copy
--sockstat Get cluster socket usage stats --sockstat Get cluster socket usage stats
-T, --time Check time synchronization -T, --time Check time synchronization
--all Perform all checks. Equal to -arudlqT --md5 --sockstat --all Perform all checks. Equal to
-arudlqT --md5 --sockstat --auditor --updater
--expirer --driveaudit --validate-servers
-z ZONE, --zone=ZONE Only query servers in specified zone -z ZONE, --zone=ZONE Only query servers in specified zone
-t SECONDS, --timeout=SECONDS -t SECONDS, --timeout=SECONDS
Time to wait for a response from a server Time to wait for a response from a server

View File

@ -1,5 +1,3 @@
.. _formpost:
==================== ====================
Form POST middleware Form POST middleware
==================== ====================
@ -19,9 +17,7 @@ URL middleware uses. For information about how to set these keys, see
:ref:`secret_keys`. :ref:`secret_keys`.
For information about the form **POST** middleware configuration For information about the form **POST** middleware configuration
options, see `Form options, see :ref:`formpost` in the *Source Documentation*.
post <http://docs.openstack.org/havana/config-reference/content/object-storage-form-post.html>`__
in the *OpenStack Configuration Reference*.
Form POST format Form POST format
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~

View File

@ -293,10 +293,12 @@ a manifest object but a normal object with content same as what you would
get on a **GET** request to original manifest object. get on a **GET** request to original manifest object.
To duplicate a manifest object: To duplicate a manifest object:
* Use the **GET** operation to read the value of ``X-Object-Manifest`` and * Use the **GET** operation to read the value of ``X-Object-Manifest`` and
use this value in the ``X-Object-Manifest`` request header in a **PUT** use this value in the ``X-Object-Manifest`` request header in a **PUT**
operation. operation.
* Alternatively, you can include *``?multipart-manifest=get``* query * Alternatively, you can include *``?multipart-manifest=get``* query
string in the **COPY** request. string in the **COPY** request.
This creates a new manifest object that shares the same set of segment This creates a new manifest object that shares the same set of segment
objects as the original manifest object. objects as the original manifest object.

View File

@ -128,7 +128,24 @@ If you have a large number of containers or objects, you can use query
parameters to page through large lists of containers or objects. Use the parameters to page through large lists of containers or objects. Use the
*``marker``*, *``limit``*, and *``end_marker``* query parameters to *``marker``*, *``limit``*, and *``end_marker``* query parameters to
control how many items are returned in a list and where the list starts control how many items are returned in a list and where the list starts
or ends. or ends. If you want to page through in reverse order, you can use the query
parameter *``reverse``*, noting that your marker and end_markers should be
switched when applied to a reverse listing. I.e, for a list of objects
``[a, b, c, d, e]`` the non-reversed could be:
.. code::
/v1/{account}/{container}/?marker=a&end_marker=d
b
c
However, when reversed marker and end_marker are applied to a reversed list:
.. code::
/v1/{account}/{container}/?marker=d&end_marker=a&reverse=on
c
b
Object Storage HTTP requests have the following default constraints. Object Storage HTTP requests have the following default constraints.
Your service provider might use different default values. Your service provider might use different default values.

View File

@ -15,9 +15,7 @@ downloads the object directly from Object Storage, eliminating the need
for the website to act as a proxy for the request. for the website to act as a proxy for the request.
Ask your cloud administrator to enable the temporary URL feature. For Ask your cloud administrator to enable the temporary URL feature. For
information, see `Temporary information, see :ref:`tempurl` in the *Source Documentation*.
URL <http://docs.openstack.org/havana/config-reference/content/object-storage-tempurl.html>`__
in the *OpenStack Configuration Reference*.
Note Note
~~~~ ~~~~

View File

@ -23,12 +23,15 @@ Application Bindings
* `SwiftBox <https://github.com/suniln/SwiftBox>`_ - C# library using RestSharp * `SwiftBox <https://github.com/suniln/SwiftBox>`_ - C# library using RestSharp
* `jclouds <http://jclouds.incubator.apache.org/documentation/quickstart/openstack/>`_ - Java library offering bindings for all OpenStack projects * `jclouds <http://jclouds.incubator.apache.org/documentation/quickstart/openstack/>`_ - Java library offering bindings for all OpenStack projects
* `java-openstack-swift <https://github.com/dkocher/java-openstack-swift>`_ - Java bindings for OpenStack Swift * `java-openstack-swift <https://github.com/dkocher/java-openstack-swift>`_ - Java bindings for OpenStack Swift
* `swift_client <https://github.com/mrkamel/swift_client>`_ - Small but powerful Ruby client to interact with OpenStack Swift
* `nightcrawler_swift <https://github.com/tulios/nightcrawler_swift>`_ - This Ruby gem teleports your assets to a OpenStack Swift bucket/container
* `swift storage <https://rubygems.org/gems/swift-storage>`_ - Simple Openstack Swift storage client.
Authentication Authentication
-------------- --------------
* `Keystone <https://github.com/openstack/keystone>`_ - Official Identity Service for OpenStack. * `Keystone <https://github.com/openstack/keystone>`_ - Official Identity Service for OpenStack.
* `Swauth <https://github.com/gholt/swauth>`_ - Older Swift authentication service that only requires Swift itself. * `Swauth <https://github.com/openstack/swauth>`_ - An alternative Swift authentication service that only requires Swift itself.
* `Basicauth <https://github.com/CloudVPS/swift-basicauth>`_ - HTTP Basic authentication support (keystone backed). * `Basicauth <https://github.com/CloudVPS/swift-basicauth>`_ - HTTP Basic authentication support (keystone backed).
@ -60,7 +63,7 @@ Content Distribution Network Integration
Alternative API Alternative API
--------------- ---------------
* `Swift3 <https://github.com/stackforge/swift3>`_ - Amazon S3 API emulation. * `Swift3 <https://github.com/openstack/swift3>`_ - Amazon S3 API emulation.
* `CDMI <https://github.com/osaddon/cdmi>`_ - CDMI support * `CDMI <https://github.com/osaddon/cdmi>`_ - CDMI support
@ -81,8 +84,8 @@ Custom Logger Hooks
Storage Backends (DiskFile API implementations) Storage Backends (DiskFile API implementations)
----------------------------------------------- -----------------------------------------------
* `Swift-on-File <https://github.com/stackforge/swiftonfile>`_ - Enables objects created using Swift API to be accessed as files on a POSIX filesystem and vice versa. * `Swift-on-File <https://github.com/openstack/swiftonfile>`_ - Enables objects created using Swift API to be accessed as files on a POSIX filesystem and vice versa.
* `swift-ceph-backend <https://github.com/stackforge/swift-ceph-backend>`_ - Ceph RADOS object server implementation for Swift. * `swift-ceph-backend <https://github.com/openstack/swift-ceph-backend>`_ - Ceph RADOS object server implementation for Swift.
* `kinetic-swift <https://github.com/swiftstack/kinetic-swift>`_ - Seagate Kinetic Drive as backend for Swift * `kinetic-swift <https://github.com/swiftstack/kinetic-swift>`_ - Seagate Kinetic Drive as backend for Swift
* `swift-scality-backend <https://github.com/scality/ScalitySproxydSwift>`_ - Scality sproxyd object server implementation for Swift. * `swift-scality-backend <https://github.com/scality/ScalitySproxydSwift>`_ - Scality sproxyd object server implementation for Swift.
@ -95,7 +98,7 @@ Developer Tools
* `SAIO Ansible playbook <https://github.com/thiagodasilva/swift-aio>`_ - * `SAIO Ansible playbook <https://github.com/thiagodasilva/swift-aio>`_ -
Quickly setup a standard development environment using Vagrant and Ansible in Quickly setup a standard development environment using Vagrant and Ansible in
a Fedora virtual machine (with built-in `Swift-on-File a Fedora virtual machine (with built-in `Swift-on-File
<https://github.com/stackforge/swiftonfile>`_ support). <https://github.com/openstack/swiftonfile>`_ support).
Other Other
----- -----

File diff suppressed because it is too large Load Diff

View File

@ -352,7 +352,7 @@ folks a start on their own code if they want to use repoze.what::
self.ssl = \ self.ssl = \
conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes') conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes')
self.auth_prefix = conf.get('prefix', '/') self.auth_prefix = conf.get('prefix', '/')
self.timeout = int(conf.get('node_timeout', 10)) self.timeout = float(conf.get('node_timeout', 10))
def authenticate(self, env, identity): def authenticate(self, env, identity):
token = identity.get('token') token = identity.get('token')
@ -375,7 +375,7 @@ folks a start on their own code if they want to use repoze.what::
expiration = float(resp.getheader('x-auth-ttl')) expiration = float(resp.getheader('x-auth-ttl'))
user = resp.getheader('x-auth-user') user = resp.getheader('x-auth-user')
memcache_client.set(key, (time(), expiration, user), memcache_client.set(key, (time(), expiration, user),
timeout=expiration) time=expiration)
return user return user
return None return None
@ -487,7 +487,8 @@ folks a start on their own code if they want to use repoze.what::
Allowing CORS with Auth Allowing CORS with Auth
----------------------- -----------------------
Cross Origin RequestS require that the auth system allow the OPTIONS method to Cross Origin Resource Sharing (CORS) require that the auth system allow the
pass through without a token. The preflight request will make an OPTIONS call OPTIONS method to pass through without a token. The preflight request will
against the object or container and will not work if the auth system stops it. make an OPTIONS call against the object or container and will not work if
the auth system stops it.
See TempAuth for an example of how OPTIONS requests are handled. See TempAuth for an example of how OPTIONS requests are handled.

View File

@ -51,16 +51,16 @@ To execute the unit tests:
.. note:: .. note::
As of tox version 2.0.0, most environment variables are not automatically As of tox version 2.0.0, most environment variables are not automatically
passed to the test environment. Swift's tox.ini overrides this default passed to the test environment. Swift's `tox.ini` overrides this default
behavior so that variable names matching SWIFT_* and *_proxy will be passed, behavior so that variable names matching ``SWIFT_*`` and ``*_proxy`` will be
but you may need to run tox --recreate for this to take effect after passed, but you may need to run `tox --recreate` for this to take effect
upgrading from tox<2.0.0. after upgrading from tox<2.0.0.
Conversely, if you do not want those environment variables to be passed to Conversely, if you do not want those environment variables to be passed to
the test environment then you will need to unset them before calling tox. the test environment then you will need to unset them before calling tox.
Also, if you ever encounter DistributionNotFound, try to use `tox --recreate` Also, if you ever encounter DistributionNotFound, try to use `tox --recreate`
or remove the .tox directory to force tox to recreate the dependency list. or remove the `.tox` directory to force tox to recreate the dependency list.
The functional tests may be executed against a :doc:`development_saio` or The functional tests may be executed against a :doc:`development_saio` or
other running Swift cluster using the command: other running Swift cluster using the command:

View File

@ -39,7 +39,7 @@ Installing dependencies
sudo apt-get install curl gcc memcached rsync sqlite3 xfsprogs \ sudo apt-get install curl gcc memcached rsync sqlite3 xfsprogs \
git-core libffi-dev python-setuptools git-core libffi-dev python-setuptools
sudo apt-get install python-coverage python-dev python-nose \ sudo apt-get install python-coverage python-dev python-nose \
python-simplejson python-xattr python-eventlet \ python-xattr python-eventlet \
python-greenlet python-pastedeploy \ python-greenlet python-pastedeploy \
python-netifaces python-pip python-dnspython \ python-netifaces python-pip python-dnspython \
python-mock python-mock
@ -50,14 +50,14 @@ Installing dependencies
sudo yum install curl gcc memcached rsync sqlite xfsprogs git-core \ sudo yum install curl gcc memcached rsync sqlite xfsprogs git-core \
libffi-devel xinetd python-setuptools \ libffi-devel xinetd python-setuptools \
python-coverage python-devel python-nose \ python-coverage python-devel python-nose \
python-simplejson pyxattr python-eventlet \ pyxattr python-eventlet \
python-greenlet python-paste-deploy \ python-greenlet python-paste-deploy \
python-netifaces python-pip python-dns \ python-netifaces python-pip python-dns \
python-mock python-mock
Note: This installs necessary system dependencies and *most* of the python Note: This installs necessary system dependencies and *most* of the python
dependencies. Later in the process setuptools/distribute or pip will install dependencies. Later in the process setuptools/distribute or pip will install
and/or upgrade packages. and/or upgrade packages.
Next, choose either :ref:`partition-section` or :ref:`loopback-section`. Next, choose either :ref:`partition-section` or :ref:`loopback-section`.

View File

@ -6,6 +6,13 @@ Please refer to the latest official
`Openstack Installation Guides <http://docs.openstack.org/#install-guides>`_ `Openstack Installation Guides <http://docs.openstack.org/#install-guides>`_
for the most up-to-date documentation. for the most up-to-date documentation.
Object Storage installation guide for Openstack Liberty
----------------------------------------------------
* `openSUSE 13.2 and SUSE Linux Enterprise Server 12 <http://docs.openstack.org/liberty/install-guide-obs/swift.html>`_
* `RHEL 7, CentOS 7 <http://docs.openstack.org/liberty/install-guide-rdo/swift.html>`_
* `Ubuntu 14.04 <http://docs.openstack.org/liberty/install-guide-ubuntu/swift.html>`_
Object Storage installation guide for Openstack Kilo Object Storage installation guide for Openstack Kilo
---------------------------------------------------- ----------------------------------------------------
@ -26,11 +33,3 @@ Object Storage installation guide for Openstack Icehouse
* `openSUSE and SUSE Linux Enterprise Server <http://docs.openstack.org/icehouse/install-guide/install/zypper/content/ch_swift.html>`_ * `openSUSE and SUSE Linux Enterprise Server <http://docs.openstack.org/icehouse/install-guide/install/zypper/content/ch_swift.html>`_
* `Red Hat Enterprise Linux, CentOS, and Fedora <http://docs.openstack.org/icehouse/install-guide/install/yum/content/ch_swift.html>`_ * `Red Hat Enterprise Linux, CentOS, and Fedora <http://docs.openstack.org/icehouse/install-guide/install/yum/content/ch_swift.html>`_
* `Ubuntu 12.04/14.04 (LTS) <http://docs.openstack.org/icehouse/install-guide/install/apt/content/ch_swift.html>`_ * `Ubuntu 12.04/14.04 (LTS) <http://docs.openstack.org/icehouse/install-guide/install/apt/content/ch_swift.html>`_
Object Storage installation guide for Openstack Havana
------------------------------------------------------
* `Debian 7.0 <http://docs.openstack.org/havana/install-guide/install/apt-debian/content/ch_swift.html>`_
* `openSUSE and SUSE Linux Enterprise Server <http://docs.openstack.org/havana/install-guide/install/zypper/content/ch_swift.html>`_
* `Red Hat Enterprise Linux, CentOS, and Fedora <http://docs.openstack.org/havana/install-guide/install/yum/content/ch_swift.html>`_
* `Ubuntu 12.04 (LTS) <http://docs.openstack.org/havana/install-guide/install/apt/content/ch_swift.html>`_

View File

@ -254,9 +254,11 @@ This configuration works as follows:
``admin`` or ``swiftoperator`` role(s). When validated, the service token ``admin`` or ``swiftoperator`` role(s). When validated, the service token
gives the ``service`` role. gives the ``service`` role.
* Swift interprets the above configuration as follows: * Swift interprets the above configuration as follows:
* Did the user token provide one of the roles listed in operator_roles? * Did the user token provide one of the roles listed in operator_roles?
* Did the service token have the ``service`` role as described by the * Did the service token have the ``service`` role as described by the
``SERVICE_service_roles`` options. ``SERVICE_service_roles`` options.
* If both conditions are met, the request is granted. Otherwise, Swift * If both conditions are met, the request is granted. Otherwise, Swift
rejects the request. rejects the request.

View File

@ -171,6 +171,7 @@ The sequence of events and actions are as follows:
a copy of the <user-token>. In the X-Service-Token header, place your a copy of the <user-token>. In the X-Service-Token header, place your
Service's token. If you use python-swiftclient you can achieve this Service's token. If you use python-swiftclient you can achieve this
by: by:
* Putting the URL in the ``preauthurl`` parameter * Putting the URL in the ``preauthurl`` parameter
* Putting the <user-token> in ``preauthtoken`` paramater * Putting the <user-token> in ``preauthtoken`` paramater
* Adding the X-Service-Token to the ``headers`` parameter * Adding the X-Service-Token to the ``headers`` parameter
@ -251,7 +252,7 @@ However, if one Service is compromised, that Service can access
data created by another Service. To prevent this, multiple Service Prefixes may data created by another Service. To prevent this, multiple Service Prefixes may
be used. This also requires that the operator configure multiple service be used. This also requires that the operator configure multiple service
roles. For example, in a system that has Glance and Cinder, the following roles. For example, in a system that has Glance and Cinder, the following
Swift configuration could be used: Swift configuration could be used::
[keystoneauth] [keystoneauth]
reseller_prefix = AUTH_, IMAGE_, BLOCK_ reseller_prefix = AUTH_, IMAGE_, BLOCK_

View File

@ -2,20 +2,6 @@
Erasure Code Support Erasure Code Support
==================== ====================
--------------------------
Beta: Not production ready
--------------------------
The erasure code support in Swift is considered "beta" at this point.
Most major functionality is included, but it has not been tested or validated
at large scale. This feature relies on ssync for durability. Deployers are
urged to do extensive testing and not deploy production data using an
erasure code storage policy.
If any bugs are found during testing, please report them to
https://bugs.launchpad.net/swift
------------------------------- -------------------------------
History and Theory of Operation History and Theory of Operation
------------------------------- -------------------------------

View File

@ -57,7 +57,7 @@ deployers. Each container has a new special immutable metadata element called
the storage policy index. Note that internally, Swift relies on policy the storage policy index. Note that internally, Swift relies on policy
indexes and not policy names. Policy names exist for human readability and indexes and not policy names. Policy names exist for human readability and
translation is managed in the proxy. When a container is created, one new translation is managed in the proxy. When a container is created, one new
optional header is supported to specify the policy name. If nothing is optional header is supported to specify the policy name. If no name is
specified, the default policy is used (and if no other policies defined, specified, the default policy is used (and if no other policies defined,
Policy-0 is considered the default). We will be covering the difference Policy-0 is considered the default). We will be covering the difference
between default and Policy-0 in the next section. between default and Policy-0 in the next section.
@ -170,12 +170,13 @@ Storage Policies is a versatile feature intended to support both new and
pre-existing clusters with the same level of flexibility. For that reason, we pre-existing clusters with the same level of flexibility. For that reason, we
introduce the ``Policy-0`` concept which is not the same as the "default" introduce the ``Policy-0`` concept which is not the same as the "default"
policy. As you will see when we begin to configure policies, each policy has policy. As you will see when we begin to configure policies, each policy has
both a name (human friendly, configurable) as well as an index (or simply a single name and an arbitrary number of aliases (human friendly,
policy number). Swift reserves index 0 to map to the object ring that's configurable) as well as an index (or simply policy number). Swift reserves
present in all installations (e.g., ``/etc/swift/object.ring.gz``). You can index 0 to map to the object ring that's present in all installations
name this policy anything you like, and if no policies are defined it will (e.g., ``/etc/swift/object.ring.gz``). You can name this policy anything you
report itself as ``Policy-0``, however you cannot change the index as there must like, and if no policies are defined it will report itself as ``Policy-0``,
always be a policy with index 0. however you cannot change the index as there must always be a policy with
index 0.
Another important concept is the default policy which can be any policy Another important concept is the default policy which can be any policy
in the cluster. The default policy is the policy that is automatically in the cluster. The default policy is the policy that is automatically
@ -273,6 +274,8 @@ file:
* Policy names must contain only letters, digits or a dash * Policy names must contain only letters, digits or a dash
* Policy names must be unique * Policy names must be unique
* The policy name 'Policy-0' can only be used for the policy with index 0 * The policy name 'Policy-0' can only be used for the policy with index 0
* Multiple names can be assigned to one policy using aliases. All names
must follow the Swift naming rules.
* If any policies are defined, exactly one policy must be declared default * If any policies are defined, exactly one policy must be declared default
* Deprecated policies cannot be declared the default * Deprecated policies cannot be declared the default
* If no ``policy_type`` is provided, ``replication`` is the default value. * If no ``policy_type`` is provided, ``replication`` is the default value.
@ -288,6 +291,7 @@ example configuration.::
[storage-policy:0] [storage-policy:0]
name = gold name = gold
aliases = yellow, orange
policy_type = replication policy_type = replication
default = yes default = yes
@ -301,8 +305,10 @@ information about the ``default`` and ``deprecated`` options.
There are some other considerations when managing policies: There are some other considerations when managing policies:
* Policy names can be changed (but be sure that users are aware, aliases are * Policy names can be changed.
not currently supported but could be implemented in custom middleware!) * Aliases are supported and can be added and removed. If the primary name
of a policy is removed the next available alias will be adopted as the
primary name. A policy must always have at least one name.
* You cannot change the index of a policy once it has been created * You cannot change the index of a policy once it has been created
* The default policy can be changed at any time, by adding the * The default policy can be changed at any time, by adding the
default directive to the desired policy section default directive to the desired policy section
@ -399,7 +405,7 @@ The module, :ref:`storage_policy`, is responsible for parsing the
configured policies via class :class:`.StoragePolicyCollection`. This configured policies via class :class:`.StoragePolicyCollection`. This
collection is made up of policies of class :class:`.StoragePolicy`. The collection is made up of policies of class :class:`.StoragePolicy`. The
collection class includes handy functions for getting to a policy either by collection class includes handy functions for getting to a policy either by
name or by index , getting info about the policies, etc. There's also one name or by index , getting info about the policies, etc. There's also one
very important function, :meth:`~.StoragePolicyCollection.get_object_ring`. very important function, :meth:`~.StoragePolicyCollection.get_object_ring`.
Object rings are members of the :class:`.StoragePolicy` class and are Object rings are members of the :class:`.StoragePolicy` class and are
actually not instantiated until the :meth:`~.StoragePolicy.load_ring` actually not instantiated until the :meth:`~.StoragePolicy.load_ring`

4
doc/source/policies_saio.rst Normal file → Executable file
View File

@ -26,6 +26,7 @@ to implement a usable set of policies.
[storage-policy:0] [storage-policy:0]
name = gold name = gold
aliases = yellow, orange
default = yes default = yes
[storage-policy:1] [storage-policy:1]
@ -82,7 +83,8 @@ Storage Policies effect placement of data in Swift.
You should see this: (only showing the policy output here):: You should see this: (only showing the policy output here)::
policies: [{'default': True, 'name': 'gold'}, {'name': 'silver'}] policies: [{'aliases': 'gold, yellow, orange', 'default': True,
'name': 'gold'}, {'aliases': 'silver', 'name': 'silver'}]
3. Now create a container without specifying a policy, it will use the 3. Now create a container without specifying a policy, it will use the
default, 'gold' and then put a test object in it (create the file ``file0.txt`` default, 'gold' and then put a test object in it (create the file ``file0.txt``

View File

@ -70,7 +70,7 @@ use = egg:swift#account
# "replication_server" (this is the default). To only handle replication, # "replication_server" (this is the default). To only handle replication,
# set to a True value (e.g. "True" or "1"). To handle only non-replication # set to a True value (e.g. "True" or "1"). To handle only non-replication
# verbs, set to "False". Unless you have a separate replication network, you # verbs, set to "False". Unless you have a separate replication network, you
# should not specify any value for "replication_server". # should not specify any value for "replication_server". Default is empty.
# replication_server = false # replication_server = false
[filter:healthcheck] [filter:healthcheck]
@ -90,8 +90,19 @@ use = egg:swift#recon
# log_level = INFO # log_level = INFO
# log_address = /dev/log # log_address = /dev/log
# #
# Maximum number of database rows that will be sync'd in a single HTTP
# replication request. Databases with less than or equal to this number of
# differing rows will always be sync'd using an HTTP replication request rather
# than using rsync.
# per_diff = 1000 # per_diff = 1000
#
# Maximum number of HTTP replication requests attempted on each replication
# pass for any one container. This caps how long the replicator will spend
# trying to sync a given database per pass so the other databases don't get
# starved.
# max_diffs = 100 # max_diffs = 100
#
# Number of replication workers to spawn.
# concurrency = 8 # concurrency = 8
# #
# Time in seconds to wait between replication passes # Time in seconds to wait between replication passes
@ -126,8 +137,6 @@ use = egg:swift#recon
# Will audit each account at most once per interval # Will audit each account at most once per interval
# interval = 1800 # interval = 1800
# #
# log_facility = LOG_LOCAL0
# log_level = INFO
# accounts_per_second = 200 # accounts_per_second = 200
# recon_cache_path = /var/cache/swift # recon_cache_path = /var/cache/swift

View File

@ -99,8 +99,19 @@ use = egg:swift#recon
# log_level = INFO # log_level = INFO
# log_address = /dev/log # log_address = /dev/log
# #
# Maximum number of database rows that will be sync'd in a single HTTP
# replication request. Databases with less than or equal to this number of
# differing rows will always be sync'd using an HTTP replication request rather
# than using rsync.
# per_diff = 1000 # per_diff = 1000
#
# Maximum number of HTTP replication requests attempted on each replication
# pass for any one container. This caps how long the replicator will spend
# trying to sync a given database per pass so the other databases don't get
# starved.
# max_diffs = 100 # max_diffs = 100
#
# Number of replication workers to spawn.
# concurrency = 8 # concurrency = 8
# #
# Time in seconds to wait between replication passes # Time in seconds to wait between replication passes

View File

@ -1,11 +1,15 @@
[drive-audit] [drive-audit]
# device_dir = /srv/node # device_dir = /srv/node
#
# You can specify default log routing here if you want:
# log_name = drive-audit
# log_facility = LOG_LOCAL0 # log_facility = LOG_LOCAL0
# log_level = INFO # log_level = INFO
# log_address = /dev/log # log_address = /dev/log
# The following caps the length of log lines to the value given; no limit if # The following caps the length of log lines to the value given; no limit if
# set to 0, the default. # set to 0, the default.
# log_max_line_length = 0 # log_max_line_length = 0
#
# minutes = 60 # minutes = 60
# error_limit = 1 # error_limit = 1
# recon_cache_path = /var/cache/swift # recon_cache_path = /var/cache/swift

View File

@ -172,10 +172,7 @@ use = egg:swift#recon
# concurrency = 1 # concurrency = 1
# stats_interval = 300 # stats_interval = 300
# #
# The sync method to use; default is rsync but you can use ssync to try the # default is rsync, alternative is ssync
# EXPERIMENTAL all-swift-code-no-rsync-callouts method. Once ssync is verified
# as having performance comparable to, or better than, rsync, we plan to
# deprecate rsync so we can move on with more features for replication.
# sync_method = rsync # sync_method = rsync
# #
# max duration of a partition rsync # max duration of a partition rsync

View File

@ -77,8 +77,15 @@ bind_port = 8080
# eventlet_debug = false # eventlet_debug = false
[pipeline:main] [pipeline:main]
# This sample pipeline uses tempauth and is used for SAIO dev work and
# testing. See below for a pipeline using keystone.
pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit tempauth container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit tempauth container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server
# The following pipeline shows keystone integration. Comment out the one
# above and uncomment this one. Additional steps for integrating keystone are
# covered further below in the filter sections for authtoken and keystoneauth.
#pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit authtoken keystoneauth container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server
[app:proxy-server] [app:proxy-server]
use = egg:swift#proxy use = egg:swift#proxy
# You can override the default log routing for this app here: # You can override the default log routing for this app here:
@ -277,9 +284,8 @@ user_test5_tester5 = testing5 service
# refer to the keystone's documentation for details about the # refer to the keystone's documentation for details about the
# different settings. # different settings.
# #
# You'll need to have as well the keystoneauth middleware enabled # You'll also need to have the keystoneauth middleware enabled and have it in
# and have it in your main pipeline so instead of having tempauth in # your main pipeline, as show in the sample pipeline at the top of this file.
# there you can change it to: authtoken keystoneauth
# #
# [filter:authtoken] # [filter:authtoken]
# paste.filter_factory = keystonemiddleware.auth_token:filter_factory # paste.filter_factory = keystonemiddleware.auth_token:filter_factory
@ -499,6 +505,12 @@ use = egg:swift#cname_lookup
# Note: Put staticweb just after your auth filter(s) in the pipeline # Note: Put staticweb just after your auth filter(s) in the pipeline
[filter:staticweb] [filter:staticweb]
use = egg:swift#staticweb use = egg:swift#staticweb
# You can override the default log routing for this filter here:
# set log_name = staticweb
# set log_facility = LOG_LOCAL0
# set log_level = INFO
# set log_headers = false
# set log_address = /dev/log
# Note: Put tempurl before dlo, slo and your auth filter(s) in the pipeline # Note: Put tempurl before dlo, slo and your auth filter(s) in the pipeline
[filter:tempurl] [filter:tempurl]

15
etc/swift.conf-sample Normal file → Executable file
View File

@ -21,7 +21,7 @@ swift_hash_path_prefix = changeme
# policy with index 0 will be declared the default. If multiple policies are # policy with index 0 will be declared the default. If multiple policies are
# defined you must define a policy with index 0 and you must specify a # defined you must define a policy with index 0 and you must specify a
# default. It is recommended you always define a section for # default. It is recommended you always define a section for
# storage-policy:0. # storage-policy:0. Aliases are not required when defining a storage policy.
# #
# A 'policy_type' argument is also supported but is not mandatory. Default # A 'policy_type' argument is also supported but is not mandatory. Default
# policy type 'replication' is used when 'policy_type' is unspecified. # policy type 'replication' is used when 'policy_type' is unspecified.
@ -29,6 +29,7 @@ swift_hash_path_prefix = changeme
name = Policy-0 name = Policy-0
default = yes default = yes
#policy_type = replication #policy_type = replication
aliases = yellow, orange
# the following section would declare a policy called 'silver', the number of # the following section would declare a policy called 'silver', the number of
# replicas will be determined by how the ring is built. In this example the # replicas will be determined by how the ring is built. In this example the
@ -40,7 +41,10 @@ default = yes
# this config has specified it as the default. However if a legacy container # this config has specified it as the default. However if a legacy container
# (one created with a pre-policy version of swift) is accessed, it is known # (one created with a pre-policy version of swift) is accessed, it is known
# implicitly to be assigned to the policy with index 0 as opposed to the # implicitly to be assigned to the policy with index 0 as opposed to the
# current default. # current default. Note that even without specifying any aliases, a policy
# always has at least the default name stored in aliases because this field is
# used to contain all human readable names for a storage policy.
#
#[storage-policy:1] #[storage-policy:1]
#name = silver #name = silver
#policy_type = replication #policy_type = replication
@ -67,12 +71,13 @@ default = yes
# refer to Swift documentation for details on how to configure EC policies. # refer to Swift documentation for details on how to configure EC policies.
# #
# The example 'deepfreeze10-4' policy defined below is a _sample_ # The example 'deepfreeze10-4' policy defined below is a _sample_
# configuration with 10 'data' and 4 'parity' fragments. 'ec_type' # configuration with an alias of 'df10-4' as well as 10 'data' and 4 'parity'
# defines the Erasure Coding scheme. 'jerasure_rs_vand' (Reed-Solomon # fragments. 'ec_type' defines the Erasure Coding scheme.
# Vandermonde) is used as an example below. # 'jerasure_rs_vand' (Reed-Solomon Vandermonde) is used as an example below.
# #
#[storage-policy:2] #[storage-policy:2]
#name = deepfreeze10-4 #name = deepfreeze10-4
#aliases = df10-4
#policy_type = erasure_coding #policy_type = erasure_coding
#ec_type = jerasure_rs_vand #ec_type = jerasure_rs_vand
#ec_num_data_fragments = 10 #ec_num_data_fragments = 10

View File

@ -2,12 +2,12 @@
# of appearance. Changing the order has an impact on the overall integration # of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later. # process, which may cause wedges in the gate later.
dnspython>=1.9.4 dnspython>=1.12.0;python_version<'3.0'
dnspython3>=1.12.0;python_version>='3.0'
eventlet>=0.16.1,!=0.17.0 eventlet>=0.16.1,!=0.17.0
greenlet>=0.3.1 greenlet>=0.3.1
netifaces>=0.5,!=0.10.0,!=0.10.1 netifaces>=0.5,!=0.10.0,!=0.10.1
pastedeploy>=1.3.3 pastedeploy>=1.3.3
simplejson>=2.0.9
six>=1.9.0 six>=1.9.0
xattr>=0.4 xattr>=0.4
PyECLib==1.0.7 # BSD PyECLib>=1.0.7 # BSD

View File

@ -366,7 +366,7 @@ class AccountBroker(DatabaseBroker):
''').fetchone()) ''').fetchone())
def list_containers_iter(self, limit, marker, end_marker, prefix, def list_containers_iter(self, limit, marker, end_marker, prefix,
delimiter): delimiter, reverse=False):
""" """
Get a list of containers sorted by name starting at marker onward, up Get a list of containers sorted by name starting at marker onward, up
to limit entries. Entries will begin with the prefix and will not have to limit entries. Entries will begin with the prefix and will not have
@ -377,15 +377,21 @@ class AccountBroker(DatabaseBroker):
:param end_marker: end marker query :param end_marker: end marker query
:param prefix: prefix query :param prefix: prefix query
:param delimiter: delimiter for query :param delimiter: delimiter for query
:param reverse: reverse the result order.
:returns: list of tuples of (name, object_count, bytes_used, 0) :returns: list of tuples of (name, object_count, bytes_used, 0)
""" """
delim_force_gte = False delim_force_gte = False
(marker, end_marker, prefix, delimiter) = utf8encode( (marker, end_marker, prefix, delimiter) = utf8encode(
marker, end_marker, prefix, delimiter) marker, end_marker, prefix, delimiter)
if reverse:
# Reverse the markers if we are reversing the listing.
marker, end_marker = end_marker, marker
self._commit_puts_stale_ok() self._commit_puts_stale_ok()
if delimiter and not prefix: if delimiter and not prefix:
prefix = '' prefix = ''
if prefix:
end_prefix = prefix[:-1] + chr(ord(prefix[-1]) + 1)
orig_marker = marker orig_marker = marker
with self.get() as conn: with self.get() as conn:
results = [] results = []
@ -395,9 +401,13 @@ class AccountBroker(DatabaseBroker):
FROM container FROM container
WHERE """ WHERE """
query_args = [] query_args = []
if end_marker: if end_marker and (not prefix or end_marker < end_prefix):
query += ' name < ? AND' query += ' name < ? AND'
query_args.append(end_marker) query_args.append(end_marker)
elif prefix:
query += ' name < ? AND'
query_args.append(end_prefix)
if delim_force_gte: if delim_force_gte:
query += ' name >= ? AND' query += ' name >= ? AND'
query_args.append(marker) query_args.append(marker)
@ -413,38 +423,40 @@ class AccountBroker(DatabaseBroker):
query += ' +deleted = 0' query += ' +deleted = 0'
else: else:
query += ' deleted = 0' query += ' deleted = 0'
query += ' ORDER BY name LIMIT ?' query += ' ORDER BY name %s LIMIT ?' % \
('DESC' if reverse else '')
query_args.append(limit - len(results)) query_args.append(limit - len(results))
curs = conn.execute(query, query_args) curs = conn.execute(query, query_args)
curs.row_factory = None curs.row_factory = None
if prefix is None: # Delimiters without a prefix is ignored, further if there
# A delimiter without a specified prefix is ignored # is no delimiter then we can simply return the result as
# prefixes are now handled in the SQL statement.
if prefix is None or not delimiter:
return [r for r in curs] return [r for r in curs]
if not delimiter:
if not prefix:
# It is possible to have a delimiter but no prefix
# specified. As above, the prefix will be set to the
# empty string, so avoid performing the extra work to
# check against an empty prefix.
return [r for r in curs]
else:
return [r for r in curs if r[0].startswith(prefix)]
# We have a delimiter and a prefix (possibly empty string) to # We have a delimiter and a prefix (possibly empty string) to
# handle # handle
rowcount = 0 rowcount = 0
for row in curs: for row in curs:
rowcount += 1 rowcount += 1
marker = name = row[0] name = row[0]
if len(results) >= limit or not name.startswith(prefix): if reverse:
end_marker = name
else:
marker = name
if len(results) >= limit:
curs.close() curs.close()
return results return results
end = name.find(delimiter, len(prefix)) end = name.find(delimiter, len(prefix))
if end > 0: if end > 0:
marker = name[:end] + chr(ord(delimiter) + 1) if reverse:
# we want result to be inclusive of delim+1 end_marker = name[:end + 1]
delim_force_gte = True else:
marker = name[:end] + chr(ord(delimiter) + 1)
# we want result to be inclusive of delim+1
delim_force_gte = True
dir_name = name[:end + 1] dir_name = name[:end + 1]
if dir_name != orig_marker: if dir_name != orig_marker:
results.append([dir_name, 0, 0, 1]) results.append([dir_name, 0, 0, 1])

View File

@ -70,10 +70,10 @@ class AccountReaper(Daemon):
self.account_ring = None self.account_ring = None
self.container_ring = None self.container_ring = None
self.object_ring = None self.object_ring = None
self.node_timeout = int(conf.get('node_timeout', 10)) self.node_timeout = float(conf.get('node_timeout', 10))
self.conn_timeout = float(conf.get('conn_timeout', 0.5)) self.conn_timeout = float(conf.get('conn_timeout', 0.5))
self.myips = whataremyips(conf.get('bind_ip', '0.0.0.0')) self.myips = whataremyips(conf.get('bind_ip', '0.0.0.0'))
self.bind_port = int(conf.get('bind_port', 0)) self.bind_port = int(conf.get('bind_port', 6002))
self.concurrency = int(conf.get('concurrency', 25)) self.concurrency = int(conf.get('concurrency', 25))
self.container_concurrency = self.object_concurrency = \ self.container_concurrency = self.object_concurrency = \
sqrt(self.concurrency) sqrt(self.concurrency)
@ -311,8 +311,8 @@ class AccountReaper(Daemon):
delete_timestamp = Timestamp(info['delete_timestamp']) delete_timestamp = Timestamp(info['delete_timestamp'])
if self.stats_containers_remaining and \ if self.stats_containers_remaining and \
begin - float(delete_timestamp) >= self.reap_not_done_after: begin - float(delete_timestamp) >= self.reap_not_done_after:
self.logger.warn(_('Account %s has not been reaped since %s') % self.logger.warning(_('Account %s has not been reaped since %s') %
(account, delete_timestamp.isoformat)) (account, delete_timestamp.isoformat))
return True return True
def reap_container(self, account, account_partition, account_nodes, def reap_container(self, account, account_partition, account_nodes,

View File

@ -191,6 +191,7 @@ class AccountController(BaseStorageServer):
return HTTPPreconditionFailed(body='Bad delimiter') return HTTPPreconditionFailed(body='Bad delimiter')
limit = constraints.ACCOUNT_LISTING_LIMIT limit = constraints.ACCOUNT_LISTING_LIMIT
given_limit = get_param(req, 'limit') given_limit = get_param(req, 'limit')
reverse = config_true_value(get_param(req, 'reverse'))
if given_limit and given_limit.isdigit(): if given_limit and given_limit.isdigit():
limit = int(given_limit) limit = int(given_limit)
if limit > constraints.ACCOUNT_LISTING_LIMIT: if limit > constraints.ACCOUNT_LISTING_LIMIT:
@ -211,7 +212,7 @@ class AccountController(BaseStorageServer):
return self._deleted_response(broker, req, HTTPNotFound) return self._deleted_response(broker, req, HTTPNotFound)
return account_listing_response(account, req, out_content_type, broker, return account_listing_response(account, req, out_content_type, broker,
limit, marker, end_marker, prefix, limit, marker, end_marker, prefix,
delimiter) delimiter, reverse)
@public @public
@replication @replication

View File

@ -13,11 +13,12 @@
# 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.
import json
import time import time
from xml.sax import saxutils from xml.sax import saxutils
from swift.common.swob import HTTPOk, HTTPNoContent from swift.common.swob import HTTPOk, HTTPNoContent
from swift.common.utils import json, Timestamp from swift.common.utils import Timestamp
from swift.common.storage_policy import POLICIES from swift.common.storage_policy import POLICIES
@ -70,14 +71,14 @@ def get_response_headers(broker):
def account_listing_response(account, req, response_content_type, broker=None, def account_listing_response(account, req, response_content_type, broker=None,
limit='', marker='', end_marker='', prefix='', limit='', marker='', end_marker='', prefix='',
delimiter=''): delimiter='', reverse=False):
if broker is None: if broker is None:
broker = FakeAccountBroker() broker = FakeAccountBroker()
resp_headers = get_response_headers(broker) resp_headers = get_response_headers(broker)
account_list = broker.list_containers_iter(limit, marker, end_marker, account_list = broker.list_containers_iter(limit, marker, end_marker,
prefix, delimiter) prefix, delimiter, reverse)
if response_content_type == 'application/json': if response_content_type == 'application/json':
data = [] data = []
for (name, object_count, bytes_used, is_subdir) in account_list: for (name, object_count, bytes_used, is_subdir) in account_list:

View File

@ -978,7 +978,8 @@ class SwiftRecon(object):
order.') order.')
args.add_option('--all', action="store_true", args.add_option('--all', action="store_true",
help="Perform all checks. Equal to \t\t\t-arudlqT " help="Perform all checks. Equal to \t\t\t-arudlqT "
"--md5 --sockstat --auditor --updater --expirer") "--md5 --sockstat --auditor --updater --expirer "
"--driveaudit --validate-servers")
args.add_option('--region', type="int", args.add_option('--region', type="int",
help="Only query servers in specified region") help="Only query servers in specified region")
args.add_option('--zone', '-z', type="int", args.add_option('--zone', '-z', type="int",
@ -1018,22 +1019,21 @@ class SwiftRecon(object):
if options.all: if options.all:
if self.server_type == 'object': if self.server_type == 'object':
self.async_check(hosts) self.async_check(hosts)
self.replication_check(hosts)
self.object_auditor_check(hosts) self.object_auditor_check(hosts)
self.updater_check(hosts) self.updater_check(hosts)
self.expirer_check(hosts) self.expirer_check(hosts)
elif self.server_type == 'container': elif self.server_type == 'container':
self.replication_check(hosts)
self.auditor_check(hosts) self.auditor_check(hosts)
self.updater_check(hosts) self.updater_check(hosts)
elif self.server_type == 'account': elif self.server_type == 'account':
self.replication_check(hosts)
self.auditor_check(hosts) self.auditor_check(hosts)
self.replication_check(hosts)
self.umount_check(hosts) self.umount_check(hosts)
self.load_check(hosts) self.load_check(hosts)
self.disk_usage(hosts, options.top, options.lowest, self.disk_usage(hosts, options.top, options.lowest,
options.human_readable) options.human_readable)
self.get_ringmd5(hosts, swift_dir) self.get_ringmd5(hosts, swift_dir)
self.get_swiftconfmd5(hosts)
self.quarantine_check(hosts) self.quarantine_check(hosts)
self.socket_usage(hosts) self.socket_usage(hosts)
self.server_type_check(hosts) self.server_type_check(hosts)

View File

@ -306,18 +306,20 @@ def run_scenario(scenario):
command_f(*command) command_f(*command)
rebalance_number = 1 rebalance_number = 1
parts_moved, old_balance = rb.rebalance(seed=seed) parts_moved, old_balance, removed_devs = rb.rebalance(seed=seed)
rb.pretend_min_part_hours_passed() rb.pretend_min_part_hours_passed()
print "\tRebalance 1: moved %d parts, balance is %.6f" % ( print "\tRebalance 1: moved %d parts, balance is %.6f, \
parts_moved, old_balance) %d removed devs" % (
parts_moved, old_balance, removed_devs)
while True: while True:
rebalance_number += 1 rebalance_number += 1
parts_moved, new_balance = rb.rebalance(seed=seed) parts_moved, new_balance, removed_devs = rb.rebalance(seed=seed)
rb.pretend_min_part_hours_passed() rb.pretend_min_part_hours_passed()
print "\tRebalance %d: moved %d parts, balance is %.6f" % ( print "\tRebalance %d: moved %d parts, balance is %.6f, \
rebalance_number, parts_moved, new_balance) %d removed devs" % (
if parts_moved == 0: rebalance_number, parts_moved, new_balance, removed_devs)
if parts_moved == 0 and removed_devs == 0:
break break
if abs(new_balance - old_balance) < 1 and not ( if abs(new_balance - old_balance) < 1 and not (
old_balance == builder.MAX_BALANCE and old_balance == builder.MAX_BALANCE and

View File

@ -293,14 +293,14 @@ def _parse_set_info_values(argvish):
devs = builder.search_devs(parse_search_value(search_value)) devs = builder.search_devs(parse_search_value(search_value))
change = {} change = {}
ip = '' ip = ''
if len(change_value) and change_value[0].isdigit(): if change_value and change_value[0].isdigit():
i = 1 i = 1
while (i < len(change_value) and while (i < len(change_value) and
change_value[i] in '0123456789.'): change_value[i] in '0123456789.'):
i += 1 i += 1
ip = change_value[:i] ip = change_value[:i]
change_value = change_value[i:] change_value = change_value[i:]
elif len(change_value) and change_value[0] == '[': elif change_value and change_value.startswith('['):
i = 1 i = 1
while i < len(change_value) and change_value[i] != ']': while i < len(change_value) and change_value[i] != ']':
i += 1 i += 1
@ -318,14 +318,14 @@ def _parse_set_info_values(argvish):
if change_value.startswith('R'): if change_value.startswith('R'):
change_value = change_value[1:] change_value = change_value[1:]
replication_ip = '' replication_ip = ''
if len(change_value) and change_value[0].isdigit(): if change_value and change_value[0].isdigit():
i = 1 i = 1
while (i < len(change_value) and while (i < len(change_value) and
change_value[i] in '0123456789.'): change_value[i] in '0123456789.'):
i += 1 i += 1
replication_ip = change_value[:i] replication_ip = change_value[:i]
change_value = change_value[i:] change_value = change_value[i:]
elif len(change_value) and change_value[0] == '[': elif change_value and change_value.startswith('['):
i = 1 i = 1
while i < len(change_value) and change_value[i] != ']': while i < len(change_value) and change_value[i] != ']':
i += 1 i += 1
@ -421,6 +421,8 @@ swift-ring-builder <builder_file> create <part_power> <replicas>
""" """
swift-ring-builder <builder_file> swift-ring-builder <builder_file>
Shows information about the ring and the devices within. Shows information about the ring and the devices within.
Flags:
DEL - marked for removal and will be removed next rebalance.
""" """
print('%s, build version %d' % (builder_file, builder.version)) print('%s, build version %d' % (builder_file, builder.version))
regions = 0 regions = 0
@ -446,28 +448,19 @@ swift-ring-builder <builder_file>
print('The overload factor is %0.2f%% (%.6f)' % ( print('The overload factor is %0.2f%% (%.6f)' % (
builder.overload * 100, builder.overload)) builder.overload * 100, builder.overload))
if builder.devs: if builder.devs:
balance_per_dev = builder._build_balance_per_dev()
print('Devices: id region zone ip address port ' print('Devices: id region zone ip address port '
'replication ip replication port name ' 'replication ip replication port name '
'weight partitions balance meta') 'weight partitions balance flags meta')
weighted_parts = builder.parts * builder.replicas / \ for dev in builder._iter_devs():
sum(d['weight'] for d in builder.devs if d is not None) flags = 'DEL' if dev in builder._remove_devs else ''
for dev in builder.devs:
if dev is None:
continue
if not dev['weight']:
if dev['parts']:
balance = MAX_BALANCE
else:
balance = 0
else:
balance = 100.0 * dev['parts'] / \
(dev['weight'] * weighted_parts) - 100.0
print(' %5d %7d %5d %15s %5d %15s %17d %9s %6.02f ' print(' %5d %7d %5d %15s %5d %15s %17d %9s %6.02f '
'%10s %7.02f %s' % '%10s %7.02f %5s %s' %
(dev['id'], dev['region'], dev['zone'], dev['ip'], (dev['id'], dev['region'], dev['zone'], dev['ip'],
dev['port'], dev['replication_ip'], dev['port'], dev['replication_ip'],
dev['replication_port'], dev['device'], dev['weight'], dev['replication_port'], dev['device'], dev['weight'],
dev['parts'], balance, dev['meta'])) dev['parts'], balance_per_dev[dev['id']], flags,
dev['meta']))
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def search(): def search():
@ -797,7 +790,7 @@ swift-ring-builder <builder_file> rebalance [options]
devs_changed = builder.devs_changed devs_changed = builder.devs_changed
try: try:
last_balance = builder.get_balance() last_balance = builder.get_balance()
parts, balance = builder.rebalance(seed=get_seed(3)) parts, balance, removed_devs = builder.rebalance(seed=get_seed(3))
except exceptions.RingBuilderError as e: except exceptions.RingBuilderError as e:
print('-' * 79) print('-' * 79)
print("An error has occurred during ring validation. Common\n" print("An error has occurred during ring validation. Common\n"
@ -807,7 +800,7 @@ swift-ring-builder <builder_file> rebalance [options]
(e,)) (e,))
print('-' * 79) print('-' * 79)
exit(EXIT_ERROR) exit(EXIT_ERROR)
if not (parts or options.force): if not (parts or options.force or removed_devs):
print('No partitions could be reassigned.') print('No partitions could be reassigned.')
print('Either none need to be or none can be due to ' print('Either none need to be or none can be due to '
'min_part_hours [%s].' % builder.min_part_hours) 'min_part_hours [%s].' % builder.min_part_hours)
@ -921,6 +914,8 @@ swift-ring-builder <builder_file> dispersion <search_filter> [options]
verbose=options.verbose) verbose=options.verbose)
print('Dispersion is %.06f, Balance is %.06f, Overload is %0.2f%%' % ( print('Dispersion is %.06f, Balance is %.06f, Overload is %0.2f%%' % (
builder.dispersion, builder.get_balance(), builder.overload * 100)) builder.dispersion, builder.get_balance(), builder.overload * 100))
print('Required overload is %.6f%%' % (
builder.get_required_overload() * 100))
if report['worst_tier']: if report['worst_tier']:
status = EXIT_WARNING status = EXIT_WARNING
print('Worst tier is %.06f (%s)' % (report['max_dispersion'], print('Worst tier is %.06f (%s)' % (report['max_dispersion'],
@ -1031,10 +1026,22 @@ swift-ring-builder <ring_file> write_builder [min_part_hours]
for parts in builder._replica2part2dev: for parts in builder._replica2part2dev:
for dev_id in parts: for dev_id in parts:
builder.devs[dev_id]['parts'] += 1 builder.devs[dev_id]['parts'] += 1
builder._set_parts_wanted()
builder.save(builder_file) builder.save(builder_file)
def pretend_min_part_hours_passed(): def pretend_min_part_hours_passed():
"""
swift-ring-builder <builder_file> pretend_min_part_hours_passed
Resets the clock on the last time a rebalance happened, thus
circumventing the min_part_hours check.
*****************************
USE THIS WITH EXTREME CAUTION
*****************************
If you run this command and deploy rebalanced rings before a replication
pass completes, you may introduce unavailability in your cluster. This
has an end-user impact.
"""
builder.pretend_min_part_hours_passed() builder.pretend_min_part_hours_passed()
builder.save(builder_file) builder.save(builder_file)
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
@ -1144,7 +1151,7 @@ def main(arguments=None):
print(Commands.default.__doc__.strip()) print(Commands.default.__doc__.strip())
print() print()
cmds = [c for c, f in Commands.__dict__.items() cmds = [c for c, f in Commands.__dict__.items()
if f.__doc__ and c[0] != '_' and c != 'default'] if f.__doc__ and not c.startswith('_') and c != 'default']
cmds.sort() cmds.sort()
for cmd in cmds: for cmd in cmds:
print(Commands.__dict__[cmd].__doc__.strip()) print(Commands.__dict__[cmd].__doc__.strip())

View File

@ -17,6 +17,7 @@
from contextlib import contextmanager, closing from contextlib import contextmanager, closing
import hashlib import hashlib
import json
import logging import logging
import os import os
from uuid import uuid4 from uuid import uuid4
@ -32,7 +33,7 @@ from eventlet import sleep, Timeout
import sqlite3 import sqlite3
from swift.common.constraints import MAX_META_COUNT, MAX_META_OVERALL_SIZE from swift.common.constraints import MAX_META_COUNT, MAX_META_OVERALL_SIZE
from swift.common.utils import json, Timestamp, renamer, \ from swift.common.utils import Timestamp, renamer, \
mkdirs, lock_parent_directory, fallocate mkdirs, lock_parent_directory, fallocate
from swift.common.exceptions import LockTimeout from swift.common.exceptions import LockTimeout
from swift.common.swob import HTTPBadRequest from swift.common.swob import HTTPBadRequest

View File

@ -166,7 +166,7 @@ class Replicator(Daemon):
self.max_diffs = int(conf.get('max_diffs') or 100) self.max_diffs = int(conf.get('max_diffs') or 100)
self.interval = int(conf.get('interval') or self.interval = int(conf.get('interval') or
conf.get('run_pause') or 30) conf.get('run_pause') or 30)
self.node_timeout = int(conf.get('node_timeout', 10)) self.node_timeout = float(conf.get('node_timeout', 10))
self.conn_timeout = float(conf.get('conn_timeout', 0.5)) self.conn_timeout = float(conf.get('conn_timeout', 0.5))
self.rsync_compress = config_true_value( self.rsync_compress = config_true_value(
conf.get('rsync_compress', 'no')) conf.get('rsync_compress', 'no'))
@ -174,11 +174,12 @@ class Replicator(Daemon):
if not self.rsync_module: if not self.rsync_module:
self.rsync_module = '{replication_ip}::%s' % self.server_type self.rsync_module = '{replication_ip}::%s' % self.server_type
if config_true_value(conf.get('vm_test_mode', 'no')): if config_true_value(conf.get('vm_test_mode', 'no')):
self.logger.warn('Option %(type)s-replicator/vm_test_mode is ' self.logger.warning('Option %(type)s-replicator/vm_test_mode '
'deprecated and will be removed in a future ' 'is deprecated and will be removed in a '
'version. Update your configuration to use ' 'future version. Update your configuration'
'option %(type)s-replicator/rsync_module.' ' to use option %(type)s-replicator/'
% {'type': self.server_type}) 'rsync_module.'
% {'type': self.server_type})
self.rsync_module += '{replication_port}' self.rsync_module += '{replication_port}'
self.reclaim_age = float(conf.get('reclaim_age', 86400 * 7)) self.reclaim_age = float(conf.get('reclaim_age', 86400 * 7))
swift.common.db.DB_PREALLOCATION = \ swift.common.db.DB_PREALLOCATION = \
@ -434,8 +435,12 @@ class Replicator(Daemon):
if self._in_sync(rinfo, info, broker, local_sync): if self._in_sync(rinfo, info, broker, local_sync):
return True return True
# if the difference in rowids between the two differs by # if the difference in rowids between the two differs by
# more than 50%, rsync then do a remote merge. # more than 50% and the difference is greater than per_diff,
if rinfo['max_row'] / float(info['max_row']) < 0.5: # rsync then do a remote merge.
# NOTE: difference > per_diff stops us from dropping to rsync
# on smaller containers, who have only a few rows to sync.
if rinfo['max_row'] / float(info['max_row']) < 0.5 and \
info['max_row'] - rinfo['max_row'] > self.per_diff:
self.stats['remote_merge'] += 1 self.stats['remote_merge'] += 1
self.logger.increment('remote_merges') self.logger.increment('remote_merges')
return self._rsync_db(broker, node, http, info['id'], return self._rsync_db(broker, node, http, info['id'],
@ -616,17 +621,19 @@ class Replicator(Daemon):
self.logger.error(_('ERROR Failed to get my own IPs?')) self.logger.error(_('ERROR Failed to get my own IPs?'))
return return
self._local_device_ids = set() self._local_device_ids = set()
found_local = False
for node in self.ring.devs: for node in self.ring.devs:
if node and is_local_device(ips, self.port, if node and is_local_device(ips, self.port,
node['replication_ip'], node['replication_ip'],
node['replication_port']): node['replication_port']):
found_local = True
if self.mount_check and not ismount( if self.mount_check and not ismount(
os.path.join(self.root, node['device'])): os.path.join(self.root, node['device'])):
self._add_failure_stats( self._add_failure_stats(
[(failure_dev['replication_ip'], [(failure_dev['replication_ip'],
failure_dev['device']) failure_dev['device'])
for failure_dev in self.ring.devs if failure_dev]) for failure_dev in self.ring.devs if failure_dev])
self.logger.warn( self.logger.warning(
_('Skipping %(device)s as it is not mounted') % node) _('Skipping %(device)s as it is not mounted') % node)
continue continue
unlink_older_than( unlink_older_than(
@ -636,6 +643,10 @@ class Replicator(Daemon):
if os.path.isdir(datadir): if os.path.isdir(datadir):
self._local_device_ids.add(node['id']) self._local_device_ids.add(node['id'])
dirs.append((datadir, node['id'])) dirs.append((datadir, node['id']))
if not found_local:
self.logger.error("Can't find itself %s with port %s in ring "
"file, not replicating",
", ".join(ips), self.port)
self.logger.info(_('Beginning replication run')) self.logger.info(_('Beginning replication run'))
for part, object_file, node_id in roundrobin_datadirs(dirs): for part, object_file, node_id in roundrobin_datadirs(dirs):
self.cpool.spawn_n( self.cpool.spawn_n(

View File

@ -18,6 +18,7 @@ Internal client library for making calls directly to the servers rather than
through the proxy. through the proxy.
""" """
import json
import os import os
import socket import socket
from time import time from time import time
@ -34,11 +35,6 @@ from swift.common.http import HTTP_NO_CONTENT, HTTP_INSUFFICIENT_STORAGE, \
from swift.common.swob import HeaderKeyDict from swift.common.swob import HeaderKeyDict
from swift.common.utils import quote from swift.common.utils import quote
try:
import simplejson as json
except ImportError:
import json
class DirectClientException(ClientException): class DirectClientException(ClientException):
@ -54,7 +50,7 @@ class DirectClientException(ClientException):
def _get_direct_account_container(path, stype, node, part, def _get_direct_account_container(path, stype, node, part,
account, marker=None, limit=None, marker=None, limit=None,
prefix=None, delimiter=None, conn_timeout=5, prefix=None, delimiter=None, conn_timeout=5,
response_timeout=15): response_timeout=15):
"""Base class for get direct account and container. """Base class for get direct account and container.
@ -117,7 +113,7 @@ def direct_get_account(node, part, account, marker=None, limit=None,
""" """
path = '/' + account path = '/' + account
return _get_direct_account_container(path, "Account", node, part, return _get_direct_account_container(path, "Account", node, part,
account, marker=marker, marker=marker,
limit=limit, prefix=prefix, limit=limit, prefix=prefix,
delimiter=delimiter, delimiter=delimiter,
conn_timeout=conn_timeout, conn_timeout=conn_timeout,
@ -193,7 +189,7 @@ def direct_get_container(node, part, account, container, marker=None,
""" """
path = '/%s/%s' % (account, container) path = '/%s/%s' % (account, container)
return _get_direct_account_container(path, "Container", node, return _get_direct_account_container(path, "Container", node,
part, account, marker=marker, part, marker=marker,
limit=limit, prefix=prefix, limit=limit, prefix=prefix,
delimiter=delimiter, delimiter=delimiter,
conn_timeout=conn_timeout, conn_timeout=conn_timeout,

View File

@ -27,7 +27,7 @@ from time import gmtime, strftime, time
from zlib import compressobj from zlib import compressobj
from swift.common.utils import quote from swift.common.utils import quote
from swift.common.http import HTTP_NOT_FOUND from swift.common.http import HTTP_NOT_FOUND, HTTP_MULTIPLE_CHOICES
from swift.common.swob import Request from swift.common.swob import Request
from swift.common.wsgi import loadapp, pipeline_property from swift.common.wsgi import loadapp, pipeline_property
@ -256,6 +256,8 @@ class InternalClient(object):
(path, quote(marker), quote(end_marker)), (path, quote(marker), quote(end_marker)),
{}, acceptable_statuses) {}, acceptable_statuses)
if not resp.status_int == 200: if not resp.status_int == 200:
if resp.status_int >= HTTP_MULTIPLE_CHOICES:
''.join(resp.app_iter)
break break
data = json.loads(resp.body) data = json.loads(resp.body)
if not data: if not data:

View File

@ -42,6 +42,8 @@ ALL_SERVERS = ['account-auditor', 'account-server', 'container-auditor',
MAIN_SERVERS = ['proxy-server', 'account-server', 'container-server', MAIN_SERVERS = ['proxy-server', 'account-server', 'container-server',
'object-server'] 'object-server']
REST_SERVERS = [s for s in ALL_SERVERS if s not in MAIN_SERVERS] REST_SERVERS = [s for s in ALL_SERVERS if s not in MAIN_SERVERS]
# aliases mapping
ALIASES = {'all': ALL_SERVERS, 'main': MAIN_SERVERS, 'rest': REST_SERVERS}
GRACEFUL_SHUTDOWN_SERVERS = MAIN_SERVERS + ['auth-server'] GRACEFUL_SHUTDOWN_SERVERS = MAIN_SERVERS + ['auth-server']
START_ONCE_SERVERS = REST_SERVERS START_ONCE_SERVERS = REST_SERVERS
# These are servers that match a type (account-*, container-*, object-*) but # These are servers that match a type (account-*, container-*, object-*) but
@ -173,18 +175,17 @@ class Manager(object):
def __init__(self, servers, run_dir=RUN_DIR): def __init__(self, servers, run_dir=RUN_DIR):
self.server_names = set() self.server_names = set()
self._default_strict = True
for server in servers: for server in servers:
if server == 'all': if server in ALIASES:
self.server_names.update(ALL_SERVERS) self.server_names.update(ALIASES[server])
elif server == 'main': self._default_strict = False
self.server_names.update(MAIN_SERVERS)
elif server == 'rest':
self.server_names.update(REST_SERVERS)
elif '*' in server: elif '*' in server:
# convert glob to regex # convert glob to regex
self.server_names.update([ self.server_names.update([
s for s in ALL_SERVERS if s for s in ALL_SERVERS if
re.match(server.replace('*', '.*'), s)]) re.match(server.replace('*', '.*'), s)])
self._default_strict = False
else: else:
self.server_names.add(server) self.server_names.add(server)
@ -211,8 +212,17 @@ class Manager(object):
setup_env() setup_env()
status = 0 status = 0
strict = kwargs.get('strict')
# if strict not set explicitly
if strict is None:
strict = self._default_strict
for server in self.servers: for server in self.servers:
server.launch(**kwargs) status += 0 if server.launch(**kwargs) else 1
if not strict:
status = 0
if not kwargs.get('daemon', True): if not kwargs.get('daemon', True):
for server in self.servers: for server in self.servers:
try: try:

View File

@ -45,6 +45,7 @@ http://github.com/memcached/memcached/blob/1.4.2/doc/protocol.txt
""" """
import six.moves.cPickle as pickle import six.moves.cPickle as pickle
import json
import logging import logging
import time import time
from bisect import bisect from bisect import bisect
@ -56,7 +57,6 @@ from eventlet.pools import Pool
from eventlet import Timeout from eventlet import Timeout
from six.moves import range from six.moves import range
from swift.common.utils import json
DEFAULT_MEMCACHED_PORT = 11211 DEFAULT_MEMCACHED_PORT = 11211
@ -223,7 +223,7 @@ class MemcacheRing(object):
"""Returns a server connection to the pool.""" """Returns a server connection to the pool."""
self._client_cache[server].put((fp, sock)) self._client_cache[server].put((fp, sock))
def set(self, key, value, serialize=True, timeout=0, time=0, def set(self, key, value, serialize=True, time=0,
min_compress_len=0): min_compress_len=0):
""" """
Set a key/value pair in memcache Set a key/value pair in memcache
@ -233,22 +233,14 @@ class MemcacheRing(object):
:param serialize: if True, value is serialized with JSON before sending :param serialize: if True, value is serialized with JSON before sending
to memcache, or with pickle if configured to use to memcache, or with pickle if configured to use
pickle instead of JSON (to avoid cache poisoning) pickle instead of JSON (to avoid cache poisoning)
:param timeout: ttl in memcache, this parameter is now deprecated. It :param time: the time to live
will be removed in next release of OpenStack,
use time parameter instead in the future
:time: equivalent to timeout, this parameter is added to keep the
signature compatible with python-memcached interface. This
implementation will take this value and sign it to the
parameter timeout
:min_compress_len: minimum compress length, this parameter was added :min_compress_len: minimum compress length, this parameter was added
to keep the signature compatible with to keep the signature compatible with
python-memcached interface. This implementation python-memcached interface. This implementation
ignores it. ignores it.
""" """
key = md5hash(key) key = md5hash(key)
if timeout: timeout = sanitize_timeout(time)
logging.warn("parameter timeout has been deprecated, use time")
timeout = sanitize_timeout(time or timeout)
flags = 0 flags = 0
if serialize and self._allow_pickle: if serialize and self._allow_pickle:
value = pickle.dumps(value, PICKLE_PROTOCOL) value = pickle.dumps(value, PICKLE_PROTOCOL)
@ -302,7 +294,7 @@ class MemcacheRing(object):
except (Exception, Timeout) as e: except (Exception, Timeout) as e:
self._exception_occurred(server, e, sock=sock, fp=fp) self._exception_occurred(server, e, sock=sock, fp=fp)
def incr(self, key, delta=1, time=0, timeout=0): def incr(self, key, delta=1, time=0):
""" """
Increments a key which has a numeric value by delta. Increments a key which has a numeric value by delta.
If the key can't be found, it's added as delta or 0 if delta < 0. If the key can't be found, it's added as delta or 0 if delta < 0.
@ -315,22 +307,16 @@ class MemcacheRing(object):
:param key: key :param key: key
:param delta: amount to add to the value of key (or set as the value :param delta: amount to add to the value of key (or set as the value
if the key is not found) will be cast to an int if the key is not found) will be cast to an int
:param time: the time to live. This parameter deprecates parameter :param time: the time to live
timeout. The addition of this parameter is to make the
interface consistent with set and set_multi methods
:param timeout: ttl in memcache, deprecated, will be removed in future
OpenStack releases
:returns: result of incrementing :returns: result of incrementing
:raises MemcacheConnectionError: :raises MemcacheConnectionError:
""" """
if timeout:
logging.warn("parameter timeout has been deprecated, use time")
key = md5hash(key) key = md5hash(key)
command = 'incr' command = 'incr'
if delta < 0: if delta < 0:
command = 'decr' command = 'decr'
delta = str(abs(int(delta))) delta = str(abs(int(delta)))
timeout = sanitize_timeout(time or timeout) timeout = sanitize_timeout(time)
for (server, fp, sock) in self._get_conns(key): for (server, fp, sock) in self._get_conns(key):
try: try:
with Timeout(self._io_timeout): with Timeout(self._io_timeout):
@ -358,7 +344,7 @@ class MemcacheRing(object):
self._exception_occurred(server, e, sock=sock, fp=fp) self._exception_occurred(server, e, sock=sock, fp=fp)
raise MemcacheConnectionError("No Memcached connections succeeded.") raise MemcacheConnectionError("No Memcached connections succeeded.")
def decr(self, key, delta=1, time=0, timeout=0): def decr(self, key, delta=1, time=0):
""" """
Decrements a key which has a numeric value by delta. Calls incr with Decrements a key which has a numeric value by delta. Calls incr with
-delta. -delta.
@ -367,18 +353,11 @@ class MemcacheRing(object):
:param delta: amount to subtract to the value of key (or set the :param delta: amount to subtract to the value of key (or set the
value to 0 if the key is not found) will be cast to value to 0 if the key is not found) will be cast to
an int an int
:param time: the time to live. This parameter depcates parameter :param time: the time to live
timeout. The addition of this parameter is to make the
interface consistent with set and set_multi methods
:param timeout: ttl in memcache, deprecated, will be removed in future
OpenStack releases
:returns: result of decrementing :returns: result of decrementing
:raises MemcacheConnectionError: :raises MemcacheConnectionError:
""" """
if timeout: return self.incr(key, delta=-delta, time=time)
logging.warn("parameter timeout has been deprecated, use time")
return self.incr(key, delta=-delta, time=(time or timeout))
def delete(self, key): def delete(self, key):
""" """
@ -398,8 +377,8 @@ class MemcacheRing(object):
except (Exception, Timeout) as e: except (Exception, Timeout) as e:
self._exception_occurred(server, e, sock=sock, fp=fp) self._exception_occurred(server, e, sock=sock, fp=fp)
def set_multi(self, mapping, server_key, serialize=True, timeout=0, def set_multi(self, mapping, server_key, serialize=True, time=0,
time=0, min_compress_len=0): min_compress_len=0):
""" """
Sets multiple key/value pairs in memcache. Sets multiple key/value pairs in memcache.
@ -409,23 +388,14 @@ class MemcacheRing(object):
:param serialize: if True, value is serialized with JSON before sending :param serialize: if True, value is serialized with JSON before sending
to memcache, or with pickle if configured to use to memcache, or with pickle if configured to use
pickle instead of JSON (to avoid cache poisoning) pickle instead of JSON (to avoid cache poisoning)
:param timeout: ttl for memcache. This parameter is now deprecated, it :param time: the time to live
will be removed in next release of OpenStack, use time
parameter instead in the future
:time: equalvent to timeout, this parameter is added to keep the
signature compatible with python-memcached interface. This
implementation will take this value and sign it to parameter
timeout
:min_compress_len: minimum compress length, this parameter was added :min_compress_len: minimum compress length, this parameter was added
to keep the signature compatible with to keep the signature compatible with
python-memcached interface. This implementation python-memcached interface. This implementation
ignores it ignores it
""" """
if timeout:
logging.warn("parameter timeout has been deprecated, use time")
server_key = md5hash(server_key) server_key = md5hash(server_key)
timeout = sanitize_timeout(time or timeout) timeout = sanitize_timeout(time)
msg = '' msg = ''
for key, value in mapping.items(): for key, value in mapping.items():
key = md5hash(key) key = md5hash(key)

View File

@ -13,7 +13,9 @@
# 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.
from swift.common.utils import urlparse, json import json
from swift.common.utils import urlparse
def clean_acl(name, value): def clean_acl(name, value):
@ -95,17 +97,17 @@ def clean_acl(name, value):
values.append(raw_value) values.append(raw_value)
continue continue
first, second = (v.strip() for v in raw_value.split(':', 1)) first, second = (v.strip() for v in raw_value.split(':', 1))
if not first or first[0] != '.': if not first or not first.startswith('.'):
values.append(raw_value) values.append(raw_value)
elif first in ('.r', '.ref', '.referer', '.referrer'): elif first in ('.r', '.ref', '.referer', '.referrer'):
if 'write' in name: if 'write' in name:
raise ValueError('Referrers not allowed in write ACL: ' raise ValueError('Referrers not allowed in write ACL: '
'%s' % repr(raw_value)) '%s' % repr(raw_value))
negate = False negate = False
if second and second[0] == '-': if second and second.startswith('-'):
negate = True negate = True
second = second[1:].strip() second = second[1:].strip()
if second and second != '*' and second[0] == '*': if second and second != '*' and second.startswith('*'):
second = second[1:].strip() second = second[1:].strip()
if not second or second == '.': if not second or second == '.':
raise ValueError('No host/domain value after referrer ' raise ValueError('No host/domain value after referrer '
@ -261,13 +263,13 @@ def referrer_allowed(referrer, referrer_acl):
if referrer_acl: if referrer_acl:
rhost = urlparse(referrer or '').hostname or 'unknown' rhost = urlparse(referrer or '').hostname or 'unknown'
for mhost in referrer_acl: for mhost in referrer_acl:
if mhost[0] == '-': if mhost.startswith('-'):
mhost = mhost[1:] mhost = mhost[1:]
if mhost == rhost or (mhost[0] == '.' and if mhost == rhost or (mhost.startswith('.') and
rhost.endswith(mhost)): rhost.endswith(mhost)):
allow = False allow = False
elif mhost == '*' or mhost == rhost or \ elif mhost == '*' or mhost == rhost or \
(mhost[0] == '.' and rhost.endswith(mhost)): (mhost.startswith('.') and rhost.endswith(mhost)):
allow = True allow = True
return allow return allow

View File

@ -13,6 +13,7 @@
# 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.
import json
from six.moves.urllib.parse import quote, unquote from six.moves.urllib.parse import quote, unquote
import tarfile import tarfile
from xml.sax import saxutils from xml.sax import saxutils
@ -23,7 +24,7 @@ from swift.common.swob import Request, HTTPBadGateway, \
HTTPCreated, HTTPBadRequest, HTTPNotFound, HTTPUnauthorized, HTTPOk, \ HTTPCreated, HTTPBadRequest, HTTPNotFound, HTTPUnauthorized, HTTPOk, \
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPNotAcceptable, \ HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPNotAcceptable, \
HTTPLengthRequired, HTTPException, HTTPServerError, wsgify HTTPLengthRequired, HTTPException, HTTPServerError, wsgify
from swift.common.utils import json, get_logger, register_swift_info from swift.common.utils import get_logger, register_swift_info
from swift.common import constraints from swift.common import constraints
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND, HTTP_CONFLICT from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND, HTTP_CONFLICT
@ -32,7 +33,7 @@ class CreateContainerError(Exception):
def __init__(self, msg, status_int, status): def __init__(self, msg, status_int, status):
self.status_int = status_int self.status_int = status_int
self.status = status self.status = status
Exception.__init__(self, msg) super(CreateContainerError, self).__init__(msg)
ACCEPTABLE_FORMATS = ['text/plain', 'application/json', 'application/xml', ACCEPTABLE_FORMATS = ['text/plain', 'application/json', 'application/xml',
@ -121,6 +122,67 @@ class Bulk(object):
Only regular files will be uploaded. Empty directories, symlinks, etc will Only regular files will be uploaded. Empty directories, symlinks, etc will
not be uploaded. not be uploaded.
Content Type:
If the content-type header is set in the extract-archive call, Swift will
assign that content-type to all the underlying files. The bulk middleware
will extract the archive file and send the internal files using PUT
operations using the same headers from the original request
(e.g. auth-tokens, content-Type, etc.). Notice that any middleware call
that follows the bulk middleware does not know if this was a bulk request
or if these were individual requests sent by the user.
In order to make Swift detect the content-type for the files based on the
file extension, the content-type in the extract-archive call should not be
set. Alternatively, it is possible to explicitly tell swift to detect the
content type using this header:
X-Detect-Content-Type:true
For example:
curl -X PUT http://127.0.0.1/v1/AUTH_acc/cont/$?extract-archive=tar -T
backup.tar -H "Content-Type: application/x-tar" -H "X-Auth-Token: xxx"
-H "X-Detect-Content-Type:true"
Assigning Metadata:
The tar file format (1) allows for UTF-8 key/value pairs to be associated
with each file in an archive. If a file has extended attributes, then tar
will store those as key/value pairs. The bulk middleware can read those
extended attributes and convert them to Swift object metadata. Attributes
starting with "user.meta" are converted to object metadata, and
"user.mime_type" is converted to Content-Type.
For example:
setfattr -n user.mime_type -v "application/python-setup" setup.py
setfattr -n user.meta.lunch -v "burger and fries" setup.py
setfattr -n user.meta.dinner -v "baked ziti" setup.py
setfattr -n user.stuff -v "whee" setup.py
Will get translated to headers:
Content-Type: application/python-setup
X-Object-Meta-Lunch: burger and fries
X-Object-Meta-Dinner: baked ziti
The bulk middleware will handle xattrs stored by both GNU and BSD tar (2).
Only xattrs user.mime_type and user.meta.* are processed. Other attributes
are ignored.
Notes:
(1) The POSIX 1003.1-2001 (pax) format. The default format on GNU tar
1.27.1 or later.
(2) Even with pax-format tarballs, different encoders store xattrs slightly
differently; for example, GNU tar stores the xattr "user.userattribute" as
pax header "SCHILY.xattr.user.userattribute", while BSD tar (which uses
libarchive) stores it as "LIBARCHIVE.xattr.user.userattribute".
Response:
The response from bulk operations functions differently from other swift The response from bulk operations functions differently from other swift
responses. This is because a short request body sent from the client could responses. This is because a short request body sent from the client could
result in many operations on the proxy server and precautions need to be result in many operations on the proxy server and precautions need to be

View File

@ -114,6 +114,7 @@ Here's an example using ``curl`` with tiny 1-byte segments::
http://<storage_url>/container/myobject http://<storage_url>/container/myobject
""" """
import json
import os import os
import six import six
@ -126,7 +127,7 @@ from swift.common.exceptions import ListingIterError, SegmentError
from swift.common.http import is_success from swift.common.http import is_success
from swift.common.swob import Request, Response, \ from swift.common.swob import Request, Response, \
HTTPRequestedRangeNotSatisfiable, HTTPBadRequest, HTTPConflict HTTPRequestedRangeNotSatisfiable, HTTPBadRequest, HTTPConflict
from swift.common.utils import get_logger, json, \ from swift.common.utils import get_logger, \
RateLimitedIterator, read_conf_dir, quote, close_if_possible, \ RateLimitedIterator, read_conf_dir, quote, close_if_possible, \
closing_if_possible closing_if_possible
from swift.common.request_helpers import SegmentedIterable from swift.common.request_helpers import SegmentedIterable
@ -415,13 +416,12 @@ class DynamicLargeObject(object):
return GetContext(self, self.logger).\ return GetContext(self, self.logger).\
handle_request(req, start_response) handle_request(req, start_response)
elif req.method == 'PUT': elif req.method == 'PUT':
error_response = self.validate_x_object_manifest_header( error_response = self._validate_x_object_manifest_header(req)
req, start_response)
if error_response: if error_response:
return error_response(env, start_response) return error_response(env, start_response)
return self.app(env, start_response) return self.app(env, start_response)
def validate_x_object_manifest_header(self, req, start_response): def _validate_x_object_manifest_header(self, req):
""" """
Make sure that X-Object-Manifest is valid if present. Make sure that X-Object-Manifest is valid if present.
""" """
@ -433,7 +433,7 @@ class DynamicLargeObject(object):
except ValueError: except ValueError:
pass pass
if not container or not prefix or '?' in value or '&' in value or \ if not container or not prefix or '?' in value or '&' in value or \
prefix[0] == '/': prefix.startswith('/'):
return HTTPBadRequest( return HTTPBadRequest(
request=req, request=req,
body=('X-Object-Manifest must be in the ' body=('X-Object-Manifest must be in the '

View File

@ -68,7 +68,7 @@ class DomainRemapMiddleware(object):
def __init__(self, app, conf): def __init__(self, app, conf):
self.app = app self.app = app
self.storage_domain = conf.get('storage_domain', 'example.com') self.storage_domain = conf.get('storage_domain', 'example.com')
if self.storage_domain and self.storage_domain[0] != '.': if self.storage_domain and not self.storage_domain.startswith('.'):
self.storage_domain = '.' + self.storage_domain self.storage_domain = '.' + self.storage_domain
self.path_root = conf.get('path_root', 'v1').strip('/') self.path_root = conf.get('path_root', 'v1').strip('/')
prefixes = conf.get('reseller_prefixes', 'AUTH') prefixes = conf.get('reseller_prefixes', 'AUTH')

View File

@ -272,7 +272,7 @@ class FormPost(object):
hdrs['Content-Type'] or 'application/octet-stream' hdrs['Content-Type'] or 'application/octet-stream'
status, subheaders, message = \ status, subheaders, message = \
self._perform_subrequest(env, attributes, fp, keys) self._perform_subrequest(env, attributes, fp, keys)
if status[:1] != '2': if not status.startswith('2'):
break break
else: else:
data = '' data = ''
@ -337,7 +337,7 @@ class FormPost(object):
del subenv['QUERY_STRING'] del subenv['QUERY_STRING']
subenv['HTTP_TRANSFER_ENCODING'] = 'chunked' subenv['HTTP_TRANSFER_ENCODING'] = 'chunked'
subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size) subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size)
if subenv['PATH_INFO'][-1] != '/' and \ if not subenv['PATH_INFO'].endswith('/') and \
subenv['PATH_INFO'].count('/') < 4: subenv['PATH_INFO'].count('/') < 4:
subenv['PATH_INFO'] += '/' subenv['PATH_INFO'] += '/'
subenv['PATH_INFO'] += attributes['filename'] or 'filename' subenv['PATH_INFO'] += attributes['filename'] or 'filename'

View File

@ -31,17 +31,17 @@ UNKNOWN_ID = '_unknown'
class KeystoneAuth(object): class KeystoneAuth(object):
"""Swift middleware to Keystone authorization system. """Swift middleware to Keystone authorization system.
In Swift's proxy-server.conf add this middleware to your pipeline:: In Swift's proxy-server.conf add this keystoneauth middleware and the
authtoken middleware to your pipeline. Make sure you have the authtoken
[pipeline:main] middleware before the keystoneauth middleware.
pipeline = catch_errors cache authtoken keystoneauth proxy-server
Make sure you have the authtoken middleware before the
keystoneauth middleware.
The authtoken middleware will take care of validating the user and The authtoken middleware will take care of validating the user and
keystoneauth will authorize access. keystoneauth will authorize access.
The sample proxy-server.conf shows a sample pipeline that uses keystone.
:download:`proxy-server.conf-sample </../../etc/proxy-server.conf-sample>`
The authtoken middleware is shipped with keystonemiddleware - it The authtoken middleware is shipped with keystonemiddleware - it
does not have any other dependencies than itself so you can either does not have any other dependencies than itself so you can either
install it by copying the file directly in your python path or by install it by copying the file directly in your python path or by
@ -196,7 +196,7 @@ class KeystoneAuth(object):
conf.get('allow_names_in_acls', 'true')) conf.get('allow_names_in_acls', 'true'))
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
identity = self._keystone_identity(environ) env_identity = self._keystone_identity(environ)
# Check if one of the middleware like tempurl or formpost have # Check if one of the middleware like tempurl or formpost have
# set the swift.authorize_override environ and want to control the # set the swift.authorize_override environ and want to control the
@ -207,14 +207,13 @@ class KeystoneAuth(object):
self.logger.debug(msg) self.logger.debug(msg)
return self.app(environ, start_response) return self.app(environ, start_response)
if identity: if env_identity:
self.logger.debug('Using identity: %r', identity) self.logger.debug('Using identity: %r', env_identity)
environ['keystone.identity'] = identity environ['REMOTE_USER'] = env_identity.get('tenant')
environ['REMOTE_USER'] = identity.get('tenant') environ['keystone.identity'] = env_identity
env_identity = self._integral_keystone_identity(environ)
environ['swift.authorize'] = functools.partial( environ['swift.authorize'] = functools.partial(
self.authorize, env_identity) self.authorize, env_identity)
user_roles = (r.lower() for r in identity.get('roles', [])) user_roles = (r.lower() for r in env_identity.get('roles', []))
if self.reseller_admin_role in user_roles: if self.reseller_admin_role in user_roles:
environ['reseller_request'] = True environ['reseller_request'] = True
else: else:
@ -238,26 +237,11 @@ class KeystoneAuth(object):
def _keystone_identity(self, environ): def _keystone_identity(self, environ):
"""Extract the identity from the Keystone auth component.""" """Extract the identity from the Keystone auth component."""
# In next release, we would add user id in env['keystone.identity'] by
# using _integral_keystone_identity to replace current
# _keystone_identity. The purpose of keeping it in this release it for
# back compatibility.
if (environ.get('HTTP_X_IDENTITY_STATUS') != 'Confirmed' if (environ.get('HTTP_X_IDENTITY_STATUS') != 'Confirmed'
or environ.get( or environ.get(
'HTTP_X_SERVICE_IDENTITY_STATUS') not in (None, 'Confirmed')): 'HTTP_X_SERVICE_IDENTITY_STATUS') not in (None, 'Confirmed')):
return return
roles = list_from_csv(environ.get('HTTP_X_ROLES', '')) roles = list_from_csv(environ.get('HTTP_X_ROLES', ''))
identity = {'user': environ.get('HTTP_X_USER_NAME'),
'tenant': (environ.get('HTTP_X_TENANT_ID'),
environ.get('HTTP_X_TENANT_NAME')),
'roles': roles}
return identity
def _integral_keystone_identity(self, environ):
"""Extract the identity from the Keystone auth component."""
if environ.get('HTTP_X_IDENTITY_STATUS') != 'Confirmed':
return
roles = list_from_csv(environ.get('HTTP_X_ROLES', ''))
service_roles = list_from_csv(environ.get('HTTP_X_SERVICE_ROLES', '')) service_roles = list_from_csv(environ.get('HTTP_X_SERVICE_ROLES', ''))
identity = {'user': (environ.get('HTTP_X_USER_ID'), identity = {'user': (environ.get('HTTP_X_USER_ID'),
environ.get('HTTP_X_USER_NAME')), environ.get('HTTP_X_USER_NAME')),
@ -341,9 +325,9 @@ class KeystoneAuth(object):
# unknown domain, update if req confirms domain # unknown domain, update if req confirms domain
new_id = req_id or '' new_id = req_id or ''
elif req_has_id and sysmeta_id != req_id: elif req_has_id and sysmeta_id != req_id:
self.logger.warn("Inconsistent project domain id: " + self.logger.warning("Inconsistent project domain id: " +
"%s in token vs %s in account metadata." "%s in token vs %s in account metadata."
% (req_id, sysmeta_id)) % (req_id, sysmeta_id))
if new_id is not None: if new_id is not None:
req.headers[PROJECT_DOMAIN_ID_SYSMETA_HEADER] = new_id req.headers[PROJECT_DOMAIN_ID_SYSMETA_HEADER] = new_id

View File

@ -78,11 +78,12 @@ with this middleware enabled should not be open to an untrusted
environment (everyone can query the locality data using this middleware). environment (everyone can query the locality data using this middleware).
""" """
import json
from six.moves.urllib.parse import quote, unquote from six.moves.urllib.parse import quote, unquote
from swift.common.ring import Ring from swift.common.ring import Ring
from swift.common.utils import json, get_logger, split_path from swift.common.utils import get_logger, split_path
from swift.common.swob import Request, Response from swift.common.swob import Request, Response
from swift.common.swob import HTTPBadRequest, HTTPMethodNotAllowed from swift.common.swob import HTTPBadRequest, HTTPMethodNotAllowed
from swift.common.storage_policy import POLICIES from swift.common.storage_policy import POLICIES

View File

@ -79,12 +79,7 @@ class NameCheckMiddleware(object):
self.logger.debug("name_check: self.forbidden_chars %s" % self.logger.debug("name_check: self.forbidden_chars %s" %
self.forbidden_chars) self.forbidden_chars)
for c in unquote(req.path): return any((c in unquote(req.path)) for c in self.forbidden_chars)
if c in self.forbidden_chars:
return True
else:
pass
return False
def check_length(self, req): def check_length(self, req):
''' '''
@ -93,10 +88,7 @@ class NameCheckMiddleware(object):
Returns False if the length is <= the maximum Returns False if the length is <= the maximum
''' '''
length = len(unquote(req.path)) length = len(unquote(req.path))
if length > self.maximum_length: return length > self.maximum_length
return True
else:
return False
def check_regexp(self, req): def check_regexp(self, req):
''' '''

View File

@ -14,6 +14,7 @@
# limitations under the License. # limitations under the License.
import errno import errno
import json
import os import os
import time import time
from swift import gettext_ as _ from swift import gettext_ as _
@ -21,7 +22,7 @@ from swift import gettext_ as _
from swift import __version__ as swiftver from swift import __version__ as swiftver
from swift.common.storage_policy import POLICIES from swift.common.storage_policy import POLICIES
from swift.common.swob import Request, Response from swift.common.swob import Request, Response
from swift.common.utils import get_logger, config_true_value, json, \ from swift.common.utils import get_logger, config_true_value, \
SWIFT_CONF_FILE SWIFT_CONF_FILE
from swift.common.constraints import check_mount from swift.common.constraints import check_mount
from resource import getpagesize from resource import getpagesize

View File

@ -26,26 +26,31 @@ defined manifest of the object segments is used.
Uploading the Manifest Uploading the Manifest
---------------------- ----------------------
After the user has uploaded the objects to be concatenated a manifest is After the user has uploaded the objects to be concatenated, a manifest is
uploaded. The request must be a PUT with the query parameter:: uploaded. The request must be a PUT with the query parameter::
?multipart-manifest=put ?multipart-manifest=put
The body of this request will be an ordered list of files in The body of this request will be an ordered list of segment descriptions in
json data format. The data to be supplied for each segment is:: JSON format. The data to be supplied for each segment is:
path: the path to the segment object (not including account) =========== ========================================================
/container/object_name Key Description
etag: the etag given back when the segment object was PUT, =========== ========================================================
or null path the path to the segment object (not including account)
size_bytes: the size of the complete segment object in /container/object_name
bytes, or null etag the ETag given back when the segment object was PUT,
range: (Optional) the range within the object to use as a or null
segment. If omitted, the entire object is used. size_bytes the size of the complete segment object in
bytes, or null
range (optional) the (inclusive) range within the object to
use as a segment. If omitted, the entire object is used.
=========== ========================================================
The format of the list will be:: The format of the list will be:
.. code::
json:
[{"path": "/cont/object", [{"path": "/cont/object",
"etag": "etagoftheobjectsegment", "etag": "etagoftheobjectsegment",
"size_bytes": 10485760, "size_bytes": 10485760,
@ -84,6 +89,42 @@ segments of a SLO manifest can even be other SLO manifests. Treat them as any
other object i.e., use the Etag and Content-Length given on the PUT of the other object i.e., use the Etag and Content-Length given on the PUT of the
sub-SLO in the manifest to the parent SLO. sub-SLO in the manifest to the parent SLO.
-------------------
Range Specification
-------------------
Users now have the ability to specify ranges for SLO segments.
Users can now include an optional 'range' field in segment descriptions
to specify which bytes from the underlying object should be used for the
segment data. Only one range may be specified per segment.
.. note::
The 'etag' and 'size_bytes' fields still describe the backing object as a
whole.
If a user uploads this manifest:
.. code::
[{"path": "/con/obj_seg_1", "etag": null, "size_bytes": 2097152,
"range": "0-1048576"},
{"path": "/con/obj_seg_2", "etag": null, "size_bytes": 2097152,
"range": "512-1550000"},
{"path": "/con/obj_seg_1", "etag": null, "size_bytes": 2097152,
"range": "-2048"}]
The segment will consist of the first 1048576 bytes of /con/obj_seg_1,
followed by bytes 513 through 1550000 (inclusive) of /con/obj_seg_2, and
finally bytes 2095104 through 2097152 (i.e., the last 2048 bytes) of
/con/obj_seg_1.
.. note::
The minimum sized range is min_segment_size, which by
default is 1048576 (1MB).
------------------------- -------------------------
Retrieving a Large Object Retrieving a Large Object
------------------------- -------------------------
@ -156,6 +197,7 @@ metadata which can be used for stats purposes.
from six.moves import range from six.moves import range
from datetime import datetime from datetime import datetime
import json
import mimetypes import mimetypes
import re import re
import six import six
@ -167,7 +209,7 @@ from swift.common.swob import Request, HTTPBadRequest, HTTPServerError, \
HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \ HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \
HTTPUnauthorized, HTTPConflict, HTTPRequestedRangeNotSatisfiable,\ HTTPUnauthorized, HTTPConflict, HTTPRequestedRangeNotSatisfiable,\
Response, Range Response, Range
from swift.common.utils import json, get_logger, config_true_value, \ from swift.common.utils import get_logger, config_true_value, \
get_valid_utf8_str, override_bytes_from_content_type, split_path, \ get_valid_utf8_str, override_bytes_from_content_type, split_path, \
register_swift_info, RateLimitedIterator, quote, close_if_possible, \ register_swift_info, RateLimitedIterator, quote, close_if_possible, \
closing_if_possible closing_if_possible
@ -948,6 +990,7 @@ class StaticLargeObject(object):
:params req: a swob.Request with an obj in path :params req: a swob.Request with an obj in path
:returns: swob.Response whose app_iter set to Bulk.handle_delete_iter :returns: swob.Response whose app_iter set to Bulk.handle_delete_iter
""" """
req.headers['Content-Type'] = None # Ignore content-type from client
resp = HTTPOk(request=req) resp = HTTPOk(request=req)
out_content_type = req.accept.best_match(ACCEPTABLE_FORMATS) out_content_type = req.accept.best_match(ACCEPTABLE_FORMATS)
if out_content_type: if out_content_type:

View File

@ -92,6 +92,7 @@ Example usage of this middleware via ``swift``:
Turn on listings:: Turn on listings::
swift post -r '.r:*,.rlistings' container
swift post -m 'web-listings: true' container swift post -m 'web-listings: true' container
Now you should see object listings for paths and pseudo paths that have no Now you should see object listings for paths and pseudo paths that have no
@ -121,8 +122,8 @@ import json
import time import time
from swift.common.utils import human_readable, split_path, config_true_value, \ from swift.common.utils import human_readable, split_path, config_true_value, \
quote, register_swift_info quote, register_swift_info, get_logger
from swift.common.wsgi import make_pre_authed_env, WSGIContext from swift.common.wsgi import make_env, WSGIContext
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound
from swift.proxy.controllers.base import get_container_info from swift.proxy.controllers.base import get_container_info
@ -167,7 +168,7 @@ class _StaticWebContext(WSGIContext):
save_response_status = self._response_status save_response_status = self._response_status
save_response_headers = self._response_headers save_response_headers = self._response_headers
save_response_exc_info = self._response_exc_info save_response_exc_info = self._response_exc_info
resp = self._app_call(make_pre_authed_env( resp = self._app_call(make_env(
env, 'GET', '/%s/%s/%s/%s%s' % ( env, 'GET', '/%s/%s/%s/%s%s' % (
self.version, self.account, self.container, self.version, self.account, self.container,
self._get_status_int(), self._error), self._get_status_int(), self._error),
@ -236,7 +237,7 @@ class _StaticWebContext(WSGIContext):
body += ' </body>\n</html>\n' body += ' </body>\n</html>\n'
resp = HTTPNotFound(body=body)(env, self._start_response) resp = HTTPNotFound(body=body)(env, self._start_response)
return self._error_response(resp, env, start_response) return self._error_response(resp, env, start_response)
tmp_env = make_pre_authed_env( tmp_env = make_env(
env, 'GET', '/%s/%s/%s' % ( env, 'GET', '/%s/%s/%s' % (
self.version, self.account, self.container), self.version, self.account, self.container),
self.agent, swift_source='SW') self.agent, swift_source='SW')
@ -349,7 +350,7 @@ class _StaticWebContext(WSGIContext):
if config_true_value(env.get('HTTP_X_WEB_MODE', 'f')): if config_true_value(env.get('HTTP_X_WEB_MODE', 'f')):
return HTTPNotFound()(env, start_response) return HTTPNotFound()(env, start_response)
return self.app(env, start_response) return self.app(env, start_response)
if env['PATH_INFO'][-1] != '/': if not env['PATH_INFO'].endswith('/'):
resp = HTTPMovedPermanently( resp = HTTPMovedPermanently(
location=(env['PATH_INFO'] + '/')) location=(env['PATH_INFO'] + '/'))
return resp(env, start_response) return resp(env, start_response)
@ -414,13 +415,13 @@ class _StaticWebContext(WSGIContext):
tmp_env['HTTP_USER_AGENT'] = \ tmp_env['HTTP_USER_AGENT'] = \
'%s StaticWeb' % env.get('HTTP_USER_AGENT') '%s StaticWeb' % env.get('HTTP_USER_AGENT')
tmp_env['swift.source'] = 'SW' tmp_env['swift.source'] = 'SW'
if tmp_env['PATH_INFO'][-1] != '/': if not tmp_env['PATH_INFO'].endswith('/'):
tmp_env['PATH_INFO'] += '/' tmp_env['PATH_INFO'] += '/'
tmp_env['PATH_INFO'] += self._index tmp_env['PATH_INFO'] += self._index
resp = self._app_call(tmp_env) resp = self._app_call(tmp_env)
status_int = self._get_status_int() status_int = self._get_status_int()
if is_success(status_int) or is_redirection(status_int): if is_success(status_int) or is_redirection(status_int):
if env['PATH_INFO'][-1] != '/': if not env['PATH_INFO'].endswith('/'):
resp = HTTPMovedPermanently( resp = HTTPMovedPermanently(
location=env['PATH_INFO'] + '/') location=env['PATH_INFO'] + '/')
return resp(env, start_response) return resp(env, start_response)
@ -428,8 +429,8 @@ class _StaticWebContext(WSGIContext):
self._response_exc_info) self._response_exc_info)
return resp return resp
if status_int == HTTP_NOT_FOUND: if status_int == HTTP_NOT_FOUND:
if env['PATH_INFO'][-1] != '/': if not env['PATH_INFO'].endswith('/'):
tmp_env = make_pre_authed_env( tmp_env = make_env(
env, 'GET', '/%s/%s/%s' % ( env, 'GET', '/%s/%s/%s' % (
self.version, self.account, self.container), self.version, self.account, self.container),
self.agent, swift_source='SW') self.agent, swift_source='SW')
@ -463,6 +464,7 @@ class StaticWeb(object):
self.app = app self.app = app
#: The filter configuration dict. #: The filter configuration dict.
self.conf = conf self.conf = conf
self.logger = get_logger(conf, log_route='staticweb')
def __call__(self, env, start_response): def __call__(self, env, start_response):
""" """
@ -472,6 +474,11 @@ class StaticWeb(object):
:param start_response: The WSGI start_response hook. :param start_response: The WSGI start_response hook.
""" """
env['staticweb.start_time'] = time.time() env['staticweb.start_time'] = time.time()
if 'swift.authorize' not in env:
self.logger.warning(
'No authentication middleware authorized request yet. '
'Skipping staticweb')
return self.app(env, start_response)
try: try:
(version, account, container, obj) = \ (version, account, container, obj) = \
split_path(env['PATH_INFO'], 2, 4, True) split_path(env['PATH_INFO'], 2, 4, True)

View File

@ -72,16 +72,16 @@ class TempAuth(object):
The reseller prefix specifies which parts of the account namespace this The reseller prefix specifies which parts of the account namespace this
middleware is responsible for managing authentication and authorization. middleware is responsible for managing authentication and authorization.
By default, the prefix is AUTH so accounts and tokens are prefixed By default, the prefix is 'AUTH' so accounts and tokens are prefixed
by AUTH_. When a request's token and/or path start with AUTH_, this by 'AUTH\_'. When a request's token and/or path start with 'AUTH\_', this
middleware knows it is responsible. middleware knows it is responsible.
We allow the reseller prefix to be a list. In tempauth, the first item We allow the reseller prefix to be a list. In tempauth, the first item
in the list is used as the prefix for tokens and user groups. The in the list is used as the prefix for tokens and user groups. The
other prefixes provide alternate accounts that user's can access. For other prefixes provide alternate accounts that user's can access. For
example if the reseller prefix list is 'AUTH, OTHER', a user with example if the reseller prefix list is 'AUTH, OTHER', a user with
admin access to AUTH_account also has admin access to admin access to 'AUTH_account' also has admin access to
OTHER_account. 'OTHER_account'.
Required Group: Required Group:
@ -99,7 +99,7 @@ class TempAuth(object):
is not processed. is not processed.
The X-Service-Token is useful when combined with multiple reseller prefix The X-Service-Token is useful when combined with multiple reseller prefix
items. In the following configuration, accounts prefixed SERVICE_ items. In the following configuration, accounts prefixed 'SERVICE\_'
are only accessible if X-Auth-Token is from the end-user and are only accessible if X-Auth-Token is from the end-user and
X-Service-Token is from the ``glance`` user:: X-Service-Token is from the ``glance`` user::
@ -177,9 +177,9 @@ class TempAuth(object):
'"/auth/" (Non-empty auth prefix path ' '"/auth/" (Non-empty auth prefix path '
'is required)' % self.auth_prefix) 'is required)' % self.auth_prefix)
self.auth_prefix = '/auth/' self.auth_prefix = '/auth/'
if self.auth_prefix[0] != '/': if not self.auth_prefix.startswith('/'):
self.auth_prefix = '/' + self.auth_prefix self.auth_prefix = '/' + self.auth_prefix
if self.auth_prefix[-1] != '/': if not self.auth_prefix.endswith('/'):
self.auth_prefix += '/' self.auth_prefix += '/'
self.token_life = int(conf.get('token_life', 86400)) self.token_life = int(conf.get('token_life', 86400))
self.allow_overrides = config_true_value( self.allow_overrides = config_true_value(
@ -429,10 +429,12 @@ class TempAuth(object):
try: try:
acls = acls_from_account_info(info) acls = acls_from_account_info(info)
except ValueError as e1: except ValueError as e1:
self.logger.warn("Invalid ACL stored in metadata: %r" % e1) self.logger.warning("Invalid ACL stored in metadata: %r" % e1)
return None return None
except NotImplementedError as e2: except NotImplementedError as e2:
self.logger.warn("ACL version exceeds middleware version: %r" % e2) self.logger.warning(
"ACL version exceeds middleware version: %r"
% e2)
return None return None
return acls return acls

View File

@ -44,14 +44,18 @@ If the user were to share the link with all his friends, or
accidentally post it on a forum, etc. the direct access would be accidentally post it on a forum, etc. the direct access would be
limited to the expiration time set when the website created the link. limited to the expiration time set when the website created the link.
To create such temporary URLs, first an X-Account-Meta-Temp-URL-Key ------------
Client Usage
------------
To create such temporary URLs, first an ``X-Account-Meta-Temp-URL-Key``
header must be set on the Swift account. Then, an HMAC-SHA1 (RFC 2104) header must be set on the Swift account. Then, an HMAC-SHA1 (RFC 2104)
signature is generated using the HTTP method to allow (GET, PUT, signature is generated using the HTTP method to allow (``GET``, ``PUT``,
DELETE, etc.), the Unix timestamp the access should be allowed until, ``DELETE``, etc.), the Unix timestamp the access should be allowed until,
the full path to the object, and the key set on the account. the full path to the object, and the key set on the account.
For example, here is code generating the signature for a GET for 60 For example, here is code generating the signature for a ``GET`` for 60
seconds on /v1/AUTH_account/container/object:: seconds on ``/v1/AUTH_account/container/object``::
import hmac import hmac
from hashlib import sha1 from hashlib import sha1
@ -63,19 +67,20 @@ seconds on /v1/AUTH_account/container/object::
hmac_body = '%s\\n%s\\n%s' % (method, expires, path) hmac_body = '%s\\n%s\\n%s' % (method, expires, path)
sig = hmac.new(key, hmac_body, sha1).hexdigest() sig = hmac.new(key, hmac_body, sha1).hexdigest()
Be certain to use the full path, from the /v1/ onward. Be certain to use the full path, from the ``/v1/`` onward.
Let's say the sig ends up equaling Let's say ``sig`` ends up equaling
da39a3ee5e6b4b0d3255bfef95601890afd80709 and expires ends up ``da39a3ee5e6b4b0d3255bfef95601890afd80709`` and ``expires`` ends up
1323479485. Then, for example, the website could provide a link to:: ``1323479485``. Then, for example, the website could provide a link to::
https://swift-cluster.example.com/v1/AUTH_account/container/object? https://swift-cluster.example.com/v1/AUTH_account/container/object?
temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709& temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709&
temp_url_expires=1323479485 temp_url_expires=1323479485
Any alteration of the resource path or query arguments would result Any alteration of the resource path or query arguments would result in
in 401 Unauthorized. Similarly, a PUT where GET was the allowed method ``401 Unauthorized``. Similarly, a ``PUT`` where ``GET`` was the allowed method
would 401. HEAD is allowed if GET, PUT, or POST is allowed. would be rejected with ``401 Unauthorized``. However, ``HEAD`` is allowed if
``GET``, ``PUT``, or ``POST`` is allowed.
Using this in combination with browser form post translation Using this in combination with browser form post translation
middleware could also allow direct-from-browser uploads to specific middleware could also allow direct-from-browser uploads to specific
@ -83,13 +88,13 @@ locations in Swift.
TempURL supports both account and container level keys. Each allows up to two TempURL supports both account and container level keys. Each allows up to two
keys to be set, allowing key rotation without invalidating all existing keys to be set, allowing key rotation without invalidating all existing
temporary URLs. Account keys are specified by X-Account-Meta-Temp-URL-Key and temporary URLs. Account keys are specified by ``X-Account-Meta-Temp-URL-Key``
X-Account-Meta-Temp-URL-Key-2, while container keys are specified by and ``X-Account-Meta-Temp-URL-Key-2``, while container keys are specified by
X-Container-Meta-Temp-URL-Key and X-Container-Meta-Temp-URL-Key-2. ``X-Container-Meta-Temp-URL-Key`` and ``X-Container-Meta-Temp-URL-Key-2``.
Signatures are checked against account and container keys, if Signatures are checked against account and container keys, if
present. present.
With GET TempURLs, a Content-Disposition header will be set on the With ``GET`` TempURLs, a ``Content-Disposition`` header will be set on the
response so that browsers will interpret this as a file attachment to response so that browsers will interpret this as a file attachment to
be saved. The filename chosen is based on the object name, but you be saved. The filename chosen is based on the object name, but you
can override this with a filename query parameter. Modifying the can override this with a filename query parameter. Modifying the
@ -100,13 +105,54 @@ above example::
temp_url_expires=1323479485&filename=My+Test+File.pdf temp_url_expires=1323479485&filename=My+Test+File.pdf
If you do not want the object to be downloaded, you can cause If you do not want the object to be downloaded, you can cause
"Content-Disposition: inline" to be set on the response by adding the "inline" ``Content-Disposition: inline`` to be set on the response by adding the
parameter to the query string, like so:: ``inline`` parameter to the query string, like so::
https://swift-cluster.example.com/v1/AUTH_account/container/object? https://swift-cluster.example.com/v1/AUTH_account/container/object?
temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709& temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709&
temp_url_expires=1323479485&inline temp_url_expires=1323479485&inline
---------------------
Cluster Configuration
---------------------
This middleware understands the following configuration settings:
``incoming_remove_headers``
A whitespace-delimited list of the headers to remove from
incoming requests. Names may optionally end with ``*`` to
indicate a prefix match. ``incoming_allow_headers`` is a
list of exceptions to these removals.
Default: ``x-timestamp``
``incoming_allow_headers``
A whitespace-delimited list of the headers allowed as
exceptions to ``incoming_remove_headers``. Names may
optionally end with ``*`` to indicate a prefix match.
Default: None
``outgoing_remove_headers``
A whitespace-delimited list of the headers to remove from
outgoing responses. Names may optionally end with ``*`` to
indicate a prefix match. ``outgoing_allow_headers`` is a
list of exceptions to these removals.
Default: ``x-object-meta-*``
``outgoing_allow_headers``
A whitespace-delimited list of the headers allowed as
exceptions to ``outgoing_remove_headers``. Names may
optionally end with ``*`` to indicate a prefix match.
Default: ``x-object-meta-public-*``
``methods``
A whitespace delimited list of request methods that are
allowed to be used with a temporary URL.
Default: ``GET HEAD PUT POST DELETE``
""" """
__all__ = ['TempURL', 'filter_factory', __all__ = ['TempURL', 'filter_factory',
@ -123,7 +169,8 @@ from six.moves.urllib.parse import parse_qs
from six.moves.urllib.parse import urlencode from six.moves.urllib.parse import urlencode
from swift.proxy.controllers.base import get_account_info, get_container_info from swift.proxy.controllers.base import get_account_info, get_container_info
from swift.common.swob import HeaderKeyDict, HTTPUnauthorized, HTTPBadRequest from swift.common.swob import HeaderKeyDict, header_to_environ_key, \
HTTPUnauthorized, HTTPBadRequest
from swift.common.utils import split_path, get_valid_utf8_str, \ from swift.common.utils import split_path, get_valid_utf8_str, \
register_swift_info, get_hmac, streq_const_time, quote register_swift_info, get_hmac, streq_const_time, quote
@ -214,43 +261,6 @@ class TempURL(object):
WSGI Middleware to grant temporary URLs specific access to Swift WSGI Middleware to grant temporary URLs specific access to Swift
resources. See the overview for more information. resources. See the overview for more information.
This middleware understands the following configuration settings::
incoming_remove_headers
The headers to remove from incoming requests. Simply a
whitespace delimited list of header names and names can
optionally end with '*' to indicate a prefix match.
incoming_allow_headers is a list of exceptions to these
removals.
Default: x-timestamp
incoming_allow_headers
The headers allowed as exceptions to
incoming_remove_headers. Simply a whitespace delimited
list of header names and names can optionally end with
'*' to indicate a prefix match.
Default: None
outgoing_remove_headers
The headers to remove from outgoing responses. Simply a
whitespace delimited list of header names and names can
optionally end with '*' to indicate a prefix match.
outgoing_allow_headers is a list of exceptions to these
removals.
Default: x-object-meta-*
outgoing_allow_headers
The headers allowed as exceptions to
outgoing_remove_headers. Simply a whitespace delimited
list of header names and names can optionally end with
'*' to indicate a prefix match.
Default: x-object-meta-public-*
methods
A whitespace delimited list of request methods that are
allowed to be used with a temporary URL.
Default: 'GET HEAD PUT POST DELETE'
The proxy logs created for any subrequests made will have swift.source set The proxy logs created for any subrequests made will have swift.source set
to "TU". to "TU".
@ -259,69 +269,63 @@ class TempURL(object):
:param conf: The configuration dict for the middleware. :param conf: The configuration dict for the middleware.
""" """
def __init__(self, app, conf, def __init__(self, app, conf):
methods=('GET', 'HEAD', 'PUT', 'POST', 'DELETE')):
#: The next WSGI application/filter in the paste.deploy pipeline. #: The next WSGI application/filter in the paste.deploy pipeline.
self.app = app self.app = app
#: The filter configuration dict. #: The filter configuration dict.
self.conf = conf self.conf = conf
#: The methods allowed with Temp URLs.
self.methods = methods
self.disallowed_headers = set( self.disallowed_headers = set(
'HTTP_' + h.upper().replace('-', '_') header_to_environ_key(h)
for h in DISALLOWED_INCOMING_HEADERS.split()) for h in DISALLOWED_INCOMING_HEADERS.split())
headers = DEFAULT_INCOMING_REMOVE_HEADERS headers = [header_to_environ_key(h)
if 'incoming_remove_headers' in conf: for h in conf.get('incoming_remove_headers',
headers = conf['incoming_remove_headers'] DEFAULT_INCOMING_REMOVE_HEADERS.split())]
headers = \
['HTTP_' + h.upper().replace('-', '_') for h in headers.split()]
#: Headers to remove from incoming requests. Uppercase WSGI env style, #: Headers to remove from incoming requests. Uppercase WSGI env style,
#: like `HTTP_X_PRIVATE`. #: like `HTTP_X_PRIVATE`.
self.incoming_remove_headers = [h for h in headers if h[-1] != '*'] self.incoming_remove_headers = \
[h for h in headers if not h.endswith('*')]
#: Header with match prefixes to remove from incoming requests. #: Header with match prefixes to remove from incoming requests.
#: Uppercase WSGI env style, like `HTTP_X_SENSITIVE_*`. #: Uppercase WSGI env style, like `HTTP_X_SENSITIVE_*`.
self.incoming_remove_headers_startswith = \ self.incoming_remove_headers_startswith = \
[h[:-1] for h in headers if h[-1] == '*'] [h[:-1] for h in headers if h.endswith('*')]
headers = DEFAULT_INCOMING_ALLOW_HEADERS headers = [header_to_environ_key(h)
if 'incoming_allow_headers' in conf: for h in conf.get('incoming_allow_headers',
headers = conf['incoming_allow_headers'] DEFAULT_INCOMING_ALLOW_HEADERS.split())]
headers = \
['HTTP_' + h.upper().replace('-', '_') for h in headers.split()]
#: Headers to allow in incoming requests. Uppercase WSGI env style, #: Headers to allow in incoming requests. Uppercase WSGI env style,
#: like `HTTP_X_MATCHES_REMOVE_PREFIX_BUT_OKAY`. #: like `HTTP_X_MATCHES_REMOVE_PREFIX_BUT_OKAY`.
self.incoming_allow_headers = [h for h in headers if h[-1] != '*'] self.incoming_allow_headers = \
[h for h in headers if not h.endswith('*')]
#: Header with match prefixes to allow in incoming requests. Uppercase #: Header with match prefixes to allow in incoming requests. Uppercase
#: WSGI env style, like `HTTP_X_MATCHES_REMOVE_PREFIX_BUT_OKAY_*`. #: WSGI env style, like `HTTP_X_MATCHES_REMOVE_PREFIX_BUT_OKAY_*`.
self.incoming_allow_headers_startswith = \ self.incoming_allow_headers_startswith = \
[h[:-1] for h in headers if h[-1] == '*'] [h[:-1] for h in headers if h.endswith('*')]
headers = DEFAULT_OUTGOING_REMOVE_HEADERS headers = [h.title()
if 'outgoing_remove_headers' in conf: for h in conf.get('outgoing_remove_headers',
headers = conf['outgoing_remove_headers'] DEFAULT_OUTGOING_REMOVE_HEADERS.split())]
headers = [h.title() for h in headers.split()]
#: Headers to remove from outgoing responses. Lowercase, like #: Headers to remove from outgoing responses. Lowercase, like
#: `x-account-meta-temp-url-key`. #: `x-account-meta-temp-url-key`.
self.outgoing_remove_headers = [h for h in headers if h[-1] != '*'] self.outgoing_remove_headers = \
[h for h in headers if not h.endswith('*')]
#: Header with match prefixes to remove from outgoing responses. #: Header with match prefixes to remove from outgoing responses.
#: Lowercase, like `x-account-meta-private-*`. #: Lowercase, like `x-account-meta-private-*`.
self.outgoing_remove_headers_startswith = \ self.outgoing_remove_headers_startswith = \
[h[:-1] for h in headers if h[-1] == '*'] [h[:-1] for h in headers if h.endswith('*')]
headers = DEFAULT_OUTGOING_ALLOW_HEADERS headers = [h.title()
if 'outgoing_allow_headers' in conf: for h in conf.get('outgoing_allow_headers',
headers = conf['outgoing_allow_headers'] DEFAULT_OUTGOING_ALLOW_HEADERS.split())]
headers = [h.title() for h in headers.split()]
#: Headers to allow in outgoing responses. Lowercase, like #: Headers to allow in outgoing responses. Lowercase, like
#: `x-matches-remove-prefix-but-okay`. #: `x-matches-remove-prefix-but-okay`.
self.outgoing_allow_headers = [h for h in headers if h[-1] != '*'] self.outgoing_allow_headers = \
[h for h in headers if not h.endswith('*')]
#: Header with match prefixes to allow in outgoing responses. #: Header with match prefixes to allow in outgoing responses.
#: Lowercase, like `x-matches-remove-prefix-but-okay-*`. #: Lowercase, like `x-matches-remove-prefix-but-okay-*`.
self.outgoing_allow_headers_startswith = \ self.outgoing_allow_headers_startswith = \
[h[:-1] for h in headers if h[-1] == '*'] [h[:-1] for h in headers if h.endswith('*')]
#: HTTP user agent to use for subrequests. #: HTTP user agent to use for subrequests.
self.agent = '%(orig)s TempURL' self.agent = '%(orig)s TempURL'
@ -371,7 +375,7 @@ class TempURL(object):
break break
if not is_valid_hmac: if not is_valid_hmac:
return self._invalid(env, start_response) return self._invalid(env, start_response)
# disallowed headers prevent accidently allowing upload of a pointer # disallowed headers prevent accidentally allowing upload of a pointer
# to data that the PUT tempurl would not otherwise allow access for. # to data that the PUT tempurl would not otherwise allow access for.
# It should be safe to provide a GET tempurl for data that an # It should be safe to provide a GET tempurl for data that an
# untrusted client just uploaded with a PUT tempurl. # untrusted client just uploaded with a PUT tempurl.
@ -434,7 +438,7 @@ class TempURL(object):
:param env: The WSGI environment for the request. :param env: The WSGI environment for the request.
:returns: (Account str, container str) or (None, None). :returns: (Account str, container str) or (None, None).
""" """
if env['REQUEST_METHOD'] in self.methods: if env['REQUEST_METHOD'] in self.conf['methods']:
try: try:
ver, acc, cont, obj = split_path(env['PATH_INFO'], 4, 4, True) ver, acc, cont, obj = split_path(env['PATH_INFO'], 4, 4, True)
except ValueError: except ValueError:
@ -536,7 +540,7 @@ class TempURL(object):
def _clean_disallowed_headers(self, env, start_response): def _clean_disallowed_headers(self, env, start_response):
""" """
Validate the absense of disallowed headers for "unsafe" operations. Validate the absence of disallowed headers for "unsafe" operations.
:returns: None for safe operations or swob.HTTPBadResponse if the :returns: None for safe operations or swob.HTTPBadResponse if the
request includes disallowed headers. request includes disallowed headers.
@ -607,7 +611,15 @@ def filter_factory(global_conf, **local_conf):
conf = global_conf.copy() conf = global_conf.copy()
conf.update(local_conf) conf.update(local_conf)
methods = conf.get('methods', 'GET HEAD PUT POST DELETE').split() defaults = {
register_swift_info('tempurl', methods=methods) 'methods': 'GET HEAD PUT POST DELETE',
'incoming_remove_headers': DEFAULT_INCOMING_REMOVE_HEADERS,
'incoming_allow_headers': DEFAULT_INCOMING_ALLOW_HEADERS,
'outgoing_remove_headers': DEFAULT_OUTGOING_REMOVE_HEADERS,
'outgoing_allow_headers': DEFAULT_OUTGOING_ALLOW_HEADERS,
}
info_conf = {k: conf.get(k, v).split() for k, v in defaults.items()}
register_swift_info('tempurl', **info_conf)
conf.update(info_conf)
return lambda app: TempURL(app, conf, methods=methods) return lambda app: TempURL(app, conf)

View File

@ -93,12 +93,14 @@ See a listing of the older versions of the object::
http://<storage_url>/versions?prefix=008myobject/ http://<storage_url>/versions?prefix=008myobject/
Now delete the current version of the object and see that the older version is Now delete the current version of the object and see that the older version is
gone:: gone from 'versions' container and back in 'container' container::
curl -i -XDELETE -H "X-Auth-Token: <token>" \ curl -i -XDELETE -H "X-Auth-Token: <token>" \
http://<storage_url>/container/myobject http://<storage_url>/container/myobject
curl -i -H "X-Auth-Token: <token>" \ curl -i -H "X-Auth-Token: <token>" \
http://<storage_url>/versions?prefix=008myobject/ http://<storage_url>/versions?prefix=008myobject/
curl -i -XGET -H "X-Auth-Token: <token>" \
http://<storage_url>/container/myobject
--------------------------------------------------- ---------------------------------------------------
How to Disable Object Versioning in a Swift Cluster How to Disable Object Versioning in a Swift Cluster
@ -113,10 +115,11 @@ Disable versioning from a container (x is any value except empty)::
-H "X-Remove-Versions-Location: x" http://<storage_url>/container -H "X-Remove-Versions-Location: x" http://<storage_url>/container
""" """
import json
import six import six
from six.moves.urllib.parse import quote, unquote from six.moves.urllib.parse import quote, unquote
import time import time
from swift.common.utils import get_logger, Timestamp, json, \ from swift.common.utils import get_logger, Timestamp, \
register_swift_info, config_true_value register_swift_info, config_true_value
from swift.common.request_helpers import get_sys_meta_prefix from swift.common.request_helpers import get_sys_meta_prefix
from swift.common.wsgi import WSGIContext, make_pre_authed_request from swift.common.wsgi import WSGIContext, make_pre_authed_request
@ -138,11 +141,18 @@ class VersionedWritesContext(WSGIContext):
WSGIContext.__init__(self, wsgi_app) WSGIContext.__init__(self, wsgi_app)
self.logger = logger self.logger = logger
def _listing_iter(self, account_name, lcontainer, lprefix, env): def _listing_iter(self, account_name, lcontainer, lprefix, req):
for page in self._listing_pages_iter(account_name, try:
lcontainer, lprefix, env): for page in self._listing_pages_iter(account_name, lcontainer,
for item in page: lprefix, req.environ):
yield item for item in page:
yield item
except ListingIterNotFound:
pass
except HTTPPreconditionFailed:
raise HTTPPreconditionFailed(request=req)
except ListingIterError:
raise HTTPServerError(request=req)
def _listing_pages_iter(self, account_name, lcontainer, lprefix, env): def _listing_pages_iter(self, account_name, lcontainer, lprefix, env):
marker = '' marker = ''
@ -151,8 +161,8 @@ class VersionedWritesContext(WSGIContext):
env, method='GET', swift_source='VW', env, method='GET', swift_source='VW',
path='/v1/%s/%s' % (account_name, lcontainer)) path='/v1/%s/%s' % (account_name, lcontainer))
lreq.environ['QUERY_STRING'] = \ lreq.environ['QUERY_STRING'] = \
'format=json&prefix=%s&marker=%s' % (quote(lprefix), 'format=json&prefix=%s&reverse=on&marker=%s' % (
quote(marker)) quote(lprefix), quote(marker))
lresp = lreq.get_response(self.app) lresp = lreq.get_response(self.app)
if not is_success(lresp.status_int): if not is_success(lresp.status_int):
if lresp.status_int == HTTP_NOT_FOUND: if lresp.status_int == HTTP_NOT_FOUND:
@ -244,31 +254,22 @@ class VersionedWritesContext(WSGIContext):
lcontainer = object_versions.split('/')[0] lcontainer = object_versions.split('/')[0]
prefix_len = '%03x' % len(object_name) prefix_len = '%03x' % len(object_name)
lprefix = prefix_len + object_name + '/' lprefix = prefix_len + object_name + '/'
item_list = []
try:
for _item in self._listing_iter(account_name, lcontainer, lprefix,
req.environ):
item_list.append(_item)
except ListingIterNotFound:
pass
except HTTPPreconditionFailed:
return HTTPPreconditionFailed(request=req)
except ListingIterError:
return HTTPServerError(request=req)
if item_list: item_iter = self._listing_iter(account_name, lcontainer, lprefix, req)
# we're about to start making COPY requests - need to validate the
# write access to the versioned container
if 'swift.authorize' in req.environ:
container_info = get_container_info(
req.environ, self.app)
req.acl = container_info.get('write_acl')
aresp = req.environ['swift.authorize'](req)
if aresp:
return aresp
while len(item_list) > 0: authed = False
previous_version = item_list.pop() for previous_version in item_iter:
if not authed:
# we're about to start making COPY requests - need to
# validate the write access to the versioned container
if 'swift.authorize' in req.environ:
container_info = get_container_info(
req.environ, self.app)
req.acl = container_info.get('write_acl')
aresp = req.environ['swift.authorize'](req)
if aresp:
return aresp
authed = True
# there are older versions so copy the previous version to the # there are older versions so copy the previous version to the
# current object and delete the previous version # current object and delete the previous version

View File

@ -307,8 +307,10 @@ class SegmentedIterable(object):
'ERROR: While processing manifest %s, ' 'ERROR: While processing manifest %s, '
'max LO GET time of %ds exceeded' % 'max LO GET time of %ds exceeded' %
(self.name, self.max_get_time)) (self.name, self.max_get_time))
# Make sure that the segment is a plain old object, not some # The "multipart-manifest=get" query param ensures that the
# flavor of large object, so that we can check its MD5. # segment is a plain old object, not some flavor of large
# object; therefore, its etag is its MD5sum and hence we can
# check it.
path = seg_path + '?multipart-manifest=get' path = seg_path + '?multipart-manifest=get'
seg_req = make_subrequest( seg_req = make_subrequest(
self.req.environ, path=path, method='GET', self.req.environ, path=path, method='GET',
@ -317,21 +319,35 @@ class SegmentedIterable(object):
agent=('%(orig)s ' + self.ua_suffix), agent=('%(orig)s ' + self.ua_suffix),
swift_source=self.swift_source) swift_source=self.swift_source)
seg_req_rangeval = None
if first_byte != 0 or not go_to_end: if first_byte != 0 or not go_to_end:
seg_req.headers['Range'] = "bytes=%s-%s" % ( seg_req_rangeval = "%s-%s" % (
first_byte, '' if go_to_end else last_byte) first_byte, '' if go_to_end else last_byte)
seg_req.headers['Range'] = "bytes=" + seg_req_rangeval
# We can only coalesce if paths match and we know the segment # We can only coalesce if paths match and we know the segment
# size (so we can check that the ranges will be allowed) # size (so we can check that the ranges will be allowed)
if pending_req and pending_req.path == seg_req.path and \ if pending_req and pending_req.path == seg_req.path and \
seg_size is not None: seg_size is not None:
new_range = '%s,%s' % (
pending_req.headers.get('Range', # Make a new Range object so that we don't goof up the
'bytes=0-%s' % (seg_size - 1)), # existing one in case of invalid ranges. Note that a
seg_req.headers['Range'].split('bytes=')[1]) # range set with too many individual byteranges is
if Range(new_range).ranges_for_length(seg_size): # invalid, so we can combine N valid byteranges and 1
# valid byterange and get an invalid range set.
if pending_req.range:
new_range_str = str(pending_req.range)
else:
new_range_str = "bytes=0-%d" % (seg_size - 1)
if seg_req.range:
new_range_str += "," + seg_req_rangeval
else:
new_range_str += ",0-%d" % (seg_size - 1)
if Range(new_range_str).ranges_for_length(seg_size):
# Good news! We can coalesce the requests # Good news! We can coalesce the requests
pending_req.headers['Range'] = new_range pending_req.headers['Range'] = new_range_str
continue continue
# else, Too many ranges, or too much backtracking, or ... # else, Too many ranges, or too much backtracking, or ...

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
import array import array
import six.moves.cPickle as pickle import six.moves.cPickle as pickle
import inspect import inspect
import json
from collections import defaultdict from collections import defaultdict
from gzip import GzipFile from gzip import GzipFile
from os.path import getmtime from os.path import getmtime
@ -29,7 +30,7 @@ from tempfile import NamedTemporaryFile
from six.moves import range from six.moves import range
from swift.common.utils import hash_path, validate_configuration, json from swift.common.utils import hash_path, validate_configuration
from swift.common.ring.utils import tiers_for_dev from swift.common.ring.utils import tiers_for_dev

View File

@ -223,7 +223,7 @@ def is_valid_hostname(hostname):
""" """
if len(hostname) < 1 or len(hostname) > 255: if len(hostname) < 1 or len(hostname) > 255:
return False return False
if hostname[-1] == ".": if hostname.endswith('.'):
# strip exactly one dot from the right, if present # strip exactly one dot from the right, if present
hostname = hostname[:-1] hostname = hostname[:-1]
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE) allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
@ -328,13 +328,13 @@ def parse_search_value(search_value):
search_value = search_value[i:] search_value = search_value[i:]
if search_value.startswith('-'): if search_value.startswith('-'):
search_value = search_value[1:] search_value = search_value[1:]
if len(search_value) and search_value[0].isdigit(): if search_value and search_value[0].isdigit():
i = 1 i = 1
while i < len(search_value) and search_value[i] in '0123456789.': while i < len(search_value) and search_value[i] in '0123456789.':
i += 1 i += 1
match['ip'] = search_value[:i] match['ip'] = search_value[:i]
search_value = search_value[i:] search_value = search_value[i:]
elif len(search_value) and search_value[0] == '[': elif search_value and search_value.startswith('['):
i = 1 i = 1
while i < len(search_value) and search_value[i] != ']': while i < len(search_value) and search_value[i] != ']':
i += 1 i += 1
@ -356,14 +356,14 @@ def parse_search_value(search_value):
# replication parameters # replication parameters
if search_value.startswith('R'): if search_value.startswith('R'):
search_value = search_value[1:] search_value = search_value[1:]
if len(search_value) and search_value[0].isdigit(): if search_value and search_value[0].isdigit():
i = 1 i = 1
while (i < len(search_value) and while (i < len(search_value) and
search_value[i] in '0123456789.'): search_value[i] in '0123456789.'):
i += 1 i += 1
match['replication_ip'] = search_value[:i] match['replication_ip'] = search_value[:i]
search_value = search_value[i:] search_value = search_value[i:]
elif len(search_value) and search_value[0] == '[': elif search_value and search_value.startswith('['):
i = 1 i = 1
while i < len(search_value) and search_value[i] != ']': while i < len(search_value) and search_value[i] != ']':
i += 1 i += 1

199
swift/common/storage_policy.py Normal file → Executable file
View File

@ -16,11 +16,9 @@ import os
import string import string
import textwrap import textwrap
import six import six
from six.moves.configparser import ConfigParser from six.moves.configparser import ConfigParser
from swift.common.utils import ( from swift.common.utils import (
config_true_value, SWIFT_CONF_FILE, whataremyips) config_true_value, SWIFT_CONF_FILE, whataremyips, list_from_csv)
from swift.common.ring import Ring, RingData from swift.common.ring import Ring, RingData
from swift.common.utils import quorum_size from swift.common.utils import quorum_size
from swift.common.exceptions import RingValidationError from swift.common.exceptions import RingValidationError
@ -84,7 +82,6 @@ class BindPortsCache(object):
class PolicyError(ValueError): class PolicyError(ValueError):
def __init__(self, msg, index=None): def __init__(self, msg, index=None):
if index is not None: if index is not None:
msg += ', for index %r' % index msg += ', for index %r' % index
@ -161,7 +158,7 @@ class BaseStoragePolicy(object):
policy_type_to_policy_cls = {} policy_type_to_policy_cls = {}
def __init__(self, idx, name='', is_default=False, is_deprecated=False, def __init__(self, idx, name='', is_default=False, is_deprecated=False,
object_ring=None): object_ring=None, aliases=''):
# do not allow BaseStoragePolicy class to be instantiated directly # do not allow BaseStoragePolicy class to be instantiated directly
if type(self) == BaseStoragePolicy: if type(self) == BaseStoragePolicy:
raise TypeError("Can't instantiate BaseStoragePolicy directly") raise TypeError("Can't instantiate BaseStoragePolicy directly")
@ -172,18 +169,17 @@ class BaseStoragePolicy(object):
raise PolicyError('Invalid index', idx) raise PolicyError('Invalid index', idx)
if self.idx < 0: if self.idx < 0:
raise PolicyError('Invalid index', idx) raise PolicyError('Invalid index', idx)
if not name: self.alias_list = []
if not name or not self._validate_policy_name(name):
raise PolicyError('Invalid name %r' % name, idx) raise PolicyError('Invalid name %r' % name, idx)
# this is defensively restrictive, but could be expanded in the future self.alias_list.append(name)
if not all(c in VALID_CHARS for c in name): if aliases:
raise PolicyError('Names are used as HTTP headers, and can not ' names_list = list_from_csv(aliases)
'reliably contain any characters not in %r. ' for alias in names_list:
'Invalid name %r' % (VALID_CHARS, name)) if alias == name:
if name.upper() == LEGACY_POLICY_NAME.upper() and self.idx != 0: continue
msg = 'The name %s is reserved for policy index 0. ' \ self._validate_policy_name(alias)
'Invalid name %r' % (LEGACY_POLICY_NAME, name) self.alias_list.append(alias)
raise PolicyError(msg, idx)
self.name = name
self.is_deprecated = config_true_value(is_deprecated) self.is_deprecated = config_true_value(is_deprecated)
self.is_default = config_true_value(is_default) self.is_default = config_true_value(is_default)
if self.policy_type not in BaseStoragePolicy.policy_type_to_policy_cls: if self.policy_type not in BaseStoragePolicy.policy_type_to_policy_cls:
@ -191,9 +187,23 @@ class BaseStoragePolicy(object):
if self.is_deprecated and self.is_default: if self.is_deprecated and self.is_default:
raise PolicyError('Deprecated policy can not be default. ' raise PolicyError('Deprecated policy can not be default. '
'Invalid config', self.idx) 'Invalid config', self.idx)
self.ring_name = _get_policy_string('object', self.idx) self.ring_name = _get_policy_string('object', self.idx)
self.object_ring = object_ring self.object_ring = object_ring
@property
def name(self):
return self.alias_list[0]
@name.setter
def name_setter(self, name):
self._validate_policy_name(name)
self.alias_list[0] = name
@property
def aliases(self):
return ", ".join(self.alias_list)
def __int__(self): def __int__(self):
return self.idx return self.idx
@ -203,8 +213,8 @@ class BaseStoragePolicy(object):
def __repr__(self): def __repr__(self):
return ("%s(%d, %r, is_default=%s, " return ("%s(%d, %r, is_default=%s, "
"is_deprecated=%s, policy_type=%r)") % \ "is_deprecated=%s, policy_type=%r)") % \
(self.__class__.__name__, self.idx, self.name, (self.__class__.__name__, self.idx, self.alias_list,
self.is_default, self.is_deprecated, self.policy_type) self.is_default, self.is_deprecated, self.policy_type)
@classmethod @classmethod
def register(cls, policy_type): def register(cls, policy_type):
@ -213,6 +223,7 @@ class BaseStoragePolicy(object):
their StoragePolicy class. This will also set the policy_type their StoragePolicy class. This will also set the policy_type
attribute on the registered implementation. attribute on the registered implementation.
""" """
def register_wrapper(policy_cls): def register_wrapper(policy_cls):
if policy_type in cls.policy_type_to_policy_cls: if policy_type in cls.policy_type_to_policy_cls:
raise PolicyError( raise PolicyError(
@ -222,6 +233,7 @@ class BaseStoragePolicy(object):
cls.policy_type_to_policy_cls[policy_type] = policy_cls cls.policy_type_to_policy_cls[policy_type] = policy_cls
policy_cls.policy_type = policy_type policy_cls.policy_type = policy_type
return policy_cls return policy_cls
return register_wrapper return register_wrapper
@classmethod @classmethod
@ -231,6 +243,7 @@ class BaseStoragePolicy(object):
""" """
return { return {
'name': 'name', 'name': 'name',
'aliases': 'aliases',
'policy_type': 'policy_type', 'policy_type': 'policy_type',
'default': 'is_default', 'default': 'is_default',
'deprecated': 'is_deprecated', 'deprecated': 'is_deprecated',
@ -269,6 +282,77 @@ class BaseStoragePolicy(object):
info.pop('policy_type') info.pop('policy_type')
return info return info
def _validate_policy_name(self, name):
"""
Helper function to determine the validity of a policy name. Used
to check policy names before setting them.
:param name: a name string for a single policy name.
:returns: true if the name is valid.
:raises: PolicyError if the policy name is invalid.
"""
# this is defensively restrictive, but could be expanded in the future
if not all(c in VALID_CHARS for c in name):
raise PolicyError('Names are used as HTTP headers, and can not '
'reliably contain any characters not in %r. '
'Invalid name %r' % (VALID_CHARS, name))
if name.upper() == LEGACY_POLICY_NAME.upper() and self.idx != 0:
msg = 'The name %s is reserved for policy index 0. ' \
'Invalid name %r' % (LEGACY_POLICY_NAME, name)
raise PolicyError(msg, self.idx)
if name.upper() in (existing_name.upper() for existing_name
in self.alias_list):
msg = 'The name %s is already assigned to this policy.' % name
raise PolicyError(msg, self.idx)
return True
def add_name(self, name):
"""
Adds an alias name to the storage policy. Shouldn't be called
directly from the storage policy but instead through the
storage policy collection class, so lookups by name resolve
correctly.
:param name: a new alias for the storage policy
"""
if self._validate_policy_name(name):
self.alias_list.append(name)
def remove_name(self, name):
"""
Removes an alias name from the storage policy. Shouldn't be called
directly from the storage policy but instead through the storage
policy collection class, so lookups by name resolve correctly. If
the name removed is the primary name then the next available alias
will be adopted as the new primary name.
:param name: a name assigned to the storage policy
"""
if name not in self.alias_list:
raise PolicyError("%s is not a name assigned to policy %s"
% (name, self.idx))
if len(self.alias_list) == 1:
raise PolicyError("Cannot remove only name %s from policy %s. "
"Policies must have at least one name."
% (name, self.idx))
else:
self.alias_list.remove(name)
def change_primary_name(self, name):
"""
Changes the primary/default name of the policy to a specified name.
:param name: a string name to replace the current primary name.
"""
if name == self.name:
return
elif name in self.alias_list:
self.remove_name(name)
else:
self._validate_policy_name(name)
self.alias_list.insert(0, name)
def _validate_ring(self): def _validate_ring(self):
""" """
Hook, called when the ring is loaded. Can be used to Hook, called when the ring is loaded. Can be used to
@ -329,13 +413,15 @@ class ECStoragePolicy(BaseStoragePolicy):
:func:`~swift.common.storage_policy.reload_storage_policies` to load :func:`~swift.common.storage_policy.reload_storage_policies` to load
POLICIES from ``swift.conf``. POLICIES from ``swift.conf``.
""" """
def __init__(self, idx, name='', is_default=False,
def __init__(self, idx, name='', aliases='', is_default=False,
is_deprecated=False, object_ring=None, is_deprecated=False, object_ring=None,
ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE, ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE,
ec_type=None, ec_ndata=None, ec_nparity=None): ec_type=None, ec_ndata=None, ec_nparity=None):
super(ECStoragePolicy, self).__init__( super(ECStoragePolicy, self).__init__(
idx, name, is_default, is_deprecated, object_ring) idx=idx, name=name, aliases=aliases, is_default=is_default,
is_deprecated=is_deprecated, object_ring=object_ring)
# Validate erasure_coding policy specific members # Validate erasure_coding policy specific members
# ec_type is one of the EC implementations supported by PyEClib # ec_type is one of the EC implementations supported by PyEClib
@ -441,9 +527,9 @@ class ECStoragePolicy(BaseStoragePolicy):
def __repr__(self): def __repr__(self):
return ("%s, EC config(ec_type=%s, ec_segment_size=%d, " return ("%s, EC config(ec_type=%s, ec_segment_size=%d, "
"ec_ndata=%d, ec_nparity=%d)") % ( "ec_ndata=%d, ec_nparity=%d)") % \
super(ECStoragePolicy, self).__repr__(), self.ec_type, (super(ECStoragePolicy, self).__repr__(), self.ec_type,
self.ec_segment_size, self.ec_ndata, self.ec_nparity) self.ec_segment_size, self.ec_ndata, self.ec_nparity)
@classmethod @classmethod
def _config_options_map(cls): def _config_options_map(cls):
@ -532,6 +618,7 @@ class StoragePolicyCollection(object):
* Deprecated policies can not be declared the default * Deprecated policies can not be declared the default
""" """
def __init__(self, pols): def __init__(self, pols):
self.default = [] self.default = []
self.by_name = {} self.by_name = {}
@ -542,7 +629,8 @@ class StoragePolicyCollection(object):
""" """
Add pre-validated policies to internal indexes. Add pre-validated policies to internal indexes.
""" """
self.by_name[policy.name.upper()] = policy for name in policy.alias_list:
self.by_name[name.upper()] = policy
self.by_index[int(policy)] = policy self.by_index[int(policy)] = policy
def __repr__(self): def __repr__(self):
@ -570,9 +658,10 @@ class StoragePolicyCollection(object):
if int(policy) in self.by_index: if int(policy) in self.by_index:
raise PolicyError('Duplicate index %s conflicts with %s' % ( raise PolicyError('Duplicate index %s conflicts with %s' % (
policy, self.get_by_index(int(policy)))) policy, self.get_by_index(int(policy))))
if policy.name.upper() in self.by_name: for name in policy.alias_list:
raise PolicyError('Duplicate name %s conflicts with %s' % ( if name.upper() in self.by_name:
policy, self.get_by_name(policy.name))) raise PolicyError('Duplicate name %s conflicts with %s' % (
policy, self.get_by_name(name)))
if policy.is_default: if policy.is_default:
if not self.default: if not self.default:
self.default = policy self.default = policy
@ -667,6 +756,62 @@ class StoragePolicyCollection(object):
policy_info.append(policy_entry) policy_info.append(policy_entry)
return policy_info return policy_info
def add_policy_alias(self, policy_index, *aliases):
"""
Adds a new name or names to a policy
:param policy_index: index of a policy in this policy collection.
:param *aliases: arbitrary number of string policy names to add.
"""
policy = self.get_by_index(policy_index)
for alias in aliases:
if alias.upper() in self.by_name:
raise PolicyError('Duplicate name %s in use '
'by policy %s' % (alias,
self.get_by_name(alias)))
else:
policy.add_name(alias)
self.by_name[alias.upper()] = policy
def remove_policy_alias(self, *aliases):
"""
Removes a name or names from a policy. If the name removed is the
primary name then the next available alias will be adopted
as the new primary name.
:param *aliases: arbitrary number of existing policy names to remove.
"""
for alias in aliases:
policy = self.get_by_name(alias)
if not policy:
raise PolicyError('No policy with name %s exists.' % alias)
if len(policy.alias_list) == 1:
raise PolicyError('Policy %s with name %s has only one name. '
'Policies must have at least one name.' % (
policy, alias))
else:
policy.remove_name(alias)
del self.by_name[alias.upper()]
def change_policy_primary_name(self, policy_index, new_name):
"""
Changes the primary or default name of a policy. The new primary
name can be an alias that already belongs to the policy or a
completely new name.
:param policy_index: index of a policy in this policy collection.
:param new_name: a string name to set as the new default name.
"""
policy = self.get_by_index(policy_index)
name_taken = self.get_by_name(new_name)
# if the name belongs to some other policy in the collection
if name_taken and name_taken != policy:
raise PolicyError('Other policy %s with name %s exists.' %
(self.get_by_name(new_name).idx, new_name))
else:
policy.change_primary_name(new_name)
self.by_name[new_name.upper()] = policy
def parse_storage_policies(conf): def parse_storage_policies(conf):
""" """

View File

@ -217,6 +217,15 @@ def _header_int_property(header):
doc="Retrieve and set the %s header as an int" % header) doc="Retrieve and set the %s header as an int" % header)
def header_to_environ_key(header_name):
header_name = 'HTTP_' + header_name.replace('-', '_').upper()
if header_name == 'HTTP_CONTENT_LENGTH':
return 'CONTENT_LENGTH'
if header_name == 'HTTP_CONTENT_TYPE':
return 'CONTENT_TYPE'
return header_name
class HeaderEnvironProxy(MutableMapping): class HeaderEnvironProxy(MutableMapping):
""" """
A dict-like object that proxies requests to a wsgi environ, A dict-like object that proxies requests to a wsgi environ,
@ -235,30 +244,22 @@ class HeaderEnvironProxy(MutableMapping):
def __len__(self): def __len__(self):
return len(self.keys()) return len(self.keys())
def _normalize(self, key):
key = 'HTTP_' + key.replace('-', '_').upper()
if key == 'HTTP_CONTENT_LENGTH':
return 'CONTENT_LENGTH'
if key == 'HTTP_CONTENT_TYPE':
return 'CONTENT_TYPE'
return key
def __getitem__(self, key): def __getitem__(self, key):
return self.environ[self._normalize(key)] return self.environ[header_to_environ_key(key)]
def __setitem__(self, key, value): def __setitem__(self, key, value):
if value is None: if value is None:
self.environ.pop(self._normalize(key), None) self.environ.pop(header_to_environ_key(key), None)
elif isinstance(value, six.text_type): elif isinstance(value, six.text_type):
self.environ[self._normalize(key)] = value.encode('utf-8') self.environ[header_to_environ_key(key)] = value.encode('utf-8')
else: else:
self.environ[self._normalize(key)] = str(value) self.environ[header_to_environ_key(key)] = str(value)
def __contains__(self, key): def __contains__(self, key):
return self._normalize(key) in self.environ return header_to_environ_key(key) in self.environ
def __delitem__(self, key): def __delitem__(self, key):
del self.environ[self._normalize(key)] del self.environ[header_to_environ_key(key)]
def keys(self): def keys(self):
keys = [key[5:].replace('_', '-').title() keys = [key[5:].replace('_', '-').title()
@ -541,14 +542,15 @@ class Range(object):
def __str__(self): def __str__(self):
string = 'bytes=' string = 'bytes='
for start, end in self.ranges: for i, (start, end) in enumerate(self.ranges):
if start is not None: if start is not None:
string += str(start) string += str(start)
string += '-' string += '-'
if end is not None: if end is not None:
string += str(end) string += str(end)
string += ',' if i < len(self.ranges) - 1:
return string.rstrip(',') string += ','
return string
def ranges_for_length(self, length): def ranges_for_length(self, length):
""" """
@ -970,7 +972,7 @@ class Request(object):
the path segment. the path segment.
""" """
path_info = self.path_info path_info = self.path_info
if not path_info or path_info[0] != '/': if not path_info or not path_info.startswith('/'):
return None return None
try: try:
slash_loc = path_info.index('/', 1) slash_loc = path_info.index('/', 1)
@ -1184,7 +1186,7 @@ class Response(object):
""" """
content_size = self.content_length content_size = self.content_length
content_type = self.content_type content_type = self.headers.get('content-type')
self.content_type = ''.join(['multipart/byteranges;', self.content_type = ''.join(['multipart/byteranges;',
'boundary=', self.boundary]) 'boundary=', self.boundary])

View File

@ -21,16 +21,15 @@ import errno
import fcntl import fcntl
import grp import grp
import hmac import hmac
import json
import operator import operator
import os import os
import pwd import pwd
import re import re
import sys import sys
import threading as stdlib_threading
import time import time
import uuid import uuid
import functools import functools
import weakref
import email.parser import email.parser
from hashlib import md5, sha1 from hashlib import md5, sha1
from random import random, shuffle from random import random, shuffle
@ -40,10 +39,6 @@ import ctypes.util
from optparse import OptionParser from optparse import OptionParser
from tempfile import mkstemp, NamedTemporaryFile from tempfile import mkstemp, NamedTemporaryFile
try:
import simplejson as json
except ImportError:
import json
import glob import glob
import itertools import itertools
import stat import stat
@ -63,7 +58,6 @@ import six
from six.moves import cPickle as pickle from six.moves import cPickle as pickle
from six.moves.configparser import (ConfigParser, NoSectionError, from six.moves.configparser import (ConfigParser, NoSectionError,
NoOptionError, RawConfigParser) NoOptionError, RawConfigParser)
from six.moves.queue import Queue, Empty
from six.moves import range from six.moves import range
from six.moves.urllib.parse import ParseResult from six.moves.urllib.parse import ParseResult
from six.moves.urllib.parse import quote as _quote from six.moves.urllib.parse import quote as _quote
@ -74,6 +68,11 @@ import swift.common.exceptions
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND, \ from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND, \
HTTP_PRECONDITION_FAILED, HTTP_REQUESTED_RANGE_NOT_SATISFIABLE HTTP_PRECONDITION_FAILED, HTTP_REQUESTED_RANGE_NOT_SATISFIABLE
if six.PY3:
stdlib_queue = eventlet.patcher.original('queue')
else:
stdlib_queue = eventlet.patcher.original('Queue')
stdlib_threading = eventlet.patcher.original('threading')
# logging doesn't import patched as cleanly as one would like # logging doesn't import patched as cleanly as one would like
from logging.handlers import SysLogHandler from logging.handlers import SysLogHandler
@ -249,7 +248,7 @@ def backward(f, blocksize=4096):
f.seek(0, os.SEEK_END) f.seek(0, os.SEEK_END)
if f.tell() == 0: if f.tell() == 0:
return return
last_row = '' last_row = b''
while f.tell() != 0: while f.tell() != 0:
try: try:
f.seek(-blocksize, os.SEEK_CUR) f.seek(-blocksize, os.SEEK_CUR)
@ -258,7 +257,7 @@ def backward(f, blocksize=4096):
f.seek(-blocksize, os.SEEK_CUR) f.seek(-blocksize, os.SEEK_CUR)
block = f.read(blocksize) block = f.read(blocksize)
f.seek(-blocksize, os.SEEK_CUR) f.seek(-blocksize, os.SEEK_CUR)
rows = block.split('\n') rows = block.split(b'\n')
rows[-1] = rows[-1] + last_row rows[-1] = rows[-1] + last_row
while rows: while rows:
last_row = rows.pop(-1) last_row = rows.pop(-1)
@ -297,7 +296,7 @@ def config_auto_int_value(value, default):
def append_underscore(prefix): def append_underscore(prefix):
if prefix and prefix[-1] != '_': if prefix and not prefix.endswith('_'):
prefix += '_' prefix += '_'
return prefix return prefix
@ -390,8 +389,8 @@ def load_libc_function(func_name, log_error=True,
if fail_if_missing: if fail_if_missing:
raise raise
if log_error: if log_error:
logging.warn(_("Unable to locate %s in libc. Leaving as a " logging.warning(_("Unable to locate %s in libc. Leaving as a "
"no-op."), func_name) "no-op."), func_name)
return noop_libc_function return noop_libc_function
@ -425,7 +424,7 @@ def get_log_line(req, res, trans_time, additional_info):
:param trans_time: the time the request took to complete, a float. :param trans_time: the time the request took to complete, a float.
:param additional_info: a string to log at the end of the line :param additional_info: a string to log at the end of the line
:returns: a properly formated line for logging. :returns: a properly formatted line for logging.
""" """
policy_index = get_policy_index(req.headers, res.headers) policy_index = get_policy_index(req.headers, res.headers)
@ -440,7 +439,8 @@ def get_log_line(req, res, trans_time, additional_info):
def get_trans_id_time(trans_id): def get_trans_id_time(trans_id):
if len(trans_id) >= 34 and trans_id[:2] == 'tx' and trans_id[23] == '-': if len(trans_id) >= 34 and \
trans_id.startswith('tx') and trans_id[23] == '-':
try: try:
return int(trans_id[24:34], 16) return int(trans_id[24:34], 16)
except ValueError: except ValueError:
@ -580,8 +580,8 @@ class FallocateWrapper(object):
if self.fallocate is not noop_libc_function: if self.fallocate is not noop_libc_function:
break break
if self.fallocate is noop_libc_function: if self.fallocate is noop_libc_function:
logging.warn(_("Unable to locate fallocate, posix_fallocate in " logging.warning(_("Unable to locate fallocate, posix_fallocate in "
"libc. Leaving as a no-op.")) "libc. Leaving as a no-op."))
def __call__(self, fd, mode, offset, length): def __call__(self, fd, mode, offset, length):
"""The length parameter must be a ctypes.c_uint64.""" """The length parameter must be a ctypes.c_uint64."""
@ -664,8 +664,8 @@ def fsync_dir(dirpath):
if err.errno == errno.ENOTDIR: if err.errno == errno.ENOTDIR:
# Raise error if someone calls fsync_dir on a non-directory # Raise error if someone calls fsync_dir on a non-directory
raise raise
logging.warn(_("Unable to perform fsync() on directory %s: %s"), logging.warning(_("Unable to perform fsync() on directory %s: %s"),
dirpath, os.strerror(err.errno)) dirpath, os.strerror(err.errno))
finally: finally:
if dirfd: if dirfd:
os.close(dirfd) os.close(dirfd)
@ -686,9 +686,9 @@ def drop_buffer_cache(fd, offset, length):
ret = _posix_fadvise(fd, ctypes.c_uint64(offset), ret = _posix_fadvise(fd, ctypes.c_uint64(offset),
ctypes.c_uint64(length), 4) ctypes.c_uint64(length), 4)
if ret != 0: if ret != 0:
logging.warn("posix_fadvise64(%(fd)s, %(offset)s, %(length)s, 4) " logging.warning("posix_fadvise64(%(fd)s, %(offset)s, %(length)s, 4) "
"-> %(ret)s", {'fd': fd, 'offset': offset, "-> %(ret)s", {'fd': fd, 'offset': offset,
'length': length, 'ret': ret}) 'length': length, 'ret': ret})
NORMAL_FORMAT = "%016.05f" NORMAL_FORMAT = "%016.05f"
@ -832,6 +832,9 @@ class Timestamp(object):
other = Timestamp(other) other = Timestamp(other)
return cmp(self.internal, other.internal) return cmp(self.internal, other.internal)
def __hash__(self):
return hash(self.internal)
def normalize_timestamp(timestamp): def normalize_timestamp(timestamp):
""" """
@ -1166,14 +1169,16 @@ class StatsdClient(object):
parts.append('@%s' % (sample_rate,)) parts.append('@%s' % (sample_rate,))
else: else:
return return
if six.PY3:
parts = [part.encode('utf-8') for part in parts]
# Ideally, we'd cache a sending socket in self, but that # Ideally, we'd cache a sending socket in self, but that
# results in a socket getting shared by multiple green threads. # results in a socket getting shared by multiple green threads.
with closing(self._open_socket()) as sock: with closing(self._open_socket()) as sock:
try: try:
return sock.sendto('|'.join(parts), self._target) return sock.sendto(b'|'.join(parts), self._target)
except IOError as err: except IOError as err:
if self.logger: if self.logger:
self.logger.warn( self.logger.warning(
'Error sending UDP message to %r: %s', 'Error sending UDP message to %r: %s',
self._target, err) self._target, err)
@ -1227,7 +1232,7 @@ def timing_stats(**dec_kwargs):
swift's wsgi server controllers, based on response code. swift's wsgi server controllers, based on response code.
""" """
def decorating_func(func): def decorating_func(func):
method = func.func_name method = func.__name__
@functools.wraps(func) @functools.wraps(func)
def _timing_stats(ctrl, *args, **kwargs): def _timing_stats(ctrl, *args, **kwargs):
@ -1245,27 +1250,6 @@ def timing_stats(**dec_kwargs):
return decorating_func return decorating_func
class LoggingHandlerWeakRef(weakref.ref):
"""
Like a weak reference, but passes through a couple methods that logging
handlers need.
"""
def close(self):
referent = self()
try:
if referent:
referent.close()
except KeyError:
# This is to catch an issue with old py2.6 versions
pass
def flush(self):
referent = self()
if referent:
referent.flush()
# double inheritance to support property with setter # double inheritance to support property with setter
class LogAdapter(logging.LoggerAdapter, object): class LogAdapter(logging.LoggerAdapter, object):
""" """
@ -1279,7 +1263,6 @@ class LogAdapter(logging.LoggerAdapter, object):
def __init__(self, logger, server): def __init__(self, logger, server):
logging.LoggerAdapter.__init__(self, logger, {}) logging.LoggerAdapter.__init__(self, logger, {})
self.server = server self.server = server
setattr(self, 'warn', self.warning)
@property @property
def txn_id(self): def txn_id(self):
@ -1334,13 +1317,10 @@ class LogAdapter(logging.LoggerAdapter, object):
_junk, exc, _junk = sys.exc_info() _junk, exc, _junk = sys.exc_info()
call = self.error call = self.error
emsg = '' emsg = ''
if isinstance(exc, OSError): if isinstance(exc, (OSError, socket.error)):
if exc.errno in (errno.EIO, errno.ENOSPC): if exc.errno in (errno.EIO, errno.ENOSPC):
emsg = str(exc) emsg = str(exc)
else: elif exc.errno == errno.ECONNREFUSED:
call = self._exception
elif isinstance(exc, socket.error):
if exc.errno == errno.ECONNREFUSED:
emsg = _('Connection refused') emsg = _('Connection refused')
elif exc.errno == errno.EHOSTUNREACH: elif exc.errno == errno.EHOSTUNREACH:
emsg = _('Host unreachable') emsg = _('Host unreachable')
@ -1426,7 +1406,7 @@ class SwiftLogFormatter(logging.Formatter):
record.exc_text = self.formatException( record.exc_text = self.formatException(
record.exc_info).replace('\n', '#012') record.exc_info).replace('\n', '#012')
if record.exc_text: if record.exc_text:
if msg[-3:] != '#012': if not msg.endswith('#012'):
msg = msg + '#012' msg = msg + '#012'
msg = msg + record.exc_text msg = msg + record.exc_text
@ -1565,31 +1545,6 @@ def get_logger(conf, name=None, log_to_console=False, log_route=None,
print('Invalid custom handler format [%s]' % hook, print('Invalid custom handler format [%s]' % hook,
file=sys.stderr) file=sys.stderr)
# Python 2.6 has the undesirable property of keeping references to all log
# handlers around forever in logging._handlers and logging._handlerList.
# Combine that with handlers that keep file descriptors, and you get an fd
# leak.
#
# And no, we can't share handlers; a SyslogHandler has a socket, and if
# two greenthreads end up logging at the same time, you could get message
# overlap that garbles the logs and makes eventlet complain.
#
# Python 2.7 uses weakrefs to avoid the leak, so let's do that too.
if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
try:
logging._acquireLock() # some thread-safety thing
for handler in adapted_logger.logger.handlers:
if handler in logging._handlers:
wr = LoggingHandlerWeakRef(handler)
del logging._handlers[handler]
logging._handlers[wr] = 1
for i, handler_ref in enumerate(logging._handlerList):
if handler_ref is handler:
logging._handlerList[i] = LoggingHandlerWeakRef(
handler)
finally:
logging._releaseLock()
return adapted_logger return adapted_logger
@ -1739,7 +1694,7 @@ def expand_ipv6(address):
def whataremyips(bind_ip=None): def whataremyips(bind_ip=None):
""" """
Get "our" IP addresses ("us" being the set of services configured by Get "our" IP addresses ("us" being the set of services configured by
one *.conf file). If our REST listens on a specific address, return it. one `*.conf` file). If our REST listens on a specific address, return it.
Otherwise, if listen on '0.0.0.0' or '::' return all addresses, including Otherwise, if listen on '0.0.0.0' or '::' return all addresses, including
the loopback. the loopback.
@ -2085,6 +2040,9 @@ def search_tree(root, glob_match, ext='', exts=None, dir_ext=None):
: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 exts: a list of file extensions; only files that end in one of these
extensions will be returned; if set this list overrides any
extension specified using the 'ext' param.
:param dir_ext: if present directories that end with dir_ext will not be :param dir_ext: if present directories that end with dir_ext will not be
traversed and instead will be returned as a matched path traversed and instead will be returned as a matched path
@ -2333,7 +2291,7 @@ class GreenAsyncPile(object):
def next(self): def next(self):
try: try:
rv = self._responses.get_nowait() rv = self._responses.get_nowait()
except Empty: except eventlet.queue.Empty:
if self._inflight == 0: if self._inflight == 0:
raise StopIteration() raise StopIteration()
rv = self._responses.get() rv = self._responses.get()
@ -2984,8 +2942,8 @@ class ThreadPool(object):
def __init__(self, nthreads=2): def __init__(self, nthreads=2):
self.nthreads = nthreads self.nthreads = nthreads
self._run_queue = Queue() self._run_queue = stdlib_queue.Queue()
self._result_queue = Queue() self._result_queue = stdlib_queue.Queue()
self._threads = [] self._threads = []
self._alive = True self._alive = True
@ -3010,7 +2968,7 @@ class ThreadPool(object):
# multiple instances instantiated. Since the object server uses one # multiple instances instantiated. Since the object server uses one
# pool per disk, we have to reimplement this stuff. # pool per disk, we have to reimplement this stuff.
_raw_rpipe, self.wpipe = os.pipe() _raw_rpipe, self.wpipe = os.pipe()
self.rpipe = greenio.GreenPipe(_raw_rpipe, 'rb', bufsize=0) self.rpipe = greenio.GreenPipe(_raw_rpipe, 'rb')
for _junk in range(nthreads): for _junk in range(nthreads):
thr = stdlib_threading.Thread( thr = stdlib_threading.Thread(
@ -3065,7 +3023,7 @@ class ThreadPool(object):
while True: while True:
try: try:
ev, success, result = queue.get(block=False) ev, success, result = queue.get(block=False)
except Empty: except stdlib_queue.Empty:
break break
try: try:
@ -3078,15 +3036,15 @@ class ThreadPool(object):
def run_in_thread(self, func, *args, **kwargs): def run_in_thread(self, func, *args, **kwargs):
""" """
Runs func(*args, **kwargs) in a thread. Blocks the current greenlet Runs ``func(*args, **kwargs)`` in a thread. Blocks the current greenlet
until results are available. until results are available.
Exceptions thrown will be reraised in the calling thread. Exceptions thrown will be reraised in the calling thread.
If the threadpool was initialized with nthreads=0, it invokes If the threadpool was initialized with nthreads=0, it invokes
func(*args, **kwargs) directly, followed by eventlet.sleep() to ensure ``func(*args, **kwargs)`` directly, followed by eventlet.sleep() to
the eventlet hub has a chance to execute. It is more likely the hub ensure the eventlet hub has a chance to execute. It is more likely the
will be invoked when queuing operations to an external thread. hub will be invoked when queuing operations to an external thread.
:returns: result of calling func :returns: result of calling func
:raises: whatever func raises :raises: whatever func raises
@ -3126,7 +3084,7 @@ class ThreadPool(object):
def force_run_in_thread(self, func, *args, **kwargs): def force_run_in_thread(self, func, *args, **kwargs):
""" """
Runs func(*args, **kwargs) in a thread. Blocks the current greenlet Runs ``func(*args, **kwargs)`` in a thread. Blocks the current greenlet
until results are available. until results are available.
Exceptions thrown will be reraised in the calling thread. Exceptions thrown will be reraised in the calling thread.
@ -3604,7 +3562,8 @@ def document_iters_to_http_response_body(ranges_iter, boundary, multipart,
except StopIteration: except StopIteration:
pass pass
else: else:
logger.warn("More than one part in a single-part response?") logger.warning(
"More than one part in a single-part response?")
return string_along(response_body_iter, ranges_iter, logger) return string_along(response_body_iter, ranges_iter, logger)

View File

@ -407,7 +407,8 @@ def run_server(conf, logger, sock, global_conf=None):
wsgi.WRITE_TIMEOUT = int(conf.get('client_timeout') or 60) wsgi.WRITE_TIMEOUT = int(conf.get('client_timeout') or 60)
eventlet.hubs.use_hub(get_hub()) eventlet.hubs.use_hub(get_hub())
eventlet.patcher.monkey_patch(all=False, socket=True) # NOTE(sileht): monkey-patching thread is required by python-keystoneclient
eventlet.patcher.monkey_patch(all=False, socket=True, thread=True)
eventlet_debug = config_true_value(conf.get('eventlet_debug', 'no')) eventlet_debug = config_true_value(conf.get('eventlet_debug', 'no'))
eventlet.debug.hub_exceptions(eventlet_debug) eventlet.debug.hub_exceptions(eventlet_debug)
wsgi_logger = NullLogger() wsgi_logger = NullLogger()
@ -597,6 +598,8 @@ class PortPidState(object):
def port_index_pairs(self): def port_index_pairs(self):
""" """
Returns current (port, server index) pairs.
:returns: A set of (port, server_idx) tuples for currently-tracked :returns: A set of (port, server_idx) tuples for currently-tracked
ports, sockets, and PIDs. ports, sockets, and PIDs.
""" """
@ -711,6 +714,8 @@ class ServersPerPortStrategy(object):
def loop_timeout(self): def loop_timeout(self):
""" """
Return timeout before checking for reloaded rings.
:returns: The time to wait for a child to exit before checking for :returns: The time to wait for a child to exit before checking for
reloaded rings (new ports). reloaded rings (new ports).
""" """
@ -1090,7 +1095,8 @@ def make_env(env, method=None, path=None, agent='Swift', query_string=None,
'HTTP_ORIGIN', 'HTTP_ACCESS_CONTROL_REQUEST_METHOD', 'HTTP_ORIGIN', 'HTTP_ACCESS_CONTROL_REQUEST_METHOD',
'SERVER_PROTOCOL', 'swift.cache', 'swift.source', 'SERVER_PROTOCOL', 'swift.cache', 'swift.source',
'swift.trans_id', 'swift.authorize_override', 'swift.trans_id', 'swift.authorize_override',
'swift.authorize', 'HTTP_X_USER_ID', 'HTTP_X_PROJECT_ID'): 'swift.authorize', 'HTTP_X_USER_ID', 'HTTP_X_PROJECT_ID',
'HTTP_REFERER'):
if name in env: if name in env:
newenv[name] = env[name] newenv[name] = env[name]
if method: if method:

View File

@ -557,7 +557,7 @@ class ContainerBroker(DatabaseBroker):
conn.commit() conn.commit()
def list_objects_iter(self, limit, marker, end_marker, prefix, delimiter, def list_objects_iter(self, limit, marker, end_marker, prefix, delimiter,
path=None, storage_policy_index=0): path=None, storage_policy_index=0, reverse=False):
""" """
Get a list of objects sorted by name starting at marker onward, up Get a list of objects sorted by name starting at marker onward, up
to limit entries. Entries will begin with the prefix and will not to limit entries. Entries will begin with the prefix and will not
@ -570,6 +570,7 @@ class ContainerBroker(DatabaseBroker):
:param delimiter: delimiter for query :param delimiter: delimiter for query
:param path: if defined, will set the prefix and delimiter based on :param path: if defined, will set the prefix and delimiter based on
the path the path
:param reverse: reverse the result order.
:returns: list of tuples of (name, created_at, size, content_type, :returns: list of tuples of (name, created_at, size, content_type,
etag) etag)
@ -578,6 +579,9 @@ class ContainerBroker(DatabaseBroker):
(marker, end_marker, prefix, delimiter, path) = utf8encode( (marker, end_marker, prefix, delimiter, path) = utf8encode(
marker, end_marker, prefix, delimiter, path) marker, end_marker, prefix, delimiter, path)
self._commit_puts_stale_ok() self._commit_puts_stale_ok()
if reverse:
# Reverse the markers if we are reversing the listing.
marker, end_marker = end_marker, marker
if path is not None: if path is not None:
prefix = path prefix = path
if path: if path:
@ -585,6 +589,8 @@ class ContainerBroker(DatabaseBroker):
delimiter = '/' delimiter = '/'
elif delimiter and not prefix: elif delimiter and not prefix:
prefix = '' prefix = ''
if prefix:
end_prefix = prefix[:-1] + chr(ord(prefix[-1]) + 1)
orig_marker = marker orig_marker = marker
with self.get() as conn: with self.get() as conn:
results = [] results = []
@ -592,9 +598,13 @@ class ContainerBroker(DatabaseBroker):
query = '''SELECT name, created_at, size, content_type, etag query = '''SELECT name, created_at, size, content_type, etag
FROM object WHERE''' FROM object WHERE'''
query_args = [] query_args = []
if end_marker: if end_marker and (not prefix or end_marker < end_prefix):
query += ' name < ? AND' query += ' name < ? AND'
query_args.append(end_marker) query_args.append(end_marker)
elif prefix:
query += ' name < ? AND'
query_args.append(end_prefix)
if delim_force_gte: if delim_force_gte:
query += ' name >= ? AND' query += ' name >= ? AND'
query_args.append(marker) query_args.append(marker)
@ -611,8 +621,8 @@ class ContainerBroker(DatabaseBroker):
else: else:
query += ' deleted = 0' query += ' deleted = 0'
orig_tail_query = ''' orig_tail_query = '''
ORDER BY name LIMIT ? ORDER BY name %s LIMIT ?
''' ''' % ('DESC' if reverse else '')
orig_tail_args = [limit - len(results)] orig_tail_args = [limit - len(results)]
# storage policy filter # storage policy filter
policy_tail_query = ''' policy_tail_query = '''
@ -633,26 +643,24 @@ class ContainerBroker(DatabaseBroker):
tuple(query_args + tail_args)) tuple(query_args + tail_args))
curs.row_factory = None curs.row_factory = None
if prefix is None: # Delimiters without a prefix is ignored, further if there
# A delimiter without a specified prefix is ignored # is no delimiter then we can simply return the result as
# prefixes are now handled in the SQL statement.
if prefix is None or not delimiter:
return [r for r in curs] return [r for r in curs]
if not delimiter:
if not prefix:
# It is possible to have a delimiter but no prefix
# specified. As above, the prefix will be set to the
# empty string, so avoid performing the extra work to
# check against an empty prefix.
return [r for r in curs]
else:
return [r for r in curs if r[0].startswith(prefix)]
# We have a delimiter and a prefix (possibly empty string) to # We have a delimiter and a prefix (possibly empty string) to
# handle # handle
rowcount = 0 rowcount = 0
for row in curs: for row in curs:
rowcount += 1 rowcount += 1
marker = name = row[0] name = row[0]
if len(results) >= limit or not name.startswith(prefix): if reverse:
end_marker = name
else:
marker = name
if len(results) >= limit:
curs.close() curs.close()
return results return results
end = name.find(delimiter, len(prefix)) end = name.find(delimiter, len(prefix))
@ -660,13 +668,19 @@ class ContainerBroker(DatabaseBroker):
if name == path: if name == path:
continue continue
if end >= 0 and len(name) > end + len(delimiter): if end >= 0 and len(name) > end + len(delimiter):
marker = name[:end] + chr(ord(delimiter) + 1) if reverse:
end_marker = name[:end + 1]
else:
marker = name[:end] + chr(ord(delimiter) + 1)
curs.close() curs.close()
break break
elif end > 0: elif end > 0:
marker = name[:end] + chr(ord(delimiter) + 1) if reverse:
# we want result to be inclusive of delim+1 end_marker = name[:end + 1]
delim_force_gte = True else:
marker = name[:end] + chr(ord(delimiter) + 1)
# we want result to be inclusive of delim+1
delim_force_gte = True
dir_name = name[:end + 1] dir_name = name[:end + 1]
if dir_name != orig_marker: if dir_name != orig_marker:
results.append([dir_name, '0', 0, None, '']) results.append([dir_name, '0', 0, None, ''])

View File

@ -15,6 +15,7 @@
import os import os
import itertools import itertools
import json
import time import time
from collections import defaultdict from collections import defaultdict
from eventlet import Timeout from eventlet import Timeout
@ -28,7 +29,7 @@ from swift.common.storage_policy import POLICIES
from swift.common.exceptions import DeviceUnavailable from swift.common.exceptions import DeviceUnavailable
from swift.common.http import is_success from swift.common.http import is_success
from swift.common.db import DatabaseAlreadyExists from swift.common.db import DatabaseAlreadyExists
from swift.common.utils import (json, Timestamp, hash_path, from swift.common.utils import (Timestamp, hash_path,
storage_directory, quorum_size) storage_directory, quorum_size)

View File

@ -13,6 +13,7 @@
# 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.
import json
import os import os
import time import time
import traceback import traceback
@ -30,7 +31,7 @@ from swift.common.request_helpers import get_param, get_listing_content_type, \
split_and_validate_path, is_sys_or_user_meta split_and_validate_path, is_sys_or_user_meta
from swift.common.utils import get_logger, hash_path, public, \ from swift.common.utils import get_logger, hash_path, public, \
Timestamp, storage_directory, validate_sync_to, \ Timestamp, storage_directory, validate_sync_to, \
config_true_value, json, timing_stats, replication, \ config_true_value, timing_stats, replication, \
override_bytes_from_content_type, get_log_line override_bytes_from_content_type, get_log_line
from swift.common.constraints import check_mount, valid_timestamp, check_utf8 from swift.common.constraints import check_mount, valid_timestamp, check_utf8
from swift.common import constraints from swift.common import constraints
@ -86,7 +87,7 @@ class ContainerController(BaseStorageServer):
self.log_requests = config_true_value(conf.get('log_requests', 'true')) self.log_requests = config_true_value(conf.get('log_requests', 'true'))
self.root = conf.get('devices', '/srv/node') self.root = conf.get('devices', '/srv/node')
self.mount_check = config_true_value(conf.get('mount_check', 'true')) self.mount_check = config_true_value(conf.get('mount_check', 'true'))
self.node_timeout = int(conf.get('node_timeout', 3)) self.node_timeout = float(conf.get('node_timeout', 3))
self.conn_timeout = float(conf.get('conn_timeout', 0.5)) self.conn_timeout = float(conf.get('conn_timeout', 0.5))
#: ContainerSyncCluster instance for validating sync-to values. #: ContainerSyncCluster instance for validating sync-to values.
self.realms_conf = ContainerSyncRealms( self.realms_conf = ContainerSyncRealms(
@ -452,6 +453,7 @@ class ContainerController(BaseStorageServer):
end_marker = get_param(req, 'end_marker') end_marker = get_param(req, 'end_marker')
limit = constraints.CONTAINER_LISTING_LIMIT limit = constraints.CONTAINER_LISTING_LIMIT
given_limit = get_param(req, 'limit') given_limit = get_param(req, 'limit')
reverse = config_true_value(get_param(req, 'reverse'))
if given_limit and given_limit.isdigit(): if given_limit and given_limit.isdigit():
limit = int(given_limit) limit = int(given_limit)
if limit > constraints.CONTAINER_LISTING_LIMIT: if limit > constraints.CONTAINER_LISTING_LIMIT:
@ -471,7 +473,7 @@ class ContainerController(BaseStorageServer):
return HTTPNotFound(request=req, headers=resp_headers) return HTTPNotFound(request=req, headers=resp_headers)
container_list = broker.list_objects_iter( container_list = broker.list_objects_iter(
limit, marker, end_marker, prefix, delimiter, path, limit, marker, end_marker, prefix, delimiter, path,
storage_policy_index=info['storage_policy_index']) storage_policy_index=info['storage_policy_index'], reverse=reverse)
return self.create_listing(req, out_content_type, info, resp_headers, return self.create_listing(req, out_content_type, info, resp_headers,
broker.metadata, container_list, container) broker.metadata, container_list, container)

View File

@ -49,7 +49,7 @@ class ContainerUpdater(Daemon):
self.account_ring = None self.account_ring = None
self.concurrency = int(conf.get('concurrency', 4)) self.concurrency = int(conf.get('concurrency', 4))
self.slowdown = float(conf.get('slowdown', 0.01)) self.slowdown = float(conf.get('slowdown', 0.01))
self.node_timeout = int(conf.get('node_timeout', 3)) self.node_timeout = float(conf.get('node_timeout', 3))
self.conn_timeout = float(conf.get('conn_timeout', 0.5)) self.conn_timeout = float(conf.get('conn_timeout', 0.5))
self.no_changes = 0 self.no_changes = 0
self.successes = 0 self.successes = 0
@ -89,7 +89,7 @@ class ContainerUpdater(Daemon):
for device in self._listdir(self.devices): for device in self._listdir(self.devices):
dev_path = os.path.join(self.devices, device) dev_path = os.path.join(self.devices, device)
if self.mount_check and not ismount(dev_path): if self.mount_check and not ismount(dev_path):
self.logger.warn(_('%s is not mounted'), device) self.logger.warning(_('%s is not mounted'), device)
continue continue
con_path = os.path.join(dev_path, DATADIR) con_path = os.path.join(dev_path, DATADIR)
if not os.path.exists(con_path): if not os.path.exists(con_path):

View File

@ -10,14 +10,13 @@
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata # Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-08-11 11:22+0000\n" "PO-Revision-Date: 2015-08-11 11:22+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
"Language: de\n" "Language: de\n"
"Language-Team: German (http://www.transifex.com/openstack/swift/language/" "Language-Team: German\n"
"de/)\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -8,14 +8,13 @@
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata # Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-09-09 05:36+0000\n" "PO-Revision-Date: 2015-09-09 05:36+0000\n"
"Last-Translator: Carlos A. Muñoz <camunoz@redhat.com>\n" "Last-Translator: Carlos A. Muñoz <camunoz@redhat.com>\n"
"Language: es\n" "Language: es\n"
"Language-Team: Spanish (http://www.transifex.com/openstack/swift/language/" "Language-Team: Spanish\n"
"es/)\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -8,14 +8,13 @@
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata # Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-08-11 11:22+0000\n" "PO-Revision-Date: 2015-08-11 11:22+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
"Language: fr\n" "Language: fr\n"
"Language-Team: French (http://www.transifex.com/openstack/swift/language/" "Language-Team: French\n"
"fr/)\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -7,14 +7,13 @@
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata # Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-08-11 11:22+0000\n" "PO-Revision-Date: 2015-08-11 11:22+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
"Language: it\n" "Language: it\n"
"Language-Team: Italian (http://www.transifex.com/openstack/swift/language/" "Language-Team: Italian\n"
"it/)\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -9,14 +9,13 @@
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata # Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-09-26 09:26+0000\n" "PO-Revision-Date: 2015-09-26 09:26+0000\n"
"Last-Translator: Akihiro Motoki <amotoki@gmail.com>\n" "Last-Translator: Akihiro Motoki <amotoki@gmail.com>\n"
"Language: ja\n" "Language: ja\n"
"Language-Team: Japanese (http://www.transifex.com/openstack/swift/language/" "Language-Team: Japanese\n"
"ja/)\n"
"Plural-Forms: nplurals=1; plural=0\n" "Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -9,14 +9,13 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata # OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-09-09 05:10+0000\n" "PO-Revision-Date: 2015-09-09 05:10+0000\n"
"Last-Translator: Ying Chun Guo <daisy.ycguo@gmail.com>\n" "Last-Translator: Ying Chun Guo <daisy.ycguo@gmail.com>\n"
"Language: ko_KR\n" "Language: ko_KR\n"
"Language-Team: Korean (Korea) (http://www.transifex.com/openstack/swift/" "Language-Team: Korean (South Korea)\n"
"language/ko_KR/)\n"
"Plural-Forms: nplurals=1; plural=0\n" "Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -11,14 +11,13 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata # OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-08-11 11:22+0000\n" "PO-Revision-Date: 2015-08-11 11:22+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
"Language: pt_BR\n" "Language: pt_BR\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/openstack/swift/" "Language-Team: Portuguese (Brazil)\n"
"language/pt_BR/)\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -7,14 +7,13 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata # OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-08-11 11:22+0000\n" "PO-Revision-Date: 2015-08-11 11:22+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
"Language: ru\n" "Language: ru\n"
"Language-Team: Russian (http://www.transifex.com/openstack/swift/language/" "Language-Team: Russian\n"
"ru/)\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" "%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3)\n" "%100>=11 && n%100<=14)? 2 : 3)\n"

View File

@ -1,19 +0,0 @@
# Translations template for swift.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the swift project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: swift 2.3.1.dev213\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-07-29 06:35+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.0\n"

View File

@ -1,19 +0,0 @@
# Translations template for swift.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the swift project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: swift 2.3.1.dev213\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-07-29 06:35+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.0\n"

View File

@ -1,19 +0,0 @@
# Translations template for swift.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the swift project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: swift 2.3.1.dev213\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-07-29 06:35+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.0\n"

View File

@ -1,19 +0,0 @@
# Translations template for swift.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the swift project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: swift 2.3.1.dev213\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-07-29 06:35+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.0\n"

View File

@ -7,14 +7,13 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata # OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-09-04 07:42+0000\n" "PO-Revision-Date: 2015-09-04 07:42+0000\n"
"Last-Translator: İşbaran Akçayır <isbaran@gmail.com>\n" "Last-Translator: İşbaran Akçayır <isbaran@gmail.com>\n"
"Language: tr_TR\n" "Language: tr_TR\n"
"Language-Team: Turkish (Turkey) (http://www.transifex.com/openstack/swift/" "Language-Team: Turkish (Turkey)\n"
"language/tr_TR/)\n"
"Plural-Forms: nplurals=1; plural=0\n" "Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -8,14 +8,13 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata # OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-08-11 11:22+0000\n" "PO-Revision-Date: 2015-08-11 11:22+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
"Language: zh_Hans_CN\n" "Language: zh_Hans_CN\n"
"Language-Team: Chinese (China) (http://www.transifex.com/openstack/swift/" "Language-Team: Chinese (China)\n"
"language/zh_CN/)\n"
"Plural-Forms: nplurals=1; plural=0\n" "Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -7,14 +7,13 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata # OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: swift 2.4.1.dev48\n" "Project-Id-Version: swift 2.5.1.dev70\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-09-28 06:27+0000\n" "POT-Creation-Date: 2015-10-23 06:34+0000\n"
"PO-Revision-Date: 2015-08-11 11:22+0000\n" "PO-Revision-Date: 2015-08-11 11:22+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
"Language: zh_Hant_TW\n" "Language: zh_Hant_TW\n"
"Language-Team: Chinese (Taiwan) (http://www.transifex.com/openstack/swift/" "Language-Team: Chinese (Taiwan)\n"
"language/zh_TW/)\n"
"Plural-Forms: nplurals=1; plural=0\n" "Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"

View File

@ -13,6 +13,7 @@
# 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.
import json
import os import os
import sys import sys
import time import time
@ -24,7 +25,7 @@ from eventlet import Timeout
from swift.obj import diskfile from swift.obj import diskfile
from swift.common.utils import get_logger, ratelimit_sleep, dump_recon_cache, \ from swift.common.utils import get_logger, ratelimit_sleep, dump_recon_cache, \
list_from_csv, json, listdir list_from_csv, listdir
from swift.common.exceptions import DiskFileQuarantined, DiskFileNotExist from swift.common.exceptions import DiskFileQuarantined, DiskFileNotExist
from swift.common.daemon import Daemon from swift.common.daemon import Daemon

View File

@ -303,8 +303,8 @@ def object_audit_location_generator(devices, mount_check=True, logger=None,
base, policy = split_policy_string(dir_) base, policy = split_policy_string(dir_)
except PolicyError as e: except PolicyError as e:
if logger: if logger:
logger.warn(_('Directory %r does not map ' logger.warning(_('Directory %r does not map '
'to a valid policy (%s)') % (dir_, e)) 'to a valid policy (%s)') % (dir_, e))
continue continue
datadir_path = os.path.join(devices, device, dir_) datadir_path = os.path.join(devices, device, dir_)
partitions = listdir(datadir_path) partitions = listdir(datadir_path)
@ -420,7 +420,7 @@ class BaseDiskFileManager(object):
# If the operator wants zero-copy with splice() but we don't have the # If the operator wants zero-copy with splice() but we don't have the
# requisite kernel support, complain so they can go fix it. # requisite kernel support, complain so they can go fix it.
if conf_wants_splice and not splice.available: if conf_wants_splice and not splice.available:
self.logger.warn( self.logger.warning(
"Use of splice() requested (config says \"splice = %s\"), " "Use of splice() requested (config says \"splice = %s\"), "
"but the system does not support it. " "but the system does not support it. "
"splice() will not be used." % conf.get('splice')) "splice() will not be used." % conf.get('splice'))
@ -434,8 +434,8 @@ class BaseDiskFileManager(object):
# AF_ALG support), we can't use zero-copy. # AF_ALG support), we can't use zero-copy.
if err.errno != errno.EAFNOSUPPORT: if err.errno != errno.EAFNOSUPPORT:
raise raise
self.logger.warn("MD5 sockets not supported. " self.logger.warning("MD5 sockets not supported. "
"splice() will not be used.") "splice() will not be used.")
else: else:
self.use_splice = True self.use_splice = True
with open('/proc/sys/fs/pipe-max-size') as f: with open('/proc/sys/fs/pipe-max-size') as f:
@ -447,7 +447,7 @@ class BaseDiskFileManager(object):
Parse an on disk file name. Parse an on disk file name.
:param filename: the data file name including extension :param filename: the data file name including extension
:returns: a dict, with keys for timestamp, and ext:: :returns: a dict, with keys for timestamp, and ext:
* timestamp is a :class:`~swift.common.utils.Timestamp` * timestamp is a :class:`~swift.common.utils.Timestamp`
* ext is a string, the file extension including the leading dot or * ext is a string, the file extension including the leading dot or
@ -460,92 +460,175 @@ class BaseDiskFileManager(object):
""" """
raise NotImplementedError raise NotImplementedError
def _gather_on_disk_file(self, filename, ext, context, frag_index=None, def _process_ondisk_files(self, exts, results, **kwargs):
**kwargs):
""" """
Called by gather_ondisk_files() for each file in an object Called by get_ondisk_files(). Should be over-ridden to implement
datadir in reverse sorted order. If a file is considered part of a subclass specific handling of files.
valid on-disk file set it will be added to the context dict, keyed by
its extension. If a file is considered to be obsolete it will be added
to a list stored under the key 'obsolete' in the context dict.
:param filename: name of file to be accepted or not :param exts: dict of lists of file info, keyed by extension
:param ext: extension part of filename :param results: a dict that may be updated with results
:param context: a context dict that may have been populated by previous
calls to this method
:returns: True if a valid file set has been found, False otherwise
""" """
raise NotImplementedError raise NotImplementedError
def _verify_on_disk_files(self, accepted_files, **kwargs): def _verify_ondisk_files(self, results, **kwargs):
""" """
Verify that the final combination of on disk files complies with the Verify that the final combination of on disk files complies with the
diskfile contract. diskfile contract.
:param accepted_files: files that have been found and accepted :param results: files that have been found and accepted
:returns: True if the file combination is compliant, False otherwise :returns: True if the file combination is compliant, False otherwise
""" """
raise NotImplementedError data_file, meta_file, ts_file = tuple(
[results[key]
for key in ('data_file', 'meta_file', 'ts_file')])
def gather_ondisk_files(self, files, include_obsolete=False, return ((data_file is None and meta_file is None and ts_file is None)
verify=False, **kwargs): or (ts_file is not None and data_file is None
and meta_file is None)
or (data_file is not None and ts_file is None))
def _split_list(self, original_list, condition):
""" """
Given a simple list of files names, iterate over them to determine the Split a list into two lists. The first list contains the first N items
files that constitute a valid object, and optionally determine the of the original list, in their original order, where 0 < N <=
files that are obsolete and could be deleted. Note that some files may len(original list). The second list contains the remaining items of the
fall into neither category. original list, in their original order.
The index, N, at which the original list is split is the index of the
first item in the list that does not satisfy the given condition. Note
that the original list should be appropriately sorted if the second
list is to contain no items that satisfy the given condition.
:param original_list: the list to be split.
:param condition: a single argument function that will be used to test
for the list item to split on.
:return: a tuple of two lists.
"""
for i, item in enumerate(original_list):
if not condition(item):
return original_list[:i], original_list[i:]
return original_list, []
def _split_gt_timestamp(self, file_info_list, timestamp):
"""
Given a list of file info dicts, reverse sorted by timestamp, split the
list into two: items newer than timestamp, and items at same time or
older than timestamp.
:param file_info_list: a list of file_info dicts.
:param timestamp: a Timestamp.
:return: a tuple of two lists.
"""
return self._split_list(
file_info_list, lambda x: x['timestamp'] > timestamp)
def _split_gte_timestamp(self, file_info_list, timestamp):
"""
Given a list of file info dicts, reverse sorted by timestamp, split the
list into two: items newer than or at same time as the timestamp, and
items older than timestamp.
:param file_info_list: a list of file_info dicts.
:param timestamp: a Timestamp.
:return: a tuple of two lists.
"""
return self._split_list(
file_info_list, lambda x: x['timestamp'] >= timestamp)
def get_ondisk_files(self, files, datadir, verify=True, **kwargs):
"""
Given a simple list of files names, determine the files that constitute
a valid fileset i.e. a set of files that defines the state of an
object, and determine the files that are obsolete and could be deleted.
Note that some files may fall into neither category.
If a file is considered part of a valid fileset then its info dict will
be added to the results dict, keyed by <extension>_info. Any files that
are no longer required will have their info dicts added to a list
stored under the key 'obsolete'.
The results dict will always contain entries with keys 'ts_file',
'data_file' and 'meta_file'. Their values will be the fully qualified
path to a file of the corresponding type if there is such a file in the
valid fileset, or None.
:param files: a list of file names. :param files: a list of file names.
:param include_obsolete: By default the iteration will stop when a :param datadir: directory name files are from.
valid file set has been found. Setting this
argument to True will cause the iteration to
continue in order to find all obsolete files.
:param verify: if True verify that the ondisk file contract has not :param verify: if True verify that the ondisk file contract has not
been violated, otherwise do not verify. been violated, otherwise do not verify.
:returns: a dict that may contain: valid on disk files keyed by their :returns: a dict that will contain keys:
filename extension; a list of obsolete files stored under the ts_file -> path to a .ts file or None
key 'obsolete'. data_file -> path to a .data file or None
meta_file -> path to a .meta file or None
and may contain keys:
ts_info -> a file info dict for a .ts file
data_info -> a file info dict for a .data file
meta_info -> a file info dict for a .meta file
obsolete -> a list of file info dicts for obsolete files
""" """
files.sort(reverse=True) # Build the exts data structure:
results = {} # exts is a dict that maps file extensions to a list of file_info
# dicts for the files having that extension. The file_info dicts are of
# the form returned by parse_on_disk_filename, with the filename added.
# Each list is sorted in reverse timestamp order.
#
# The exts dict will be modified during subsequent processing as files
# are removed to be discarded or ignored.
exts = defaultdict(list)
for afile in files: for afile in files:
ts_file = results.get('.ts') # Categorize files by extension
data_file = results.get('.data') try:
if not include_obsolete: file_info = self.parse_on_disk_filename(afile)
assert ts_file is None, "On-disk file search loop" \ file_info['filename'] = afile
" continuing after tombstone, %s, encountered" % ts_file exts[file_info['ext']].append(file_info)
assert data_file is None, "On-disk file search loop" \ except DiskFileError as e:
" continuing after data file, %s, encountered" % data_file self.logger.warning('Unexpected file %s: %s' %
(os.path.join(datadir or '', afile), e))
for ext in exts:
# For each extension sort files into reverse chronological order.
exts[ext] = sorted(
exts[ext], key=lambda info: info['timestamp'], reverse=True)
ext = splitext(afile)[1] # the results dict is used to collect results of file filtering
if self._gather_on_disk_file( results = {}
afile, ext, results, **kwargs):
if not include_obsolete: # non-tombstones older than or equal to latest tombstone are obsolete
break if exts.get('.ts'):
for ext in filter(lambda ext: ext != '.ts', exts.keys()):
exts[ext], older = self._split_gt_timestamp(
exts[ext], exts['.ts'][0]['timestamp'])
results.setdefault('obsolete', []).extend(older)
# all but most recent .meta and .ts are obsolete
for ext in ('.meta', '.ts'):
if ext in exts:
results.setdefault('obsolete', []).extend(exts[ext][1:])
exts[ext] = exts[ext][:1]
# delegate to subclass handler
self._process_ondisk_files(exts, results, **kwargs)
# set final choice of files
if exts.get('.ts'):
results['ts_info'] = exts['.ts'][0]
if 'data_info' in results and exts.get('.meta'):
# only report a meta file if there is a data file
results['meta_info'] = exts['.meta'][0]
# set ts_file, data_file and meta_file with path to chosen file or None
for info_key in ('data_info', 'meta_info', 'ts_info'):
info = results.get(info_key)
key = info_key[:-5] + '_file'
results[key] = join(datadir, info['filename']) if info else None
if verify: if verify:
assert self._verify_on_disk_files( assert self._verify_ondisk_files(
results, **kwargs), \ results, **kwargs), \
"On-disk file search algorithm contract is broken: %s" \ "On-disk file search algorithm contract is broken: %s" \
% results.values() % str(results)
return results return results
def get_ondisk_files(self, files, datadir, **kwargs):
"""
Given a simple list of files names, determine the files to use.
:param files: simple set of files as a python list
:param datadir: directory name files are from for convenience
:returns: dict of files to use having keys 'data_file', 'ts_file',
'meta_file' and optionally other policy specific keys
"""
file_info = self.gather_ondisk_files(files, verify=True, **kwargs)
for ext in ('.data', '.meta', '.ts'):
filename = file_info.get(ext)
key = '%s_file' % ext[1:]
file_info[key] = join(datadir, filename) if filename else None
return file_info
def cleanup_ondisk_files(self, hsh_path, reclaim_age=ONE_WEEK, **kwargs): def cleanup_ondisk_files(self, hsh_path, reclaim_age=ONE_WEEK, **kwargs):
""" """
Clean up on-disk files that are obsolete and gather the set of valid Clean up on-disk files that are obsolete and gather the set of valid
@ -560,27 +643,24 @@ class BaseDiskFileManager(object):
key 'obsolete'; a list of files remaining in the directory, key 'obsolete'; a list of files remaining in the directory,
reverse sorted, stored under the key 'files'. reverse sorted, stored under the key 'files'.
""" """
def is_reclaimable(filename): def is_reclaimable(timestamp):
timestamp = self.parse_on_disk_filename(filename)['timestamp']
return (time.time() - float(timestamp)) > reclaim_age return (time.time() - float(timestamp)) > reclaim_age
files = listdir(hsh_path) files = listdir(hsh_path)
files.sort(reverse=True) files.sort(reverse=True)
results = self.gather_ondisk_files(files, include_obsolete=True, results = self.get_ondisk_files(
**kwargs) files, hsh_path, verify=False, **kwargs)
# TODO ref to durables here if 'ts_info' in results and is_reclaimable(
if '.durable' in results and not results.get('fragments'): results['ts_info']['timestamp']):
# a .durable with no .data is deleted as soon as it is found remove_file(join(hsh_path, results['ts_info']['filename']))
results.setdefault('obsolete', []).append(results.pop('.durable')) files.remove(results.pop('ts_info')['filename'])
if '.ts' in results and is_reclaimable(results['.ts']): for file_info in results.get('possible_reclaim', []):
results.setdefault('obsolete', []).append(results.pop('.ts'))
for filename in results.get('fragments_without_durable', []):
# stray fragments are not deleted until reclaim-age # stray fragments are not deleted until reclaim-age
if is_reclaimable(filename): if is_reclaimable(file_info['timestamp']):
results.setdefault('obsolete', []).append(filename) results.setdefault('obsolete', []).append(file_info)
for filename in results.get('obsolete', []): for file_info in results.get('obsolete', []):
remove_file(join(hsh_path, filename)) remove_file(join(hsh_path, file_info['filename']))
files.remove(filename) files.remove(file_info['filename'])
results['files'] = files results['files'] = files
return results return results
@ -895,8 +975,10 @@ class BaseDiskFileManager(object):
be yielded. be yielded.
timestamps is a dict which may contain items mapping: timestamps is a dict which may contain items mapping:
ts_data -> timestamp of data or tombstone file, ts_data -> timestamp of data or tombstone file,
ts_meta -> timestamp of meta file, if one exists ts_meta -> timestamp of meta file, if one exists
where timestamps are instances of where timestamps are instances of
:class:`~swift.common.utils.Timestamp` :class:`~swift.common.utils.Timestamp`
""" """
@ -913,9 +995,9 @@ class BaseDiskFileManager(object):
(os.path.join(partition_path, suffix), suffix) (os.path.join(partition_path, suffix), suffix)
for suffix in suffixes) for suffix in suffixes)
key_preference = ( key_preference = (
('ts_meta', '.meta'), ('ts_meta', 'meta_info'),
('ts_data', '.data'), ('ts_data', 'data_info'),
('ts_data', '.ts'), ('ts_data', 'ts_info'),
) )
for suffix_path, suffix in suffixes: for suffix_path, suffix in suffixes:
for object_hash in self._listdir(suffix_path): for object_hash in self._listdir(suffix_path):
@ -924,14 +1006,13 @@ class BaseDiskFileManager(object):
results = self.cleanup_ondisk_files( results = self.cleanup_ondisk_files(
object_path, self.reclaim_age, **kwargs) object_path, self.reclaim_age, **kwargs)
timestamps = {} timestamps = {}
for ts_key, ext in key_preference: for ts_key, info_key in key_preference:
if ext not in results: if info_key not in results:
continue continue
timestamps[ts_key] = self.parse_on_disk_filename( timestamps[ts_key] = results[info_key]['timestamp']
results[ext])['timestamp']
if 'ts_data' not in timestamps: if 'ts_data' not in timestamps:
# file sets that do not include a .data or .ts # file sets that do not include a .data or .ts
# file can not be opened and therefore can not # file cannot be opened and therefore cannot
# be ssync'd # be ssync'd
continue continue
yield (object_path, object_hash, timestamps) yield (object_path, object_hash, timestamps)
@ -1323,7 +1404,7 @@ class BaseDiskFileReader(object):
self._quarantined_dir = self._threadpool.run_in_thread( self._quarantined_dir = self._threadpool.run_in_thread(
self.manager.quarantine_renamer, self._device_path, self.manager.quarantine_renamer, self._device_path,
self._data_file) self._data_file)
self._logger.warn("Quarantined object %s: %s" % ( self._logger.warning("Quarantined object %s: %s" % (
self._data_file, msg)) self._data_file, msg))
self._logger.increment('quarantines') self._logger.increment('quarantines')
self._quarantine_hook(msg) self._quarantine_hook(msg)
@ -1428,6 +1509,7 @@ class BaseDiskFile(object):
self._obj = None self._obj = None
self._datadir = None self._datadir = None
self._tmpdir = join(device_path, get_tmp_dir(policy)) self._tmpdir = join(device_path, get_tmp_dir(policy))
self._ondisk_info = None
self._metadata = None self._metadata = None
self._datafile_metadata = None self._datafile_metadata = None
self._metafile_metadata = None self._metafile_metadata = None
@ -1477,6 +1559,26 @@ class BaseDiskFile(object):
raise DiskFileNotOpen() raise DiskFileNotOpen()
return Timestamp(self._datafile_metadata.get('X-Timestamp')) return Timestamp(self._datafile_metadata.get('X-Timestamp'))
@property
def durable_timestamp(self):
"""
Provides the timestamp of the newest data file found in the object
directory.
:return: A Timestamp instance, or None if no data file was found.
:raises DiskFileNotOpen: if the open() method has not been previously
called on this instance.
"""
if self._ondisk_info is None:
raise DiskFileNotOpen()
if self._datafile_metadata:
return Timestamp(self._datafile_metadata.get('X-Timestamp'))
return None
@property
def fragments(self):
return None
@classmethod @classmethod
def from_hash_dir(cls, mgr, hash_dir_path, device_path, partition, policy): def from_hash_dir(cls, mgr, hash_dir_path, device_path, partition, policy):
return cls(mgr, device_path, None, partition, _datadir=hash_dir_path, return cls(mgr, device_path, None, partition, _datadir=hash_dir_path,
@ -1522,8 +1624,8 @@ class BaseDiskFile(object):
# The data directory does not exist, so the object cannot exist. # The data directory does not exist, so the object cannot exist.
files = [] files = []
# gather info about the valid files to us to open the DiskFile # gather info about the valid files to use to open the DiskFile
file_info = self._get_ondisk_file(files) file_info = self._get_ondisk_files(files)
self._data_file = file_info.get('data_file') self._data_file = file_info.get('data_file')
if not self._data_file: if not self._data_file:
@ -1572,12 +1674,12 @@ class BaseDiskFile(object):
""" """
self._quarantined_dir = self._threadpool.run_in_thread( self._quarantined_dir = self._threadpool.run_in_thread(
self.manager.quarantine_renamer, self._device_path, data_file) self.manager.quarantine_renamer, self._device_path, data_file)
self._logger.warn("Quarantined object %s: %s" % ( self._logger.warning("Quarantined object %s: %s" % (
data_file, msg)) data_file, msg))
self._logger.increment('quarantines') self._logger.increment('quarantines')
return DiskFileQuarantined(msg) return DiskFileQuarantined(msg)
def _get_ondisk_file(self, files): def _get_ondisk_files(self, files):
""" """
Determine the on-disk files to use. Determine the on-disk files to use.
@ -1948,8 +2050,9 @@ class DiskFile(BaseDiskFile):
reader_cls = DiskFileReader reader_cls = DiskFileReader
writer_cls = DiskFileWriter writer_cls = DiskFileWriter
def _get_ondisk_file(self, files): def _get_ondisk_files(self, files):
return self.manager.get_ondisk_files(files, self._datadir) self._ondisk_info = self.manager.get_ondisk_files(files, self._datadir)
return self._ondisk_info
@DiskFileRouter.register(REPL_POLICY) @DiskFileRouter.register(REPL_POLICY)
@ -1961,93 +2064,48 @@ class DiskFileManager(BaseDiskFileManager):
Returns the timestamp extracted .data file name. Returns the timestamp extracted .data file name.
:param filename: the data file name including extension :param filename: the data file name including extension
:returns: a dict, with keys for timestamp, and ext:: :returns: a dict, with keys for timestamp, and ext:
* timestamp is a :class:`~swift.common.utils.Timestamp` * timestamp is a :class:`~swift.common.utils.Timestamp`
* ext is a string, the file extension including the leading dot or * ext is a string, the file extension including the leading dot or
the empty string if the filename has no extenstion. the empty string if the filename has no extension.
:raises DiskFileError: if any part of the filename is not able to be :raises DiskFileError: if any part of the filename is not able to be
validated. validated.
""" """
filename, ext = splitext(filename) float_part, ext = splitext(filename)
try:
timestamp = Timestamp(float_part)
except ValueError:
raise DiskFileError('Invalid Timestamp value in filename %r'
% filename)
return { return {
'timestamp': Timestamp(filename), 'timestamp': timestamp,
'ext': ext, 'ext': ext,
} }
def _gather_on_disk_file(self, filename, ext, context, frag_index=None, def _process_ondisk_files(self, exts, results, **kwargs):
**kwargs):
""" """
Called by gather_ondisk_files() for each file in an object Implement replication policy specific handling of .data files.
datadir in reverse sorted order. If a file is considered part of a
valid on-disk file set it will be added to the context dict, keyed by
its extension. If a file is considered to be obsolete it will be added
to a list stored under the key 'obsolete' in the context dict.
:param filename: name of file to be accepted or not :param exts: dict of lists of file info, keyed by extension
:param ext: extension part of filename :param results: a dict that may be updated with results
:param context: a context dict that may have been populated by previous
calls to this method
:returns: True if a valid file set has been found, False otherwise
""" """
if exts.get('.data'):
for ext in exts.keys():
if ext == '.data':
# older .data's are obsolete
exts[ext], obsolete = self._split_gte_timestamp(
exts[ext], exts['.data'][0]['timestamp'])
else:
# other files at same or older timestamp as most recent
# data are obsolete
exts[ext], obsolete = self._split_gt_timestamp(
exts[ext], exts['.data'][0]['timestamp'])
results.setdefault('obsolete', []).extend(obsolete)
# if first file with given extension then add filename to context # set results
# dict and return True results['data_info'] = exts['.data'][0]
accept_first = lambda: context.setdefault(ext, filename) == filename
# add the filename to the list of obsolete files in context dict
discard = lambda: context.setdefault('obsolete', []).append(filename)
# set a flag in the context dict indicating that a valid fileset has
# been found
set_valid_fileset = lambda: context.setdefault('found_valid', True)
# return True if the valid fileset flag is set in the context dict
have_valid_fileset = lambda: context.get('found_valid')
if ext == '.data':
if have_valid_fileset():
# valid fileset means we must have a newer
# .data or .ts, so discard the older .data file
discard()
else:
accept_first()
set_valid_fileset()
elif ext == '.ts':
if have_valid_fileset() or not accept_first():
# newer .data or .ts already found so discard this
discard()
if not have_valid_fileset():
# remove any .meta that may have been previously found
context.pop('.meta', None)
set_valid_fileset()
elif ext == '.meta':
if have_valid_fileset() or not accept_first():
# newer .data, .durable or .ts already found so discard this
discard()
else:
# ignore unexpected files
pass
return have_valid_fileset()
def _verify_on_disk_files(self, accepted_files, **kwargs):
"""
Verify that the final combination of on disk files complies with the
replicated diskfile contract.
:param accepted_files: files that have been found and accepted
:returns: True if the file combination is compliant, False otherwise
"""
# mimic legacy behavior - .meta is ignored when .ts is found
if accepted_files.get('.ts'):
accepted_files.pop('.meta', None)
data_file, meta_file, ts_file, durable_file = tuple(
[accepted_files.get(ext)
for ext in ('.data', '.meta', '.ts', '.durable')])
return ((data_file is None and meta_file is None and ts_file is None)
or (ts_file is not None and data_file is None
and meta_file is None)
or (data_file is not None and ts_file is None))
def _hash_suffix(self, path, reclaim_age): def _hash_suffix(self, path, reclaim_age):
""" """
@ -2151,14 +2209,47 @@ class ECDiskFile(BaseDiskFile):
if frag_index is not None: if frag_index is not None:
self._frag_index = self.manager.validate_fragment_index(frag_index) self._frag_index = self.manager.validate_fragment_index(frag_index)
def _get_ondisk_file(self, files): @property
def durable_timestamp(self):
"""
Provides the timestamp of the newest durable file found in the object
directory.
:return: A Timestamp instance, or None if no durable file was found.
:raises DiskFileNotOpen: if the open() method has not been previously
called on this instance.
"""
if self._ondisk_info is None:
raise DiskFileNotOpen()
if self._ondisk_info.get('durable_frag_set'):
return self._ondisk_info['durable_frag_set'][0]['timestamp']
return None
@property
def fragments(self):
"""
Provides information about all fragments that were found in the object
directory, including fragments without a matching durable file, and
including any fragment chosen to construct the opened diskfile.
:return: A dict mapping <Timestamp instance> -> <list of frag indexes>,
or None if the diskfile has not been opened or no fragments
were found.
"""
if self._ondisk_info:
frag_sets = self._ondisk_info['frag_sets']
return dict([(ts, [info['frag_index'] for info in frag_set])
for ts, frag_set in frag_sets.items()])
def _get_ondisk_files(self, files):
""" """
The only difference between this method and the replication policy The only difference between this method and the replication policy
DiskFile method is passing in the frag_index kwarg to our manager's DiskFile method is passing in the frag_index kwarg to our manager's
get_ondisk_files method. get_ondisk_files method.
""" """
return self.manager.get_ondisk_files( self._ondisk_info = self.manager.get_ondisk_files(
files, self._datadir, frag_index=self._frag_index) files, self._datadir, frag_index=self._frag_index)
return self._ondisk_info
def purge(self, timestamp, frag_index): def purge(self, timestamp, frag_index):
""" """
@ -2241,20 +2332,24 @@ class ECDiskFileManager(BaseDiskFileManager):
be stripped off to retrieve the timestamp. be stripped off to retrieve the timestamp.
:param filename: the data file name including extension :param filename: the data file name including extension
:returns: a dict, with keys for timestamp, frag_index, and ext:: :returns: a dict, with keys for timestamp, frag_index, and ext:
* timestamp is a :class:`~swift.common.utils.Timestamp` * timestamp is a :class:`~swift.common.utils.Timestamp`
* frag_index is an int or None * frag_index is an int or None
* ext is a string, the file extension including the leading dot or * ext is a string, the file extension including the leading dot or
the empty string if the filename has no extenstion. the empty string if the filename has no extension.
:raises DiskFileError: if any part of the filename is not able to be :raises DiskFileError: if any part of the filename is not able to be
validated. validated.
""" """
frag_index = None frag_index = None
filename, ext = splitext(filename) float_frag, ext = splitext(filename)
parts = filename.split('#', 1) parts = float_frag.split('#', 1)
timestamp = parts[0] try:
timestamp = Timestamp(parts[0])
except ValueError:
raise DiskFileError('Invalid Timestamp value in filename %r'
% filename)
if ext == '.data': if ext == '.data':
# it is an error for an EC data file to not have a valid # it is an error for an EC data file to not have a valid
# fragment index # fragment index
@ -2265,137 +2360,94 @@ class ECDiskFileManager(BaseDiskFileManager):
pass pass
frag_index = self.validate_fragment_index(frag_index) frag_index = self.validate_fragment_index(frag_index)
return { return {
'timestamp': Timestamp(timestamp), 'timestamp': timestamp,
'frag_index': frag_index, 'frag_index': frag_index,
'ext': ext, 'ext': ext,
} }
def is_obsolete(self, filename, other_filename): def _process_ondisk_files(self, exts, results, frag_index=None, **kwargs):
""" """
Test if a given file is considered to be obsolete with respect to Implement EC policy specific handling of .data and .durable files.
another file in an object storage dir.
Implements EC policy specific behavior when comparing files against a :param exts: dict of lists of file info, keyed by extension
.durable file. :param results: a dict that may be updated with results
A simple string comparison would consider t2#1.data to be older than
t2.durable (since t2#1.data < t2.durable). By stripping off the file
extensions we get the desired behavior: t2#1 > t2 without compromising
the detection of t1#1 < t2.
:param filename: a string representing an absolute filename
:param other_filename: a string representing an absolute filename
:returns: True if filename is considered obsolete, False otherwise.
"""
if other_filename.endswith('.durable'):
return splitext(filename)[0] < splitext(other_filename)[0]
return filename < other_filename
def _gather_on_disk_file(self, filename, ext, context, frag_index=None,
**kwargs):
"""
Called by gather_ondisk_files() for each file in an object
datadir in reverse sorted order. If a file is considered part of a
valid on-disk file set it will be added to the context dict, keyed by
its extension. If a file is considered to be obsolete it will be added
to a list stored under the key 'obsolete' in the context dict.
:param filename: name of file to be accepted or not
:param ext: extension part of filename
:param context: a context dict that may have been populated by previous
calls to this method
:param frag_index: if set, search for a specific fragment index .data :param frag_index: if set, search for a specific fragment index .data
file, otherwise accept the first valid .data file. file, otherwise accept the first valid .data file.
:returns: True if a valid file set has been found, False otherwise
""" """
durable_info = None
if exts.get('.durable'):
durable_info = exts['.durable'][0]
# Mark everything older than most recent .durable as obsolete
# and remove from the exts dict.
for ext in exts.keys():
exts[ext], older = self._split_gte_timestamp(
exts[ext], durable_info['timestamp'])
results.setdefault('obsolete', []).extend(older)
# if first file with given extension then add filename to context # Split the list of .data files into sets of frags having the same
# dict and return True # timestamp, identifying the durable and newest sets (if any) as we go.
accept_first = lambda: context.setdefault(ext, filename) == filename # To do this we can take advantage of the list of .data files being
# add the filename to the list of obsolete files in context dict # reverse-time ordered. Keep the resulting per-timestamp frag sets in
discard = lambda: context.setdefault('obsolete', []).append(filename) # a frag_sets dict mapping a Timestamp instance -> frag_set.
# set a flag in the context dict indicating that a valid fileset has all_frags = exts.get('.data')
# been found frag_sets = {}
set_valid_fileset = lambda: context.setdefault('found_valid', True) durable_frag_set = None
# return True if the valid fileset flag is set in the context dict while all_frags:
have_valid_fileset = lambda: context.get('found_valid') frag_set, all_frags = self._split_gte_timestamp(
all_frags, all_frags[0]['timestamp'])
# sort the frag set into ascending frag_index order
frag_set.sort(key=lambda info: info['frag_index'])
timestamp = frag_set[0]['timestamp']
frag_sets[timestamp] = frag_set
if durable_info and durable_info['timestamp'] == timestamp:
durable_frag_set = frag_set
if context.get('.durable'): # Select a single chosen frag from the chosen frag_set, by either
# a .durable file has been found # matching against a specified frag_index or taking the highest index.
if ext == '.data': chosen_frag = None
if self.is_obsolete(filename, context.get('.durable')): if durable_frag_set:
# this and remaining data files are older than durable if frag_index is not None:
discard() # search the frag set to find the exact frag_index
set_valid_fileset() for info in durable_frag_set:
else: if info['frag_index'] == frag_index:
# accept the first .data file if it matches requested chosen_frag = info
# frag_index, or if no specific frag_index is requested break
fi = self.parse_on_disk_filename(filename)['frag_index']
if frag_index is None or frag_index == int(fi):
accept_first()
set_valid_fileset()
# else: keep searching for a .data file to match frag_index
context.setdefault('fragments', []).append(filename)
else: else:
# there can no longer be a matching .data file so mark what has chosen_frag = durable_frag_set[-1]
# been found so far as the valid fileset
discard()
set_valid_fileset()
elif ext == '.data':
# not yet found a .durable
if have_valid_fileset():
# valid fileset means we must have a newer
# .ts, so discard the older .data file
discard()
else:
# .data newer than a .durable or .ts, don't discard yet
context.setdefault('fragments_without_durable', []).append(
filename)
elif ext == '.ts':
if have_valid_fileset() or not accept_first():
# newer .data, .durable or .ts already found so discard this
discard()
if not have_valid_fileset():
# remove any .meta that may have been previously found
context.pop('.meta', None)
set_valid_fileset()
elif ext in ('.meta', '.durable'):
if have_valid_fileset() or not accept_first():
# newer .data, .durable or .ts already found so discard this
discard()
else:
# ignore unexpected files
pass
return have_valid_fileset()
def _verify_on_disk_files(self, accepted_files, frag_index=None, **kwargs): # If we successfully found a frag then set results
if chosen_frag:
results['data_info'] = chosen_frag
results['durable_frag_set'] = durable_frag_set
results['frag_sets'] = frag_sets
# Mark any isolated .durable as obsolete
if exts.get('.durable') and not durable_frag_set:
results.setdefault('obsolete', []).extend(exts['.durable'])
exts.pop('.durable')
# Fragments *may* be ready for reclaim, unless they are durable or
# at the timestamp we have just chosen for constructing the diskfile.
for frag_set in frag_sets.values():
if frag_set == durable_frag_set:
continue
results.setdefault('possible_reclaim', []).extend(frag_set)
def _verify_ondisk_files(self, results, frag_index=None, **kwargs):
""" """
Verify that the final combination of on disk files complies with the Verify that the final combination of on disk files complies with the
erasure-coded diskfile contract. erasure-coded diskfile contract.
:param accepted_files: files that have been found and accepted :param results: files that have been found and accepted
:param frag_index: specifies a specific fragment index .data file :param frag_index: specifies a specific fragment index .data file
:returns: True if the file combination is compliant, False otherwise :returns: True if the file combination is compliant, False otherwise
""" """
if not accepted_files.get('.data'): if super(ECDiskFileManager, self)._verify_ondisk_files(
# We may find only a .meta, which doesn't mean the on disk results, **kwargs):
# contract is broken. So we clear it to comply with have_data_file = results['data_file'] is not None
# superclass assertions. have_durable = results.get('durable_frag_set') is not None
accepted_files.pop('.meta', None) return have_data_file == have_durable
return False
data_file, meta_file, ts_file, durable_file = tuple(
[accepted_files.get(ext)
for ext in ('.data', '.meta', '.ts', '.durable')])
return ((data_file is None or durable_file is not None)
and (data_file is None and meta_file is None
and ts_file is None and durable_file is None)
or (ts_file is not None and data_file is None
and meta_file is None and durable_file is None)
or (data_file is not None and durable_file is not None
and ts_file is None)
or (durable_file is not None and meta_file is None
and ts_file is None))
def _hash_suffix(self, path, reclaim_age): def _hash_suffix(self, path, reclaim_age):
""" """
@ -2410,12 +2462,12 @@ class ECDiskFileManager(BaseDiskFileManager):
# here we flatten out the hashers hexdigest into a dictionary instead # here we flatten out the hashers hexdigest into a dictionary instead
# of just returning the one hexdigest for the whole suffix # of just returning the one hexdigest for the whole suffix
def mapper(filename): def mapper(filename):
info = self.parse_on_disk_filename(filename) info = self.parse_on_disk_filename(filename)
fi = info['frag_index'] fi = info['frag_index']
if fi is None: if fi is None:
return None, filename return None, filename
else: else:
return fi, info['timestamp'].internal return fi, info['timestamp'].internal
hash_per_fi = self._hash_suffix_dir(path, mapper, reclaim_age) hash_per_fi = self._hash_suffix_dir(path, mapper, reclaim_age)
return dict((fi, md5.hexdigest()) for fi, md5 in hash_per_fi.items()) return dict((fi, md5.hexdigest()) for fi, md5 in hash_per_fi.items())

View File

@ -254,6 +254,7 @@ class DiskFile(object):
self._metadata = None self._metadata = None
self._fp = None self._fp = None
self._filesystem = fs self._filesystem = fs
self.fragments = None
def open(self): def open(self):
""" """
@ -421,3 +422,5 @@ class DiskFile(object):
return Timestamp(self._metadata.get('X-Timestamp')) return Timestamp(self._metadata.get('X-Timestamp'))
data_timestamp = timestamp data_timestamp = timestamp
durable_timestamp = timestamp

View File

@ -819,8 +819,8 @@ class ObjectReconstructor(Daemon):
dev_path = self._df_router[policy].get_dev_path( dev_path = self._df_router[policy].get_dev_path(
local_dev['device']) local_dev['device'])
if not dev_path: if not dev_path:
self.logger.warn(_('%s is not mounted'), self.logger.warning(_('%s is not mounted'),
local_dev['device']) local_dev['device'])
continue continue
obj_path = join(dev_path, data_dir) obj_path = join(dev_path, data_dir)
tmp_path = join(dev_path, get_tmp_dir(int(policy))) tmp_path = join(dev_path, get_tmp_dir(int(policy)))

View File

@ -85,10 +85,11 @@ class ObjectReplicator(Daemon):
if not self.rsync_module: if not self.rsync_module:
self.rsync_module = '{replication_ip}::object' self.rsync_module = '{replication_ip}::object'
if config_true_value(conf.get('vm_test_mode', 'no')): if config_true_value(conf.get('vm_test_mode', 'no')):
self.logger.warn('Option object-replicator/vm_test_mode is ' self.logger.warning('Option object-replicator/vm_test_mode '
'deprecated and will be removed in a future ' 'is deprecated and will be removed in a '
'version. Update your configuration to use ' 'future version. Update your '
'option object-replicator/rsync_module.') 'configuration to use option '
'object-replicator/rsync_module.')
self.rsync_module += '{replication_port}' self.rsync_module += '{replication_port}'
self.http_timeout = int(conf.get('http_timeout', 60)) self.http_timeout = int(conf.get('http_timeout', 60))
self.lockup_timeout = int(conf.get('lockup_timeout', 1800)) self.lockup_timeout = int(conf.get('lockup_timeout', 1800))
@ -109,10 +110,10 @@ class ObjectReplicator(Daemon):
self.handoff_delete = config_auto_int_value( self.handoff_delete = config_auto_int_value(
conf.get('handoff_delete', 'auto'), 0) conf.get('handoff_delete', 'auto'), 0)
if any((self.handoff_delete, self.handoffs_first)): if any((self.handoff_delete, self.handoffs_first)):
self.logger.warn('Handoff only mode is not intended for normal ' self.logger.warning('Handoff only mode is not intended for normal '
'operation, please disable handoffs_first and ' 'operation, please disable handoffs_first and '
'handoff_delete before the next ' 'handoff_delete before the next '
'normal rebalance') 'normal rebalance')
self._diskfile_mgr = DiskFileManager(conf, self.logger) self._diskfile_mgr = DiskFileManager(conf, self.logger)
def _zero_stats(self): def _zero_stats(self):
@ -566,6 +567,7 @@ class ObjectReplicator(Daemon):
[(dev['replication_ip'], dev['device']) [(dev['replication_ip'], dev['device'])
for dev in policy.object_ring.devs if dev]) for dev in policy.object_ring.devs if dev])
data_dir = get_data_dir(policy) data_dir = get_data_dir(policy)
found_local = False
for local_dev in [dev for dev in policy.object_ring.devs for local_dev in [dev for dev in policy.object_ring.devs
if (dev if (dev
and is_local_device(ips, and is_local_device(ips,
@ -574,6 +576,7 @@ class ObjectReplicator(Daemon):
dev['replication_port']) dev['replication_port'])
and (override_devices is None and (override_devices is None
or dev['device'] in override_devices))]: or dev['device'] in override_devices))]:
found_local = True
dev_path = join(self.devices_dir, local_dev['device']) dev_path = join(self.devices_dir, local_dev['device'])
obj_path = join(dev_path, data_dir) obj_path = join(dev_path, data_dir)
tmp_path = join(dev_path, get_tmp_dir(policy)) tmp_path = join(dev_path, get_tmp_dir(policy))
@ -583,7 +586,8 @@ class ObjectReplicator(Daemon):
failure_dev['device']) failure_dev['device'])
for failure_dev in policy.object_ring.devs for failure_dev in policy.object_ring.devs
if failure_dev]) if failure_dev])
self.logger.warn(_('%s is not mounted'), local_dev['device']) self.logger.warning(
_('%s is not mounted'), local_dev['device'])
continue continue
unlink_older_than(tmp_path, time.time() - self.reclaim_age) unlink_older_than(tmp_path, time.time() - self.reclaim_age)
if not os.path.exists(obj_path): if not os.path.exists(obj_path):
@ -626,6 +630,10 @@ class ObjectReplicator(Daemon):
for failure_dev in policy.object_ring.devs for failure_dev in policy.object_ring.devs
if failure_dev]) if failure_dev])
continue continue
if not found_local:
self.logger.error("Can't find itself %s with port %s in ring "
"file, not replicating",
", ".join(ips), self.port)
return jobs return jobs
def collect_jobs(self, override_devices=None, override_partitions=None, def collect_jobs(self, override_devices=None, override_partitions=None,
@ -695,7 +703,7 @@ class ObjectReplicator(Daemon):
self._add_failure_stats([(failure_dev['replication_ip'], self._add_failure_stats([(failure_dev['replication_ip'],
failure_dev['device']) failure_dev['device'])
for failure_dev in job['nodes']]) for failure_dev in job['nodes']])
self.logger.warn(_('%s is not mounted'), job['device']) self.logger.warning(_('%s is not mounted'), job['device'])
continue continue
if not self.check_ring(job['policy'].object_ring): if not self.check_ring(job['policy'].object_ring):
self.logger.info(_("Ring change detected. Aborting " self.logger.info(_("Ring change detected. Aborting "

View File

@ -895,7 +895,10 @@ class ObjectController(BaseStorageServer):
container, obj, request, device, container, obj, request, device,
policy) policy)
if orig_timestamp < req_timestamp: if orig_timestamp < req_timestamp:
disk_file.delete(req_timestamp) try:
disk_file.delete(req_timestamp)
except DiskFileNoSpace:
return HTTPInsufficientStorage(drive=device, request=request)
self.container_update( self.container_update(
'DELETE', account, container, obj, request, 'DELETE', account, container, obj, request,
HeaderKeyDict({'x-timestamp': req_timestamp.internal}), HeaderKeyDict({'x-timestamp': req_timestamp.internal}),

View File

@ -48,7 +48,7 @@ class ObjectUpdater(Daemon):
self.container_ring = None self.container_ring = None
self.concurrency = int(conf.get('concurrency', 1)) self.concurrency = int(conf.get('concurrency', 1))
self.slowdown = float(conf.get('slowdown', 0.01)) self.slowdown = float(conf.get('slowdown', 0.01))
self.node_timeout = int(conf.get('node_timeout', 10)) self.node_timeout = float(conf.get('node_timeout', 10))
self.conn_timeout = float(conf.get('conn_timeout', 0.5)) self.conn_timeout = float(conf.get('conn_timeout', 0.5))
self.successes = 0 self.successes = 0
self.failures = 0 self.failures = 0
@ -84,7 +84,7 @@ class ObjectUpdater(Daemon):
if self.mount_check and \ if self.mount_check and \
not ismount(os.path.join(self.devices, device)): not ismount(os.path.join(self.devices, device)):
self.logger.increment('errors') self.logger.increment('errors')
self.logger.warn( self.logger.warning(
_('Skipping %s as it is not mounted'), device) _('Skipping %s as it is not mounted'), device)
continue continue
while len(pids) >= self.concurrency: while len(pids) >= self.concurrency:
@ -127,7 +127,7 @@ class ObjectUpdater(Daemon):
if self.mount_check and \ if self.mount_check and \
not ismount(os.path.join(self.devices, device)): not ismount(os.path.join(self.devices, device)):
self.logger.increment('errors') self.logger.increment('errors')
self.logger.warn( self.logger.warning(
_('Skipping %s as it is not mounted'), device) _('Skipping %s as it is not mounted'), device)
continue continue
self.object_sweep(os.path.join(self.devices, device)) self.object_sweep(os.path.join(self.devices, device))
@ -159,8 +159,9 @@ class ObjectUpdater(Daemon):
try: try:
base, policy = split_policy_string(asyncdir) base, policy = split_policy_string(asyncdir)
except PolicyError as e: except PolicyError as e:
self.logger.warn(_('Directory %r does not map ' self.logger.warning(_('Directory %r does not map '
'to a valid policy (%s)') % (asyncdir, e)) 'to a valid policy (%s)') %
(asyncdir, e))
continue continue
for prefix in self._listdir(async_pending): for prefix in self._listdir(async_pending):
prefix_path = os.path.join(async_pending, prefix) prefix_path = os.path.join(async_pending, prefix)

View File

@ -235,17 +235,17 @@ def cors_validation(func):
# - headers provided by the user in # - headers provided by the user in
# x-container-meta-access-control-expose-headers # x-container-meta-access-control-expose-headers
if 'Access-Control-Expose-Headers' not in resp.headers: if 'Access-Control-Expose-Headers' not in resp.headers:
expose_headers = [ expose_headers = set([
'cache-control', 'content-language', 'content-type', 'cache-control', 'content-language', 'content-type',
'expires', 'last-modified', 'pragma', 'etag', 'expires', 'last-modified', 'pragma', 'etag',
'x-timestamp', 'x-trans-id'] 'x-timestamp', 'x-trans-id'])
for header in resp.headers: for header in resp.headers:
if header.startswith('X-Container-Meta') or \ if header.startswith('X-Container-Meta') or \
header.startswith('X-Object-Meta'): header.startswith('X-Object-Meta'):
expose_headers.append(header.lower()) expose_headers.add(header.lower())
if cors_info.get('expose_headers'): if cors_info.get('expose_headers'):
expose_headers.extend( expose_headers = expose_headers.union(
[header_line.strip() [header_line.strip().lower()
for header_line in for header_line in
cors_info['expose_headers'].split(' ') cors_info['expose_headers'].split(' ')
if header_line.strip()]) if header_line.strip()])
@ -317,6 +317,7 @@ def get_account_info(env, app, swift_source=None):
This call bypasses auth. Success does not imply that the request has This call bypasses auth. Success does not imply that the request has
authorization to the account. authorization to the account.
:raises ValueError: when path can't be split(path, 2, 4) :raises ValueError: when path can't be split(path, 2, 4)
""" """
(version, account, _junk, _junk) = \ (version, account, _junk, _junk) = \
@ -336,9 +337,10 @@ def _get_cache_key(account, container):
""" """
Get the keys for both memcache (cache_key) and env (env_key) Get the keys for both memcache (cache_key) and env (env_key)
where info about accounts and containers is cached where info about accounts and containers is cached
:param account: The name of the account :param account: The name of the account
:param container: The name of the container (or None if account) :param container: The name of the container (or None if account)
:returns a tuple of (cache_key, env_key) :returns: a tuple of (cache_key, env_key)
""" """
if container: if container:
@ -356,10 +358,11 @@ def _get_cache_key(account, container):
def get_object_env_key(account, container, obj): def get_object_env_key(account, container, obj):
""" """
Get the keys for env (env_key) where info about object is cached Get the keys for env (env_key) where info about object is cached
:param account: The name of the account :param account: The name of the account
:param container: The name of the container :param container: The name of the container
:param obj: The name of the object :param obj: The name of the object
:returns a string env_key :returns: a string env_key
""" """
env_key = 'swift.object/%s/%s/%s' % (account, env_key = 'swift.object/%s/%s/%s' % (account,
container, obj) container, obj)
@ -460,7 +463,7 @@ def _get_info_cache(app, env, account, container=None):
:param app: the application object :param app: the application object
:param env: the environment used by the current request :param env: the environment used by the current request
:returns the cached info or None if not cached :returns: the cached info or None if not cached
""" """
cache_key, env_key = _get_cache_key(account, container) cache_key, env_key = _get_cache_key(account, container)
@ -932,20 +935,19 @@ class ResumingGetter(object):
self.pop_range() self.pop_range()
except StopIteration: except StopIteration:
req.environ['swift.non_client_disconnect'] = True req.environ['swift.non_client_disconnect'] = True
return
except ChunkReadTimeout: except ChunkReadTimeout:
self.app.exception_occurred(node[0], _('Object'), self.app.exception_occurred(node[0], _('Object'),
_('Trying to read during GET')) _('Trying to read during GET'))
raise raise
except ChunkWriteTimeout: except ChunkWriteTimeout:
self.app.logger.warn( self.app.logger.warning(
_('Client did not read from proxy within %ss') % _('Client did not read from proxy within %ss') %
self.app.client_timeout) self.app.client_timeout)
self.app.logger.increment('client_timeouts') self.app.logger.increment('client_timeouts')
except GeneratorExit: except GeneratorExit:
if not req.environ.get('swift.non_client_disconnect'): if not req.environ.get('swift.non_client_disconnect'):
self.app.logger.warn(_('Client disconnected on read')) self.app.logger.warning(_('Client disconnected on read'))
except Exception: except Exception:
self.app.logger.exception(_('Trying to send to client')) self.app.logger.exception(_('Trying to send to client'))
raise raise
@ -1283,7 +1285,7 @@ class Controller(object):
def generate_request_headers(self, orig_req=None, additional=None, def generate_request_headers(self, orig_req=None, additional=None,
transfer=False): transfer=False):
""" """
Create a list of headers to be used in backend requets Create a list of headers to be used in backend requests
:param orig_req: the original request sent by the client to the proxy :param orig_req: the original request sent by the client to the proxy
:param additional: additional headers to send to the backend :param additional: additional headers to send to the backend

Some files were not shown because too many files have changed in this diff Show More