Merge branch 'master' into feature/hummingbird
Change-Id: Ie90e2bc680570395791b2fb1e3bdc58466bb21a2
This commit is contained in:
commit
0330478b70
|
@ -10,6 +10,7 @@ ChangeLog
|
|||
.coverage
|
||||
*.egg
|
||||
*.egg-info
|
||||
.eggs/*
|
||||
.DS_Store
|
||||
.tox
|
||||
pycscope.*
|
||||
|
|
10
.mailmap
10
.mailmap
|
@ -93,3 +93,13 @@ Richard Hawkins <richard.hawkins@rackspace.com> <hurricanerix@gmail.com>
|
|||
Ondrej Novy <ondrej.novy@firma.seznam.cz>
|
||||
Peter Lisak <peter.lisak@firma.seznam.cz>
|
||||
Ke Liang <ke.liang@easystack.cn>
|
||||
Daisuke Morita <morita.daisuke@ntti3.com> <morita.daisuke@lab.ntt.co.jp>
|
||||
Andreas Jaeger <aj@suse.de> <aj@suse.com>
|
||||
Hugo Kuo <tonytkdk@gmail.com>
|
||||
Gage Hugo <gh159m@att.com>
|
||||
Oshrit Feder <oshritf@il.ibm.com>
|
||||
Larry Rensing <lr699s@att.com>
|
||||
Ben Keller <bjkeller@us.ibm.com>
|
||||
Chaozhe Chen <chaozhe.chen@easystack.cn>
|
||||
Brian Cline <bcline@softlayer.com> <bcline@us.ibm.com>
|
||||
Brian Cline <bcline@softlayer.com> <brian.cline@gmail.com>
|
||||
|
|
20
AUTHORS
20
AUTHORS
|
@ -13,7 +13,7 @@ Jay Payne (letterj@gmail.com)
|
|||
Will Reese (wreese@gmail.com)
|
||||
Chuck Thier (cthier@gmail.com)
|
||||
|
||||
CORE Emeritus
|
||||
Core Emeritus
|
||||
-------------
|
||||
Chmouel Boudjnah (chmouel@enovance.com)
|
||||
Florian Hines (syn@ronin.io)
|
||||
|
@ -33,6 +33,7 @@ Joe Arnold (joe@swiftstack.com)
|
|||
Ionuț Arțăriși (iartarisi@suse.cz)
|
||||
Minwoo Bae (minwoob@us.ibm.com)
|
||||
Bob Ball (bob.ball@citrix.com)
|
||||
Christopher Bartz (bartz@dkrz.de)
|
||||
Christian Berendt (berendt@b1-systems.de)
|
||||
Luis de Bethencourt (luis@debethencourt.com)
|
||||
Keshava Bharadwaj (kb.sankethi@gmail.com)
|
||||
|
@ -54,6 +55,7 @@ Emmanuel Cazenave (contact@emcaz.fr)
|
|||
Mahati Chamarthy (mahati.chamarthy@gmail.com)
|
||||
Zap Chang (zapchang@gmail.com)
|
||||
François Charlier (francois.charlier@enovance.com)
|
||||
Chaozhe Chen (chaozhe.chen@easystack.cn)
|
||||
Ray Chen (oldsharp@163.com)
|
||||
Harshit Chitalia (harshit@acelio.com)
|
||||
Brian Cline (bcline@softlayer.com)
|
||||
|
@ -61,6 +63,7 @@ Alistair Coles (alistair.coles@hpe.com)
|
|||
Clément Contini (ccontini@cloudops.com)
|
||||
Brian Curtin (brian.curtin@rackspace.com)
|
||||
Thiago da Silva (thiago@redhat.com)
|
||||
dangming (dangming@unitedstack.com)
|
||||
Julien Danjou (julien@danjou.info)
|
||||
Paul Dardeau (paul.dardeau@intel.com)
|
||||
Zack M. Davis (zdavis@swiftstack.com)
|
||||
|
@ -86,9 +89,11 @@ Filippo Giunchedi (fgiunchedi@wikimedia.org)
|
|||
Mark Gius (launchpad@markgius.com)
|
||||
David Goetz (david.goetz@rackspace.com)
|
||||
Tushar Gohad (tushar.gohad@intel.com)
|
||||
Thomas Goirand (thomas@goirand.fr)
|
||||
Jonathan Gonzalez V (jonathan.abdiel@gmail.com)
|
||||
Joe Gordon (jogo@cloudscaling.com)
|
||||
ChangBo Guo(gcb) (eric.guo@easystack.cn)
|
||||
Ankur Gupta (ankur.gupta@intel.com)
|
||||
David Hadas (davidh@il.ibm.com)
|
||||
Andrew Hale (andy@wwwdata.eu)
|
||||
Soren Hansen (soren@linux2go.dk)
|
||||
|
@ -106,6 +111,7 @@ Charles Hsu (charles0126@gmail.com)
|
|||
Joanna H. Huang (joanna.huitzu.huang@gmail.com)
|
||||
Kun Huang (gareth@unitedstack.com)
|
||||
Bill Huber (wbhuber@us.ibm.com)
|
||||
Gage Hugo (gh159m@att.com)
|
||||
Matthieu Huin (mhu@enovance.com)
|
||||
Hodong Hwang (hodong.hwang@kt.com)
|
||||
Motonobu Ichimura (motonobu@gmail.com)
|
||||
|
@ -127,6 +133,7 @@ Ilya Kharin (ikharin@mirantis.com)
|
|||
Dae S. Kim (dae@velatum.com)
|
||||
Nathan Kinder (nkinder@redhat.com)
|
||||
Eugene Kirpichov (ekirpichov@gmail.com)
|
||||
Ben Keller (bjkeller@us.ibm.com)
|
||||
Leah Klearman (lklrmn@gmail.com)
|
||||
Martin Kletzander (mkletzan@redhat.com)
|
||||
Jaivish Kothari (jaivish.kothari@nectechnologies.in)
|
||||
|
@ -134,6 +141,7 @@ Steve Kowalik (steven@wedontsleep.org)
|
|||
Sergey Kraynev (skraynev@mirantis.com)
|
||||
Sushil Kumar (sushil.kumar2@globallogic.com)
|
||||
Madhuri Kumari (madhuri.rai07@gmail.com)
|
||||
Hugo Kuo (tonytkdk@gmail.com)
|
||||
Steven Lang (Steven.Lang@hgst.com)
|
||||
Gonéri Le Bouder (goneri.lebouder@enovance.com)
|
||||
Romain Le Disez (romain.ledisez@ovh.net)
|
||||
|
@ -143,6 +151,8 @@ Thomas Leaman (thomas.leaman@hp.com)
|
|||
Eohyung Lee (liquidnuker@gmail.com)
|
||||
Zhao Lei (zhaolei@cn.fujitsu.com)
|
||||
Jamie Lennox (jlennox@redhat.com)
|
||||
Cheng Li (shcli@cn.ibm.com)
|
||||
Mingyu Li (li.mingyu@99cloud.net)
|
||||
Tong Li (litong01@us.ibm.com)
|
||||
Ke Liang (ke.liang@easystack.cn)
|
||||
Peter Lisak (peter.lisak@firma.seznam.cz)
|
||||
|
@ -161,6 +171,7 @@ Juan J. Martinez (juan@memset.com)
|
|||
Marcelo Martins (btorch@gmail.com)
|
||||
Nakagawa Masaaki (nakagawamsa@nttdata.co.jp)
|
||||
Dolph Mathews (dolph.mathews@gmail.com)
|
||||
Tomas Matlocha (tomas.matlocha@firma.seznam.cz)
|
||||
Kenichiro Matsuda (matsuda_kenichi@jp.fujitsu.com)
|
||||
Michael Matur (michael.matur@gmail.com)
|
||||
Donagh McCabe (donagh.mccabe@hpe.com)
|
||||
|
@ -171,7 +182,7 @@ Samuel Merritt (sam@swiftstack.com)
|
|||
Stephen Milton (milton@isomedia.com)
|
||||
Jola Mirecka (jola.mirecka@hp.com)
|
||||
Kazuhiro Miyahara (miyahara.kazuhiro@lab.ntt.co.jp)
|
||||
Daisuke Morita (morita.daisuke@lab.ntt.co.jp)
|
||||
Daisuke Morita (morita.daisuke@ntti3.com)
|
||||
Dirk Mueller (dirk@dmllr.de)
|
||||
Takashi Natsume (natsume.takashi@lab.ntt.co.jp)
|
||||
Russ Nelson (russ@crynwr.com)
|
||||
|
@ -198,11 +209,13 @@ Sivasathurappan Radhakrishnan (siva.radhakrishnan@intel.com)
|
|||
Sarvesh Ranjan (saranjan@cisco.com)
|
||||
Falk Reimann (falk.reimann@sap.com)
|
||||
Brian Reitz (brian.reitz@oracle.com)
|
||||
Qiaowei Ren (qiaowei.ren@intel.com)
|
||||
Felipe Reyes (freyes@tty.cl)
|
||||
Janie Richling (jrichli@us.ibm.com)
|
||||
Matt Riedemann (mriedem@us.ibm.com)
|
||||
Li Riqiang (lrqrun@gmail.com)
|
||||
Rafael Rivero (rafael@cloudscaling.com)
|
||||
Larry Rensing (lr699s@att.com)
|
||||
Victor Rodionov (victor.rodionov@nexenta.com)
|
||||
Eran Rom (eranr@il.ibm.com)
|
||||
Aaron Rosen (arosen@nicira.com)
|
||||
|
@ -211,6 +224,7 @@ Hamdi Roumani (roumani@ca.ibm.com)
|
|||
Shilla Saebi (shilla.saebi@gmail.com)
|
||||
Atsushi Sakai (sakaia@jp.fujitsu.com)
|
||||
Cristian A Sanchez (cristian.a.sanchez@intel.com)
|
||||
Olga Saprycheva (osapryc@us.ibm.com)
|
||||
Christian Schwede (cschwede@redhat.com)
|
||||
Mark Seger (mark.seger@hpe.com)
|
||||
Azhagu Selvan SP (tamizhgeek@gmail.com)
|
||||
|
@ -223,6 +237,7 @@ Michael Shuler (mshuler@gmail.com)
|
|||
David Moreau Simard (dmsimard@iweb.com)
|
||||
Scott Simpson (sasimpson@gmail.com)
|
||||
Pradeep Kumar Singh (pradeep.singh@nectechnologies.in)
|
||||
Sarafraj Singh (Sarafraj.Singh@intel.com)
|
||||
Liu Siqi (meizu647@gmail.com)
|
||||
Adrian Smith (adrian_f_smith@dell.com)
|
||||
Jon Snitow (otherjon@swiftstack.com)
|
||||
|
@ -259,6 +274,7 @@ Yaguang Wang (yaguang.wang@intel.com)
|
|||
Chris Wedgwood (cw@f00f.org)
|
||||
Conrad Weidenkeller (conrad.weidenkeller@rackspace.com)
|
||||
Doug Weimer (dweimer@gmail.com)
|
||||
Andrew Welleck (awellec@us.ibm.com)
|
||||
Wu Wenxiang (wu.wenxiang@99cloud.net)
|
||||
Cory Wright (cory.wright@rackspace.com)
|
||||
Ye Jia Xu (xyj.asmy@gmail.com)
|
||||
|
|
148
CHANGELOG
148
CHANGELOG
|
@ -1,3 +1,151 @@
|
|||
swift (2.7.0, OpenStack Mitaka)
|
||||
|
||||
* Bump PyECLib requirement to >= 1.2.0
|
||||
|
||||
* Update container on fast-POST
|
||||
|
||||
"Fast-POST" is the mode where `object_post_as_copy` is set to
|
||||
`False` in the proxy server config. This mode now allows for
|
||||
fast, efficient updates of metadata without needing to fully
|
||||
recopy the contents of the object. While the default still is
|
||||
`object_post_as_copy` as True, the plan is to change the default
|
||||
to False and then deprecate post-as-copy functionality in later
|
||||
releases. Fast-POST now supports container-sync functionality.
|
||||
|
||||
* Add concurrent reads option to proxy.
|
||||
|
||||
This change adds 2 new parameters to enable and control concurrent
|
||||
GETs in Swift, these are `concurrent_gets` and `concurrency_timeout`.
|
||||
|
||||
`concurrent_gets` allows you to turn on or off concurrent
|
||||
GETs; when on, it will set the GET/HEAD concurrency to the
|
||||
replica count. And in the case of EC HEADs it will set it to
|
||||
ndata. The proxy will then serve only the first valid source to
|
||||
respond. This applies to all account, container, and replicated
|
||||
object GETs and HEADs. For EC only HEAD requests are affected.
|
||||
The default for `concurrent_gets` is off.
|
||||
|
||||
`concurrency_timeout` is related to `concurrent_gets` and is
|
||||
the amount of time to wait before firing the next thread. A
|
||||
value of 0 will fire at the same time (fully concurrent), but
|
||||
setting another value will stagger the firing allowing you the
|
||||
ability to give a node a short chance to respond before firing
|
||||
the next. This value is a float and should be somewhere between
|
||||
0 and `node_timeout`. The default is `conn_timeout`, meaning by
|
||||
default it will stagger the firing.
|
||||
|
||||
* Added an operational procedures guide to the docs. It can be
|
||||
found at http://swift.openstack.org/ops_runbook/index.html and
|
||||
includes information on detecting and handling day-to-day
|
||||
operational issues in a Swift cluster.
|
||||
|
||||
* Make `handoffs_first` a more useful mode for the object replicator.
|
||||
|
||||
The `handoffs_first` replication mode is used during periods of
|
||||
problematic cluster behavior (e.g. full disks) when replication
|
||||
needs to quickly drain partitions from a handoff node and move
|
||||
them to a primary node.
|
||||
|
||||
Previously, `handoffs_first` would sort that handoff work before
|
||||
"normal" replication jobs, but the normal replication work could
|
||||
take quite some time and result in handoffs not being drained
|
||||
quickly enough.
|
||||
|
||||
In order to focus on getting handoff partitions off the node
|
||||
`handoffs_first` mode will now abort the current replication
|
||||
sweep before attempting any primary suffix syncing if any of the
|
||||
handoff partitions were not removed for any reason - and start
|
||||
over with replication of handoffs jobs as the highest priority.
|
||||
|
||||
Note that `handoffs_first` being enabled will emit a warning on
|
||||
start up, even if no handoff jobs fail, because of the negative
|
||||
impact it can have during normal operations by dog-piling on a
|
||||
node that was temporarily unavailable.
|
||||
|
||||
* By default, inbound `X-Timestamp` headers are now disallowed
|
||||
(except when in an authorized container-sync request). This
|
||||
header is useful for allowing data migration from other storage
|
||||
systems to Swift and keeping the original timestamp of the data.
|
||||
If you have this migration use case (or any other requirement on
|
||||
allowing the clients to set an object's timestamp), set the
|
||||
`shunt_inbound_x_timestamp` config variable to False in the
|
||||
gatekeeper middleware config section of the proxy server config.
|
||||
|
||||
* Requesting a SLO manifest file with the query parameters
|
||||
"?multipart-manifest=get&format=raw" will return the contents of
|
||||
the manifest in the format as was originally sent by the client.
|
||||
The "format=raw" is new.
|
||||
|
||||
* Static web page listings can now be rendered with a custom
|
||||
label. By default listings are rendered with a label of:
|
||||
"Listing of /v1/<account>/<container>/<path>". This change adds
|
||||
a new custom metadata key/value pair
|
||||
`X-Container-Meta-Web-Listings-Label: My Label` that when set,
|
||||
will cause the following: "Listing of My Label/<path>" to be
|
||||
rendered instead.
|
||||
|
||||
* Previously, static large objects (SLOs) had a minimum segment
|
||||
size (default to 1MiB). This limit has been removed, but small
|
||||
segments will be ratelimited. The config parameter
|
||||
`rate_limit_under_size` controls the definition of "small"
|
||||
segments (1MiB by default), and `rate_limit_segments_per_sec`
|
||||
controls how many segments per second can be served (default is 1).
|
||||
With the default values, the effective behavior is identical to the
|
||||
previous behavior when serving SLOs.
|
||||
|
||||
* Container sync has been improved to perform a HEAD on the remote
|
||||
side of the sync for each object being synced. If the object
|
||||
exists on the remote side, container-sync will no longer
|
||||
transfer the object, thus significantly lowering the network
|
||||
requirements to use the feature.
|
||||
|
||||
* The object auditor will now clean up any old, stale rsync temp
|
||||
files that it finds. These rsync temp files are left if the
|
||||
rsync process fails without completing a full transfer of an
|
||||
object. Since these files can be large, the temp files may end
|
||||
up filling a disk. The new auditor functionality will reap these
|
||||
rsync temp files if they are old. The new object-auditor config
|
||||
variable `rsync_tempfile_timeout` is the number of seconds old a
|
||||
tempfile must be before it is reaped. By default, this variable
|
||||
is set to "auto" or the rsync_timeout plus 900 seconds (falling
|
||||
back to a value of 1 day).
|
||||
|
||||
* The Erasure Code reconstruction process has been made more
|
||||
efficient by not syncing data files when only the durable commit
|
||||
file is missing.
|
||||
|
||||
* Fixed a bug where 304 and 416 response may not have the right
|
||||
Etag and Accept-Ranges headers when the object is stored in an
|
||||
Erasure Coded policy.
|
||||
|
||||
* Versioned writes now correctly stores the date of previous versions
|
||||
using GMT instead of local time.
|
||||
|
||||
* The deprecated Keystone middleware option is_admin has been removed.
|
||||
|
||||
* Fixed log format in object auditor.
|
||||
|
||||
* The zero-byte mode (ZBF) of the object auditor will now properly
|
||||
observe the `--once` option.
|
||||
|
||||
* Swift keeps track, internally, of "dirty" parts of the partition
|
||||
keyspace with a "hashes.pkl" file. Operations on this file no
|
||||
longer require a read-modify-write cycle and use a new
|
||||
"hashes.invalid" file to track dirty partitions. This change
|
||||
will improve end-user performance for PUT and DELETE operations.
|
||||
|
||||
* The object replicator's succeeded and failed counts are now logged.
|
||||
|
||||
* `swift-recon` can now query hosts by storage policy.
|
||||
|
||||
* The log_statsd_host value can now be an IPv6 address or a hostname
|
||||
which only resolves to an IPv6 address.
|
||||
|
||||
* Erasure coded fragments now properly call fallocate to reserve disk
|
||||
space before being written.
|
||||
|
||||
* Various other minor bug fixes and improvements.
|
||||
|
||||
swift (2.6.0)
|
||||
|
||||
* Dependency changes
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in this page: [http://docs.openstack.org/infra/manual/developers.html](http://docs.openstack.org/infra/manual/developers.html)
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at [http://docs.openstack.org/infra/manual/developers.html#development-workflow](http://docs.openstack.org/infra/manual/developers.html#development-workflow).
|
||||
|
||||
Gerrit is the review system used in the OpenStack projects. We're sorry, but
|
||||
we won't be able to respond to pull requests submitted through GitHub.
|
||||
|
||||
Bugs should be filed [on Launchpad](https://bugs.launchpad.net/swift),
|
||||
not in GitHub's issue tracker.
|
||||
|
||||
|
||||
Swift Design Principles
|
||||
=======================
|
||||
|
||||
* [The Zen of Python](http://legacy.python.org/dev/peps/pep-0020/)
|
||||
* Simple Scales
|
||||
* Minimal dependencies
|
||||
* Re-use existing tools and libraries when reasonable
|
||||
* Leverage the economies of scale
|
||||
* Small, loosely coupled RESTful services
|
||||
* No single points of failure
|
||||
* Start with the use case
|
||||
* ... then design from the cluster operator up
|
||||
* If you haven't argued about it, you don't have the right answer yet :)
|
||||
* If it is your first implementation, you probably aren't done yet :)
|
||||
|
||||
Please don't feel offended by difference of opinion. Be prepared to advocate
|
||||
for your change and iterate on it based on feedback. Reach out to other people
|
||||
working on the project on
|
||||
[IRC](http://eavesdrop.openstack.org/irclogs/%23openstack-swift/) or the
|
||||
[mailing list](http://lists.openstack.org/pipermail/openstack-dev/) - we want
|
||||
to help.
|
||||
|
||||
Recommended workflow
|
||||
====================
|
||||
|
||||
* Set up a [Swift All-In-One VM](http://docs.openstack.org/developer/swift/development_saio.html)(SAIO).
|
||||
|
||||
* Make your changes. Docs and tests for your patch must land before
|
||||
or with your patch.
|
||||
|
||||
* Run unit tests, functional tests, probe tests
|
||||
``./.unittests``
|
||||
``./.functests``
|
||||
``./.probetests``
|
||||
|
||||
* Run ``tox`` (no command-line args needed)
|
||||
|
||||
* ``git review``
|
||||
|
||||
Notes on Testing
|
||||
================
|
||||
|
||||
Running the tests above against Swift in your development environment (ie
|
||||
your SAIO) will catch most issues. Any patch you propose is expected to be
|
||||
both tested and documented and all tests should pass.
|
||||
|
||||
If you want to run just a subset of the tests while you are developing, you
|
||||
can use nosetests::
|
||||
|
||||
cd test/unit/common/middleware/ && nosetests test_healthcheck.py
|
||||
|
||||
To check which parts of your code are being exercised by a test, you can run
|
||||
tox and then point your browser to swift/cover/index.html::
|
||||
|
||||
tox -e py27 -- test.unit.common.middleware.test_healthcheck:TestHealthCheck.test_healthcheck
|
||||
|
||||
Swift's unit tests are designed to test small parts of the code in isolation.
|
||||
The functional tests validate that the entire system is working from an
|
||||
external perspective (they are "black-box" tests). You can even run functional
|
||||
tests against public Swift endpoints. The probetests are designed to test much
|
||||
of Swift's internal processes. For example, a test may write data,
|
||||
intentionally corrupt it, and then ensure that the correct processes detect
|
||||
and repair it.
|
||||
|
||||
When your patch is submitted for code review, it will automatically be tested
|
||||
on the OpenStack CI infrastructure. In addition to many of the tests above, it
|
||||
will also be tested by several other OpenStack test jobs.
|
||||
|
||||
Once your patch has been reviewed and approved by two core reviewers and has
|
||||
passed all automated tests, it will be merged into the Swift source tree.
|
||||
|
||||
Specs
|
||||
=====
|
||||
|
||||
The [``swift-specs``](https://github.com/openstack/swift-specs) repo
|
||||
can be used for collaborative design work before a feature is implemented.
|
||||
|
||||
OpenStack's gerrit system is used to collaborate on the design spec. Once
|
||||
approved OpenStack provides a doc site to easily read these [specs](http://specs.openstack.org/openstack/swift-specs/)
|
||||
|
||||
A spec is needed for more impactful features. Coordinating a feature between
|
||||
many devs (especially across companies) is a great example of when a spec is
|
||||
needed. If you are unsure if a spec document is needed, please feel free to
|
||||
ask in #openstack-swift on freenode IRC.
|
|
@ -0,0 +1,182 @@
|
|||
Contributing to OpenStack Swift
|
||||
===============================
|
||||
|
||||
Who is a Contributor?
|
||||
---------------------
|
||||
|
||||
Put simply, if you improve Swift, you're a contributor. The easiest way to
|
||||
improve the project is to tell us where there's a bug. In other words, filing
|
||||
a bug is a valuable and helpful way to contribute to the project.
|
||||
|
||||
Once a bug has been filed, someone will work on writing a patch to fix the
|
||||
bug. Perhaps you'd like to fix a bug. Writing code to fix a bug or add new
|
||||
functionality is tremendously important.
|
||||
|
||||
Once code has been written, it is submitted upstream for review. All code,
|
||||
even that written by the most senior members of the community, must pass code
|
||||
review and all tests before it can be included in the project. Reviewing
|
||||
proposed patches is a very helpful way to be a contributor.
|
||||
|
||||
Swift is nothing without the community behind it. We'd love to welcome you to
|
||||
our community. Come find us in #openstack-swift on freenode IRC or on the
|
||||
OpenStack dev mailing list.
|
||||
|
||||
Filing a Bug
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Filing a bug is the easiest way to contribute. We use Launchpad as a bug
|
||||
tracker; you can find currently-tracked bugs at
|
||||
https://bugs.launchpad.net/swift.
|
||||
Use the `Report a bug <https://bugs.launchpad.net/swift/+filebug>`__ link to
|
||||
file a new bug.
|
||||
|
||||
If you find something in Swift that doesn't match the documentation or doesn't
|
||||
meet your expectations with how it should work, please let us know. Of course,
|
||||
if you ever get an error (like a Traceback message in the logs), we definitely
|
||||
want to know about that. We'll do our best to diagnose any problem and patch
|
||||
it as soon as possible.
|
||||
|
||||
A bug report, at minimum, should describe what you were doing that caused the
|
||||
bug. "Swift broke, pls fix" is not helpful. Instead, something like "When I
|
||||
restarted syslog, Swift started logging traceback messages" is very helpful.
|
||||
The goal is that we can reproduce the bug and isolate the issue in order to
|
||||
apply a fix. If you don't have full details, that's ok. Anything you can
|
||||
provide is helpful.
|
||||
|
||||
You may have noticed that there are many tracked bugs, but not all of them
|
||||
have been confirmed. If you take a look at an old bug report and you can
|
||||
reproduce the issue described, please leave a comment on the bug about that.
|
||||
It lets us all know that the bug is very likely to be valid.
|
||||
|
||||
Reviewing Someone Else's Code
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All code reviews in OpenStack projects are done on
|
||||
https://review.openstack.org/. Reviewing patches is one of the most effective
|
||||
ways you can contribute to the community.
|
||||
|
||||
We've written REVIEW_GUIDELINES.rst (found in this source tree) to help you
|
||||
give good reviews.
|
||||
|
||||
https://wiki.openstack.org/wiki/Swift/PriorityReviews is a starting point to
|
||||
find what reviews are priority in the community.
|
||||
|
||||
What do I work on?
|
||||
------------------
|
||||
|
||||
If you're looking for a way to write and contribute code, but you're not sure
|
||||
what to work on, check out the "wishlist" bugs in the bug tracker. These are
|
||||
normally smaller items that someone took the time to write down but didn't
|
||||
have time to implement.
|
||||
|
||||
And please join #openstack-swift on freenode IRC to tell us what you're
|
||||
working on.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
http://docs.openstack.org/developer/swift/first_contribution_swift.html
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow.
|
||||
|
||||
Gerrit is the review system used in the OpenStack projects. We're sorry, but
|
||||
we won't be able to respond to pull requests submitted through GitHub.
|
||||
|
||||
Bugs should be filed `on Launchpad <https://bugs.launchpad.net/swift>`__,
|
||||
not in GitHub's issue tracker.
|
||||
|
||||
Swift Design Principles
|
||||
=======================
|
||||
|
||||
- `The Zen of Python <http://legacy.python.org/dev/peps/pep-0020/>`__
|
||||
- Simple Scales
|
||||
- Minimal dependencies
|
||||
- Re-use existing tools and libraries when reasonable
|
||||
- Leverage the economies of scale
|
||||
- Small, loosely coupled RESTful services
|
||||
- No single points of failure
|
||||
- Start with the use case
|
||||
- ... then design from the cluster operator up
|
||||
- If you haven't argued about it, you don't have the right answer yet
|
||||
:)
|
||||
- If it is your first implementation, you probably aren't done yet :)
|
||||
|
||||
Please don't feel offended by difference of opinion. Be prepared to
|
||||
advocate for your change and iterate on it based on feedback. Reach out
|
||||
to other people working on the project on
|
||||
`IRC <http://eavesdrop.openstack.org/irclogs/%23openstack-swift/>`__ or
|
||||
the `mailing
|
||||
list <http://lists.openstack.org/pipermail/openstack-dev/>`__ - we want
|
||||
to help.
|
||||
|
||||
Recommended workflow
|
||||
====================
|
||||
|
||||
- Set up a `Swift All-In-One
|
||||
VM <http://docs.openstack.org/developer/swift/development_saio.html>`__\ (SAIO).
|
||||
|
||||
- Make your changes. Docs and tests for your patch must land before or
|
||||
with your patch.
|
||||
|
||||
- Run unit tests, functional tests, probe tests ``./.unittests``
|
||||
``./.functests`` ``./.probetests``
|
||||
|
||||
- Run ``tox`` (no command-line args needed)
|
||||
|
||||
- ``git review``
|
||||
|
||||
Notes on Testing
|
||||
================
|
||||
|
||||
Running the tests above against Swift in your development environment
|
||||
(ie your SAIO) will catch most issues. Any patch you propose is expected
|
||||
to be both tested and documented and all tests should pass.
|
||||
|
||||
If you want to run just a subset of the tests while you are developing,
|
||||
you can use nosetests:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
cd test/unit/common/middleware/ && nosetests test_healthcheck.py
|
||||
|
||||
To check which parts of your code are being exercised by a test, you can
|
||||
run tox and then point your browser to swift/cover/index.html:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
tox -e py27 -- test.unit.common.middleware.test_healthcheck:TestHealthCheck.test_healthcheck
|
||||
|
||||
Swift's unit tests are designed to test small parts of the code in
|
||||
isolation. The functional tests validate that the entire system is
|
||||
working from an external perspective (they are "black-box" tests). You
|
||||
can even run functional tests against public Swift endpoints. The
|
||||
probetests are designed to test much of Swift's internal processes. For
|
||||
example, a test may write data, intentionally corrupt it, and then
|
||||
ensure that the correct processes detect and repair it.
|
||||
|
||||
When your patch is submitted for code review, it will automatically be
|
||||
tested on the OpenStack CI infrastructure. In addition to many of the
|
||||
tests above, it will also be tested by several other OpenStack test
|
||||
jobs.
|
||||
|
||||
Once your patch has been reviewed and approved by two core reviewers and
|
||||
has passed all automated tests, it will be merged into the Swift source
|
||||
tree.
|
||||
|
||||
Ideas
|
||||
=====
|
||||
|
||||
https://wiki.openstack.org/wiki/Swift/ideas
|
||||
|
||||
If you're working on something, it's a very good idea to write down
|
||||
what you're thinking about. This lets others get up to speed, helps
|
||||
you collaborate, and serves as a great record for future reference.
|
||||
Write down your thoughts somewhere and put a link to it here. It
|
||||
doesn't matter what form your thoughts are in; use whatever is best
|
||||
for you. Your document should include why your idea is needed and your
|
||||
thoughts on particular design choices and tradeoffs. Please include
|
||||
some contact information (ideally, your IRC nick) so that people can
|
||||
collaborate with you.
|
|
@ -1,12 +1,12 @@
|
|||
include AUTHORS LICENSE .functests .unittests .probetests test/__init__.py
|
||||
include CHANGELOG CONTRIBUTING.md README.md
|
||||
include CHANGELOG CONTRIBUTING.rst README.rst
|
||||
include babel.cfg
|
||||
include test/sample.conf
|
||||
include tox.ini
|
||||
include requirements.txt test-requirements.txt
|
||||
graft doc
|
||||
graft etc
|
||||
graft locale
|
||||
graft swift/locale
|
||||
graft test/functional
|
||||
graft test/probe
|
||||
graft test/unit
|
||||
|
|
86
README.md
86
README.md
|
@ -1,86 +0,0 @@
|
|||
# Swift
|
||||
|
||||
A distributed object storage system designed to scale from a single machine
|
||||
to thousands of servers. Swift is optimized for multi-tenancy and high
|
||||
concurrency. Swift is ideal for backups, web and mobile content, and any other
|
||||
unstructured data that can grow without bound.
|
||||
|
||||
Swift provides a simple, REST-based API fully documented at
|
||||
http://docs.openstack.org/.
|
||||
|
||||
Swift was originally developed as the basis for Rackspace's Cloud Files and
|
||||
was open-sourced in 2010 as part of the OpenStack project. It has since grown
|
||||
to include contributions from many companies and has spawned a thriving
|
||||
ecosystem of 3rd party tools. Swift's contributors are listed in the AUTHORS
|
||||
file.
|
||||
|
||||
## Docs
|
||||
|
||||
To build documentation install sphinx (`pip install sphinx`), run
|
||||
`python setup.py build_sphinx`, and then browse to /doc/build/html/index.html.
|
||||
These docs are auto-generated after every commit and available online at
|
||||
http://docs.openstack.org/developer/swift/.
|
||||
|
||||
## For Developers
|
||||
|
||||
The best place to get started is the ["SAIO - Swift All In One"](http://docs.openstack.org/developer/swift/development_saio.html).
|
||||
This document will walk you through setting up a development cluster of Swift
|
||||
in a VM. The SAIO environment is ideal for running small-scale tests against
|
||||
swift and trying out new features and bug fixes.
|
||||
|
||||
You can run unit tests with `.unittests` and functional tests with
|
||||
`.functests`.
|
||||
|
||||
If you would like to start contributing, check out these [notes](CONTRIBUTING.md)
|
||||
to help you get started.
|
||||
|
||||
### Code Organization
|
||||
|
||||
* bin/: Executable scripts that are the processes run by the deployer
|
||||
* doc/: Documentation
|
||||
* etc/: Sample config files
|
||||
* swift/: Core code
|
||||
* account/: account server
|
||||
* common/: code shared by different modules
|
||||
* middleware/: "standard", officially-supported middleware
|
||||
* ring/: code implementing Swift's ring
|
||||
* container/: container server
|
||||
* obj/: object server
|
||||
* proxy/: proxy server
|
||||
* test/: Unit and functional tests
|
||||
|
||||
### Data Flow
|
||||
|
||||
Swift is a WSGI application and uses eventlet's WSGI server. After the
|
||||
processes are running, the entry point for new requests is the `Application`
|
||||
class in `swift/proxy/server.py`. From there, a controller is chosen, and the
|
||||
request is processed. The proxy may choose to forward the request to a back-
|
||||
end server. For example, the entry point for requests to the object server is
|
||||
the `ObjectController` class in `swift/obj/server.py`.
|
||||
|
||||
|
||||
## For Deployers
|
||||
|
||||
Deployer docs are also available at
|
||||
http://docs.openstack.org/developer/swift/. A good starting point is at
|
||||
http://docs.openstack.org/developer/swift/deployment_guide.html
|
||||
|
||||
You can run functional tests against a swift cluster with `.functests`. These
|
||||
functional tests require `/etc/swift/test.conf` to run. A sample config file
|
||||
can be found in this source tree in `test/sample.conf`.
|
||||
|
||||
## For Client Apps
|
||||
|
||||
For client applications, official Python language bindings are provided at
|
||||
http://github.com/openstack/python-swiftclient.
|
||||
|
||||
Complete API documentation at
|
||||
http://docs.openstack.org/api/openstack-object-storage/1.0/content/
|
||||
|
||||
----
|
||||
|
||||
For more information come hang out in #openstack-swift on freenode.
|
||||
|
||||
Thanks,
|
||||
|
||||
The Swift Development Team
|
|
@ -0,0 +1,145 @@
|
|||
Swift
|
||||
=====
|
||||
|
||||
A distributed object storage system designed to scale from a single
|
||||
machine to thousands of servers. Swift is optimized for multi-tenancy
|
||||
and high concurrency. Swift is ideal for backups, web and mobile
|
||||
content, and any other unstructured data that can grow without bound.
|
||||
|
||||
Swift provides a simple, REST-based API fully documented at
|
||||
http://docs.openstack.org/.
|
||||
|
||||
Swift was originally developed as the basis for Rackspace's Cloud Files
|
||||
and was open-sourced in 2010 as part of the OpenStack project. It has
|
||||
since grown to include contributions from many companies and has spawned
|
||||
a thriving ecosystem of 3rd party tools. Swift's contributors are listed
|
||||
in the AUTHORS file.
|
||||
|
||||
Docs
|
||||
----
|
||||
|
||||
To build documentation install sphinx (``pip install sphinx``), run
|
||||
``python setup.py build_sphinx``, and then browse to
|
||||
/doc/build/html/index.html. These docs are auto-generated after every
|
||||
commit and available online at
|
||||
http://docs.openstack.org/developer/swift/.
|
||||
|
||||
For Developers
|
||||
--------------
|
||||
|
||||
Getting Started
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Swift is part of OpenStack and follows the code contribution, review, and testing processes common to all OpenStack projects.
|
||||
|
||||
If you would like to start contributing, check out these
|
||||
`notes <CONTRIBUTING.rst>`__ to help you get started.
|
||||
|
||||
The best place to get started is the
|
||||
`"SAIO - Swift All In One" <http://docs.openstack.org/developer/swift/development_saio.html>`__.
|
||||
This document will walk you through setting up a development cluster of
|
||||
Swift in a VM. The SAIO environment is ideal for running small-scale
|
||||
tests against swift and trying out new features and bug fixes.
|
||||
|
||||
Tests
|
||||
~~~~~
|
||||
|
||||
There are three types of tests included in Swift's source tree.
|
||||
|
||||
#. Unit tests
|
||||
#. Functional tests
|
||||
#. Probe tests
|
||||
|
||||
Unit tests check that small sections of the code behave properly. For example,
|
||||
a unit test may test a single function to ensure that various input gives the
|
||||
expected output. This validates that the code is correct and regressions are
|
||||
not introduced.
|
||||
|
||||
Functional tests check that the client API is working as expected. These can
|
||||
be run against any endpoint claiming to support the Swift API (although some
|
||||
tests require multiple accounts with different privilege levels). These are
|
||||
"black box" tests that ensure that client apps written against Swift will
|
||||
continue to work.
|
||||
|
||||
Probe tests are "white box" tests that validate the internal workings of a
|
||||
Swift cluster. They are written to work against the
|
||||
`"SAIO - Swift All In One" <http://docs.openstack.org/developer/swift/development_saio.html>`__
|
||||
dev environment. For example, a probe test may create an object, delete one
|
||||
replica, and ensure that the background consistency processes find and correct
|
||||
the error.
|
||||
|
||||
You can run unit tests with ``.unittests``, functional tests with
|
||||
``.functests``, and probe tests with ``.probetests``. There is an
|
||||
additional ``.alltests`` script that wraps the other three.
|
||||
|
||||
Code Organization
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
- bin/: Executable scripts that are the processes run by the deployer
|
||||
- doc/: Documentation
|
||||
- etc/: Sample config files
|
||||
- examples/: Config snippets used in the docs
|
||||
- swift/: Core code
|
||||
|
||||
- account/: account server
|
||||
- cli/: code that backs some of the CLI tools in bin/
|
||||
- common/: code shared by different modules
|
||||
|
||||
- middleware/: "standard", officially-supported middleware
|
||||
- ring/: code implementing Swift's ring
|
||||
|
||||
- container/: container server
|
||||
- locale/: internationalization (translation) data
|
||||
- obj/: object server
|
||||
- proxy/: proxy server
|
||||
|
||||
- test/: Unit, functional, and probe tests
|
||||
|
||||
Data Flow
|
||||
~~~~~~~~~
|
||||
|
||||
Swift is a WSGI application and uses eventlet's WSGI server. After the
|
||||
processes are running, the entry point for new requests is the
|
||||
``Application`` class in ``swift/proxy/server.py``. From there, a
|
||||
controller is chosen, and the request is processed. The proxy may choose
|
||||
to forward the request to a back- end server. For example, the entry
|
||||
point for requests to the object server is the ``ObjectController``
|
||||
class in ``swift/obj/server.py``.
|
||||
|
||||
For Deployers
|
||||
-------------
|
||||
|
||||
Deployer docs are also available at
|
||||
http://docs.openstack.org/developer/swift/. A good starting point is at
|
||||
http://docs.openstack.org/developer/swift/deployment\_guide.html
|
||||
|
||||
There is an `ops runbook <http://docs.openstack.org/developer/swift/ops_runbook/>`__
|
||||
that gives information about how to diagnose and troubleshoot common issues
|
||||
when running a Swift cluster.
|
||||
|
||||
You can run functional tests against a swift cluster with
|
||||
``.functests``. These functional tests require ``/etc/swift/test.conf``
|
||||
to run. A sample config file can be found in this source tree in
|
||||
``test/sample.conf``.
|
||||
|
||||
For Client Apps
|
||||
---------------
|
||||
|
||||
For client applications, official Python language bindings are provided
|
||||
at http://github.com/openstack/python-swiftclient.
|
||||
|
||||
Complete API documentation at
|
||||
http://docs.openstack.org/api/openstack-object-storage/1.0/content/
|
||||
|
||||
There is a large ecosystem of applications and libraries that support and
|
||||
work with OpenStack Swift. Several are listed on the
|
||||
`associated projects <http://docs.openstack.org/developer/swift/associated_projects.html>`__
|
||||
page.
|
||||
|
||||
--------------
|
||||
|
||||
For more information come hang out in #openstack-swift on freenode.
|
||||
|
||||
Thanks,
|
||||
|
||||
The Swift Development Team
|
|
@ -0,0 +1,387 @@
|
|||
Review Guidelines
|
||||
=================
|
||||
|
||||
Effective code review is a skill like any other professional skill you
|
||||
develop with experience. Effective code review requires trust. No
|
||||
one is perfect. Everyone makes mistakes. Trust builds over time.
|
||||
|
||||
This document will enumerate behaviors commonly observed and
|
||||
associated with competent reviews of changes purposed to the Swift
|
||||
code base. No one is expected to "follow these steps". Guidelines
|
||||
are not *rules*, not all behaviors will be relevant in all situations.
|
||||
|
||||
|
||||
Checkout the Change
|
||||
-------------------
|
||||
|
||||
You will need to have a copy of the change in an environment where you
|
||||
can freely edit and experiment with the code in order to provide a
|
||||
non-superficial review. Superficial reviews are not terribly helpful.
|
||||
Always try to be helpful. ;)
|
||||
|
||||
Check out the change so that you may begin.
|
||||
|
||||
Commonly, ``git review -d <change-id>``
|
||||
|
||||
Run it
|
||||
------
|
||||
|
||||
Imagine that you submit a patch to Swift, and a reviewer starts to
|
||||
take a look at it. Your commit message on the patch claims that it
|
||||
fixes a bug or adds a feature, but as soon as the reviewer downloads
|
||||
it locally and tries to test it, a severe and obvious error shows up.
|
||||
Something like a syntax error or a missing dependency.
|
||||
|
||||
"Did you even run this?" is the review comment all contributors dread.
|
||||
|
||||
Reviewers in particular need to be fearful merging changes that just
|
||||
don't work - or at least fail in frequently common enough scenarios to
|
||||
be considered "horribly broken". A comment in our review that says
|
||||
roughly "I ran this on my machine and observed ``description of
|
||||
behavior change is supposed to achieve``" is the most powerful defense
|
||||
we have against the terrible terrible scorn from our fellow Swift
|
||||
developers and operators when we accidentally merge bad code.
|
||||
|
||||
If you're doing a fair amount of reviews - you will participate in
|
||||
merging a change that will break my clusters - it's cool - I'll do it
|
||||
to you at some point too (sorry about that). But when either of us go
|
||||
look at the reviews to understand the process gap that allowed this to
|
||||
happen - it better not be just because we were too lazy to check it out
|
||||
and run it before it got merged.
|
||||
|
||||
Or be warned, you may receive, the dreaded...
|
||||
|
||||
"Did you even *run* this?"
|
||||
|
||||
I'm sorry, I know it's rough. ;)
|
||||
|
||||
Consider edge cases very seriously
|
||||
----------------------------------
|
||||
|
||||
Saying "that should rarely happen" is the same as saying "that
|
||||
*will* happen"
|
||||
|
||||
-- Douglas Crockford
|
||||
|
||||
Scale is an *amazingly* abusive partner. If you contribute changes to
|
||||
Swift your code is running - in production - at scale - and your bugs
|
||||
cannot hide. I wish on all of us that our bugs may be exceptionally
|
||||
rare - meaning they only happen in extremely unlikely edge cases. For
|
||||
example, bad things that happen only 1 out of every 10K times an op is
|
||||
performed will be discovered in minutes. Bad things that happen only
|
||||
1 out of every one billion times something happens will be observed -
|
||||
by multiple deployments - over the course of a release. Bad things
|
||||
that happen 1/100 times some op is performed are considered "horribly
|
||||
broken". Tests must exhaustively exercise possible scenarios. Every
|
||||
system call and network connection will raise an error and timeout -
|
||||
where will that Exception be caught?
|
||||
|
||||
Run the tests
|
||||
-------------
|
||||
|
||||
Yes, I know Gerrit does this already. You can do it *too*. You might
|
||||
not need to re-run *all* the tests on your machine - it depends on the
|
||||
change. But, if you're not sure which will be most useful - running
|
||||
all of them best - unit - functional - probe. If you can't reliably
|
||||
get all tests passing in your development environment you will not be
|
||||
able to do effective reviews. Whatever tests/suites you are able to
|
||||
exercise/validate on your machine against your config you should
|
||||
mention in your review comments so that other reviewers might choose
|
||||
to do *other* testing locally when they have the change checked out.
|
||||
|
||||
e.g.
|
||||
|
||||
I went ahead and ran probe/test_object_metadata_replication.py on
|
||||
my machine with both sync_method = rsync and sync_method = ssync -
|
||||
that works for me - but I didn't try it with object_post_as_copy =
|
||||
false
|
||||
|
||||
Maintainable Code is Obvious
|
||||
----------------------------
|
||||
|
||||
Style is an important component to review. The goal is maintainability.
|
||||
|
||||
However, keep in mind that generally style, readability and
|
||||
maintainability are orthogonal to the suitability of a change for
|
||||
merge. A critical bug fix may be a well written pythonic masterpiece
|
||||
of style - or it may be a hack-y ugly mess that will absolutely need
|
||||
to be cleaned up at some point - but it absolutely should merge
|
||||
because: CRITICAL. BUG. FIX.
|
||||
|
||||
You should comment inline to praise code that is "obvious". You should
|
||||
comment inline to highlight code that that you found to be "obfuscated".
|
||||
|
||||
Unfortunately "readability" is often subjective. We should remember
|
||||
that it's probably just our own personal preference. Rather than a
|
||||
comment that says "You should use a list comprehension here" - rewrite
|
||||
the code as a list comprehension, run the specific tests that hit the
|
||||
relevant section to validate your code is correct, then leave a
|
||||
comment that says:
|
||||
|
||||
I find this more readable:
|
||||
|
||||
``diff with working tested code``
|
||||
|
||||
If the author (or another reviewer) agrees - it's possible the change will get
|
||||
updated to include that improvement before it is merged; or it may happen in a
|
||||
follow-up change.
|
||||
|
||||
However, remember that style is non-material - it is useful to provide (via
|
||||
diff) suggestions to improve maintainability as part of your review - but if
|
||||
the suggestion is functionally equivalent - it is by definition optional.
|
||||
|
||||
Commit Messages
|
||||
---------------
|
||||
|
||||
Read the commit message thoroughly before you begin the review.
|
||||
|
||||
Commit messages must answer the "why" and the "what for" - more so
|
||||
than the "how" or "what it does". Commonly this will take the form of
|
||||
a short description:
|
||||
|
||||
- What is broken - without this change
|
||||
- What is impossible to do with Swift - without this change
|
||||
- What is slower/worse/harder - without this change
|
||||
|
||||
If you're not able to discern why a change is being made or how it
|
||||
would be used - you may have to ask for more details before you can
|
||||
successfully review it.
|
||||
|
||||
Commit messages need to have a high consistent quality. While many
|
||||
things under source control can be fixed and improved in a follow-up
|
||||
change - commit messages are forever. Luckily it's easy to fix minor
|
||||
mistakes using the in-line edit feature in Gerrit! If you can avoid
|
||||
ever having to *ask* someone to change a commit message you will find
|
||||
yourself an amazingly happier and more productive reviewer.
|
||||
|
||||
Also commit messages should follow the OpenStack Commit Message
|
||||
guidelines, including references to relevant impact tags or bug
|
||||
numbers. You should hand out links to the OpenStack Commit Message
|
||||
guidelines *liberally* via comments when fixing commit messages during
|
||||
review.
|
||||
|
||||
Here you go: `GitCommitMessages <https://wiki.openstack.org/wiki/GitCommitMessages#Summary_of_Git_commit_message_structure>`_
|
||||
|
||||
New Tests
|
||||
---------
|
||||
|
||||
New tests should be added for all code changes. Historically you
|
||||
should expect good changes to have a diff line count ratio of at least
|
||||
2:1 tests to code. Even if a change has to "fix" a lot of *existing*
|
||||
tests, if a change does not include any *new* tests it probably should
|
||||
not merge.
|
||||
|
||||
If a change includes a good ratio of test changes and adds new tests -
|
||||
you should say so in your review comments.
|
||||
|
||||
If it does not - you should write some!
|
||||
|
||||
... and offer them to the patch author as a diff indicating to them that
|
||||
"something" like these tests I'm providing as an example will *need* to be
|
||||
included in this change before it is suitable to merge. Bonus points if you
|
||||
include suggestions for the author as to how they might improve or expand upon
|
||||
the tests stubs you provide.
|
||||
|
||||
Be *very* careful about asking an author to add a test for a "small change"
|
||||
before attempting to do so yourself. It's quite possible there is a lack of
|
||||
existing test infrastructure needed to develop a concise and clear test - the
|
||||
author of a small change may not be the best person to introduce a large
|
||||
amount of new test infrastructure. Also, most of the time remember it's
|
||||
*harder* to write the test than the change - if the author is unable to
|
||||
develop a test for their change on their own you may prevent a useful change
|
||||
from being merged. At a minimum you should suggest a specific unit test that
|
||||
you think they should be able to copy and modify to exercise the behavior in
|
||||
their change. If you're not sure if such a test exists - replace their change
|
||||
with an Exception and run tests until you find one that blows up.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Most changes should include documentation. New functions and code
|
||||
should have Docstrings. Tests should obviate new or changed behaviors
|
||||
with descriptive and meaningful phrases. New features should include
|
||||
changes to the documentation tree. New config options should be
|
||||
documented in example configs. The commit message should document the
|
||||
change for the change log.
|
||||
|
||||
Always point out typos or grammar mistakes when you see them in
|
||||
review, but also consider that if you were able to recognize the
|
||||
intent of the statement - documentation with typos may be easier to
|
||||
iterate and improve on than nothing.
|
||||
|
||||
If a change does not have adequate documentation it may not be suitable to
|
||||
merge. If a change includes incorrect or misleading documentation or is
|
||||
contrary to *existing* documentation is probably is not suitable to merge.
|
||||
|
||||
Every change could have better documentation.
|
||||
|
||||
Like with tests, a patch isn't done until it has docs. Any patch that
|
||||
adds a new feature, changes behavior, updates configs, or in any other
|
||||
way is different than previous behavior requires docs. manpages,
|
||||
sample configs, docstrings, descriptive prose in the source tree, etc.
|
||||
|
||||
Reviewers Write Code
|
||||
--------------------
|
||||
|
||||
Reviews have been shown to provide many benefits - one of which is shared
|
||||
ownership. After providing a positive review you should understand how the
|
||||
change works. Doing this will probably require you to "play with" the change.
|
||||
|
||||
You might functionally test the change in various scenarios. You may need to
|
||||
write a new unit test to validate the change will degrade gracefully under
|
||||
failure. You might have to write a script to exercise the change under some
|
||||
superficial load. You might have to break the change and validate the new
|
||||
tests fail and provide useful errors. You might have to step through some
|
||||
critical section of the code in a debugger to understand when all the possible
|
||||
branches are exercised in tests.
|
||||
|
||||
When you're done with your review an artifact of your effort will be
|
||||
observable in the piles of code and scripts and diffs you wrote while
|
||||
reviewing. You should make sure to capture those artifacts in a paste
|
||||
or gist and include them in your review comments so that others may
|
||||
reference them.
|
||||
|
||||
e.g.
|
||||
|
||||
When I broke the change like this:
|
||||
|
||||
``diff``
|
||||
|
||||
it blew up like this:
|
||||
|
||||
``unit test failure``
|
||||
|
||||
|
||||
It's not uncommon that a review takes more time than writing a change -
|
||||
hopefully the author also spent as much time as you did *validating* their
|
||||
change but that's not really in your control. When you provide a positive
|
||||
review you should be sure you understand the change - even seemingly trivial
|
||||
changes will take time to consider the ramifications.
|
||||
|
||||
Leave Comments
|
||||
--------------
|
||||
|
||||
Leave. Lots. Of. Comments.
|
||||
|
||||
A popular web comic has stated that
|
||||
`WTFs/Minute <http://www.osnews.com/images/comics/wtfm.jpg>`_ is the
|
||||
*only* valid measurement of code quality.
|
||||
|
||||
If something initially strikes you as questionable - you should jot
|
||||
down a note so you can loop back around to it.
|
||||
|
||||
However, because of the distributed nature of authors and reviewers
|
||||
it's *imperative* that you try your best to answer your own questions
|
||||
as part of your review.
|
||||
|
||||
Do not say "Does this blow up if it gets called when xyz" - rather try
|
||||
and find a test that specifically covers that condition and mention it
|
||||
in the comment so others can find it more quickly. Or if you can find
|
||||
no such test, add one to demonstrate the failure, and include a diff
|
||||
in a comment. Hopefully you can say "I *thought* this would blow up,
|
||||
so I wrote this test, but it seems fine."
|
||||
|
||||
But if your initial reaction is "I don't understand this" or "How does
|
||||
this even work?" you should notate it and explain whatever you *were*
|
||||
able to figure out in order to help subsequent reviewers more quickly
|
||||
identify and grok the subtle or complex issues.
|
||||
|
||||
Because you will be leaving lots of comments - many of which are
|
||||
potentially not highlighting anything specific - it is VERY important
|
||||
to leave a good summary. Your summary should include details of how
|
||||
you reviewed the change. You may include what you liked most, or
|
||||
least.
|
||||
|
||||
If you are leaving a negative score ideally you should provide clear
|
||||
instructions on how the change could be modified such that it would be
|
||||
suitable for merge - again diffs work best.
|
||||
|
||||
Scoring
|
||||
-------
|
||||
|
||||
Scoring is subjective. Try to realize you're making a judgment call.
|
||||
|
||||
A positive score means you believe Swift would be undeniably better
|
||||
off with this code merged than it would be going one more second
|
||||
without this change running in production immediately. It is indeed
|
||||
high praise - you should be sure.
|
||||
|
||||
A negative score means that to the best of your abilities you have not
|
||||
been able to your satisfaction, to justify the value of a change
|
||||
against the cost of its deficiencies and risks. It is a surprisingly
|
||||
difficult chore to be confident about the value of unproven code or a
|
||||
not well understood use-case in an uncertain world, and unfortunately
|
||||
all too easy with a **thorough** review to uncover our defects, and be
|
||||
reminded of the risk of... regression.
|
||||
|
||||
Reviewers must try *very* hard first and foremost to keep master stable.
|
||||
|
||||
If you can demonstrate a change has an incorrect *behavior* it's
|
||||
almost without exception that the change must be revised to fix the
|
||||
defect *before* merging rather than letting it in and having to also
|
||||
file a bug.
|
||||
|
||||
Every commit must be deployable to production.
|
||||
|
||||
Beyond that - almost any change might be merge-able depending on
|
||||
its merits! Here are some tips you might be able to use to find more
|
||||
changes that should merge!
|
||||
|
||||
#. Fixing bugs is HUGELY valuable - the *only* thing which has a
|
||||
higher cost than the value of fixing a bug - is adding a new
|
||||
bug - if it's broken and this change makes it fixed (without
|
||||
breaking anything else) you have a winner!
|
||||
|
||||
#. Features are INCREDIBLY difficult to justify their value against
|
||||
the cost of increased complexity, lowered maintainability, risk
|
||||
of regression, or new defects. Try to focus on what is
|
||||
*impossible* without the feature - when you make the impossible
|
||||
possible, things are better. Make things better.
|
||||
|
||||
#. Purely test/doc changes, complex refactoring, or mechanical
|
||||
cleanups are quite nuanced because there's less concrete
|
||||
objective value. I've seen lots of these kind of changes
|
||||
get lost to the backlog. I've also seen some success where
|
||||
multiple authors have collaborated to "push-over" a change
|
||||
rather than provide a "review" ultimately resulting in a
|
||||
quorum of three or more "authors" who all agree there is a lot
|
||||
of value in the change - however subjective.
|
||||
|
||||
Because the bar is high - most reviews will end with a negative score.
|
||||
|
||||
However, for non-material grievances (nits) - you should feel
|
||||
confident in a positive review if the change is otherwise complete
|
||||
correct and undeniably makes Swift better (not perfect, *better*). If
|
||||
you see something worth fixing you should point it out in review
|
||||
comments, but when applying a score consider if it *need* be fixed
|
||||
before the change is suitable to merge vs. fixing it in a follow up
|
||||
change? Consider if the change makes Swift so undeniably *better*
|
||||
and it was deployed in production without making any additional
|
||||
changes would it still be correct and complete? Would releasing the
|
||||
change to production without any additional follow up make it more
|
||||
difficult to maintain and continue to improve Swift?
|
||||
|
||||
Endeavor to leave a positive or negative score on every change you review.
|
||||
|
||||
Use your best judgment.
|
||||
|
||||
A note on Swift Core Maintainers
|
||||
================================
|
||||
|
||||
Swift Core maintainers may provide positive reviews scores that *look*
|
||||
different from your reviews - a "+2" instead of a "+1".
|
||||
|
||||
But it's *exactly the same* as your "+1".
|
||||
|
||||
It means the change has been thoroughly and positively reviewed. The
|
||||
only reason it's different is to help identify changes which have
|
||||
received multiple competent and positive reviews. If you consistently
|
||||
provide competent reviews you run a *VERY* high risk of being
|
||||
approached to have your future positive review scores changed from a
|
||||
"+1" to "+2" in order to make it easier to identify changes which need
|
||||
to get merged.
|
||||
|
||||
Ideally a review from a core maintainer should provide a clear path
|
||||
forward for the patch author. If you don't know how to proceed
|
||||
respond to the reviewers comments on the change and ask for help.
|
||||
We'd love to try and help.
|
|
@ -11,12 +11,28 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sqlite3
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
from swift.cli.info import print_info, InfoSystemExit
|
||||
|
||||
|
||||
def run_print_info(args, opts):
|
||||
try:
|
||||
print_info('account', *args, **opts)
|
||||
except InfoSystemExit:
|
||||
sys.exit(1)
|
||||
except sqlite3.OperationalError as e:
|
||||
if not opts.get('stale_reads_ok'):
|
||||
opts['stale_reads_ok'] = True
|
||||
print('Warning: Possibly Stale Data')
|
||||
run_print_info(args, opts)
|
||||
sys.exit(2)
|
||||
else:
|
||||
print('Account info failed: %s' % e)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser('%prog [options] ACCOUNT_DB_FILE')
|
||||
parser.add_option(
|
||||
|
@ -28,7 +44,4 @@ if __name__ == '__main__':
|
|||
if len(args) != 1:
|
||||
sys.exit(parser.print_help())
|
||||
|
||||
try:
|
||||
print_info('account', *args, **vars(options))
|
||||
except InfoSystemExit:
|
||||
sys.exit(1)
|
||||
run_print_info(args, vars(options))
|
||||
|
|
|
@ -11,12 +11,28 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sqlite3
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
from swift.cli.info import print_info, InfoSystemExit
|
||||
|
||||
|
||||
def run_print_info(args, opts):
|
||||
try:
|
||||
print_info('container', *args, **opts)
|
||||
except InfoSystemExit:
|
||||
sys.exit(1)
|
||||
except sqlite3.OperationalError as e:
|
||||
if not opts.get('stale_reads_ok'):
|
||||
opts['stale_reads_ok'] = True
|
||||
print('Warning: Possibly Stale Data')
|
||||
run_print_info(args, opts)
|
||||
sys.exit(2)
|
||||
else:
|
||||
print('Container info failed: %s' % e)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser('%prog [options] CONTAINER_DB_FILE')
|
||||
parser.add_option(
|
||||
|
@ -28,7 +44,4 @@ if __name__ == '__main__':
|
|||
if len(args) != 1:
|
||||
sys.exit(parser.print_help())
|
||||
|
||||
try:
|
||||
print_info('container', *args, **vars(options))
|
||||
except InfoSystemExit:
|
||||
sys.exit(1)
|
||||
run_print_info(args, vars(options))
|
||||
|
|
|
@ -77,7 +77,7 @@ def main():
|
|||
# SIGKILL daemon after kill_wait period
|
||||
parser.add_option('--kill-after-timeout', dest='kill_after_timeout',
|
||||
action='store_true',
|
||||
help="Kill daemon and all childs after kill-wait "
|
||||
help="Kill daemon and all children after kill-wait "
|
||||
"period.")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
|
|
@ -25,7 +25,7 @@ from swift.container.reconciler import add_to_reconciler_queue
|
|||
"""
|
||||
This tool is primarily for debugging and development but can be used an example
|
||||
of how an operator could enqueue objects manually if a problem is discovered -
|
||||
might be particularlly useful if you need to hack a fix into the reconciler
|
||||
might be particularly useful if you need to hack a fix into the reconciler
|
||||
and re-run it.
|
||||
"""
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ are acceptable within this section.
|
|||
IP address the account server should bind to. The default is 0.0.0.0 which will make
|
||||
it bind to all available addresses.
|
||||
.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 6202.
|
||||
.IP "\fBbind_timeout\fR"
|
||||
Timeout to bind socket. The default is 30.
|
||||
.IP \fBbacklog\fR
|
||||
|
@ -121,8 +121,10 @@ 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.
|
||||
You can set fallocate_reserve to the number of bytes or percentage of disk
|
||||
space you'd like fallocate to reserve, whether there is space for the given
|
||||
file size or not. Percentage will be used if the value ends with a '%'.
|
||||
The default is 1%.
|
||||
.RE
|
||||
.PD
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ are acceptable within this section.
|
|||
IP address the container server should bind to. The default is 0.0.0.0 which will make
|
||||
it bind to all available addresses.
|
||||
.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 6201.
|
||||
.IP "\fBbind_timeout\fR"
|
||||
Timeout to bind socket. The default is 30.
|
||||
.IP \fBbacklog\fR
|
||||
|
@ -127,8 +127,10 @@ 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.
|
||||
You can set fallocate_reserve to the number of bytes or percentage of disk
|
||||
space you'd like fallocate to reserve, whether there is space for the given
|
||||
file size or not. Percentage will be used if the value ends with a '%'.
|
||||
The default is 1%.
|
||||
.RE
|
||||
.PD
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ are acceptable within this section.
|
|||
IP address the object server should bind to. The default is 0.0.0.0 which will make
|
||||
it bind to all available addresses.
|
||||
.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 6200.
|
||||
.IP "\fBbind_timeout\fR"
|
||||
Timeout to bind socket. The default is 30.
|
||||
.IP \fBbacklog\fR
|
||||
|
@ -88,10 +88,9 @@ 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.
|
||||
Make object-server run this many worker processes per unique port of "local"
|
||||
ring devices across all storage policies. The default value of 0 disables this
|
||||
feature.
|
||||
.IP \fBlog_name\fR
|
||||
Label used when logging. The default is swift.
|
||||
.IP \fBlog_facility\fR
|
||||
|
@ -126,8 +125,10 @@ 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.
|
||||
You can set fallocate_reserve to the number of bytes or percentage of disk
|
||||
space you'd like fallocate to reserve, whether there is space for the given
|
||||
file size or not. Percentage will be used if the value ends with a '%'.
|
||||
The default is 1%.
|
||||
.IP \fBnode_timeout\fR
|
||||
Request timeout to external services. The default is 3 seconds.
|
||||
.IP \fBconn_timeout\fR
|
||||
|
@ -207,20 +208,21 @@ 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.
|
||||
Set to restrict the number of concurrent incoming SSYNC requests
|
||||
Set to 0 for unlimited (the default is 4). Note that SSYNC requests are only used
|
||||
by the object reconstructor or the object replicator when configured to use ssync.
|
||||
.IP "\fBreplication_one_per_device\fR"
|
||||
Restricts incoming REPLICATION requests to one per device,
|
||||
Restricts incoming SSYNC 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
|
||||
device, but you may wish to set this to False to allow multiple SSYNC
|
||||
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
|
||||
These two settings control when the SSYNC subrequest handler will
|
||||
abort an incoming SSYNC 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
|
||||
|
@ -498,6 +500,9 @@ and ensure that swift has read/write. The default is /var/cache/swift.
|
|||
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.
|
||||
.IP \fBrsync_tempfile_timeout\fR
|
||||
Time elapsed in seconds before rsync tempfiles will be unlinked. Config value of "auto"
|
||||
will try to use object-replicator's rsync_timeout + 900 or fall-back to 86400 (1 day).
|
||||
.RE
|
||||
|
||||
|
||||
|
|
|
@ -275,11 +275,14 @@ 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 "auth_uri = http://keystonehost:5000"
|
||||
.IP "auth_url = http://keystonehost:35357"
|
||||
.IP "auth_plugin = password"
|
||||
.IP "project_domain_id = default"
|
||||
.IP "user_domain_id = default"
|
||||
.IP "project_name = service"
|
||||
.IP "username = swift"
|
||||
.IP "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"
|
||||
|
@ -330,11 +333,6 @@ 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\fR
|
||||
[DEPRECATED] 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
|
||||
|
@ -973,8 +971,7 @@ is false.
|
|||
.IP \fBobject_post_as_copy\fR
|
||||
Set object_post_as_copy = false to turn on fast posts where only the metadata changes
|
||||
are stored as new and the original data file is kept in place. This makes for quicker
|
||||
posts; but since the container metadata isn't updated in this mode, features like
|
||||
container sync won't be able to sync posts. The default is True.
|
||||
posts. The default is True.
|
||||
.IP \fBaccount_autocreate\fR
|
||||
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.
|
||||
|
|
|
@ -51,16 +51,16 @@ where the container resides by using the container ring.
|
|||
.IP "Partition 221082"
|
||||
.IP "Hash d7e6ba68cfdce0f0e4ca7890e46cacce"
|
||||
|
||||
.IP "Server:Port Device 172.24.24.29:6002 sdd"
|
||||
.IP "Server:Port Device 172.24.24.27:6002 sdr"
|
||||
.IP "Server:Port Device 172.24.24.32:6002 sde"
|
||||
.IP "Server:Port Device 172.24.24.26:6002 sdv [Handoff]"
|
||||
.IP "Server:Port Device 172.24.24.29:6202 sdd"
|
||||
.IP "Server:Port Device 172.24.24.27:6202 sdr"
|
||||
.IP "Server:Port Device 172.24.24.32:6202 sde"
|
||||
.IP "Server:Port Device 172.24.24.26:6202 sdv [Handoff]"
|
||||
|
||||
|
||||
.IP "curl -I -XHEAD http://172.24.24.29:6002/sdd/221082/MyAccount-12ac01446be2"
|
||||
.IP "curl -I -XHEAD http://172.24.24.27:6002/sdr/221082/MyAccount-12ac01446be2"
|
||||
.IP "curl -I -XHEAD http://172.24.24.32:6002/sde/221082/MyAccount-12ac01446be2"
|
||||
.IP "curl -I -XHEAD http://172.24.24.26:6002/sdv/221082/MyAccount-12ac01446be2 # [Handoff]"
|
||||
.IP "curl -I -XHEAD http://172.24.24.29:6202/sdd/221082/MyAccount-12ac01446be2"
|
||||
.IP "curl -I -XHEAD http://172.24.24.27:6202/sdr/221082/MyAccount-12ac01446be2"
|
||||
.IP "curl -I -XHEAD http://172.24.24.32:6202/sde/221082/MyAccount-12ac01446be2"
|
||||
.IP "curl -I -XHEAD http://172.24.24.26:6202/sdv/221082/MyAccount-12ac01446be2 # [Handoff]"
|
||||
|
||||
.IP "ssh 172.24.24.29 ls -lah /srv/node/sdd/accounts/221082/cce/d7e6ba68cfdce0f0e4ca7890e46cacce/ "
|
||||
.IP "ssh 172.24.24.27 ls -lah /srv/node/sdr/accounts/221082/cce/d7e6ba68cfdce0f0e4ca7890e46cacce/"
|
||||
|
|
|
@ -14,31 +14,31 @@
|
|||
.\" implied.
|
||||
.\" See the License for the specific language governing permissions and
|
||||
.\" limitations under the License.
|
||||
.\"
|
||||
.\"
|
||||
.TH swift-object-expirer 1 "3/15/2012" "Linux" "OpenStack Swift"
|
||||
|
||||
.SH NAME
|
||||
.SH NAME
|
||||
.LP
|
||||
.B swift-object-expirer
|
||||
\- Openstack-swift object expirer
|
||||
|
||||
.SH SYNOPSIS
|
||||
.LP
|
||||
.B swift-object-expirer
|
||||
.B swift-object-expirer
|
||||
[CONFIG] [-h|--help] [-v|--verbose] [-o|--once]
|
||||
|
||||
.SH DESCRIPTION
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The swift-object-expirer offers scheduled deletion of objects. The Swift client would
|
||||
use the X-Delete-At or X-Delete-After headers during an object PUT or POST and the
|
||||
cluster would automatically quit serving that object at the specified time and would
|
||||
The swift-object-expirer offers scheduled deletion of objects. The Swift client would
|
||||
use the X-Delete-At or X-Delete-After headers during an object PUT or POST and the
|
||||
cluster would automatically quit serving that object at the specified time and would
|
||||
shortly thereafter remove the object from the system.
|
||||
|
||||
The X-Delete-At header takes a Unix Epoch timestamp, in integer form; for example:
|
||||
The X-Delete-At header takes a Unix Epoch timestamp, in integer form; for example:
|
||||
1317070737 represents Mon Sep 26 20:58:57 2011 UTC.
|
||||
|
||||
The X-Delete-After header takes a integer number of seconds. The proxy server
|
||||
that receives the request will convert this header into an X-Delete-At header
|
||||
The X-Delete-After header takes a integer number of seconds. The proxy server
|
||||
that receives the request will convert this header into an X-Delete-At header
|
||||
using its current time plus the value given.
|
||||
|
||||
The options are as follows:
|
||||
|
@ -53,19 +53,19 @@ The options are as follows:
|
|||
.IP "-o"
|
||||
.IP "--once"
|
||||
.RS 4
|
||||
.IP "only run one pass of daemon"
|
||||
.IP "only run one pass of daemon"
|
||||
.RE
|
||||
.PD
|
||||
.RE
|
||||
|
||||
|
||||
|
||||
|
||||
.SH DOCUMENTATION
|
||||
.LP
|
||||
More in depth documentation in regards to
|
||||
More in depth documentation in regards to
|
||||
.BI swift-object-expirer
|
||||
can be foud at
|
||||
can be found at
|
||||
.BI http://swift.openstack.org/overview_expiring_objects.html
|
||||
and also about Openstack-Swift as a whole can be found at
|
||||
and also about Openstack-Swift as a whole can be found at
|
||||
.BI http://swift.openstack.org/index.html
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ eventlet_debug = true
|
|||
[pipeline:main]
|
||||
# Yes, proxy-logging appears twice. This is so that
|
||||
# middleware-originated requests get logged too.
|
||||
pipeline = catch_errors gatekeeper healthcheck proxy-logging cache bulk tempurl ratelimit crossdomain container_sync tempauth staticweb container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server
|
||||
pipeline = catch_errors gatekeeper healthcheck proxy-logging cache bulk tempurl ratelimit crossdomain container_sync tempauth staticweb copy container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server
|
||||
|
||||
[filter:catch_errors]
|
||||
use = egg:swift#catch_errors
|
||||
|
@ -68,6 +68,9 @@ use = egg:swift#gatekeeper
|
|||
use = egg:swift#versioned_writes
|
||||
allow_versioned_writes = true
|
||||
|
||||
[filter:copy]
|
||||
use = egg:swift#copy
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
allow_account_management = true
|
||||
|
|
|
@ -98,6 +98,58 @@ This produces a great deal of output that is mostly useful if you are
|
|||
either (a) attempting to fix the ring builder, or (b) filing a bug
|
||||
against the ring builder.
|
||||
|
||||
You may notice in the rebalance output a 'dispersion' number. What this
|
||||
number means is explained in :ref:`ring_dispersion` but in essence
|
||||
is the percentage of partitions in the ring that have too many replicas
|
||||
within a particular failure domain. You can ask 'swift-ring-builder' what
|
||||
the dispersion is with::
|
||||
|
||||
swift-ring-builder <builder-file> dispersion
|
||||
|
||||
This will give you the percentage again, if you want a detailed view of
|
||||
the dispersion simply add a ``--verbose``::
|
||||
|
||||
swift-ring-builder <builder-file> dispersion --verbose
|
||||
|
||||
This will not only display the percentage but will also display a dispersion
|
||||
table that lists partition dispersion by tier. You can use this table to figure
|
||||
out were you need to add capacity or to help tune an :ref:`ring_overload` value.
|
||||
|
||||
Now let's take an example with 1 region, 3 zones and 4 devices. Each device has
|
||||
the same weight, and the ``dispersion --verbose`` might show the following::
|
||||
|
||||
Dispersion is 50.000000, Balance is 0.000000, Overload is 0.00%
|
||||
Required overload is 33.333333%
|
||||
Worst tier is 50.000000 (r1z3)
|
||||
--------------------------------------------------------------------------
|
||||
Tier Parts % Max 0 1 2 3
|
||||
--------------------------------------------------------------------------
|
||||
r1 256 0.00 3 0 0 0 256
|
||||
r1z1 192 0.00 1 64 192 0 0
|
||||
r1z1-127.0.0.1 192 0.00 1 64 192 0 0
|
||||
r1z1-127.0.0.1/sda 192 0.00 1 64 192 0 0
|
||||
r1z2 192 0.00 1 64 192 0 0
|
||||
r1z2-127.0.0.2 192 0.00 1 64 192 0 0
|
||||
r1z2-127.0.0.2/sda 192 0.00 1 64 192 0 0
|
||||
r1z3 256 50.00 1 0 128 128 0
|
||||
r1z3-127.0.0.3 256 50.00 1 0 128 128 0
|
||||
r1z3-127.0.0.3/sda 192 0.00 1 64 192 0 0
|
||||
r1z3-127.0.0.3/sdb 192 0.00 1 64 192 0 0
|
||||
|
||||
|
||||
The first line reports that there are 256 partitions with 3 copies in region 1;
|
||||
and this is an expected output in this case (single region with 3 replicas) as
|
||||
reported by the "Max" value.
|
||||
|
||||
However, there is some inbalance in the cluster, more precisely in zone 3. The
|
||||
"Max" reports a maximum of 1 copy in this zone; however 50.00% of the partitions
|
||||
are storing 2 replicas in this zone (which is somewhat expected, because there
|
||||
are more disks in this zone).
|
||||
|
||||
You can now either add more capacity to the other zones, decrease the total
|
||||
weight in zone 3 or set the overload to a value `greater than` 33.333333% -
|
||||
only as much overload as needed will be used.
|
||||
|
||||
-----------------------
|
||||
Scripting Ring Creation
|
||||
-----------------------
|
||||
|
@ -110,15 +162,15 @@ You can create scripts to create the account and container rings and rebalance.
|
|||
cd /etc/swift
|
||||
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 add r1z1-<account-server-1>:6002/sdb1 1
|
||||
swift-ring-builder account.builder add r1z2-<account-server-2>:6002/sdb1 1
|
||||
swift-ring-builder account.builder add r1z1-<account-server-1>:6202/sdb1 1
|
||||
swift-ring-builder account.builder add r1z2-<account-server-2>:6202/sdb1 1
|
||||
swift-ring-builder account.builder rebalance
|
||||
|
||||
You need to replace the values of <account-server-1>,
|
||||
<account-server-2>, etc. with the IP addresses of the account
|
||||
servers used in your setup. You can have as many account servers as
|
||||
you need. All account servers are assumed to be listening on port
|
||||
6002, and have a storage device called "sdb1" (this is a directory
|
||||
6202, and have a storage device called "sdb1" (this is a directory
|
||||
name created under /drives when we setup the account server). The
|
||||
"z1", "z2", etc. designate zones, and you can choose whether you
|
||||
put devices in the same or different zones. The "r1" designates
|
||||
|
@ -234,9 +286,11 @@ using the format `regex_pattern_X = regex_expression`, where `X` is a number.
|
|||
This script has been tested on Ubuntu 10.04 and Ubuntu 12.04, so if you are
|
||||
using a different distro or OS, some care should be taken before using in production.
|
||||
|
||||
--------------
|
||||
Cluster Health
|
||||
--------------
|
||||
.. _dispersion_report:
|
||||
|
||||
-----------------
|
||||
Dispersion Report
|
||||
-----------------
|
||||
|
||||
There is a swift-dispersion-report tool for measuring overall cluster health.
|
||||
This is accomplished by checking if a set of deliberately distributed
|
||||
|
@ -537,7 +591,7 @@ JSON-formatted response::
|
|||
{"async_pending": 0}
|
||||
|
||||
|
||||
Note that the default port for the object server is 6000, except on a
|
||||
Note that the default port for the object server is 6200, except on a
|
||||
Swift All-In-One installation, which uses 6010, 6020, 6030, and 6040.
|
||||
|
||||
The following metrics and telemetry are currently exposed:
|
||||
|
|
|
@ -117,9 +117,9 @@ The Swift default value for max_file_size (when not present) is 5368709122.
|
|||
For example an Apache2 serving as a web front end of a storage node::
|
||||
|
||||
#Object Service
|
||||
NameVirtualHost *:6000
|
||||
Listen 6000
|
||||
<VirtualHost *:6000>
|
||||
NameVirtualHost *:6200
|
||||
Listen 6200
|
||||
<VirtualHost *:6200>
|
||||
ServerName object-server
|
||||
WSGIDaemonProcess object-server processes=5 threads=1
|
||||
WSGIProcessGroup object-server
|
||||
|
@ -131,9 +131,9 @@ For example an Apache2 serving as a web front end of a storage node::
|
|||
</VirtualHost>
|
||||
|
||||
#Container Service
|
||||
NameVirtualHost *:6001
|
||||
Listen 6001
|
||||
<VirtualHost *:6001>
|
||||
NameVirtualHost *:6201
|
||||
Listen 6201
|
||||
<VirtualHost *:6201>
|
||||
ServerName container-server
|
||||
WSGIDaemonProcess container-server processes=5 threads=1
|
||||
WSGIProcessGroup container-server
|
||||
|
@ -145,9 +145,9 @@ For example an Apache2 serving as a web front end of a storage node::
|
|||
</VirtualHost>
|
||||
|
||||
#Account Service
|
||||
NameVirtualHost *:6002
|
||||
Listen 6002
|
||||
<VirtualHost *:6002>
|
||||
NameVirtualHost *:6202
|
||||
Listen 6202
|
||||
<VirtualHost *:6202>
|
||||
ServerName account-server
|
||||
WSGIDaemonProcess account-server processes=5 threads=1
|
||||
WSGIProcessGroup account-server
|
||||
|
|
|
@ -7,8 +7,8 @@ metadata by using the Object Storage API, which is implemented as a set
|
|||
of Representational State Transfer (REST) web services.
|
||||
|
||||
For an introduction to OpenStack Object Storage, see `Object
|
||||
Storage <http://docs.openstack.org/admin-guide-cloud/objectstorage.html>`
|
||||
in the *OpenStack Cloud Administrator Guide*.
|
||||
Storage <http://docs.openstack.org/admin-guide/objectstorage.html>`
|
||||
in the *OpenStack Administrator Guide*.
|
||||
|
||||
You use the HTTPS (SSL) protocol to interact with Object Storage, and
|
||||
you use standard HTTP calls to perform API operations. You can also use
|
||||
|
|
|
@ -26,6 +26,7 @@ Application Bindings
|
|||
* `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.
|
||||
* `javaswift <http://javaswift.org/>`_ - Collection of Java tools for Swift
|
||||
|
||||
Authentication
|
||||
--------------
|
||||
|
@ -106,7 +107,6 @@ Other
|
|||
|
||||
* `Glance <https://github.com/openstack/glance>`_ - Provides services for discovering, registering, and retrieving virtual machine images (for OpenStack Compute [Nova], for example).
|
||||
* `Better Staticweb <https://github.com/CloudVPS/better-staticweb>`_ - Makes swift containers accessible by default.
|
||||
* `Swiftsync <https://github.com/stackforge/swiftsync>`_ - A massive syncer between two swift clusters.
|
||||
* `Django Swiftbrowser <https://github.com/cschwede/django-swiftbrowser>`_ - Simple Django web app to access OpenStack Swift.
|
||||
* `Swift-account-stats <https://github.com/enovance/swift-account-stats>`_ - Swift-account-stats is a tool to report statistics on Swift usage at tenant and global levels.
|
||||
* `PyECLib <https://bitbucket.org/kmgreen2/pyeclib>`_ - High Level Erasure Code library used by Swift
|
||||
|
|
|
@ -31,6 +31,7 @@ import os
|
|||
from swift import __version__
|
||||
import subprocess
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
|
@ -160,8 +161,12 @@ modindex_common_prefix = ['swift.']
|
|||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
|
||||
"-n1"]
|
||||
html_last_updated_fmt = subprocess.Popen(
|
||||
git_cmd, stdout=subprocess.PIPE).communicate()[0]
|
||||
try:
|
||||
html_last_updated_fmt = subprocess.Popen(
|
||||
git_cmd, stdout=subprocess.PIPE).communicate()[0]
|
||||
except OSError:
|
||||
warnings.warn('Cannot get last updated time from git repository. '
|
||||
'Not setting "html_last_updated_fmt".')
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
|
|
|
@ -24,7 +24,7 @@ The supported headers are,
|
|||
+------------------------------------------------+------------------------------+
|
||||
| X-Container-Meta-Access-Control-Expose-Headers | Headers exposed to the user |
|
||||
| | agent (e.g. browser) in the |
|
||||
| | the actual request response. |
|
||||
| | actual request response. |
|
||||
| | Space separated. |
|
||||
+------------------------------------------------+------------------------------+
|
||||
|
||||
|
|
|
@ -151,11 +151,6 @@ service a request for any disk, and a slow I/O request blocks the eventlet hub,
|
|||
a single slow disk can impair an entire storage node. This also prevents
|
||||
object servers from fully utilizing all their disks during heavy load.
|
||||
|
||||
The :ref:`threads_per_disk <object-server-options>` option was one way to
|
||||
address this, but came with severe performance overhead which was worse
|
||||
than the benefit of I/O isolation. Any clusters using threads_per_disk should
|
||||
switch to using `servers_per_port`.
|
||||
|
||||
Another way to get full I/O isolation is to give each disk on a storage node a
|
||||
different port in the storage policy rings. Then set the
|
||||
:ref:`servers_per_port <object-server-default-options>`
|
||||
|
@ -169,18 +164,18 @@ Here's an example (abbreviated) old-style ring (2 node cluster with 2 disks
|
|||
each)::
|
||||
|
||||
Devices: id region zone ip address port replication ip replication port name
|
||||
0 1 1 1.1.0.1 6000 1.1.0.1 6000 d1
|
||||
1 1 1 1.1.0.1 6000 1.1.0.1 6000 d2
|
||||
2 1 2 1.1.0.2 6000 1.1.0.2 6000 d3
|
||||
3 1 2 1.1.0.2 6000 1.1.0.2 6000 d4
|
||||
0 1 1 1.1.0.1 6200 1.1.0.1 6200 d1
|
||||
1 1 1 1.1.0.1 6200 1.1.0.1 6200 d2
|
||||
2 1 2 1.1.0.2 6200 1.1.0.2 6200 d3
|
||||
3 1 2 1.1.0.2 6200 1.1.0.2 6200 d4
|
||||
|
||||
And here's the same ring set up for `servers_per_port`::
|
||||
|
||||
Devices: id region zone ip address port replication ip replication port name
|
||||
0 1 1 1.1.0.1 6000 1.1.0.1 6000 d1
|
||||
1 1 1 1.1.0.1 6001 1.1.0.1 6001 d2
|
||||
2 1 2 1.1.0.2 6000 1.1.0.2 6000 d3
|
||||
3 1 2 1.1.0.2 6001 1.1.0.2 6001 d4
|
||||
0 1 1 1.1.0.1 6200 1.1.0.1 6200 d1
|
||||
1 1 1 1.1.0.1 6201 1.1.0.1 6201 d2
|
||||
2 1 2 1.1.0.2 6200 1.1.0.2 6200 d3
|
||||
3 1 2 1.1.0.2 6201 1.1.0.2 6201 d4
|
||||
|
||||
When migrating from normal to `servers_per_port`, perform these steps in order:
|
||||
|
||||
|
@ -195,7 +190,7 @@ When migrating from normal to `servers_per_port`, perform these steps in order:
|
|||
|
||||
#. Push out new rings that actually have different ports per disk on each
|
||||
server. One of the ports in the new ring should be the same as the port
|
||||
used in the old ring ("6000" in the example above). This will cover
|
||||
used in the old ring ("6200" in the example above). This will cover
|
||||
existing proxy-server processes who haven't loaded the new ring yet. They
|
||||
can still talk to any storage node regardless of whether or not that
|
||||
storage node has loaded the ring and started object-server processes on the
|
||||
|
@ -422,7 +417,7 @@ mount_check true Whether or not check if the devices
|
|||
mounted to prevent accidentally writing
|
||||
to the root device
|
||||
bind_ip 0.0.0.0 IP Address for server to bind to
|
||||
bind_port 6000 Port for server to bind to
|
||||
bind_port 6200 Port for server to bind to
|
||||
bind_timeout 30 Seconds to attempt bind before giving up
|
||||
backlog 4096 Maximum number of allowed pending
|
||||
connections
|
||||
|
@ -489,12 +484,14 @@ log_statsd_sample_rate_factor 1.0
|
|||
log_statsd_metric_prefix
|
||||
eventlet_debug false If true, turn on debug logging for
|
||||
eventlet
|
||||
fallocate_reserve 0 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. This is useful for
|
||||
systems that behave badly when they
|
||||
completely run out of space; you can
|
||||
fallocate_reserve 1% You can set fallocate_reserve to the
|
||||
number of bytes or percentage of disk
|
||||
space you'd like fallocate to reserve,
|
||||
whether there is space for the given
|
||||
file size or not. Percentage will be used
|
||||
if the value ends with a '%'. This is
|
||||
useful for systems that behave badly when
|
||||
they completely run out of space; you can
|
||||
make the services pretend they're out of
|
||||
space early.
|
||||
conn_timeout 0.5 Time to wait while attempting to connect
|
||||
|
@ -547,15 +544,6 @@ allowed_headers Content-Disposition, Comma separated list of he
|
|||
X-Static-Large-Object Content-Type, etag, Content-Length, or deleted
|
||||
auto_create_account_prefix . Prefix used when automatically
|
||||
creating accounts.
|
||||
threads_per_disk 0 Size of the per-disk thread pool
|
||||
used for performing disk I/O. The
|
||||
default of 0 means to not use a
|
||||
per-disk thread pool.
|
||||
This option is no longer
|
||||
recommended and the
|
||||
:ref:`servers_per_port
|
||||
<server-per-port-configuration>`
|
||||
should be used instead.
|
||||
replication_server Configure parameter for creating
|
||||
specific server. To handle all verbs,
|
||||
including replication verbs, do not
|
||||
|
@ -569,15 +557,15 @@ replication_server Configure parameter for cr
|
|||
should not specify any value for
|
||||
"replication_server".
|
||||
replication_concurrency 4 Set to restrict the number of
|
||||
concurrent incoming REPLICATION
|
||||
concurrent incoming SSYNC
|
||||
requests; set to 0 for unlimited
|
||||
replication_one_per_device True Restricts incoming REPLICATION
|
||||
replication_one_per_device True Restricts incoming SSYNC
|
||||
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
|
||||
allow multiple SSYNC
|
||||
requests (up to the above
|
||||
replication_concurrency setting)
|
||||
per device.
|
||||
|
@ -589,9 +577,9 @@ replication_failure_threshold 100 The number of subrequest f
|
|||
replication_failure_ratio is
|
||||
checked
|
||||
replication_failure_ratio 1.0 If the value of failures /
|
||||
successes of REPLICATION
|
||||
successes of SSYNC
|
||||
subrequests exceeds this ratio,
|
||||
the overall REPLICATION request
|
||||
the overall SSYNC request
|
||||
will be aborted
|
||||
splice no Use splice() for zero-copy object
|
||||
GETs. This requires Linux kernel
|
||||
|
@ -738,6 +726,11 @@ concurrency 1 The number of parallel processes
|
|||
zero_byte_files_per_second 50
|
||||
object_size_stats
|
||||
recon_cache_path /var/cache/swift Path to recon cache
|
||||
rsync_tempfile_timeout auto Time elapsed in seconds before rsync
|
||||
tempfiles will be unlinked. Config value
|
||||
of "auto" try to use object-replicator's
|
||||
rsync_timeout + 900 or fallback to 86400
|
||||
(1 day).
|
||||
=========================== =================== ==========================================
|
||||
|
||||
------------------------------
|
||||
|
@ -760,7 +753,7 @@ mount_check true Whether or not check if the devices
|
|||
mounted to prevent accidentally writing
|
||||
to the root device
|
||||
bind_ip 0.0.0.0 IP Address for server to bind to
|
||||
bind_port 6001 Port for server to bind to
|
||||
bind_port 6201 Port for server to bind to
|
||||
bind_timeout 30 Seconds to attempt bind before giving up
|
||||
backlog 4096 Maximum number of allowed pending
|
||||
connections
|
||||
|
@ -804,13 +797,16 @@ log_statsd_default_sample_rate 1.0
|
|||
log_statsd_sample_rate_factor 1.0
|
||||
log_statsd_metric_prefix
|
||||
eventlet_debug false If true, turn on debug logging for eventlet
|
||||
fallocate_reserve 0 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.
|
||||
This is useful for systems that behave badly
|
||||
when they completely run out of space; you can
|
||||
make the services pretend they're out of space
|
||||
early.
|
||||
fallocate_reserve 1% You can set fallocate_reserve to the
|
||||
number of bytes or percentage of disk
|
||||
space you'd like fallocate to reserve,
|
||||
whether there is space for the given
|
||||
file size or not. Percentage will be used
|
||||
if the value ends with a '%'. This is
|
||||
useful for systems that behave badly when
|
||||
they completely run out of space; you can
|
||||
make the services pretend they're out of
|
||||
space early.
|
||||
db_preallocation off 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
|
||||
|
@ -971,7 +967,7 @@ mount_check true Whether or not check if the devices
|
|||
mounted to prevent accidentally writing
|
||||
to the root device
|
||||
bind_ip 0.0.0.0 IP Address for server to bind to
|
||||
bind_port 6002 Port for server to bind to
|
||||
bind_port 6202 Port for server to bind to
|
||||
bind_timeout 30 Seconds to attempt bind before giving up
|
||||
backlog 4096 Maximum number of allowed pending
|
||||
connections
|
||||
|
@ -1019,13 +1015,16 @@ log_statsd_default_sample_rate 1.0
|
|||
log_statsd_sample_rate_factor 1.0
|
||||
log_statsd_metric_prefix
|
||||
eventlet_debug false If true, turn on debug logging for eventlet
|
||||
fallocate_reserve 0 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.
|
||||
This is useful for systems that behave badly
|
||||
when they completely run out of space; you can
|
||||
make the services pretend they're out of space
|
||||
early.
|
||||
fallocate_reserve 1% You can set fallocate_reserve to the
|
||||
number of bytes or percentage of disk
|
||||
space you'd like fallocate to reserve,
|
||||
whether there is space for the given
|
||||
file size or not. Percentage will be used
|
||||
if the value ends with a '%'. This is
|
||||
useful for systems that behave badly when
|
||||
they completely run out of space; you can
|
||||
make the services pretend they're out of
|
||||
space early.
|
||||
=============================== ========== =============================================
|
||||
|
||||
[account-server]
|
||||
|
@ -1271,9 +1270,9 @@ expiring_objects_account_name expiring_objects
|
|||
|
||||
[proxy-server]
|
||||
|
||||
============================ =============== =============================
|
||||
============================ =============== =====================================
|
||||
Option Default Description
|
||||
---------------------------- --------------- -----------------------------
|
||||
---------------------------- --------------- -------------------------------------
|
||||
use Entry point for paste.deploy for
|
||||
the proxy server. For most
|
||||
cases, this should be
|
||||
|
@ -1325,11 +1324,7 @@ object_post_as_copy true Set object_post_as_copy = false
|
|||
the metadata changes are stored
|
||||
anew and the original data file
|
||||
is kept in place. This makes for
|
||||
quicker posts; but since the
|
||||
container metadata isn't updated
|
||||
in this mode, features like
|
||||
container sync won't be able to
|
||||
sync posts.
|
||||
quicker posts.
|
||||
account_autocreate false If set to 'true' authorized
|
||||
accounts that do not yet exist
|
||||
within the Swift cluster will
|
||||
|
@ -1367,7 +1362,37 @@ swift_owner_headers <see the sample These are the headers whose
|
|||
headers> up to the auth system in use,
|
||||
but usually indicates
|
||||
administrative responsibilities.
|
||||
============================ =============== =============================
|
||||
sorting_method shuffle Storage nodes can be chosen at
|
||||
random (shuffle), 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.
|
||||
timing_expiry 300 If the "timing" sorting_method is
|
||||
used, the timings will only be valid
|
||||
for the number of seconds configured
|
||||
by timing_expiry.
|
||||
concurrent_gets off Use replica count number of
|
||||
threads concurrently during a
|
||||
GET/HEAD and return with the
|
||||
first successful response. In
|
||||
the EC case, this parameter only
|
||||
effects an EC HEAD as an EC GET
|
||||
behaves differently.
|
||||
concurrency_timeout conn_timeout This parameter controls how long
|
||||
to wait before firing off the
|
||||
next concurrent_get thread. A
|
||||
value of 0 would we fully concurrent
|
||||
any other number will stagger the
|
||||
firing of the threads. This number
|
||||
should be between 0 and node_timeout.
|
||||
The default is conn_timeout (0.5).
|
||||
============================ =============== =====================================
|
||||
|
||||
[tempauth]
|
||||
|
||||
|
|
|
@ -9,63 +9,70 @@ Coding Guidelines
|
|||
For the most part we try to follow PEP 8 guidelines which can be viewed
|
||||
here: http://www.python.org/dev/peps/pep-0008/
|
||||
|
||||
There is a useful pep8 command line tool for checking files for pep8
|
||||
compliance which can be installed with ``easy_install pep8``.
|
||||
|
||||
------------------
|
||||
Testing Guidelines
|
||||
------------------
|
||||
|
||||
Swift has a comprehensive suite of tests that are run on all submitted code,
|
||||
and it is recommended that developers execute the tests themselves to
|
||||
catch regressions early. Developers are also expected to keep the
|
||||
test suite up-to-date with any submitted code changes.
|
||||
Swift has a comprehensive suite of tests and pep8 checks that are run on all
|
||||
submitted code, and it is recommended that developers execute the tests
|
||||
themselves to catch regressions early. Developers are also expected to keep
|
||||
the test suite up-to-date with any submitted code changes.
|
||||
|
||||
Swift's suite of unit tests can be executed in an isolated environment
|
||||
Swift's tests and pep8 checks can be executed in an isolated environment
|
||||
with Tox: http://tox.testrun.org/
|
||||
|
||||
To execute the unit tests:
|
||||
To execute the tests:
|
||||
|
||||
* Install Tox:
|
||||
* Install Tox::
|
||||
|
||||
- `pip install tox`
|
||||
pip install tox
|
||||
|
||||
* If you do not have python 2.6 installed (as in 12.04):
|
||||
* Generate list of distribution packages to install for testing::
|
||||
|
||||
- Add `export TOXENV=py27,pep8` to your `~/.bashrc`
|
||||
tox -e bindep
|
||||
|
||||
- `. ~/.bashrc`
|
||||
Now install these packages using your distribution package manager
|
||||
like apt-get, dnf, yum, or zypper.
|
||||
|
||||
* Run Tox from the root of the swift repo:
|
||||
* Run Tox from the root of the swift repo::
|
||||
|
||||
- `tox`
|
||||
tox
|
||||
|
||||
Remarks:
|
||||
If you installed using: `cd ~/swift; sudo python setup.py develop`,
|
||||
you may need to do: `cd ~/swift; sudo chown -R ${USER}:${USER} swift.egg-info`
|
||||
prior to running tox.
|
||||
If you installed using ``cd ~/swift; sudo python setup.py develop``, you may
|
||||
need to do ``cd ~/swift; sudo chown -R ${USER}:${USER} swift.egg-info`` prior
|
||||
to running tox.
|
||||
|
||||
* Optionally, run only specific tox builds:
|
||||
* By default ``tox`` will run all of the unit test and pep8 checks listed in
|
||||
the ``tox.ini`` file ``envlist`` option. A subset of the test environments
|
||||
can be specified on the tox command line or by setting the ``TOXENV``
|
||||
environment variable. For example, to run only the pep8 checks and python2.7
|
||||
unit tests use::
|
||||
|
||||
- `tox -e pep8,py27`
|
||||
tox -e pep8,py27
|
||||
|
||||
or::
|
||||
|
||||
TOXENV=py27,pep8 tox
|
||||
|
||||
.. note::
|
||||
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, but you may need to run `tox --recreate` for this to take effect
|
||||
passed, but you may need to run ``tox --recreate`` for this to take effect
|
||||
after upgrading from tox<2.0.0.
|
||||
|
||||
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`
|
||||
or remove the `.tox` directory to force tox to recreate the dependency list.
|
||||
Also, if you ever encounter DistributionNotFound, try to use ``tox
|
||||
--recreate`` 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
|
||||
other running Swift cluster using the command:
|
||||
Swift's functional tests may be executed against a :doc:`development_saio` or
|
||||
other running Swift cluster using the command::
|
||||
|
||||
- `tox -e func`
|
||||
tox -e func
|
||||
|
||||
The endpoint and authorization credentials to be used by functional tests
|
||||
should be configured in the ``test.conf`` file as described in the section
|
||||
|
|
|
@ -591,3 +591,17 @@ doesn't work, here are some good starting places to look for issues:
|
|||
you check that you can ``GET`` account, use ``sudo service memcached status``
|
||||
and check if memcache is running. If memcache is not running, start it using
|
||||
``sudo service memcached start``. Once memcache is running, rerun ``GET`` account.
|
||||
|
||||
------------
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
Listed here are some "gotcha's" that you may run into when using or testing your SAIO:
|
||||
|
||||
#. fallocate_reserve - in most cases a SAIO doesn't have a very large XFS partition
|
||||
so having fallocate enabled and fallocate_reserve set can cause issues, specifically
|
||||
when trying to run the functional tests. For this reason fallocate has been turned
|
||||
off on the object-servers in the SAIO. If you want to play with the fallocate_reserve
|
||||
settings then know that functional tests will fail unless you change the max_file_size
|
||||
constraint to something more reasonable then the default (5G). Ideally you'd make
|
||||
it 1/4 of your XFS file system size so the tests can pass.
|
||||
|
|
|
@ -15,6 +15,7 @@ Swift is written in Python and has these dependencies:
|
|||
* rsync 3.0
|
||||
* The Python packages listed in `the requirements file <https://github.com/openstack/swift/blob/master/requirements.txt>`_
|
||||
* Testing additionally requires `the test dependencies <https://github.com/openstack/swift/blob/master/test-requirements.txt>`_
|
||||
* Testing requires `these distribution packages <https://github.com/openstack/swift/blob/master/other-requirements.txt>`_
|
||||
|
||||
There is no current support for Python 3.
|
||||
|
||||
|
|
|
@ -6,6 +6,13 @@ Please refer to the latest official
|
|||
`OpenStack Installation Guides <http://docs.openstack.org/#install-guides>`_
|
||||
for the most up-to-date documentation.
|
||||
|
||||
Object Storage installation guide for OpenStack Mitaka
|
||||
------------------------------------------------------
|
||||
|
||||
* `openSUSE Leap 42.1 and SUSE Linux Enterprise Server 12 SP1 <http://docs.openstack.org/mitaka/install-guide-obs/swift.html>`_
|
||||
* `RHEL 7, CentOS 7 <http://docs.openstack.org/mitaka/install-guide-rdo/swift.html>`_
|
||||
* `Ubuntu 14.04 <http://docs.openstack.org/mitaka/install-guide-ubuntu/swift.html>`_
|
||||
|
||||
Object Storage installation guide for OpenStack Liberty
|
||||
-------------------------------------------------------
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ Overview and Concepts
|
|||
crossdomain
|
||||
overview_erasure_code
|
||||
overview_backing_store
|
||||
ring_background
|
||||
associated_projects
|
||||
|
||||
Developer Documentation
|
||||
|
|
|
@ -103,6 +103,7 @@ LE :ref:`list_endpoints`
|
|||
KS :ref:`keystoneauth`
|
||||
RL :ref:`ratelimit`
|
||||
VW :ref:`versioned_writes`
|
||||
SSC :ref:`copy`
|
||||
======================= =============================
|
||||
|
||||
|
||||
|
|
|
@ -187,6 +187,15 @@ Recon
|
|||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _copy:
|
||||
|
||||
Server Side Copy
|
||||
================
|
||||
|
||||
.. automodule:: swift.common.middleware.copy
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Static Large Objects
|
||||
====================
|
||||
|
||||
|
|
|
@ -2,15 +2,53 @@
|
|||
Identifying issues and resolutions
|
||||
==================================
|
||||
|
||||
Is the system up?
|
||||
-----------------
|
||||
|
||||
If you have a report that Swift is down, perform the following basic checks:
|
||||
|
||||
#. Run swift functional tests.
|
||||
|
||||
#. From a server in your data center, use ``curl`` to check ``/healthcheck``
|
||||
(see below).
|
||||
|
||||
#. If you have a monitoring system, check your monitoring system.
|
||||
|
||||
#. Check your hardware load balancers infrastructure.
|
||||
|
||||
#. Run swift-recon on a proxy node.
|
||||
|
||||
Functional tests usage
|
||||
-----------------------
|
||||
|
||||
We would recommend that you set up the functional tests to run against your
|
||||
production system. Run regularly this can be a useful tool to validate
|
||||
that the system is configured correctly. In addition, it can provide
|
||||
early warning about failures in your system (if the functional tests stop
|
||||
working, user applications will also probably stop working).
|
||||
|
||||
A script for running the function tests is located in ``swift/.functests``.
|
||||
|
||||
|
||||
External monitoring
|
||||
-------------------
|
||||
|
||||
We use pingdom.com to monitor the external Swift API. We suggest the
|
||||
following:
|
||||
|
||||
- Do a GET on ``/healthcheck``
|
||||
|
||||
- Create a container, make it public (x-container-read:
|
||||
.r*,.rlistings), create a small file in the container; do a GET
|
||||
on the object
|
||||
|
||||
Diagnose: General approach
|
||||
--------------------------
|
||||
|
||||
- Look at service status in your monitoring system.
|
||||
|
||||
- In addition to system monitoring tools and issue logging by users,
|
||||
swift errors will often result in log entries in the ``/var/log/swift``
|
||||
files: ``proxy.log``, ``server.log`` and ``background.log`` (see:``Swift
|
||||
logs``).
|
||||
swift errors will often result in log entries (see :ref:`swift_logs`).
|
||||
|
||||
- Look at any logs your deployment tool produces.
|
||||
|
||||
|
@ -33,22 +71,24 @@ Diagnose: Swift-dispersion-report
|
|||
---------------------------------
|
||||
|
||||
The swift-dispersion-report is a useful tool to gauge the general
|
||||
health of the system. Configure the ``swift-dispersion`` report for
|
||||
100% coverage. The dispersion report regularly monitors
|
||||
these and gives a report of the amount of objects/containers are still
|
||||
available as well as how many copies of them are also there.
|
||||
health of the system. Configure the ``swift-dispersion`` report to cover at
|
||||
a minimum every disk drive in your system (usually 1% coverage).
|
||||
See :ref:`dispersion_report` for details of how to configure and
|
||||
use the dispersion reporting tool.
|
||||
|
||||
The dispersion-report output is logged on the first proxy of the first
|
||||
AZ or each system (proxy with the monitoring role) under
|
||||
``/var/log/swift/swift-dispersion-report.log``.
|
||||
The ``swift-dispersion-report`` tool can take a long time to run, especially
|
||||
if any servers are down. We suggest you run it regularly
|
||||
(e.g., in a cron job) and save the results. This makes it easy to refer
|
||||
to the last report without having to wait for a long-running command
|
||||
to complete.
|
||||
|
||||
Diagnose: Is swift running?
|
||||
---------------------------
|
||||
Diagnose: Is system responding to /healthcheck?
|
||||
-----------------------------------------------
|
||||
|
||||
When you want to establish if a swift endpoint is running, run ``curl -k``
|
||||
against either: https://*[REPLACEABLE]*./healthcheck OR
|
||||
https:*[REPLACEABLE]*.crossdomain.xml
|
||||
against https://*[ENDPOINT]*/healthcheck.
|
||||
|
||||
.. _swift_logs:
|
||||
|
||||
Diagnose: Interpreting messages in ``/var/log/swift/`` files
|
||||
------------------------------------------------------------
|
||||
|
@ -70,25 +110,20 @@ The following table lists known issues:
|
|||
- **Signature**
|
||||
- **Issue**
|
||||
- **Steps to take**
|
||||
* - /var/log/syslog
|
||||
- kernel: [] hpsa .... .... .... has check condition: unknown type:
|
||||
Sense: 0x5, ASC: 0x20, ASC Q: 0x0 ....
|
||||
- An unsupported command was issued to the storage hardware
|
||||
- Understood to be a benign monitoring issue, ignore
|
||||
* - /var/log/syslog
|
||||
- kernel: [] sd .... [csbu:sd...] Sense Key: Medium Error
|
||||
- Suggests disk surface issues
|
||||
- Run swift diagnostics on the target node to check for disk errors,
|
||||
- Run ``swift-drive-audit`` on the target node to check for disk errors,
|
||||
repair disk errors
|
||||
* - /var/log/syslog
|
||||
- kernel: [] sd .... [csbu:sd...] Sense Key: Hardware Error
|
||||
- Suggests storage hardware issues
|
||||
- Run swift diagnostics on the target node to check for disk failures,
|
||||
- Run diagnostics on the target node to check for disk failures,
|
||||
replace failed disks
|
||||
* - /var/log/syslog
|
||||
- kernel: [] .... I/O error, dev sd.... ,sector ....
|
||||
-
|
||||
- Run swift diagnostics on the target node to check for disk errors
|
||||
- Run diagnostics on the target node to check for disk errors
|
||||
* - /var/log/syslog
|
||||
- pound: NULL get_thr_arg
|
||||
- Multiple threads woke up
|
||||
|
@ -96,58 +131,60 @@ The following table lists known issues:
|
|||
* - /var/log/swift/proxy.log
|
||||
- .... ERROR .... ConnectionTimeout ....
|
||||
- A storage node is not responding in a timely fashion
|
||||
- Run swift diagnostics on the target node to check for node down,
|
||||
node unconfigured, storage off-line or network issues between the
|
||||
- Check if node is down, not running Swift,
|
||||
unconfigured, storage off-line or for network issues between the
|
||||
proxy and non responding node
|
||||
* - /var/log/swift/proxy.log
|
||||
- proxy-server .... HTTP/1.0 500 ....
|
||||
- A proxy server has reported an internal server error
|
||||
- Run swift diagnostics on the target node to check for issues
|
||||
- Examine the logs for any errors at the time the error was reported to
|
||||
attempt to understand the cause of the error.
|
||||
* - /var/log/swift/server.log
|
||||
- .... ERROR .... ConnectionTimeout ....
|
||||
- A storage server is not responding in a timely fashion
|
||||
- Run swift diagnostics on the target node to check for a node or
|
||||
service, down, unconfigured, storage off-line or network issues
|
||||
between the two nodes
|
||||
- Check if node is down, not running Swift,
|
||||
unconfigured, storage off-line or for network issues between the
|
||||
server and non responding node
|
||||
* - /var/log/swift/server.log
|
||||
- .... ERROR .... Remote I/O error: '/srv/node/disk....
|
||||
- A storage device is not responding as expected
|
||||
- Run swift diagnostics and check the filesystem named in the error
|
||||
for corruption (unmount & xfs_repair)
|
||||
- Run ``swift-drive-audit`` and check the filesystem named in the error
|
||||
for corruption (unmount & xfs_repair). Check if the filesystem
|
||||
is mounted and working.
|
||||
* - /var/log/swift/background.log
|
||||
- object-server ERROR container update failed .... Connection refused
|
||||
- Peer node is not responding
|
||||
- Check status of the network and peer node
|
||||
- A container server node could not be contacted
|
||||
- Check if node is down, not running Swift,
|
||||
unconfigured, storage off-line or for network issues between the
|
||||
server and non responding node
|
||||
* - /var/log/swift/background.log
|
||||
- object-updater ERROR with remote .... ConnectionTimeout
|
||||
-
|
||||
- Check status of the network and peer node
|
||||
- The remote container server is busy
|
||||
- If the container is very large, some errors updating it can be
|
||||
expected. However, this error can also occur if there is a networking
|
||||
issue.
|
||||
* - /var/log/swift/background.log
|
||||
- account-reaper STDOUT: .... error: ECONNREFUSED
|
||||
- Network connectivity issue
|
||||
- Resolve network issue and re-run diagnostics
|
||||
- Network connectivity issue or the target server is down.
|
||||
- Resolve network issue or reboot the target server
|
||||
* - /var/log/swift/background.log
|
||||
- .... ERROR .... ConnectionTimeout
|
||||
- A storage server is not responding in a timely fashion
|
||||
- Run swift diagnostics on the target node to check for a node
|
||||
or service, down, unconfigured, storage off-line or network issues
|
||||
between the two nodes
|
||||
- The target server may be busy. However, this error can also occur if
|
||||
there is a networking issue.
|
||||
* - /var/log/swift/background.log
|
||||
- .... ERROR syncing .... Timeout
|
||||
- A storage server is not responding in a timely fashion
|
||||
- Run swift diagnostics on the target node to check for a node
|
||||
or service, down, unconfigured, storage off-line or network issues
|
||||
between the two nodes
|
||||
- A timeout occurred syncing data to another node.
|
||||
- The target server may be busy. However, this error can also occur if
|
||||
there is a networking issue.
|
||||
* - /var/log/swift/background.log
|
||||
- .... ERROR Remote drive not mounted ....
|
||||
- A storage server disk is unavailable
|
||||
- Run swift diagnostics on the target node to check for a node or
|
||||
service, failed or unmounted disk on the target, or a network issue
|
||||
- Repair and remount the file system (on the remote node)
|
||||
* - /var/log/swift/background.log
|
||||
- object-replicator .... responded as unmounted
|
||||
- A storage server disk is unavailable
|
||||
- Run swift diagnostics on the target node to check for a node or
|
||||
service, failed or unmounted disk on the target, or a network issue
|
||||
- Repair and remount the file system (on the remote node)
|
||||
* - /var/log/swift/\*.log
|
||||
- STDOUT: EXCEPTION IN
|
||||
- A unexpected error occurred
|
||||
|
@ -157,19 +194,14 @@ The following table lists known issues:
|
|||
* - /var/log/rsyncd.log
|
||||
- rsync: mkdir "/disk....failed: No such file or directory....
|
||||
- A local storage server disk is unavailable
|
||||
- Run swift diagnostics on the node to check for a failed or
|
||||
- Run diagnostics on the node to check for a failed or
|
||||
unmounted disk
|
||||
* - /var/log/swift*
|
||||
- Exception: Could not bind to 0.0.0.0:600xxx
|
||||
- Exception: Could not bind to 0.0.0.0:6xxx
|
||||
- Possible Swift process restart issue. This indicates an old swift
|
||||
process is still running.
|
||||
- Run swift diagnostics, if some swift services are reported down,
|
||||
- Restart Swift services. If some swift services are reported down,
|
||||
check if they left residual process behind.
|
||||
* - /var/log/rsyncd.log
|
||||
- rsync: recv_generator: failed to stat "/disk....." (in object)
|
||||
failed: Not a directory (20)
|
||||
- Swift directory structure issues
|
||||
- Run swift diagnostics on the node to check for issues
|
||||
|
||||
Diagnose: Parted reports the backup GPT table is corrupt
|
||||
--------------------------------------------------------
|
||||
|
@ -188,7 +220,7 @@ Diagnose: Parted reports the backup GPT table is corrupt
|
|||
|
||||
OK/Cancel?
|
||||
|
||||
To fix, go to: Fix broken GPT table (broken disk partition)
|
||||
To fix, go to :ref:`fix_broken_gpt_table`
|
||||
|
||||
|
||||
Diagnose: Drives diagnostic reports a FS label is not acceptable
|
||||
|
@ -240,9 +272,10 @@ Diagnose: Failed LUNs
|
|||
|
||||
.. note::
|
||||
|
||||
The HPE Helion Public Cloud uses direct attach SmartArry
|
||||
The HPE Helion Public Cloud uses direct attach SmartArray
|
||||
controllers/drives. The information here is specific to that
|
||||
environment.
|
||||
environment. The hpacucli utility mentioned here may be called
|
||||
hpssacli in your environment.
|
||||
|
||||
The ``swift_diagnostics`` mount checks may return a warning that a LUN has
|
||||
failed, typically accompanied by DriveAudit check failures and device
|
||||
|
@ -254,7 +287,7 @@ the procedure to replace the disk.
|
|||
|
||||
Otherwise the lun can be re-enabled as follows:
|
||||
|
||||
#. Generate a hpssacli diagnostic report. This report allows the swift
|
||||
#. Generate a hpssacli diagnostic report. This report allows the DC
|
||||
team to troubleshoot potential cabling or hardware issues so it is
|
||||
imperative that you run it immediately when troubleshooting a failed
|
||||
LUN. You will come back later and grep this file for more details, but
|
||||
|
@ -262,8 +295,7 @@ Otherwise the lun can be re-enabled as follows:
|
|||
|
||||
.. code::
|
||||
|
||||
sudo hpssacli controller all diag file=/tmp/hpacu.diag ris=on \
|
||||
xml=off zip=off
|
||||
sudo hpssacli controller all diag file=/tmp/hpacu.diag ris=on xml=off zip=off
|
||||
|
||||
Export the following variables using the below instructions before
|
||||
proceeding further.
|
||||
|
@ -317,8 +349,7 @@ proceeding further.
|
|||
|
||||
.. code::
|
||||
|
||||
sudo hpssacli controller slot=1 ld ${LDRIVE} show detail \
|
||||
grep -i "Disk Name"
|
||||
sudo hpssacli controller slot=1 ld ${LDRIVE} show detail | grep -i "Disk Name"
|
||||
|
||||
#. Export the device name variable from the preceding command (example:
|
||||
/dev/sdk):
|
||||
|
@ -396,6 +427,8 @@ proceeding further.
|
|||
should be checked. For example, log a DC ticket to check the sas cables
|
||||
between the drive and the expander.
|
||||
|
||||
.. _diagnose_slow_disk_drives:
|
||||
|
||||
Diagnose: Slow disk devices
|
||||
---------------------------
|
||||
|
||||
|
@ -404,7 +437,8 @@ Diagnose: Slow disk devices
|
|||
collectl is an open-source performance gathering/analysis tool.
|
||||
|
||||
If the diagnostics report a message such as ``sda: drive is slow``, you
|
||||
should log onto the node and run the following comand:
|
||||
should log onto the node and run the following command (remove ``-c 1`` option to continuously monitor
|
||||
the data):
|
||||
|
||||
.. code::
|
||||
|
||||
|
@ -431,13 +465,12 @@ should log onto the node and run the following comand:
|
|||
dm-3 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
dm-4 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
dm-5 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
...
|
||||
(repeats -- type Ctrl/C to stop)
|
||||
|
||||
|
||||
Look at the ``Wait`` and ``SvcTime`` values. It is not normal for
|
||||
these values to exceed 50msec. This is known to impact customer
|
||||
performance (upload/download. For a controller problem, many/all drives
|
||||
will show how wait and service times. A reboot may correct the prblem;
|
||||
performance (upload/download). For a controller problem, many/all drives
|
||||
will show long wait and service times. A reboot may correct the problem;
|
||||
otherwise hardware replacement is needed.
|
||||
|
||||
Another way to look at the data is as follows:
|
||||
|
@ -526,12 +559,12 @@ be disabled on a per-drive basis.
|
|||
Diagnose: Slow network link - Measuring network performance
|
||||
-----------------------------------------------------------
|
||||
|
||||
Network faults can cause performance between Swift nodes to degrade. The
|
||||
following tests are recommended. Other methods (such as copying large
|
||||
Network faults can cause performance between Swift nodes to degrade. Testing
|
||||
with ``netperf`` is recommended. Other methods (such as copying large
|
||||
files) may also work, but can produce inconclusive results.
|
||||
|
||||
Use netperf on all production systems. Install on all systems if not
|
||||
already installed. And the UFW rules for its control port are in place.
|
||||
Install ``netperf`` on all systems if not
|
||||
already installed. Check that the UFW rules for its control port are in place.
|
||||
However, there are no pre-opened ports for netperf's data connection. Pick a
|
||||
port number. In this example, 12866 is used because it is one higher
|
||||
than netperf's default control port number, 12865. If you get very
|
||||
|
@ -542,7 +575,7 @@ command-line wrong.
|
|||
Pick a ``source`` and ``target`` node. The source is often a proxy node
|
||||
and the target is often an object node. Using the same source proxy you
|
||||
can test communication to different object nodes in different AZs to
|
||||
identity possible bottlekecks.
|
||||
identity possible bottlenecks.
|
||||
|
||||
Running tests
|
||||
^^^^^^^^^^^^^
|
||||
|
@ -561,11 +594,11 @@ Running tests
|
|||
|
||||
#. On the ``source`` node, run the following command to check
|
||||
throughput. Note the double-dash before the -P option.
|
||||
The command takes 10 seconds to complete.
|
||||
The command takes 10 seconds to complete. The ``target`` node is 192.168.245.5.
|
||||
|
||||
.. code::
|
||||
|
||||
$ netperf -H <redacted>.72.4
|
||||
$ netperf -H 192.168.245.5 -- -P 12866
|
||||
MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 12866 AF_INET to
|
||||
<redacted>.72.4 (<redacted>.72.4) port 12866 AF_INET : demo
|
||||
Recv Send Send
|
||||
|
@ -578,7 +611,7 @@ Running tests
|
|||
|
||||
.. code::
|
||||
|
||||
$ netperf -H <redacted>.72.4 -t TCP_RR -- -P 12866
|
||||
$ netperf -H 192.168.245.5 -t TCP_RR -- -P 12866
|
||||
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 12866
|
||||
AF_INET to <redacted>.72.4 (<redacted>.72.4) port 12866 AF_INET : demo
|
||||
: first burst 0
|
||||
|
@ -763,7 +796,7 @@ Diagnose: High system latency
|
|||
used by the monitor program happen to live on the bad object server.
|
||||
|
||||
- A general network problem within the data canter. Compare the results
|
||||
with the Pingdom monitors too see if they also have a problem.
|
||||
with the Pingdom monitors to see if they also have a problem.
|
||||
|
||||
Diagnose: Interface reports errors
|
||||
----------------------------------
|
||||
|
@ -802,59 +835,21 @@ If the nick supports self test, this can be performed with:
|
|||
Self tests should read ``PASS`` if the nic is operating correctly.
|
||||
|
||||
Nic module drivers can be re-initialised by carefully removing and
|
||||
re-installing the modules. Case in point being the mellanox drivers on
|
||||
Swift Proxy servers. which use a two part driver mlx4_en and
|
||||
re-installing the modules (this avoids rebooting the server).
|
||||
For example, mellanox drivers use a two part driver mlx4_en and
|
||||
mlx4_core. To reload these you must carefully remove the mlx4_en
|
||||
(ethernet) then the mlx4_core modules, and reinstall them in the
|
||||
reverse order.
|
||||
|
||||
As the interface will be disabled while the modules are unloaded, you
|
||||
must be very careful not to lock the interface out. The following
|
||||
script can be used to reload the melanox drivers, as a side effect, this
|
||||
resets error counts on the interface.
|
||||
|
||||
|
||||
Diagnose: CorruptDir diagnostic reports corrupt directories
|
||||
-----------------------------------------------------------
|
||||
|
||||
From time to time Swift data structures may become corrupted by
|
||||
misplaced files in filesystem locations that swift would normally place
|
||||
a directory. This causes issues for swift when directory creation is
|
||||
attempted at said location, it may fail due to the pre-existent file. If
|
||||
the CorruptDir diagnostic reports Corrupt directories, they should be
|
||||
checked to see if they exist.
|
||||
|
||||
Checking existence of entries
|
||||
-----------------------------
|
||||
|
||||
Swift data filesystems are located under the ``/srv/node/disk``
|
||||
mountpoints and contain accounts, containers and objects
|
||||
subdirectories which in turn contain partition number subdirectories.
|
||||
The partition number directories contain md5 hash subdirectories. md5
|
||||
hash directories contain md5sum subdirectories. md5sum directories
|
||||
contain the Swift data payload as either a database (.db), for
|
||||
accounts and containers, or a data file (.data) for objects.
|
||||
If the entries reported in diagnostics correspond to a partition
|
||||
number, md5 hash or md5sum directory, check the entry with ``ls
|
||||
-ld *entry*``.
|
||||
If it turns out to be a file rather than a directory, it should be
|
||||
carefully removed.
|
||||
|
||||
.. note::
|
||||
|
||||
Please do not ``ls`` the partition level directory contents, as
|
||||
this *especially objects* may take a lot of time and system resources,
|
||||
if you need to check the contents, use:
|
||||
|
||||
.. code::
|
||||
|
||||
echo /srv/node/disk#/type/partition#/
|
||||
must be very careful not to lock yourself out so it may be better
|
||||
to script this.
|
||||
|
||||
Diagnose: Hung swift object replicator
|
||||
--------------------------------------
|
||||
|
||||
The swift diagnostic message ``Object replicator: remaining exceeds
|
||||
100hrs:`` may indicate that the swift ``object-replicator`` is stuck and not
|
||||
A replicator reports in its log that remaining time exceeds
|
||||
100 hours. This may indicate that the swift ``object-replicator`` is stuck and not
|
||||
making progress. Another useful way to check this is with the
|
||||
'swift-recon -r' command on a swift proxy server:
|
||||
|
||||
|
@ -866,42 +861,41 @@ making progress. Another useful way to check this is with the
|
|||
--> Starting reconnaissance on 384 hosts
|
||||
===============================================================================
|
||||
[2013-07-17 12:56:19] Checking on replication
|
||||
http://<redacted>.72.63:6000/recon/replication: <urlopen error timed out>
|
||||
[replication_time] low: 2, high: 80, avg: 28.8, total: 11037, Failed: 0.0%, no_result: 0, reported: 383
|
||||
Oldest completion was 2013-06-12 22:46:50 (12 days ago) by <redacted>.31:6000.
|
||||
Most recent completion was 2013-07-17 12:56:19 (5 seconds ago) by <redacted>.204.113:6000.
|
||||
Oldest completion was 2013-06-12 22:46:50 (12 days ago) by 192.168.245.3:6200.
|
||||
Most recent completion was 2013-07-17 12:56:19 (5 seconds ago) by 192.168.245.5:6200.
|
||||
===============================================================================
|
||||
|
||||
The ``Oldest completion`` line in this example indicates that the
|
||||
object-replicator on swift object server <redacted>.31 has not completed
|
||||
object-replicator on swift object server 192.168.245.3 has not completed
|
||||
the replication cycle in 12 days. This replicator is stuck. The object
|
||||
replicator cycle is generally less than 1 hour. Though an replicator
|
||||
cycle of 15-20 hours can occur if nodes are added to the system and a
|
||||
new ring has been deployed.
|
||||
|
||||
You can further check if the object replicator is stuck by logging on
|
||||
the the object server and checking the object replicator progress with
|
||||
the object server and checking the object replicator progress with
|
||||
the following command:
|
||||
|
||||
.. code::
|
||||
|
||||
# sudo grep object-rep /var/log/swift/background.log | grep -e "Starting object replication" -e "Object replication complete" -e "partitions rep"
|
||||
Jul 16 06:25:46 <redacted> object-replicator 15344/16450 (93.28%) partitions replicated in 69018.48s (0.22/sec, 22h remaining)
|
||||
Jul 16 06:30:46 <redacted> object-replicator 15344/16450 (93.28%) partitions replicated in 69318.58s (0.22/sec, 22h remaining)
|
||||
Jul 16 06:35:46 <redacted> object-replicator 15344/16450 (93.28%) partitions replicated in 69618.63s (0.22/sec, 23h remaining)
|
||||
Jul 16 06:40:46 <redacted> object-replicator 15344/16450 (93.28%) partitions replicated in 69918.73s (0.22/sec, 23h remaining)
|
||||
Jul 16 06:45:46 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 70218.75s (0.22/sec, 24h remaining)
|
||||
Jul 16 06:50:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 70518.85s (0.22/sec, 24h remaining)
|
||||
Jul 16 06:55:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 70818.95s (0.22/sec, 25h remaining)
|
||||
Jul 16 07:00:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 71119.05s (0.22/sec, 25h remaining)
|
||||
Jul 16 07:05:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 71419.15s (0.21/sec, 26h remaining)
|
||||
Jul 16 07:10:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 71719.25s (0.21/sec, 26h remaining)
|
||||
Jul 16 07:15:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 72019.27s (0.21/sec, 27h remaining)
|
||||
Jul 16 07:20:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 72319.37s (0.21/sec, 27h remaining)
|
||||
Jul 16 07:25:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 72619.47s (0.21/sec, 28h remaining)
|
||||
Jul 16 07:30:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 72919.56s (0.21/sec, 28h remaining)
|
||||
Jul 16 07:35:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 73219.67s (0.21/sec, 29h remaining)
|
||||
Jul 16 07:40:47 <redacted> object-replicator 15348/16450 (93.30%) partitions replicated in 73519.76s (0.21/sec, 29h remaining)
|
||||
Jul 16 06:25:46 192.168.245.4 object-replicator 15344/16450 (93.28%) partitions replicated in 69018.48s (0.22/sec, 22h remaining)
|
||||
Jul 16 06:30:46 192.168.245.4object-replicator 15344/16450 (93.28%) partitions replicated in 69318.58s (0.22/sec, 22h remaining)
|
||||
Jul 16 06:35:46 192.168.245.4 object-replicator 15344/16450 (93.28%) partitions replicated in 69618.63s (0.22/sec, 23h remaining)
|
||||
Jul 16 06:40:46 192.168.245.4 object-replicator 15344/16450 (93.28%) partitions replicated in 69918.73s (0.22/sec, 23h remaining)
|
||||
Jul 16 06:45:46 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 70218.75s (0.22/sec, 24h remaining)
|
||||
Jul 16 06:50:47 192.168.245.4object-replicator 15348/16450 (93.30%) partitions replicated in 70518.85s (0.22/sec, 24h remaining)
|
||||
Jul 16 06:55:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 70818.95s (0.22/sec, 25h remaining)
|
||||
Jul 16 07:00:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 71119.05s (0.22/sec, 25h remaining)
|
||||
Jul 16 07:05:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 71419.15s (0.21/sec, 26h remaining)
|
||||
Jul 16 07:10:47 192.168.245.4object-replicator 15348/16450 (93.30%) partitions replicated in 71719.25s (0.21/sec, 26h remaining)
|
||||
Jul 16 07:15:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 72019.27s (0.21/sec, 27h remaining)
|
||||
Jul 16 07:20:47 192.168.245.4object-replicator 15348/16450 (93.30%) partitions replicated in 72319.37s (0.21/sec, 27h remaining)
|
||||
Jul 16 07:25:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 72619.47s (0.21/sec, 28h remaining)
|
||||
Jul 16 07:30:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 72919.56s (0.21/sec, 28h remaining)
|
||||
Jul 16 07:35:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 73219.67s (0.21/sec, 29h remaining)
|
||||
Jul 16 07:40:47 192.168.245.4 object-replicator 15348/16450 (93.30%) partitions replicated in 73519.76s (0.21/sec, 29h remaining)
|
||||
|
||||
The above status is output every 5 minutes to ``/var/log/swift/background.log``.
|
||||
|
||||
|
@ -921,7 +915,7 @@ of a corrupted filesystem detected by the object replicator:
|
|||
.. code::
|
||||
|
||||
# sudo bzgrep "Remote I/O error" /var/log/swift/background.log* |grep srv | - tail -1
|
||||
Jul 12 03:33:30 <redacted> object-replicator STDOUT: ERROR:root:Error hashing suffix#012Traceback (most recent call last):#012 File
|
||||
Jul 12 03:33:30 192.168.245.4 object-replicator STDOUT: ERROR:root:Error hashing suffix#012Traceback (most recent call last):#012 File
|
||||
"/usr/lib/python2.7/dist-packages/swift/obj/replicator.py", line 199, in get_hashes#012 hashes[suffix] = hash_suffix(suffix_dir,
|
||||
reclaim_age)#012 File "/usr/lib/python2.7/dist-packages/swift/obj/replicator.py", line 84, in hash_suffix#012 path_contents =
|
||||
sorted(os.listdir(path))#012OSError: [Errno 121] Remote I/O error: '/srv/node/disk4/objects/1643763/b51'
|
||||
|
@ -996,7 +990,7 @@ to repair the problem filesystem.
|
|||
# sudo xfs_repair -P /dev/sde1
|
||||
|
||||
#. If the ``xfs_repair`` fails then it may be necessary to re-format the
|
||||
filesystem. See Procedure: fix broken XFS filesystem. If the
|
||||
filesystem. See :ref:`fix_broken_xfs_filesystem`. If the
|
||||
``xfs_repair`` is successful, re-enable chef using the following command
|
||||
and replication should commence again.
|
||||
|
||||
|
@ -1025,7 +1019,183 @@ load:
|
|||
$ uptime
|
||||
07:44:02 up 18:22, 1 user, load average: 407.12, 406.36, 404.59
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
Further issues and resolutions
|
||||
------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
The urgency levels in each **Action** column indicates whether or
|
||||
not it is required to take immediate action, or if the problem can be worked
|
||||
on during business hours.
|
||||
|
||||
.. list-table::
|
||||
:widths: 33 33 33
|
||||
:header-rows: 1
|
||||
|
||||
* - **Scenario**
|
||||
- **Description**
|
||||
- **Action**
|
||||
* - ``/healthcheck`` latency is high.
|
||||
- The ``/healthcheck`` test does not tax the proxy very much so any drop in value is probably related to
|
||||
network issues, rather than the proxies being very busy. A very slow proxy might impact the average
|
||||
number, but it would need to be very slow to shift the number that much.
|
||||
- Check networks. Do a ``curl https://<ip-address>:<port>/healthcheck`` where
|
||||
``ip-address`` is individual proxy IP address.
|
||||
Repeat this for every proxy server to see if you can pin point the problem.
|
||||
|
||||
Urgency: If there are other indications that your system is slow, you should treat
|
||||
this as an urgent problem.
|
||||
* - Swift process is not running.
|
||||
- You can use ``swift-init`` status to check if swift processes are running on any
|
||||
given server.
|
||||
- Run this command:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo swift-init all start
|
||||
|
||||
Examine messages in the swift log files to see if there are any
|
||||
error messages related to any of the swift processes since the time you
|
||||
ran the ``swift-init`` command.
|
||||
|
||||
Take any corrective actions that seem necessary.
|
||||
|
||||
Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - ntpd is not running.
|
||||
- NTP is not running.
|
||||
- Configure and start NTP.
|
||||
|
||||
Urgency: For proxy servers, this is vital.
|
||||
|
||||
* - Host clock is not syncd to an NTP server.
|
||||
- Node time settings does not match NTP server time.
|
||||
This may take some time to sync after a reboot.
|
||||
- Assuming NTP is configured and running, you have to wait until the times sync.
|
||||
* - A swift process has hundreds, to thousands of open file descriptors.
|
||||
- May happen to any of the swift processes.
|
||||
Known to have happened with a ``rsyslod`` restart and where ``/tmp`` was hanging.
|
||||
|
||||
- Restart the swift processes on the affected node:
|
||||
|
||||
.. code::
|
||||
|
||||
% sudo swift-init all reload
|
||||
|
||||
Urgency:
|
||||
If known performance problem: Immediate
|
||||
|
||||
If system seems fine: Medium
|
||||
* - A swift process is not owned by the swift user.
|
||||
- If the UID of the swift user has changed, then the processes might not be
|
||||
owned by that UID.
|
||||
- Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - Object account or container files not owned by swift.
|
||||
- This typically happens if during a reinstall or a re-image of a server that the UID
|
||||
of the swift user was changed. The data files in the object account and container
|
||||
directories are owned by the original swift UID. As a result, the current swift
|
||||
user does not own these files.
|
||||
- Correct the UID of the swift user to reflect that of the original UID. An alternate
|
||||
action is to change the ownership of every file on all file systems. This alternate
|
||||
action is often impractical and will take considerable time.
|
||||
|
||||
Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - A disk drive has a high IO wait or service time.
|
||||
- If high wait IO times are seen for a single disk, then the disk drive is the problem.
|
||||
If most/all devices are slow, the controller is probably the source of the problem.
|
||||
The controller cache may also be miss configured – which will cause similar long
|
||||
wait or service times.
|
||||
- As a first step, if your controllers have a cache, check that it is enabled and their battery/capacitor
|
||||
is working.
|
||||
|
||||
Second, reboot the server.
|
||||
If problem persists, file a DC ticket to have the drive or controller replaced.
|
||||
See :ref:`diagnose_slow_disk_drives` on how to check the drive wait or service times.
|
||||
|
||||
Urgency: Medium
|
||||
* - The network interface is not up.
|
||||
- Use the ``ifconfig`` and ``ethtool`` commands to determine the network state.
|
||||
- You can try restarting the interface. However, generally the interface
|
||||
(or cable) is probably broken, especially if the interface is flapping.
|
||||
|
||||
Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - Network interface card (NIC) is not operating at the expected speed.
|
||||
- The NIC is running at a slower speed than its nominal rated speed.
|
||||
For example, it is running at 100 Mb/s and the NIC is a 1Ge NIC.
|
||||
- 1. Try resetting the interface with:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo ethtool -s eth0 speed 1000
|
||||
|
||||
... and then run:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo lshw -class
|
||||
|
||||
See if size goes to the expected speed. Failing
|
||||
that, check hardware (NIC cable/switch port).
|
||||
|
||||
2. If persistent, consider shutting down the server (especially if a proxy)
|
||||
until the problem is identified and resolved. If you leave this server
|
||||
running it can have a large impact on overall performance.
|
||||
|
||||
Urgency: High
|
||||
* - The interface RX/TX error count is non-zero.
|
||||
- A value of 0 is typical, but counts of 1 or 2 do not indicate a problem.
|
||||
- 1. For low numbers (For example, 1 or 2), you can simply ignore. Numbers in the range
|
||||
3-30 probably indicate that the error count has crept up slowly over a long time.
|
||||
Consider rebooting the server to remove the report from the noise.
|
||||
|
||||
Typically, when a cable or interface is bad, the error count goes to 400+. For example,
|
||||
it stands out. There may be other symptoms such as the interface going up and down or
|
||||
not running at correct speed. A server with a high error count should be watched.
|
||||
|
||||
2. If the error count continues to climb, consider taking the server down until
|
||||
it can be properly investigated. In any case, a reboot should be done to clear
|
||||
the error count.
|
||||
|
||||
Urgency: High, if the error count increasing.
|
||||
|
||||
* - In a swift log you see a message that a process has not replicated in over 24 hours.
|
||||
- The replicator has not successfully completed a run in the last 24 hours.
|
||||
This indicates that the replicator has probably hung.
|
||||
- Use ``swift-init`` to stop and then restart the replicator process.
|
||||
|
||||
Urgency: Low. However if you
|
||||
recently added or replaced disk drives then you should treat this urgently.
|
||||
* - Container Updater has not run in 4 hour(s).
|
||||
- The service may appear to be running however, it may be hung. Examine their swift
|
||||
logs to see if there are any error messages relating to the container updater. This
|
||||
may potentially explain why the container is not running.
|
||||
- Urgency: Medium
|
||||
This may have been triggered by a recent restart of the rsyslog daemon.
|
||||
Restart the service with:
|
||||
.. code::
|
||||
|
||||
sudo swift-init <service> reload
|
||||
* - Object replicator: Reports the remaining time and that time is more than 100 hours.
|
||||
- Each replication cycle the object replicator writes a log message to its log
|
||||
reporting statistics about the current cycle. This includes an estimate for the
|
||||
remaining time needed to replicate all objects. If this time is longer than
|
||||
100 hours, there is a problem with the replication process.
|
||||
- Urgency: Medium
|
||||
Restart the service with:
|
||||
.. code::
|
||||
|
||||
sudo swift-init object-replicator reload
|
||||
|
||||
Check that the remaining replication time is going down.
|
||||
|
||||
sec-furtherdiagnose.rst
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
==================
|
||||
General Procedures
|
||||
==================
|
||||
|
||||
Getting a swift account stats
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
``swift-direct`` is specific to the HPE Helion Public Cloud. Go look at
|
||||
``swifty`` for an alternate, this is an example.
|
||||
|
||||
This procedure describes how you determine the swift usage for a given
|
||||
swift account, that is the number of containers, number of objects and
|
||||
total bytes used. To do this you will need the project ID.
|
||||
|
||||
Log onto one of the swift proxy servers.
|
||||
|
||||
Use swift-direct to show this accounts usage:
|
||||
|
||||
.. code::
|
||||
|
||||
$ sudo -u swift /opt/hp/swift/bin/swift-direct show AUTH_redacted-9a11-45f8-aa1c-9e7b1c7904c8
|
||||
Status: 200
|
||||
Content-Length: 0
|
||||
Accept-Ranges: bytes
|
||||
X-Timestamp: 1379698586.88364
|
||||
X-Account-Bytes-Used: 67440225625994
|
||||
X-Account-Container-Count: 1
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
X-Account-Object-Count: 8436776
|
||||
Status: 200
|
||||
name: my_container count: 8436776 bytes: 67440225625994
|
||||
|
||||
This account has 1 container. That container has 8436776 objects. The
|
||||
total bytes used is 67440225625994.
|
|
@ -13,67 +13,15 @@ information, suggestions or recommendations. This document are provided
|
|||
for reference only. We are not responsible for your use of any
|
||||
information, suggestions or recommendations contained herein.
|
||||
|
||||
This document also contains references to certain tools that we use to
|
||||
operate the Swift system within the HPE Helion Public Cloud.
|
||||
Descriptions of these tools are provided for reference only, as the tools themselves
|
||||
are not publically available at this time.
|
||||
|
||||
- ``swift-direct``: This is similar to the ``swiftly`` tool.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
general.rst
|
||||
diagnose.rst
|
||||
procedures.rst
|
||||
maintenance.rst
|
||||
troubleshooting.rst
|
||||
|
||||
Is the system up?
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you have a report that Swift is down, perform the following basic checks:
|
||||
|
||||
#. Run swift functional tests.
|
||||
|
||||
#. From a server in your data center, use ``curl`` to check ``/healthcheck``.
|
||||
|
||||
#. If you have a monitoring system, check your monitoring system.
|
||||
|
||||
#. Check on your hardware load balancers infrastructure.
|
||||
|
||||
#. Run swift-recon on a proxy node.
|
||||
|
||||
Run swift function tests
|
||||
------------------------
|
||||
|
||||
We would recommend that you set up your function tests against your production
|
||||
system.
|
||||
|
||||
A script for running the function tests is located in ``swift/.functests``.
|
||||
|
||||
|
||||
External monitoring
|
||||
-------------------
|
||||
|
||||
- We use pingdom.com to monitor the external Swift API. We suggest the
|
||||
following:
|
||||
|
||||
- Do a GET on ``/healthcheck``
|
||||
|
||||
- Create a container, make it public (x-container-read:
|
||||
.r\*,.rlistings), create a small file in the container; do a GET
|
||||
on the object
|
||||
|
||||
Reference information
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference: Swift startup/shutdown
|
||||
---------------------------------
|
||||
|
||||
- Use reload - not stop/start/restart.
|
||||
|
||||
- Try to roll sets of servers (especially proxy) in groups of less
|
||||
than 20% of your servers.
|
||||
|
||||
|
|
|
@ -54,8 +54,8 @@ system. Rules-of-thumb for 'good' recon output are:
|
|||
|
||||
.. code::
|
||||
|
||||
\-> [http://<redacted>.29:6000/recon/load:] <urlopen error [Errno 111] ECONNREFUSED>
|
||||
\-> [http://<redacted>.31:6000/recon/load:] <urlopen error timed out>
|
||||
-> [http://<redacted>.29:6200/recon/load:] <urlopen error [Errno 111] ECONNREFUSED>
|
||||
-> [http://<redacted>.31:6200/recon/load:] <urlopen error timed out>
|
||||
|
||||
- That could be okay or could require investigation.
|
||||
|
||||
|
@ -86,51 +86,51 @@ two entire racks of Swift are down:
|
|||
.. code::
|
||||
|
||||
[2012-03-10 16:56:33] Checking async pendings on 384 hosts...
|
||||
-> http://<redacted>.22:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.18:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.16:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.13:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.30:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.6:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.22:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.18:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.16:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.13:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.30:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.6:6200/recon/async: <urlopen error timed out>
|
||||
.........
|
||||
-> http://<redacted>.5:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.15:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.9:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.27:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.4:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.8:6000/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.5:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.15:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.9:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.27:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.4:6200/recon/async: <urlopen error timed out>
|
||||
-> http://<redacted>.8:6200/recon/async: <urlopen error timed out>
|
||||
Async stats: low: 243, high: 659, avg: 413, total: 132275
|
||||
===============================================================================
|
||||
[2012-03-10 16:57:48] Checking replication times on 384 hosts...
|
||||
-> http://<redacted>.22:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.18:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.16:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.13:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.30:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.6:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.22:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.18:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.16:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.13:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.30:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.6:6200/recon/replication: <urlopen error timed out>
|
||||
............
|
||||
-> http://<redacted>.5:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.15:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.9:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.27:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.4:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.8:6000/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.5:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.15:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.9:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.27:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.4:6200/recon/replication: <urlopen error timed out>
|
||||
-> http://<redacted>.8:6200/recon/replication: <urlopen error timed out>
|
||||
[Replication Times] shortest: 1.38144306739, longest: 112.620954418, avg: 10.285
|
||||
9475361
|
||||
===============================================================================
|
||||
[2012-03-10 16:59:03] Checking load avg's on 384 hosts...
|
||||
-> http://<redacted>.22:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.18:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.16:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.13:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.30:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.6:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.22:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.18:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.16:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.13:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.30:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.6:6200/recon/load: <urlopen error timed out>
|
||||
............
|
||||
-> http://<redacted>.15:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.9:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.27:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.4:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.8:6000/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.15:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.9:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.27:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.4:6200/recon/load: <urlopen error timed out>
|
||||
-> http://<redacted>.8:6200/recon/load: <urlopen error timed out>
|
||||
[5m load average] lowest: 1.71, highest: 4.91, avg: 2.486375
|
||||
[15m load average] lowest: 1.79, highest: 5.04, avg: 2.506125
|
||||
[1m load average] lowest: 1.46, highest: 4.55, avg: 2.4929375
|
||||
|
@ -154,18 +154,18 @@ Running reccon shows some async pendings:
|
|||
|
||||
.. code::
|
||||
|
||||
bob@notso:~/swift-1.4.4/swift$ ssh \\-q <redacted>.132.7 sudo swift-recon \\-alr
|
||||
bob@notso:~/swift-1.4.4/swift$ ssh -q <redacted>.132.7 sudo swift-recon -alr
|
||||
===============================================================================
|
||||
\[2012-03-14 17:25:55\\] Checking async pendings on 384 hosts...
|
||||
[2012-03-14 17:25:55] Checking async pendings on 384 hosts...
|
||||
Async stats: low: 0, high: 23, avg: 8, total: 3356
|
||||
===============================================================================
|
||||
\[2012-03-14 17:25:55\\] Checking replication times on 384 hosts...
|
||||
\[Replication Times\\] shortest: 1.49303831657, longest: 39.6982825994, avg: 4.2418222066
|
||||
[2012-03-14 17:25:55] Checking replication times on 384 hosts...
|
||||
[Replication Times] shortest: 1.49303831657, longest: 39.6982825994, avg: 4.2418222066
|
||||
===============================================================================
|
||||
\[2012-03-14 17:25:56\\] Checking load avg's on 384 hosts...
|
||||
\[5m load average\\] lowest: 2.35, highest: 8.88, avg: 4.45911458333
|
||||
\[15m load average\\] lowest: 2.41, highest: 9.11, avg: 4.504765625
|
||||
\[1m load average\\] lowest: 1.95, highest: 8.56, avg: 4.40588541667
|
||||
[2012-03-14 17:25:56] Checking load avg's on 384 hosts...
|
||||
[5m load average] lowest: 2.35, highest: 8.88, avg: 4.45911458333
|
||||
[15m load average] lowest: 2.41, highest: 9.11, avg: 4.504765625
|
||||
[1m load average] lowest: 1.95, highest: 8.56, avg: 4.40588541667
|
||||
===============================================================================
|
||||
|
||||
Why? Running recon again with -av swift (not shown here) tells us that
|
||||
|
@ -176,33 +176,33 @@ files on <redacted>.72.61 we see:
|
|||
|
||||
souzab@<redacted>:~$ sudo tail -f /var/log/swift/background.log | - grep -i ERROR
|
||||
Mar 14 17:28:06 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6201}
|
||||
Mar 14 17:28:06 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6201}
|
||||
Mar 14 17:28:09 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:11 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:13 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6201}
|
||||
Mar 14 17:28:13 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6201}
|
||||
Mar 14 17:28:15 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:15 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:19 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:19 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:20 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.119', 'id': 5481, 'meta': '', 'device': 'disk6', 'port': 6201}
|
||||
Mar 14 17:28:21 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:21 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
Mar 14 17:28:22 <redacted> container-replicator ERROR Remote drive not mounted
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6001}
|
||||
{'zone': 5, 'weight': 1952.0, 'ip': '<redacted>.204.20', 'id': 2311, 'meta': '', 'device': 'disk5', 'port': 6201}
|
||||
|
||||
That is why this node has a lot of async pendings: a bunch of disks that
|
||||
are not mounted on <redacted> and <redacted>. There may be other issues,
|
||||
|
@ -231,7 +231,7 @@ Procedure
|
|||
This procedure should be run three times, each time specifying the
|
||||
appropriate ``*.builder`` file.
|
||||
|
||||
#. Determine whether all three nodes are different Swift zones by
|
||||
#. Determine whether all three nodes are in different Swift zones by
|
||||
running the ring builder on a proxy node to determine which zones
|
||||
the storage nodes are in. For example:
|
||||
|
||||
|
@ -241,22 +241,22 @@ Procedure
|
|||
/etc/swift/object.builder, build version 1467
|
||||
2097152 partitions, 3 replicas, 5 zones, 1320 devices, 0.02 balance
|
||||
The minimum number of hours before a partition can be reassigned is 24
|
||||
Devices: id zone ip address port name weight partitions balance meta
|
||||
0 1 <redacted>.4 6000 disk0 1708.00 4259 -0.00
|
||||
1 1 <redacted>.4 6000 disk1 1708.00 4260 0.02
|
||||
2 1 <redacted>.4 6000 disk2 1952.00 4868 0.01
|
||||
3 1 <redacted>.4 6000 disk3 1952.00 4868 0.01
|
||||
4 1 <redacted>.4 6000 disk4 1952.00 4867 -0.01
|
||||
Devices: id zone ip address port name weight partitions balance meta
|
||||
0 1 <redacted>.4 6200 disk0 1708.00 4259 -0.00
|
||||
1 1 <redacted>.4 6200 disk1 1708.00 4260 0.02
|
||||
2 1 <redacted>.4 6200 disk2 1952.00 4868 0.01
|
||||
3 1 <redacted>.4 6200 disk3 1952.00 4868 0.01
|
||||
4 1 <redacted>.4 6200 disk4 1952.00 4867 -0.01
|
||||
|
||||
#. Here, node <redacted>.4 is in zone 1. If two or more of the three
|
||||
nodes under consideration are in the same Swift zone, they do not
|
||||
have any ring partitions in common; there is little/no data
|
||||
availability risk if all three nodes are down.
|
||||
|
||||
#. If the nodes are in three distinct Swift zonesit is necessary to
|
||||
#. If the nodes are in three distinct Swift zones it is necessary to
|
||||
whether the nodes have ring partitions in common. Run ``swift-ring``
|
||||
builder again, this time with the ``list_parts`` option and specify
|
||||
the nodes under consideration. For example (all on one line):
|
||||
the nodes under consideration. For example:
|
||||
|
||||
.. code::
|
||||
|
||||
|
@ -302,12 +302,12 @@ Procedure
|
|||
|
||||
.. code::
|
||||
|
||||
% sudo swift-ring-builder /etc/swift/object.builder list_parts <redacted>.8 <redacted>.15 <redacted>.72.2 | grep “3$” - wc \\-l
|
||||
% sudo swift-ring-builder /etc/swift/object.builder list_parts <redacted>.8 <redacted>.15 <redacted>.72.2 | grep "3$" | wc -l
|
||||
|
||||
30
|
||||
|
||||
#. In this case the nodes have 30 out of a total of 2097152 partitions
|
||||
in common; about 0.001%. In this case the risk is small nonzero.
|
||||
in common; about 0.001%. In this case the risk is small/nonzero.
|
||||
Recall that a partition is simply a portion of the ring mapping
|
||||
space, not actual data. So having partitions in common is a necessary
|
||||
but not sufficient condition for data unavailability.
|
||||
|
@ -320,3 +320,11 @@ Procedure
|
|||
If three nodes that have 3 partitions in common are all down, there is
|
||||
a nonzero probability that data are unavailable and we should work to
|
||||
bring some or all of the nodes up ASAP.
|
||||
|
||||
Swift startup/shutdown
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Use reload - not stop/start/restart.
|
||||
|
||||
- Try to roll sets of servers (especially proxy) in groups of less
|
||||
than 20% of your servers.
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
Software configuration procedures
|
||||
=================================
|
||||
|
||||
.. _fix_broken_gpt_table:
|
||||
|
||||
Fix broken GPT table (broken disk partition)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -102,6 +104,8 @@ Fix broken GPT table (broken disk partition)
|
|||
|
||||
$ sudo aptitude remove gdisk
|
||||
|
||||
.. _fix_broken_xfs_filesystem:
|
||||
|
||||
Procedure: Fix broken XFS filesystem
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -165,7 +169,7 @@ Procedure: Fix broken XFS filesystem
|
|||
|
||||
.. code::
|
||||
|
||||
$ sudo dd if=/dev/zero of=/dev/sdb2 bs=$((1024\*1024)) count=1
|
||||
$ sudo dd if=/dev/zero of=/dev/sdb2 bs=$((1024*1024)) count=1
|
||||
1+0 records in
|
||||
1+0 records out
|
||||
1048576 bytes (1.0 MB) copied, 0.00480617 s, 218 MB/s
|
||||
|
@ -187,129 +191,173 @@ Procedure: Fix broken XFS filesystem
|
|||
|
||||
$ mount
|
||||
|
||||
.. _checking_if_account_ok:
|
||||
|
||||
Procedure: Checking if an account is okay
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
``swift-direct`` is only available in the HPE Helion Public Cloud.
|
||||
Use ``swiftly`` as an alternate.
|
||||
Use ``swiftly`` as an alternate (or use ``swift-get-nodes`` as explained
|
||||
here).
|
||||
|
||||
If you have a tenant ID you can check the account is okay as follows from a proxy.
|
||||
You must know the tenant/project ID. You can check if the account is okay as follows from a proxy.
|
||||
|
||||
.. code::
|
||||
|
||||
$ sudo -u swift /opt/hp/swift/bin/swift-direct show <Api-Auth-Hash-or-TenantId>
|
||||
$ sudo -u swift /opt/hp/swift/bin/swift-direct show AUTH_<project-id>
|
||||
|
||||
The response will either be similar to a swift list of the account
|
||||
containers, or an error indicating that the resource could not be found.
|
||||
|
||||
In the latter case you can establish if a backend database exists for
|
||||
the tenantId by running the following on a proxy:
|
||||
Alternatively, you can use ``swift-get-nodes`` to find the account database
|
||||
files. Run the following on a proxy:
|
||||
|
||||
.. code::
|
||||
|
||||
$ sudo -u swift swift-get-nodes /etc/swift/account.ring.gz <Api-Auth-Hash-or-TenantId>
|
||||
$ sudo swift-get-nodes /etc/swift/account.ring.gz AUTH_<project-id>
|
||||
|
||||
The response will list ssh commands that will list the replicated
|
||||
account databases, if they exist.
|
||||
The response will print curl/ssh commands that will list the replicated
|
||||
account databases. Use the indicated ``curl`` or ``ssh`` commands to check
|
||||
the status and existence of the account.
|
||||
|
||||
Procedure: Getting swift account stats
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
``swift-direct`` is specific to the HPE Helion Public Cloud. Go look at
|
||||
``swifty`` for an alternate or use ``swift-get-nodes`` as explained
|
||||
in :ref:`checking_if_account_ok`.
|
||||
|
||||
This procedure describes how you determine the swift usage for a given
|
||||
swift account, that is the number of containers, number of objects and
|
||||
total bytes used. To do this you will need the project ID.
|
||||
|
||||
Log onto one of the swift proxy servers.
|
||||
|
||||
Use swift-direct to show this accounts usage:
|
||||
|
||||
.. code::
|
||||
|
||||
$ sudo -u swift /opt/hp/swift/bin/swift-direct show AUTH_<project-id>
|
||||
Status: 200
|
||||
Content-Length: 0
|
||||
Accept-Ranges: bytes
|
||||
X-Timestamp: 1379698586.88364
|
||||
X-Account-Bytes-Used: 67440225625994
|
||||
X-Account-Container-Count: 1
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
X-Account-Object-Count: 8436776
|
||||
Status: 200
|
||||
name: my_container count: 8436776 bytes: 67440225625994
|
||||
|
||||
This account has 1 container. That container has 8436776 objects. The
|
||||
total bytes used is 67440225625994.
|
||||
|
||||
Procedure: Revive a deleted account
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Swift accounts are normally not recreated. If a tenant unsubscribes from
|
||||
Swift, the account is deleted. To re-subscribe to Swift, you can create
|
||||
a new tenant (new tenant ID), and subscribe to Swift. This creates a
|
||||
new Swift account with the new tenant ID.
|
||||
Swift accounts are normally not recreated. If a tenant/project is deleted,
|
||||
the account can then be deleted. If the user wishes to use Swift again,
|
||||
the normal process is to create a new tenant/project -- and hence a
|
||||
new Swift account.
|
||||
|
||||
However, until the unsubscribe/new tenant process is supported, you may
|
||||
hit a situation where a Swift account is deleted and the user is locked
|
||||
out of Swift.
|
||||
However, if the Swift account is deleted, but the tenant/project is not
|
||||
deleted from Keystone, the user can no longer access the account. This
|
||||
is because the account is marked deleted in Swift. You can revive
|
||||
the account as described in this process.
|
||||
|
||||
Deleting the account database files
|
||||
-----------------------------------
|
||||
.. note::
|
||||
|
||||
Here is one possible solution. The containers and objects may be lost
|
||||
forever. The solution is to delete the account database files and
|
||||
re-create the account. This may only be done once the containers and
|
||||
objects are completely deleted. This process is untested, but could
|
||||
work as follows:
|
||||
The containers and objects in the "old" account cannot be listed
|
||||
anymore. In addition, if the Account Reaper process has not
|
||||
finished reaping the containers and objects in the "old" account, these
|
||||
are effectively orphaned and it is virtually impossible to find and delete
|
||||
them to free up disk space.
|
||||
|
||||
#. Use swift-get-nodes to locate the account's database file (on three
|
||||
servers).
|
||||
The solution is to delete the account database files and
|
||||
re-create the account as follows:
|
||||
|
||||
#. Rename the database files (on three servers).
|
||||
#. You must know the tenant/project ID. The account name is AUTH_<project-id>.
|
||||
In this example, the tenant/project is is ``4ebe3039674d4864a11fe0864ae4d905``
|
||||
so the Swift account name is ``AUTH_4ebe3039674d4864a11fe0864ae4d905``.
|
||||
|
||||
#. Use ``swiftly`` to create the account (use original name).
|
||||
|
||||
Renaming account database so it can be revived
|
||||
----------------------------------------------
|
||||
|
||||
Get the locations of the database files that hold the account data.
|
||||
#. Use ``swift-get-nodes`` to locate the account's database files (on three
|
||||
servers). The output has been truncated so we can focus on the import pieces
|
||||
of data:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo swift-get-nodes /etc/swift/account.ring.gz AUTH_redacted-1856-44ae-97db-31242f7ad7a1
|
||||
$ sudo swift-get-nodes /etc/swift/account.ring.gz AUTH_4ebe3039674d4864a11fe0864ae4d905
|
||||
...
|
||||
curl -I -XHEAD "http://192.168.245.5:6202/disk1/3934/AUTH_4ebe3039674d4864a11fe0864ae4d905"
|
||||
curl -I -XHEAD "http://192.168.245.3:6202/disk0/3934/AUTH_4ebe3039674d4864a11fe0864ae4d905"
|
||||
curl -I -XHEAD "http://192.168.245.4:6202/disk1/3934/AUTH_4ebe3039674d4864a11fe0864ae4d905"
|
||||
...
|
||||
Use your own device location of servers:
|
||||
such as "export DEVICE=/srv/node"
|
||||
ssh 192.168.245.5 "ls -lah ${DEVICE:-/srv/node*}/disk1/accounts/3934/052/f5ecf8b40de3e1b0adb0dbe576874052"
|
||||
ssh 192.168.245.3 "ls -lah ${DEVICE:-/srv/node*}/disk0/accounts/3934/052/f5ecf8b40de3e1b0adb0dbe576874052"
|
||||
ssh 192.168.245.4 "ls -lah ${DEVICE:-/srv/node*}/disk1/accounts/3934/052/f5ecf8b40de3e1b0adb0dbe576874052"
|
||||
...
|
||||
note: `/srv/node*` is used as default value of `devices`, the real value is set in the config file on each storage node.
|
||||
|
||||
Account AUTH_redacted-1856-44ae-97db-31242f7ad7a1
|
||||
Container None
|
||||
|
||||
Object None
|
||||
#. Before proceeding check that the account is really deleted by using curl. Execute the
|
||||
commands printed by ``swift-get-nodes``. For example:
|
||||
|
||||
Partition 18914
|
||||
.. code::
|
||||
|
||||
Hash 93c41ef56dd69173a9524193ab813e78
|
||||
$ curl -I -XHEAD "http://192.168.245.5:6202/disk1/3934/AUTH_4ebe3039674d4864a11fe0864ae4d905"
|
||||
HTTP/1.1 404 Not Found
|
||||
Content-Length: 0
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
Server:Port Device 15.184.9.126:6002 disk7
|
||||
Server:Port Device 15.184.9.94:6002 disk11
|
||||
Server:Port Device 15.184.9.103:6002 disk10
|
||||
Server:Port Device 15.184.9.80:6002 disk2 [Handoff]
|
||||
Server:Port Device 15.184.9.120:6002 disk2 [Handoff]
|
||||
Server:Port Device 15.184.9.98:6002 disk2 [Handoff]
|
||||
Repeat for the other two servers (192.168.245.3 and 192.168.245.4).
|
||||
A ``404 Not Found`` indicates that the account is deleted (or never existed).
|
||||
|
||||
curl -I -XHEAD "`*http://15.184.9.126:6002/disk7/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.126:6002/disk7/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_
|
||||
curl -I -XHEAD "`*http://15.184.9.94:6002/disk11/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.94:6002/disk11/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_
|
||||
If you get a ``204 No Content`` response, do **not** proceed.
|
||||
|
||||
curl -I -XHEAD "`*http://15.184.9.103:6002/disk10/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.103:6002/disk10/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_
|
||||
#. Use the ssh commands printed by ``swift-get-nodes`` to check if database
|
||||
files exist. For example:
|
||||
|
||||
curl -I -XHEAD "`*http://15.184.9.80:6002/disk2/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.80:6002/disk2/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ # [Handoff]
|
||||
curl -I -XHEAD "`*http://15.184.9.120:6002/disk2/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.120:6002/disk2/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ # [Handoff]
|
||||
curl -I -XHEAD "`*http://15.184.9.98:6002/disk2/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.98:6002/disk2/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ # [Handoff]
|
||||
.. code::
|
||||
|
||||
ssh 15.184.9.126 "ls -lah /srv/node/disk7/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/"
|
||||
ssh 15.184.9.94 "ls -lah /srv/node/disk11/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/"
|
||||
ssh 15.184.9.103 "ls -lah /srv/node/disk10/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/"
|
||||
ssh 15.184.9.80 "ls -lah /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/" # [Handoff]
|
||||
ssh 15.184.9.120 "ls -lah /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/" # [Handoff]
|
||||
ssh 15.184.9.98 "ls -lah /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/" # [Handoff]
|
||||
$ ssh 192.168.245.5 "ls -lah ${DEVICE:-/srv/node*}/disk1/accounts/3934/052/f5ecf8b40de3e1b0adb0dbe576874052"
|
||||
total 20K
|
||||
drwxr-xr-x 2 swift swift 110 Mar 9 10:22 .
|
||||
drwxr-xr-x 3 swift swift 45 Mar 9 10:18 ..
|
||||
-rw------- 1 swift swift 17K Mar 9 10:22 f5ecf8b40de3e1b0adb0dbe576874052.db
|
||||
-rw-r--r-- 1 swift swift 0 Mar 9 10:22 f5ecf8b40de3e1b0adb0dbe576874052.db.pending
|
||||
-rwxr-xr-x 1 swift swift 0 Mar 9 10:18 .lock
|
||||
|
||||
$ sudo swift-get-nodes /etc/swift/account.ring.gz AUTH\_redacted-1856-44ae-97db-31242f7ad7a1Account AUTH_redacted-1856-44ae-97db-
|
||||
31242f7ad7a1Container NoneObject NonePartition 18914Hash 93c41ef56dd69173a9524193ab813e78Server:Port Device 15.184.9.126:6002 disk7Server:Port Device 15.184.9.94:6002 disk11Server:Port Device 15.184.9.103:6002 disk10Server:Port Device 15.184.9.80:6002
|
||||
disk2 [Handoff]Server:Port Device 15.184.9.120:6002 disk2 [Handoff]Server:Port Device 15.184.9.98:6002 disk2 [Handoff]curl -I -XHEAD
|
||||
"`*http://15.184.9.126:6002/disk7/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"*<http://15.184.9.126:6002/disk7/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ curl -I -XHEAD
|
||||
Repeat for the other two servers (192.168.245.3 and 192.168.245.4).
|
||||
|
||||
"`*http://15.184.9.94:6002/disk11/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.94:6002/disk11/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ curl -I -XHEAD
|
||||
If no files exist, no further action is needed.
|
||||
|
||||
"`*http://15.184.9.103:6002/disk10/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.103:6002/disk10/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ curl -I -XHEAD
|
||||
#. Stop Swift processes on all nodes listed by ``swift-get-nodes``
|
||||
(In this example, that is 192.168.245.3, 192.168.245.4 and 192.168.245.5).
|
||||
|
||||
"`*http://15.184.9.80:6002/disk2/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.80:6002/disk2/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ # [Handoff]curl -I -XHEAD
|
||||
#. We recommend you make backup copies of the database files.
|
||||
|
||||
"`*http://15.184.9.120:6002/disk2/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.120:6002/disk2/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ # [Handoff]curl -I -XHEAD
|
||||
#. Delete the database files. For example:
|
||||
|
||||
"`*http://15.184.9.98:6002/disk2/18914/AUTH_redacted-1856-44ae-97db-31242f7ad7a1"* <http://15.184.9.98:6002/disk2/18914/AUTH_cc9ebdb8-1856-44ae-97db-31242f7ad7a1>`_ # [Handoff]ssh 15.184.9.126
|
||||
.. code::
|
||||
|
||||
"ls -lah /srv/node/disk7/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/"ssh 15.184.9.94 "ls -lah /srv/node/disk11/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/"ssh 15.184.9.103
|
||||
"ls -lah /srv/node/disk10/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/"ssh 15.184.9.80 "ls -lah /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/" # [Handoff]ssh 15.184.9.120
|
||||
"ls -lah /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/" # [Handoff]ssh 15.184.9.98 "ls -lah /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/" # [Handoff]
|
||||
$ ssh 192.168.245.5
|
||||
$ cd /srv/node/disk1/accounts/3934/052/f5ecf8b40de3e1b0adb0dbe576874052
|
||||
$ sudo rm *
|
||||
|
||||
Check that the handoff nodes do not have account databases:
|
||||
Repeat for the other two servers (192.168.245.3 and 192.168.245.4).
|
||||
|
||||
.. code::
|
||||
#. Restart Swift on all three servers
|
||||
|
||||
$ ssh 15.184.9.80 "ls -lah /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/"
|
||||
ls: cannot access /srv/node/disk2/accounts/18914/e78/93c41ef56dd69173a9524193ab813e78/: No such file or directory
|
||||
At this stage, the account is fully deleted. If you enable the auto-create option, the
|
||||
next time the user attempts to access the account, the account will be created.
|
||||
You may also use swiftly to recreate the account.
|
||||
|
||||
If the handoff node has a database, wait for rebalancing to occur.
|
||||
|
||||
Procedure: Temporarily stop load balancers from directing traffic to a proxy server
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -319,7 +367,7 @@ follows. This can be useful when a proxy is misbehaving but you need
|
|||
Swift running to help diagnose the problem. By removing from the load
|
||||
balancers, customer's are not impacted by the misbehaving proxy.
|
||||
|
||||
#. Ensure that in proxyserver.com the ``disable_path`` variable is set to
|
||||
#. Ensure that in /etc/swift/proxy-server.conf the ``disable_path`` variable is set to
|
||||
``/etc/swift/disabled-by-file``.
|
||||
|
||||
#. Log onto the proxy node.
|
||||
|
@ -330,9 +378,9 @@ balancers, customer's are not impacted by the misbehaving proxy.
|
|||
|
||||
sudo swift-init proxy shutdown
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
|
||||
Shutdown, not stop.
|
||||
Shutdown, not stop.
|
||||
|
||||
#. Create the ``/etc/swift/disabled-by-file`` file. For example:
|
||||
|
||||
|
@ -346,13 +394,10 @@ balancers, customer's are not impacted by the misbehaving proxy.
|
|||
|
||||
sudo swift-init proxy start
|
||||
|
||||
It works because the healthcheck middleware looks for this file. If it
|
||||
find it, it will return 503 error instead of 200/OK. This means the load balancer
|
||||
It works because the healthcheck middleware looks for /etc/swift/disabled-by-file.
|
||||
If it exists, the middleware will return 503/error instead of 200/OK. This means the load balancer
|
||||
should stop sending traffic to the proxy.
|
||||
|
||||
``/healthcheck`` will report
|
||||
``FAIL: disabled by file`` if the ``disabled-by-file`` file exists.
|
||||
|
||||
Procedure: Ad-Hoc disk performance test
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
==============================
|
||||
Further issues and resolutions
|
||||
==============================
|
||||
|
||||
.. note::
|
||||
|
||||
The urgency levels in each **Action** column indicates whether or
|
||||
not it is required to take immediate action, or if the problem can be worked
|
||||
on during business hours.
|
||||
|
||||
.. list-table::
|
||||
:widths: 33 33 33
|
||||
:header-rows: 1
|
||||
|
||||
* - **Scenario**
|
||||
- **Description**
|
||||
- **Action**
|
||||
* - ``/healthcheck`` latency is high.
|
||||
- The ``/healthcheck`` test does not tax the proxy very much so any drop in value is probably related to
|
||||
network issues, rather than the proxies being very busy. A very slow proxy might impact the average
|
||||
number, but it would need to be very slow to shift the number that much.
|
||||
- Check networks. Do a ``curl https://<ip-address>/healthcheck where ip-address`` is individual proxy
|
||||
IP address to see if you can pin point a problem in the network.
|
||||
|
||||
Urgency: If there are other indications that your system is slow, you should treat
|
||||
this as an urgent problem.
|
||||
* - Swift process is not running.
|
||||
- You can use ``swift-init`` status to check if swift processes are running on any
|
||||
given server.
|
||||
- Run this command:
|
||||
.. code::
|
||||
|
||||
sudo swift-init all start
|
||||
|
||||
Examine messages in the swift log files to see if there are any
|
||||
error messages related to any of the swift processes since the time you
|
||||
ran the ``swift-init`` command.
|
||||
|
||||
Take any corrective actions that seem necessary.
|
||||
|
||||
Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - ntpd is not running.
|
||||
- NTP is not running.
|
||||
- Configure and start NTP.
|
||||
Urgency: For proxy servers, this is vital.
|
||||
|
||||
* - Host clock is not syncd to an NTP server.
|
||||
- Node time settings does not match NTP server time.
|
||||
This may take some time to sync after a reboot.
|
||||
- Assuming NTP is configured and running, you have to wait until the times sync.
|
||||
* - A swift process has hundreds, to thousands of open file descriptors.
|
||||
- May happen to any of the swift processes.
|
||||
Known to have happened with a ``rsyslod restart`` and where ``/tmp`` was hanging.
|
||||
|
||||
- Restart the swift processes on the affected node:
|
||||
|
||||
.. code::
|
||||
|
||||
% sudo swift-init all reload
|
||||
|
||||
Urgency:
|
||||
If known performance problem: Immediate
|
||||
|
||||
If system seems fine: Medium
|
||||
* - A swift process is not owned by the swift user.
|
||||
- If the UID of the swift user has changed, then the processes might not be
|
||||
owned by that UID.
|
||||
- Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - Object account or container files not owned by swift.
|
||||
- This typically happens if during a reinstall or a re-image of a server that the UID
|
||||
of the swift user was changed. The data files in the object account and container
|
||||
directories are owned by the original swift UID. As a result, the current swift
|
||||
user does not own these files.
|
||||
- Correct the UID of the swift user to reflect that of the original UID. An alternate
|
||||
action is to change the ownership of every file on all file systems. This alternate
|
||||
action is often impractical and will take considerable time.
|
||||
|
||||
Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - A disk drive has a high IO wait or service time.
|
||||
- If high wait IO times are seen for a single disk, then the disk drive is the problem.
|
||||
If most/all devices are slow, the controller is probably the source of the problem.
|
||||
The controller cache may also be miss configured – which will cause similar long
|
||||
wait or service times.
|
||||
- As a first step, if your controllers have a cache, check that it is enabled and their battery/capacitor
|
||||
is working.
|
||||
|
||||
Second, reboot the server.
|
||||
If problem persists, file a DC ticket to have the drive or controller replaced.
|
||||
See `Diagnose: Slow disk devices` on how to check the drive wait or service times.
|
||||
|
||||
Urgency: Medium
|
||||
* - The network interface is not up.
|
||||
- Use the ``ifconfig`` and ``ethtool`` commands to determine the network state.
|
||||
- You can try restarting the interface. However, generally the interface
|
||||
(or cable) is probably broken, especially if the interface is flapping.
|
||||
|
||||
Urgency: If this only affects one server, and you have more than one,
|
||||
identifying and fixing the problem can wait until business hours.
|
||||
If this same problem affects many servers, then you need to take corrective
|
||||
action immediately.
|
||||
* - Network interface card (NIC) is not operating at the expected speed.
|
||||
- The NIC is running at a slower speed than its nominal rated speed.
|
||||
For example, it is running at 100 Mb/s and the NIC is a 1Ge NIC.
|
||||
- 1. Try resetting the interface with:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo ethtool -s eth0 speed 1000
|
||||
|
||||
... and then run:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo lshw -class
|
||||
|
||||
See if size goes to the expected speed. Failing
|
||||
that, check hardware (NIC cable/switch port).
|
||||
|
||||
2. If persistent, consider shutting down the server (especially if a proxy)
|
||||
until the problem is identified and resolved. If you leave this server
|
||||
running it can have a large impact on overall performance.
|
||||
|
||||
Urgency: High
|
||||
* - The interface RX/TX error count is non-zero.
|
||||
- A value of 0 is typical, but counts of 1 or 2 do not indicate a problem.
|
||||
- 1. For low numbers (For example, 1 or 2), you can simply ignore. Numbers in the range
|
||||
3-30 probably indicate that the error count has crept up slowly over a long time.
|
||||
Consider rebooting the server to remove the report from the noise.
|
||||
|
||||
Typically, when a cable or interface is bad, the error count goes to 400+. For example,
|
||||
it stands out. There may be other symptoms such as the interface going up and down or
|
||||
not running at correct speed. A server with a high error count should be watched.
|
||||
|
||||
2. If the error count continue to climb, consider taking the server down until
|
||||
it can be properly investigated. In any case, a reboot should be done to clear
|
||||
the error count.
|
||||
|
||||
Urgency: High, if the error count increasing.
|
||||
|
||||
* - In a swift log you see a message that a process has not replicated in over 24 hours.
|
||||
- The replicator has not successfully completed a run in the last 24 hours.
|
||||
This indicates that the replicator has probably hung.
|
||||
- Use ``swift-init`` to stop and then restart the replicator process.
|
||||
|
||||
Urgency: Low (high if recent adding or replacement of disk drives), however if you
|
||||
recently added or replaced disk drives then you should treat this urgently.
|
||||
* - Container Updater has not run in 4 hour(s).
|
||||
- The service may appear to be running however, it may be hung. Examine their swift
|
||||
logs to see if there are any error messages relating to the container updater. This
|
||||
may potentially explain why the container is not running.
|
||||
- Urgency: Medium
|
||||
This may have been triggered by a recent restart of the rsyslog daemon.
|
||||
Restart the service with:
|
||||
.. code::
|
||||
|
||||
sudo swift-init <service> reload
|
||||
* - Object replicator: Reports the remaining time and that time is more than 100 hours.
|
||||
- Each replication cycle the object replicator writes a log message to its log
|
||||
reporting statistics about the current cycle. This includes an estimate for the
|
||||
remaining time needed to replicate all objects. If this time is longer than
|
||||
100 hours, there is a problem with the replication process.
|
||||
- Urgency: Medium
|
||||
Restart the service with:
|
||||
.. code::
|
||||
|
||||
sudo swift-init object-replicator reload
|
||||
|
||||
Check that the remaining replication time is going down.
|
|
@ -18,16 +18,14 @@ files. For example:
|
|||
|
||||
.. code::
|
||||
|
||||
$ PDSH_SSH_ARGS_APPEND="-o StrictHostKeyChecking=no" pdsh -l <yourusername> -R ssh
|
||||
|
||||
-w <redacted>.68.[4-11,132-139 4-11,132-139],<redacted>.132.[4-11,132-139
|
||||
4-11,132-139] 'sudo bzgrep -w AUTH_redacted-4962-4692-98fb-52ddda82a5af /var/log/swift/proxy.log\*'
|
||||
dshbak -c
|
||||
$ PDSH_SSH_ARGS_APPEND="-o StrictHostKeyChecking=no" pdsh -l <yourusername> -R ssh \
|
||||
-w <redacted>.68.[4-11,132-139 4-11,132-139],<redacted>.132.[4-11,132-139] \
|
||||
'sudo bzgrep -w AUTH_redacted-4962-4692-98fb-52ddda82a5af /var/log/swift/proxy.log*' | dshbak -c
|
||||
.
|
||||
.
|
||||
\---------------\-
|
||||
----------------
|
||||
<redacted>.132.6
|
||||
\---------------\-
|
||||
----------------
|
||||
Feb 29 08:51:57 sw-aw2az2-proxy011 proxy-server <redacted>.16.132
|
||||
<redacted>.66.8 29/Feb/2012/08/51/57 GET /v1.0/AUTH_redacted-4962-4692-98fb-52ddda82a5af
|
||||
/%3Fformat%3Djson HTTP/1.0 404 - - <REDACTED>_4f4d50c5e4b064d88bd7ab82 - - -
|
||||
|
@ -37,52 +35,49 @@ This shows a ``GET`` operation on the users account.
|
|||
|
||||
.. note::
|
||||
|
||||
The HTTP status returned is 404, not found, rather than 500 as reported by the user.
|
||||
The HTTP status returned is 404, Not found, rather than 500 as reported by the user.
|
||||
|
||||
Using the transaction ID, ``tx429fc3be354f434ab7f9c6c4206c1dc3`` you can
|
||||
search the swift object servers log files for this transaction ID:
|
||||
|
||||
.. code::
|
||||
|
||||
$ PDSH_SSH_ARGS_APPEND="-o StrictHostKeyChecking=no" pdsh -l <yourusername>
|
||||
|
||||
-R ssh
|
||||
-w <redacted>.72.[4-67|4-67],<redacted>.[4-67|4-67],<redacted>.[4-67|4-67],<redacted>.204.[4-131| 4-131]
|
||||
'sudo bzgrep tx429fc3be354f434ab7f9c6c4206c1dc3 /var/log/swift/server.log*'
|
||||
| dshbak -c
|
||||
$ PDSH_SSH_ARGS_APPEND="-o StrictHostKeyChecking=no" pdsh -l <yourusername> -R ssh \
|
||||
-w <redacted>.72.[4-67|4-67],<redacted>.[4-67|4-67],<redacted>.[4-67|4-67],<redacted>.204.[4-131] \
|
||||
'sudo bzgrep tx429fc3be354f434ab7f9c6c4206c1dc3 /var/log/swift/server.log*' | dshbak -c
|
||||
.
|
||||
.
|
||||
\---------------\-
|
||||
----------------
|
||||
<redacted>.72.16
|
||||
\---------------\-
|
||||
----------------
|
||||
Feb 29 08:51:57 sw-aw2az1-object013 account-server <redacted>.132.6 - -
|
||||
|
||||
[29/Feb/2012:08:51:57 +0000|] "GET /disk9/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
404 - "tx429fc3be354f434ab7f9c6c4206c1dc3" "-" "-"
|
||||
|
||||
0.0016 ""
|
||||
\---------------\-
|
||||
<redacted>.31
|
||||
\---------------\-
|
||||
Feb 29 08:51:57 node-az2-object060 account-server <redacted>.132.6 - -
|
||||
[29/Feb/2012:08:51:57 +0000|] "GET /disk6/198875/AUTH_redacted-4962-
|
||||
4692-98fb-52ddda82a5af" 404 - "tx429fc3be354f434ab7f9c6c4206c1dc3" "-" "-" 0.0011 ""
|
||||
\---------------\-
|
||||
<redacted>.204.70
|
||||
\---------------\-
|
||||
----------------
|
||||
<redacted>.31
|
||||
----------------
|
||||
Feb 29 08:51:57 node-az2-object060 account-server <redacted>.132.6 - -
|
||||
[29/Feb/2012:08:51:57 +0000|] "GET /disk6/198875/AUTH_redacted-4962-
|
||||
4692-98fb-52ddda82a5af" 404 - "tx429fc3be354f434ab7f9c6c4206c1dc3" "-" "-" 0.0011 ""
|
||||
----------------
|
||||
<redacted>.204.70
|
||||
----------------
|
||||
|
||||
Feb 29 08:51:57 sw-aw2az3-object0067 account-server <redacted>.132.6 - -
|
||||
[29/Feb/2012:08:51:57 +0000|] "GET /disk6/198875/AUTH_redacted-4962-
|
||||
4692-98fb-52ddda82a5af" 404 - "tx429fc3be354f434ab7f9c6c4206c1dc3" "-" "-" 0.0014 ""
|
||||
Feb 29 08:51:57 sw-aw2az3-object0067 account-server <redacted>.132.6 - -
|
||||
[29/Feb/2012:08:51:57 +0000|] "GET /disk6/198875/AUTH_redacted-4962-
|
||||
4692-98fb-52ddda82a5af" 404 - "tx429fc3be354f434ab7f9c6c4206c1dc3" "-" "-" 0.0014 ""
|
||||
|
||||
.. note::
|
||||
|
||||
The 3 GET operations to 3 different object servers that hold the 3
|
||||
replicas of this users account. Each ``GET`` returns a HTTP status of 404,
|
||||
not found.
|
||||
Not found.
|
||||
|
||||
Next, use the ``swift-get-nodes`` command to determine exactly where the
|
||||
users account data is stored:
|
||||
user's account data is stored:
|
||||
|
||||
.. code::
|
||||
|
||||
|
@ -94,43 +89,43 @@ users account data is stored:
|
|||
Partition 198875
|
||||
Hash 1846d99185f8a0edaf65cfbf37439696
|
||||
|
||||
Server:Port Device <redacted>.31:6002 disk6
|
||||
Server:Port Device <redacted>.204.70:6002 disk6
|
||||
Server:Port Device <redacted>.72.16:6002 disk9
|
||||
Server:Port Device <redacted>.204.64:6002 disk11 [Handoff]
|
||||
Server:Port Device <redacted>.26:6002 disk11 [Handoff]
|
||||
Server:Port Device <redacted>.72.27:6002 disk11 [Handoff]
|
||||
Server:Port Device <redacted>.31:6202 disk6
|
||||
Server:Port Device <redacted>.204.70:6202 disk6
|
||||
Server:Port Device <redacted>.72.16:6202 disk9
|
||||
Server:Port Device <redacted>.204.64:6202 disk11 [Handoff]
|
||||
Server:Port Device <redacted>.26:6202 disk11 [Handoff]
|
||||
Server:Port Device <redacted>.72.27:6202 disk11 [Handoff]
|
||||
|
||||
curl -I -XHEAD "`http://<redacted>.31:6002/disk6/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.138.31:6002/disk6/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_
|
||||
curl -I -XHEAD "`http://<redacted>.204.70:6002/disk6/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.204.70:6002/disk6/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_
|
||||
curl -I -XHEAD "`http://<redacted>.72.16:6002/disk9/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.72.16:6002/disk9/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_
|
||||
curl -I -XHEAD "`http://<redacted>.204.64:6002/disk11/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.204.64:6002/disk11/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_ # [Handoff]
|
||||
curl -I -XHEAD "`http://<redacted>.26:6002/disk11/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.136.26:6002/disk11/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_ # [Handoff]
|
||||
curl -I -XHEAD "`http://<redacted>.72.27:6002/disk11/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.72.27:6002/disk11/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_ # [Handoff]
|
||||
curl -I -XHEAD "`http://<redacted>.31:6202/disk6/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.138.31:6202/disk6/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_
|
||||
curl -I -XHEAD "`http://<redacted>.204.70:6202/disk6/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.204.70:6202/disk6/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_
|
||||
curl -I -XHEAD "`http://<redacted>.72.16:6202/disk9/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.72.16:6202/disk9/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_
|
||||
curl -I -XHEAD "`http://<redacted>.204.64:6202/disk11/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.204.64:6202/disk11/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_ # [Handoff]
|
||||
curl -I -XHEAD "`http://<redacted>.26:6202/disk11/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.136.26:6202/disk11/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_ # [Handoff]
|
||||
curl -I -XHEAD "`http://<redacted>.72.27:6202/disk11/198875/AUTH_redacted-4962-4692-98fb-52ddda82a5af"
|
||||
<http://15.185.72.27:6202/disk11/198875/AUTH_db0050ad-4962-4692-98fb-52ddda82a5af>`_ # [Handoff]
|
||||
|
||||
ssh <redacted>.31 "ls \-lah /srv/node/disk6/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/"
|
||||
ssh <redacted>.204.70 "ls \-lah /srv/node/disk6/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/"
|
||||
ssh <redacted>.72.16 "ls \-lah /srv/node/disk9/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/"
|
||||
ssh <redacted>.204.64 "ls \-lah /srv/node/disk11/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/" # [Handoff]
|
||||
ssh <redacted>.26 "ls \-lah /srv/node/disk11/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/" # [Handoff]
|
||||
ssh <redacted>.72.27 "ls \-lah /srv/node/disk11/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/" # [Handoff]
|
||||
ssh <redacted>.31 "ls -lah /srv/node/disk6/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/"
|
||||
ssh <redacted>.204.70 "ls -lah /srv/node/disk6/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/"
|
||||
ssh <redacted>.72.16 "ls -lah /srv/node/disk9/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/"
|
||||
ssh <redacted>.204.64 "ls -lah /srv/node/disk11/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/" # [Handoff]
|
||||
ssh <redacted>.26 "ls -lah /srv/node/disk11/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/" # [Handoff]
|
||||
ssh <redacted>.72.27 "ls -lah /srv/node/disk11/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/" # [Handoff]
|
||||
|
||||
Check each of the primary servers, <redacted>.31, <redacted>.204.70 and <redacted>.72.16, for
|
||||
this users account. For example on <redacted>.72.16:
|
||||
|
||||
.. code::
|
||||
|
||||
$ ls \\-lah /srv/node/disk9/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/
|
||||
$ ls -lah /srv/node/disk9/accounts/198875/696/1846d99185f8a0edaf65cfbf37439696/
|
||||
total 1.0M
|
||||
drwxrwxrwx 2 swift swift 98 2012-02-23 14:49 .
|
||||
drwxrwxrwx 3 swift swift 45 2012-02-03 23:28 ..
|
||||
-rw-\\-----\\- 1 swift swift 15K 2012-02-23 14:49 1846d99185f8a0edaf65cfbf37439696.db
|
||||
-rw------- 1 swift swift 15K 2012-02-23 14:49 1846d99185f8a0edaf65cfbf37439696.db
|
||||
-rw-rw-rw- 1 swift swift 0 2012-02-23 14:49 1846d99185f8a0edaf65cfbf37439696.db.pending
|
||||
|
||||
So this users account db, an sqlite db is present. Use sqlite to
|
||||
|
@ -155,7 +150,7 @@ checkout the account:
|
|||
status_changed_at = 1330001026.00514
|
||||
metadata =
|
||||
|
||||
.. note::
|
||||
.. note:
|
||||
|
||||
The status is ``DELETED``. So this account was deleted. This explains
|
||||
why the GET operations are returning 404, not found. Check the account
|
||||
|
@ -174,14 +169,14 @@ server logs:
|
|||
|
||||
.. code::
|
||||
|
||||
$ PDSH_SSH_ARGS_APPEND="-o StrictHostKeyChecking=no" pdsh -l <yourusername> -R ssh -w <redacted>.68.[4-11,132-139 4-11,132-
|
||||
139],<redacted>.132.[4-11,132-139|4-11,132-139] 'sudo bzgrep AUTH_redacted-4962-4692-98fb-52ddda82a5af /var/log/swift/proxy.log\* | grep -w
|
||||
DELETE |awk "{print \\$3,\\$10,\\$12}"' |- dshbak -c
|
||||
$ PDSH_SSH_ARGS_APPEND="-o StrictHostKeyChecking=no" pdsh -l <yourusername> -R ssh \
|
||||
-w <redacted>.68.[4-11,132-139 4-11,132-139],<redacted>.132.[4-11,132-139|4-11,132-139] \
|
||||
'sudo bzgrep AUTH_redacted-4962-4692-98fb-52ddda82a5af /var/log/swift/proxy.log* \
|
||||
| grep -w DELETE | awk "{print $3,$10,$12}"' |- dshbak -c
|
||||
.
|
||||
.
|
||||
Feb 23 12:43:46 sw-aw2az2-proxy001 proxy-server 15.203.233.76 <redacted>.66.7 23/Feb/2012/12/43/46 DELETE /v1.0/AUTH_redacted-4962-4692-98fb-
|
||||
Feb 23 12:43:46 sw-aw2az2-proxy001 proxy-server <redacted> <redacted>.66.7 23/Feb/2012/12/43/46 DELETE /v1.0/AUTH_redacted-4962-4692-98fb-
|
||||
52ddda82a5af/ HTTP/1.0 204 - Apache-HttpClient/4.1.2%20%28java%201.5%29 <REDACTED>_4f458ee4e4b02a869c3aad02 - - -
|
||||
|
||||
tx4471188b0b87406899973d297c55ab53 - 0.0086
|
||||
|
||||
From this you can see the operation that resulted in the account being deleted.
|
||||
|
@ -252,8 +247,8 @@ Finally, use ``swift-direct`` to delete the container.
|
|||
Procedure: Decommissioning swift nodes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Should Swift nodes need to be decommissioned. For example, where they are being
|
||||
re-purposed, it is very important to follow the following steps.
|
||||
Should Swift nodes need to be decommissioned (e.g.,, where they are being
|
||||
re-purposed), it is very important to follow the following steps.
|
||||
|
||||
#. In the case of object servers, follow the procedure for removing
|
||||
the node from the rings.
|
||||
|
|
|
@ -154,11 +154,14 @@ add the configuration for the authtoken middleware::
|
|||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||
identity_uri = http://keystonehost:35357/
|
||||
admin_tenant_name = service
|
||||
admin_user = swift
|
||||
admin_password = password
|
||||
auth_uri = http://keystonehost:5000/
|
||||
auth_url = http://keystonehost:35357/
|
||||
auth_plugin = password
|
||||
project_domain_id = default
|
||||
user_domain_id = default
|
||||
project_name = service
|
||||
username = swift
|
||||
password = password
|
||||
cache = swift.cache
|
||||
include_service_catalog = False
|
||||
delay_auth_decision = True
|
||||
|
@ -166,16 +169,17 @@ add the configuration for the authtoken middleware::
|
|||
The actual values for these variables will need to be set depending on
|
||||
your situation, but in short:
|
||||
|
||||
* ``identity_uri`` points to the Keystone Admin service. This information is
|
||||
used by the middleware to actually query Keystone about the validity of the
|
||||
authentication tokens. It is not necessary to append any Keystone API version
|
||||
number to this URI.
|
||||
* The admin auth credentials (``admin_user``, ``admin_tenant_name``,
|
||||
``admin_password``) will be used to retrieve an admin token. That
|
||||
token will be used to authorize user tokens behind the scenes.
|
||||
* ``auth_uri`` should point to a Keystone service from which users may
|
||||
retrieve tokens. This value is used in the `WWW-Authenticate` header that
|
||||
auth_token sends with any denial response.
|
||||
* ``auth_url`` points to the Keystone Admin service. This information is
|
||||
used by the middleware to actually query Keystone about the validity of the
|
||||
authentication tokens. It is not necessary to append any Keystone API version
|
||||
number to this URI.
|
||||
* The auth credentials (``project_domain_id``, ``user_domain_id``,
|
||||
``username``, ``project_name``, ``password``) will be used to retrieve an
|
||||
admin token. That token will be used to authorize user tokens behind the
|
||||
scenes.
|
||||
* ``cache`` is set to ``swift.cache``. This means that the middleware
|
||||
will get the Swift memcache from the request environment.
|
||||
* ``include_service_catalog`` defaults to ``True`` if not set. This means
|
||||
|
|
|
@ -12,13 +12,6 @@ configure their cluster to allow/accept sync requests to/from other clusters,
|
|||
and the user specifies where to sync their container to along with a secret
|
||||
synchronization key.
|
||||
|
||||
.. note::
|
||||
|
||||
Container sync will sync object POSTs only if the proxy server is set to
|
||||
use "object_post_as_copy = true" which is the default. So-called fast
|
||||
object posts, "object_post_as_copy = false" do not update the container
|
||||
listings and therefore can't be detected for synchronization.
|
||||
|
||||
.. note::
|
||||
|
||||
If you are using the large objects feature you will need to ensure both
|
||||
|
@ -128,6 +121,50 @@ should be noted there is no way for an end user to detect sync progress or
|
|||
problems other than HEADing both containers and comparing the overall
|
||||
information.
|
||||
|
||||
|
||||
|
||||
-----------------------------
|
||||
Container Sync Statistics
|
||||
-----------------------------
|
||||
|
||||
Container Sync INFO level logs contains activity metrics and accounting
|
||||
information foe insightful tracking.
|
||||
Currently two different statistics are collected:
|
||||
|
||||
About once an hour or so, accumulated statistics of all operations performed
|
||||
by Container Sync are reported to the log file with the following format:
|
||||
"Since (time): (sync) synced [(delete) deletes, (put) puts], (skip) skipped,
|
||||
(fail) failed"
|
||||
time: last report time
|
||||
sync: number of containers with sync turned on that were successfully synced
|
||||
delete: number of successful DELETE object requests to the target cluster
|
||||
put: number of successful PUT object request to the target cluster
|
||||
skip: number of containers whose sync has been turned off, but are not
|
||||
yet cleared from the sync store
|
||||
fail: number of containers with failure (due to exception, timeout or other
|
||||
reason)
|
||||
|
||||
For each container synced, per container statistics are reported with the
|
||||
following format:
|
||||
Container sync report: (container), time window start: (start), time window
|
||||
end: %(end), puts: (puts), posts: (posts), deletes: (deletes), bytes: (bytes),
|
||||
sync_point1: (point1), sync_point2: (point2), total_rows: (total)
|
||||
container: account/container statistics are for
|
||||
start: report start time
|
||||
end: report end time
|
||||
puts: number of successful PUT object requests to the target container
|
||||
posts: N/A (0)
|
||||
deletes: number of successful DELETE object requests to the target container
|
||||
bytes: number of bytes sent over the network to the target container
|
||||
point1: progress indication - the container's x_container_sync_point1
|
||||
point2: progress indication - the container's x_container_sync_point2
|
||||
total: number of objects processed at the container
|
||||
|
||||
it is possible that more than one server syncs a container, therefore logfiles
|
||||
from all servers need to be evaluated
|
||||
|
||||
|
||||
|
||||
----------------------------------------------------------
|
||||
Using the ``swift`` tool to set up synchronized containers
|
||||
----------------------------------------------------------
|
||||
|
@ -386,13 +423,6 @@ from ``sync-containers``.
|
|||
cluster. Therefore, the container servers must be permitted to initiate
|
||||
outbound connections to the remote proxy servers (or load balancers).
|
||||
|
||||
.. note::
|
||||
|
||||
Container sync will sync object POSTs only if the proxy server is set to
|
||||
use "object_post_as_copy = true" which is the default. So-called fast
|
||||
object posts, "object_post_as_copy = false" do not update the container
|
||||
listings and therefore can't be detected for synchronization.
|
||||
|
||||
The actual syncing is slightly more complicated to make use of the three
|
||||
(or number-of-replicas) main nodes for a container without each trying to
|
||||
do the exact same work but also without missing work if one node happens to
|
||||
|
|
|
@ -103,10 +103,14 @@ meta string A general-use field for storing additional information for the
|
|||
====== ======= ==============================================================
|
||||
|
||||
Note: The list of devices may contain holes, or indexes set to None, for
|
||||
devices that have been removed from the cluster. Generally, device ids are not
|
||||
reused. Also, some devices may be temporarily disabled by setting their weight
|
||||
to 0.0. To obtain a list of active devices (for uptime polling, for example)
|
||||
the Python code would look like: ``devices = list(self._iter_devs())``
|
||||
devices that have been removed from the cluster. However, device ids are
|
||||
reused. Device ids are reused to avoid potentially running out of device id
|
||||
slots when there are available slots (from prior removal of devices). A
|
||||
consequence of this device id reuse is that the device id (integer value) does
|
||||
not necessarily correspond with the chronology of when the device was added to
|
||||
the ring. Also, some devices may be temporarily disabled by setting their
|
||||
weight to 0.0. To obtain a list of active devices (for uptime polling, for
|
||||
example) the Python code would look like: ``devices = list(self._iter_devs())``
|
||||
|
||||
*************************
|
||||
Partition Assignment List
|
||||
|
@ -154,6 +158,8 @@ for the ring. This means that some partitions will have more replicas than
|
|||
others. For example, if a ring has 3.25 replicas, then 25% of its partitions
|
||||
will have four replicas, while the remaining 75% will have just three.
|
||||
|
||||
.. _ring_dispersion:
|
||||
|
||||
**********
|
||||
Dispersion
|
||||
**********
|
||||
|
@ -169,6 +175,8 @@ the dispersion metric.
|
|||
A lower dispersion value is better, and the value can be used to find the
|
||||
proper value for "overload".
|
||||
|
||||
.. _ring_overload:
|
||||
|
||||
********
|
||||
Overload
|
||||
********
|
||||
|
@ -422,3 +430,5 @@ for rings that were close to balanceable, like 3 machines with 60TB, 60TB, and
|
|||
didn't always get it. After that, overload was added to the ring builder so
|
||||
that operators could choose a balance between dispersion and device weights.
|
||||
In time the overload concept was improved and made more accurate.
|
||||
|
||||
For more background on consistent hashing rings, please see :doc:`ring_background`.
|
||||
|
|
|
@ -0,0 +1,957 @@
|
|||
==================================
|
||||
Building a Consistent Hashing Ring
|
||||
==================================
|
||||
|
||||
---------------------
|
||||
Authored by Greg Holt
|
||||
---------------------
|
||||
|
||||
This is compilation of five posts I made earlier discussing how to build
|
||||
a consistent hashing ring. The posts seemed to be accessed quite frequently,
|
||||
so I've gathered them all here on one page for easier reading.
|
||||
|
||||
Part 1
|
||||
======
|
||||
“Consistent Hashing” is a term used to describe a process where data is
|
||||
distributed using a hashing algorithm to determine its location. Using
|
||||
only the hash of the id of the data you can determine exactly where that
|
||||
data should be. This mapping of hashes to locations is usually termed a
|
||||
“ring”.
|
||||
|
||||
Probably the simplest hash is just a modulus of the id. For instance, if
|
||||
all ids are numbers and you have two machines you wish to distribute data
|
||||
to, you could just put all odd numbered ids on one machine and even numbered
|
||||
ids on the other. Assuming you have a balanced number of odd and even
|
||||
numbered ids, and a balanced data size per id, your data would be balanced
|
||||
between the two machines.
|
||||
|
||||
Since data ids are often textual names and not numbers, like paths for
|
||||
files or URLs, it makes sense to use a “real” hashing algorithm to convert
|
||||
the names to numbers first. Using MD5 for instance, the hash of the name
|
||||
‘mom.png’ is ‘4559a12e3e8da7c2186250c2f292e3af’ and the hash of ‘dad.png’
|
||||
is ‘096edcc4107e9e18d6a03a43b3853bea’. Now, using the modulus, we can
|
||||
place ‘mom.jpg’ on the odd machine and ‘dad.png’ on the even one. Another
|
||||
benefit of using a hashing algorithm like MD5 is that the resulting hashes
|
||||
have a known even distribution, meaning your ids will be evenly distributed
|
||||
without worrying about keeping the id values themselves evenly distributed.
|
||||
|
||||
Here is a simple example of this in action:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
NODE_COUNT = 100
|
||||
DATA_ID_COUNT = 10000000
|
||||
|
||||
node_counts = [0] * NODE_COUNT
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
# This just pulls part of the hash out as an integer
|
||||
hsh = unpack_from('>I', md5(data_id).digest())[0]
|
||||
node_id = hsh % NODE_COUNT
|
||||
node_counts[node_id] += 1
|
||||
desired_count = DATA_ID_COUNT / NODE_COUNT
|
||||
print '%d: Desired data ids per node' % desired_count
|
||||
max_count = max(node_counts)
|
||||
over = 100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids on one node, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(node_counts)
|
||||
under = 100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids on one node, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
|
||||
::
|
||||
|
||||
100000: Desired data ids per node
|
||||
100695: Most data ids on one node, 0.69% over
|
||||
99073: Least data ids on one node, 0.93% under
|
||||
|
||||
So that’s not bad at all; less than a percent over/under for distribution
|
||||
per node. In the next part of this series we’ll examine where modulus
|
||||
distribution causes problems and how to improve our ring to overcome them.
|
||||
|
||||
Part 2
|
||||
======
|
||||
In Part 1 of this series, we did a simple test of using the modulus of a
|
||||
hash to locate data. We saw very good distribution, but that’s only part
|
||||
of the story. Distributed systems not only need to distribute load, but
|
||||
they often also need to grow as more and more data is placed in it.
|
||||
|
||||
So let’s imagine we have a 100 node system up and running using our
|
||||
previous algorithm, but it’s starting to get full so we want to add
|
||||
another node. When we add that 101st node to our algorithm we notice
|
||||
that many ids now map to different nodes than they previously did.
|
||||
We’re going to have to shuffle a ton of data around our system to get
|
||||
it all into place again.
|
||||
|
||||
Let’s examine what’s happened on a much smaller scale: just 2 nodes
|
||||
again, node 0 gets even ids and node 1 gets odd ids. So data id 100
|
||||
would map to node 0, data id 101 to node 1, data id 102 to node 0, etc.
|
||||
This is simply node = id % 2. Now we add a third node (node 2) for more
|
||||
space, so we want node = id % 3. So now data id 100 maps to node id 1,
|
||||
data id 101 to node 2, and data id 102 to node 0. So we have to move
|
||||
data for 2 of our 3 ids so they can be found again.
|
||||
|
||||
Let’s examine this at a larger scale:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
NODE_COUNT = 100
|
||||
NEW_NODE_COUNT = 101
|
||||
DATA_ID_COUNT = 10000000
|
||||
|
||||
moved_ids = 0
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
hsh = unpack_from('>I', md5(str(data_id)).digest())[0]
|
||||
node_id = hsh % NODE_COUNT
|
||||
new_node_id = hsh % NEW_NODE_COUNT
|
||||
if node_id != new_node_id:
|
||||
moved_ids += 1
|
||||
percent_moved = 100.0 * moved_ids / DATA_ID_COUNT
|
||||
print '%d ids moved, %.02f%%' % (moved_ids, percent_moved)
|
||||
|
||||
::
|
||||
|
||||
9900989 ids moved, 99.01%
|
||||
|
||||
Wow, that’s severe. We’d have to shuffle around 99% of our data just
|
||||
to increase our capacity 1%! We need a new algorithm that combats this
|
||||
behavior.
|
||||
|
||||
This is where the “ring” really comes in. We can assign ranges of hashes
|
||||
directly to nodes and then use an algorithm that minimizes the changes
|
||||
to those ranges. Back to our small scale, let’s say our ids range from 0
|
||||
to 999. We have two nodes and we’ll assign data ids 0–499 to node 0 and
|
||||
500–999 to node 1. Later, when we add node 2, we can take half the data
|
||||
ids from node 0 and half from node 1, minimizing the amount of data that
|
||||
needs to move.
|
||||
|
||||
Let’s examine this at a larger scale:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from bisect import bisect_left
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
NODE_COUNT = 100
|
||||
NEW_NODE_COUNT = 101
|
||||
DATA_ID_COUNT = 10000000
|
||||
|
||||
node_range_starts = []
|
||||
for node_id in xrange(NODE_COUNT):
|
||||
node_range_starts.append(DATA_ID_COUNT /
|
||||
NODE_COUNT * node_id)
|
||||
new_node_range_starts = []
|
||||
for new_node_id in xrange(NEW_NODE_COUNT):
|
||||
new_node_range_starts.append(DATA_ID_COUNT /
|
||||
NEW_NODE_COUNT * new_node_id)
|
||||
moved_ids = 0
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
hsh = unpack_from('>I', md5(str(data_id)).digest())[0]
|
||||
node_id = bisect_left(node_range_starts,
|
||||
hsh % DATA_ID_COUNT) % NODE_COUNT
|
||||
new_node_id = bisect_left(new_node_range_starts,
|
||||
hsh % DATA_ID_COUNT) % NEW_NODE_COUNT
|
||||
if node_id != new_node_id:
|
||||
moved_ids += 1
|
||||
percent_moved = 100.0 * moved_ids / DATA_ID_COUNT
|
||||
print '%d ids moved, %.02f%%' % (moved_ids, percent_moved)
|
||||
|
||||
::
|
||||
|
||||
4901707 ids moved, 49.02%
|
||||
|
||||
Okay, that is better. But still, moving 50% of our data to add 1% capacity
|
||||
is not very good. If we examine what happened more closely we’ll see what
|
||||
is an “accordion effect”. We shrunk node 0’s range a bit to give to the
|
||||
new node, but that shifted all the other node’s ranges by the same amount.
|
||||
|
||||
We can minimize the change to a node’s assigned range by assigning several
|
||||
smaller ranges instead of the single broad range we were before. This can
|
||||
be done by creating “virtual nodes” for each node. So 100 nodes might have
|
||||
1000 virtual nodes. Let’s examine how that might work.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from bisect import bisect_left
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
NODE_COUNT = 100
|
||||
DATA_ID_COUNT = 10000000
|
||||
VNODE_COUNT = 1000
|
||||
|
||||
vnode_range_starts = []
|
||||
vnode2node = []
|
||||
for vnode_id in xrange(VNODE_COUNT):
|
||||
vnode_range_starts.append(DATA_ID_COUNT /
|
||||
VNODE_COUNT * vnode_id)
|
||||
vnode2node.append(vnode_id % NODE_COUNT)
|
||||
new_vnode2node = list(vnode2node)
|
||||
new_node_id = NODE_COUNT
|
||||
NEW_NODE_COUNT = NODE_COUNT + 1
|
||||
vnodes_to_reassign = VNODE_COUNT / NEW_NODE_COUNT
|
||||
while vnodes_to_reassign > 0:
|
||||
for node_to_take_from in xrange(NODE_COUNT):
|
||||
for vnode_id, node_id in enumerate(new_vnode2node):
|
||||
if node_id == node_to_take_from:
|
||||
new_vnode2node[vnode_id] = new_node_id
|
||||
vnodes_to_reassign -= 1
|
||||
break
|
||||
if vnodes_to_reassign <= 0:
|
||||
break
|
||||
moved_ids = 0
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
hsh = unpack_from('>I', md5(str(data_id)).digest())[0]
|
||||
vnode_id = bisect_left(vnode_range_starts,
|
||||
hsh % DATA_ID_COUNT) % VNODE_COUNT
|
||||
node_id = vnode2node[vnode_id]
|
||||
new_node_id = new_vnode2node[vnode_id]
|
||||
if node_id != new_node_id:
|
||||
moved_ids += 1
|
||||
percent_moved = 100.0 * moved_ids / DATA_ID_COUNT
|
||||
print '%d ids moved, %.02f%%' % (moved_ids, percent_moved)
|
||||
|
||||
::
|
||||
|
||||
90423 ids moved, 0.90%
|
||||
|
||||
There we go, we added 1% capacity and only moved 0.9% of existing data.
|
||||
The vnode_range_starts list seems a bit out of place though. It’s values
|
||||
are calculated and never change for the lifetime of the cluster, so let’s
|
||||
optimize that out.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from bisect import bisect_left
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
NODE_COUNT = 100
|
||||
DATA_ID_COUNT = 10000000
|
||||
VNODE_COUNT = 1000
|
||||
|
||||
vnode2node = []
|
||||
for vnode_id in xrange(VNODE_COUNT):
|
||||
vnode2node.append(vnode_id % NODE_COUNT)
|
||||
new_vnode2node = list(vnode2node)
|
||||
new_node_id = NODE_COUNT
|
||||
vnodes_to_reassign = VNODE_COUNT / (NODE_COUNT + 1)
|
||||
while vnodes_to_reassign > 0:
|
||||
for node_to_take_from in xrange(NODE_COUNT):
|
||||
for vnode_id, node_id in enumerate(vnode2node):
|
||||
if node_id == node_to_take_from:
|
||||
vnode2node[vnode_id] = new_node_id
|
||||
vnodes_to_reassign -= 1
|
||||
break
|
||||
if vnodes_to_reassign <= 0:
|
||||
break
|
||||
moved_ids = 0
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
hsh = unpack_from('>I', md5(str(data_id)).digest())[0]
|
||||
vnode_id = hsh % VNODE_COUNT
|
||||
node_id = vnode2node[vnode_id]
|
||||
new_node_id = new_vnode2node[vnode_id]
|
||||
if node_id != new_node_id:
|
||||
moved_ids += 1
|
||||
percent_moved = 100.0 * moved_ids / DATA_ID_COUNT
|
||||
print '%d ids moved, %.02f%%' % (moved_ids, percent_moved)
|
||||
|
||||
::
|
||||
|
||||
89841 ids moved, 0.90%
|
||||
|
||||
There we go. In the next part of this series, will further examine the
|
||||
algorithm’s limitations and how to improve on it.
|
||||
|
||||
Part 3
|
||||
======
|
||||
In Part 2 of this series, we reached an algorithm that performed well
|
||||
even when adding new nodes to the cluster. We used 1000 virtual nodes
|
||||
that could be independently assigned to nodes, allowing us to minimize
|
||||
the amount of data moved when a node was added.
|
||||
|
||||
The number of virtual nodes puts a cap on how many real nodes you can
|
||||
have. For example, if you have 1000 virtual nodes and you try to add a
|
||||
1001st real node, you can’t assign a virtual node to it without leaving
|
||||
another real node with no assignment, leaving you with just 1000 active
|
||||
real nodes still.
|
||||
|
||||
Unfortunately, the number of virtual nodes created at the beginning can
|
||||
never change for the life of the cluster without a lot of careful work.
|
||||
For example, you could double the virtual node count by splitting each
|
||||
existing virtual node in half and assigning both halves to the same real
|
||||
node. However, if the real node uses the virtual node’s id to optimally
|
||||
store the data (for example, all data might be stored in /[virtual node
|
||||
id]/[data id]) it would have to move data around locally to reflect the
|
||||
change. And it would have to resolve data using both the new and old
|
||||
locations while the moves were taking place, making atomic operations
|
||||
difficult or impossible.
|
||||
|
||||
Let’s continue with this assumption that changing the virtual node
|
||||
count is more work than it’s worth, but keep in mind that some applications
|
||||
might be fine with this.
|
||||
|
||||
The easiest way to deal with this limitation is to make the limit high
|
||||
enough that it won’t matter. For instance, if we decide our cluster will
|
||||
never exceed 60,000 real nodes, we can just make 60,000 virtual nodes.
|
||||
|
||||
Also, we should include in our calculations the relative size of our
|
||||
nodes. For instance, a year from now we might have real nodes that can
|
||||
handle twice the capacity of our current nodes. So we’d want to assign
|
||||
twice the virtual nodes to those future nodes, so maybe we should raise
|
||||
our virtual node estimate to 120,000.
|
||||
|
||||
A good rule to follow might be to calculate 100 virtual nodes to each
|
||||
real node at maximum capacity. This would allow you to alter the load
|
||||
on any given node by 1%, even at max capacity, which is pretty fine
|
||||
tuning. So now we’re at 6,000,000 virtual nodes for a max capacity cluster
|
||||
of 60,000 real nodes.
|
||||
|
||||
6 million virtual nodes seems like a lot, and it might seem like we’d
|
||||
use up way too much memory. But the only structure this affects is the
|
||||
virtual node to real node mapping. The base amount of memory required
|
||||
would be 6 million times 2 bytes (to store a real node id from 0 to
|
||||
65,535). 12 megabytes of memory just isn’t that much to use these days.
|
||||
|
||||
Even with all the overhead of flexible data types, things aren’t that
|
||||
bad. I changed the code from the previous part in this series to have
|
||||
60,000 real and 6,000,000 virtual nodes, changed the list to an array(‘H’),
|
||||
and python topped out at 27m of resident memory – and that includes two
|
||||
rings.
|
||||
|
||||
To change terminology a bit, we’re going to start calling these virtual
|
||||
nodes “partitions”. This will make it a bit easier to discern between the
|
||||
two types of nodes we’ve been talking about so far. Also, it makes sense
|
||||
to talk about partitions as they are really just unchanging sections
|
||||
of the hash space.
|
||||
|
||||
We’re also going to always keep the partition count a power of two. This
|
||||
makes it easy to just use bit manipulation on the hash to determine the
|
||||
partition rather than modulus. It isn’t much faster, but it is a little.
|
||||
So, here’s our updated ring code, using 8,388,608 (2 ** 23) partitions
|
||||
and 65,536 nodes. We’ve upped the sample data id set and checked the
|
||||
distribution to make sure we haven’t broken anything.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from array import array
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
PARTITION_POWER = 23
|
||||
PARTITION_SHIFT = 32 - PARTITION_POWER
|
||||
NODE_COUNT = 65536
|
||||
DATA_ID_COUNT = 100000000
|
||||
|
||||
part2node = array('H')
|
||||
for part in xrange(2 ** PARTITION_POWER):
|
||||
part2node.append(part % NODE_COUNT)
|
||||
node_counts = [0] * NODE_COUNT
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
part = unpack_from('>I',
|
||||
md5(str(data_id)).digest())[0] >> PARTITION_SHIFT
|
||||
node_id = part2node[part]
|
||||
node_counts[node_id] += 1
|
||||
desired_count = DATA_ID_COUNT / NODE_COUNT
|
||||
print '%d: Desired data ids per node' % desired_count
|
||||
max_count = max(node_counts)
|
||||
over = 100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids on one node, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(node_counts)
|
||||
under = 100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids on one node, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
|
||||
::
|
||||
|
||||
1525: Desired data ids per node
|
||||
1683: Most data ids on one node, 10.36% over
|
||||
1360: Least data ids on one node, 10.82% under
|
||||
|
||||
Hmm. +–10% seems a bit high, but I reran with 65,536 partitions and
|
||||
256 nodes and got +–0.4% so it’s just that our sample size (100m) is
|
||||
too small for our number of partitions (8m). It’ll take way too long
|
||||
to run experiments with an even larger sample size, so let’s reduce
|
||||
back down to these lesser numbers. (To be certain, I reran at the full
|
||||
version with a 10 billion data id sample set and got +–1%, but it took
|
||||
6.5 hours to run.)
|
||||
|
||||
In the next part of this series, we’ll talk about how to increase the
|
||||
durability of our data in the cluster.
|
||||
|
||||
Part 4
|
||||
======
|
||||
In Part 3 of this series, we just further discussed partitions (virtual
|
||||
nodes) and cleaned up our code a bit based on that. Now, let’s talk
|
||||
about how to increase the durability and availability of our data in the
|
||||
cluster.
|
||||
|
||||
For many distributed data stores, durability is quite important. Either
|
||||
RAID arrays or individually distinct copies of data are required. While
|
||||
RAID will increase the durability, it does nothing to increase the
|
||||
availability – if the RAID machine crashes, the data may be safe but
|
||||
inaccessible until repairs are done. If we keep distinct copies of the
|
||||
data on different machines and a machine crashes, the other copies will
|
||||
still be available while we repair the broken machine.
|
||||
|
||||
An easy way to gain this multiple copy durability/availability is to
|
||||
just use multiple rings and groups of nodes. For instance, to achieve
|
||||
the industry standard of three copies, you’d split the nodes into three
|
||||
groups and each group would have its own ring and each would receive a
|
||||
copy of each data item. This can work well enough, but has the drawback
|
||||
that expanding capacity requires adding three nodes at a time and that
|
||||
losing one node essentially lowers capacity by three times that node’s
|
||||
capacity.
|
||||
|
||||
Instead, let’s use a different, but common, approach of meeting our
|
||||
requirements with a single ring. This can be done by walking the ring
|
||||
from the starting point and looking for additional distinct nodes.
|
||||
Here’s code that supports a variable number of replicas (set to 3 for
|
||||
testing):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from array import array
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
REPLICAS = 3
|
||||
PARTITION_POWER = 16
|
||||
PARTITION_SHIFT = 32 - PARTITION_POWER
|
||||
PARTITION_MAX = 2 ** PARTITION_POWER - 1
|
||||
NODE_COUNT = 256
|
||||
DATA_ID_COUNT = 10000000
|
||||
|
||||
part2node = array('H')
|
||||
for part in xrange(2 ** PARTITION_POWER):
|
||||
part2node.append(part % NODE_COUNT)
|
||||
node_counts = [0] * NODE_COUNT
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
part = unpack_from('>I',
|
||||
md5(str(data_id)).digest())[0] >> PARTITION_SHIFT
|
||||
node_ids = [part2node[part]]
|
||||
node_counts[node_ids[0]] += 1
|
||||
for replica in xrange(1, REPLICAS):
|
||||
while part2node[part] in node_ids:
|
||||
part += 1
|
||||
if part > PARTITION_MAX:
|
||||
part = 0
|
||||
node_ids.append(part2node[part])
|
||||
node_counts[node_ids[-1]] += 1
|
||||
desired_count = DATA_ID_COUNT / NODE_COUNT * REPLICAS
|
||||
print '%d: Desired data ids per node' % desired_count
|
||||
max_count = max(node_counts)
|
||||
over = 100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids on one node, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(node_counts)
|
||||
under = 100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids on one node, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
|
||||
::
|
||||
|
||||
117186: Desired data ids per node
|
||||
118133: Most data ids on one node, 0.81% over
|
||||
116093: Least data ids on one node, 0.93% under
|
||||
|
||||
That’s pretty good; less than 1% over/under. While this works well,
|
||||
there are a couple of problems.
|
||||
|
||||
First, because of how we’ve initially assigned the partitions to nodes,
|
||||
all the partitions for a given node have their extra copies on the same
|
||||
other two nodes. The problem here is that when a machine fails, the load
|
||||
on these other nodes will jump by that amount. It’d be better if we
|
||||
initially shuffled the partition assignment to distribute the failover
|
||||
load better.
|
||||
|
||||
The other problem is a bit harder to explain, but deals with physical
|
||||
separation of machines. Imagine you can only put 16 machines in a rack
|
||||
in your datacenter. The 256 nodes we’ve been using would fill 16 racks.
|
||||
With our current code, if a rack goes out (power problem, network issue,
|
||||
etc.) there is a good chance some data will have all three copies in that
|
||||
rack, becoming inaccessible. We can fix this shortcoming by adding the
|
||||
concept of zones to our nodes, and then ensuring that replicas are stored
|
||||
in distinct zones.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from array import array
|
||||
from hashlib import md5
|
||||
from random import shuffle
|
||||
from struct import unpack_from
|
||||
|
||||
REPLICAS = 3
|
||||
PARTITION_POWER = 16
|
||||
PARTITION_SHIFT = 32 - PARTITION_POWER
|
||||
PARTITION_MAX = 2 ** PARTITION_POWER - 1
|
||||
NODE_COUNT = 256
|
||||
ZONE_COUNT = 16
|
||||
DATA_ID_COUNT = 10000000
|
||||
|
||||
node2zone = []
|
||||
while len(node2zone) < NODE_COUNT:
|
||||
zone = 0
|
||||
while zone < ZONE_COUNT and len(node2zone) < NODE_COUNT:
|
||||
node2zone.append(zone)
|
||||
zone += 1
|
||||
part2node = array('H')
|
||||
for part in xrange(2 ** PARTITION_POWER):
|
||||
part2node.append(part % NODE_COUNT)
|
||||
shuffle(part2node)
|
||||
node_counts = [0] * NODE_COUNT
|
||||
zone_counts = [0] * ZONE_COUNT
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
part = unpack_from('>I',
|
||||
md5(str(data_id)).digest())[0] >> PARTITION_SHIFT
|
||||
node_ids = [part2node[part]]
|
||||
zones = [node2zone[node_ids[0]]]
|
||||
node_counts[node_ids[0]] += 1
|
||||
zone_counts[zones[0]] += 1
|
||||
for replica in xrange(1, REPLICAS):
|
||||
while part2node[part] in node_ids and \
|
||||
node2zone[part2node[part]] in zones:
|
||||
part += 1
|
||||
if part > PARTITION_MAX:
|
||||
part = 0
|
||||
node_ids.append(part2node[part])
|
||||
zones.append(node2zone[node_ids[-1]])
|
||||
node_counts[node_ids[-1]] += 1
|
||||
zone_counts[zones[-1]] += 1
|
||||
desired_count = DATA_ID_COUNT / NODE_COUNT * REPLICAS
|
||||
print '%d: Desired data ids per node' % desired_count
|
||||
max_count = max(node_counts)
|
||||
over = 100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids on one node, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(node_counts)
|
||||
under = 100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids on one node, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
desired_count = DATA_ID_COUNT / ZONE_COUNT * REPLICAS
|
||||
print '%d: Desired data ids per zone' % desired_count
|
||||
max_count = max(zone_counts)
|
||||
over = 100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids in one zone, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(zone_counts)
|
||||
under = 100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids in one zone, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
|
||||
::
|
||||
|
||||
117186: Desired data ids per node
|
||||
118782: Most data ids on one node, 1.36% over
|
||||
115632: Least data ids on one node, 1.33% under
|
||||
1875000: Desired data ids per zone
|
||||
1878533: Most data ids in one zone, 0.19% over
|
||||
1869070: Least data ids in one zone, 0.32% under
|
||||
|
||||
So the shuffle and zone distinctions affected our distribution some,
|
||||
but still definitely good enough. This test took about 64 seconds to
|
||||
run on my machine.
|
||||
|
||||
There’s a completely alternate, and quite common, way of accomplishing
|
||||
these same requirements. This alternate method doesn’t use partitions
|
||||
at all, but instead just assigns anchors to the nodes within the hash
|
||||
space. Finding the first node for a given hash just involves walking
|
||||
this anchor ring for the next node, and finding additional nodes works
|
||||
similarly as before. To attain the equivalent of our virtual nodes,
|
||||
each real node is assigned multiple anchors.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from bisect import bisect_left
|
||||
from hashlib import md5
|
||||
from struct import unpack_from
|
||||
|
||||
REPLICAS = 3
|
||||
NODE_COUNT = 256
|
||||
ZONE_COUNT = 16
|
||||
DATA_ID_COUNT = 10000000
|
||||
VNODE_COUNT = 100
|
||||
|
||||
node2zone = []
|
||||
while len(node2zone) < NODE_COUNT:
|
||||
zone = 0
|
||||
while zone < ZONE_COUNT and len(node2zone) < NODE_COUNT:
|
||||
node2zone.append(zone)
|
||||
zone += 1
|
||||
hash2index = []
|
||||
index2node = []
|
||||
for node in xrange(NODE_COUNT):
|
||||
for vnode in xrange(VNODE_COUNT):
|
||||
hsh = unpack_from('>I', md5(str(node)).digest())[0]
|
||||
index = bisect_left(hash2index, hsh)
|
||||
if index > len(hash2index):
|
||||
index = 0
|
||||
hash2index.insert(index, hsh)
|
||||
index2node.insert(index, node)
|
||||
node_counts = [0] * NODE_COUNT
|
||||
zone_counts = [0] * ZONE_COUNT
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
data_id = str(data_id)
|
||||
hsh = unpack_from('>I', md5(str(data_id)).digest())[0]
|
||||
index = bisect_left(hash2index, hsh)
|
||||
if index >= len(hash2index):
|
||||
index = 0
|
||||
node_ids = [index2node[index]]
|
||||
zones = [node2zone[node_ids[0]]]
|
||||
node_counts[node_ids[0]] += 1
|
||||
zone_counts[zones[0]] += 1
|
||||
for replica in xrange(1, REPLICAS):
|
||||
while index2node[index] in node_ids and \
|
||||
node2zone[index2node[index]] in zones:
|
||||
index += 1
|
||||
if index >= len(hash2index):
|
||||
index = 0
|
||||
node_ids.append(index2node[index])
|
||||
zones.append(node2zone[node_ids[-1]])
|
||||
node_counts[node_ids[-1]] += 1
|
||||
zone_counts[zones[-1]] += 1
|
||||
desired_count = DATA_ID_COUNT / NODE_COUNT * REPLICAS
|
||||
print '%d: Desired data ids per node' % desired_count
|
||||
max_count = max(node_counts)
|
||||
over = 100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids on one node, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(node_counts)
|
||||
under = 100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids on one node, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
desired_count = DATA_ID_COUNT / ZONE_COUNT * REPLICAS
|
||||
print '%d: Desired data ids per zone' % desired_count
|
||||
max_count = max(zone_counts)
|
||||
over = 100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids in one zone, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(zone_counts)
|
||||
under = 100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids in one zone, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
|
||||
::
|
||||
|
||||
117186: Desired data ids per node
|
||||
351282: Most data ids on one node, 199.76% over
|
||||
15965: Least data ids on one node, 86.38% under
|
||||
1875000: Desired data ids per zone
|
||||
2248496: Most data ids in one zone, 19.92% over
|
||||
1378013: Least data ids in one zone, 26.51% under
|
||||
|
||||
This test took over 15 minutes to run! Unfortunately, this method also
|
||||
gives much less control over the distribution. To get better distribution,
|
||||
you have to add more virtual nodes, which eats up more memory and takes
|
||||
even more time to build the ring and perform distinct node lookups. The
|
||||
most common operation, data id lookup, can be improved (by predetermining
|
||||
each virtual nodes’ failover nodes, for instance) but it starts off so
|
||||
far behind our first approach that we’ll just stick with that.
|
||||
|
||||
In the next part of this series, we’ll start to wrap all this up into
|
||||
a useful Python module.
|
||||
|
||||
Part 5
|
||||
======
|
||||
In Part 4 of this series, we ended up with a multiple copy, distinctly
|
||||
zoned ring. Or at least the start of it. In this final part we’ll package
|
||||
the code up into a useable Python module and then add one last feature.
|
||||
First, let’s separate the ring itself from the building of the data for
|
||||
the ring and its testing.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from array import array
|
||||
from hashlib import md5
|
||||
from random import shuffle
|
||||
from struct import unpack_from
|
||||
from time import time
|
||||
|
||||
class Ring(object):
|
||||
|
||||
def __init__(self, nodes, part2node, replicas):
|
||||
self.nodes = nodes
|
||||
self.part2node = part2node
|
||||
self.replicas = replicas
|
||||
partition_power = 1
|
||||
while 2 ** partition_power < len(part2node):
|
||||
partition_power += 1
|
||||
if len(part2node) != 2 ** partition_power:
|
||||
raise Exception("part2node's length is not an "
|
||||
"exact power of 2")
|
||||
self.partition_shift = 32 - partition_power
|
||||
|
||||
def get_nodes(self, data_id):
|
||||
data_id = str(data_id)
|
||||
part = unpack_from('>I',
|
||||
md5(data_id).digest())[0] >> self.partition_shift
|
||||
node_ids = [self.part2node[part]]
|
||||
zones = [self.nodes[node_ids[0]]]
|
||||
for replica in xrange(1, self.replicas):
|
||||
while self.part2node[part] in node_ids and \
|
||||
self.nodes[self.part2node[part]] in zones:
|
||||
part += 1
|
||||
if part >= len(self.part2node):
|
||||
part = 0
|
||||
node_ids.append(self.part2node[part])
|
||||
zones.append(self.nodes[node_ids[-1]])
|
||||
return [self.nodes[n] for n in node_ids]
|
||||
|
||||
def build_ring(nodes, partition_power, replicas):
|
||||
begin = time()
|
||||
part2node = array('H')
|
||||
for part in xrange(2 ** partition_power):
|
||||
part2node.append(part % len(nodes))
|
||||
shuffle(part2node)
|
||||
ring = Ring(nodes, part2node, replicas)
|
||||
print '%.02fs to build ring' % (time() - begin)
|
||||
return ring
|
||||
|
||||
def test_ring(ring):
|
||||
begin = time()
|
||||
DATA_ID_COUNT = 10000000
|
||||
node_counts = {}
|
||||
zone_counts = {}
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
for node in ring.get_nodes(data_id):
|
||||
node_counts[node['id']] = \
|
||||
node_counts.get(node['id'], 0) + 1
|
||||
zone_counts[node['zone']] = \
|
||||
zone_counts.get(node['zone'], 0) + 1
|
||||
print '%ds to test ring' % (time() - begin)
|
||||
desired_count = \
|
||||
DATA_ID_COUNT / len(ring.nodes) * REPLICAS
|
||||
print '%d: Desired data ids per node' % desired_count
|
||||
max_count = max(node_counts.itervalues())
|
||||
over = \
|
||||
100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids on one node, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(node_counts.itervalues())
|
||||
under = \
|
||||
100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids on one node, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
zone_count = \
|
||||
len(set(n['zone'] for n in ring.nodes.itervalues()))
|
||||
desired_count = \
|
||||
DATA_ID_COUNT / zone_count * ring.replicas
|
||||
print '%d: Desired data ids per zone' % desired_count
|
||||
max_count = max(zone_counts.itervalues())
|
||||
over = \
|
||||
100.0 * (max_count - desired_count) / desired_count
|
||||
print '%d: Most data ids in one zone, %.02f%% over' % \
|
||||
(max_count, over)
|
||||
min_count = min(zone_counts.itervalues())
|
||||
under = \
|
||||
100.0 * (desired_count - min_count) / desired_count
|
||||
print '%d: Least data ids in one zone, %.02f%% under' % \
|
||||
(min_count, under)
|
||||
|
||||
if __name__ == '__main__':
|
||||
PARTITION_POWER = 16
|
||||
REPLICAS = 3
|
||||
NODE_COUNT = 256
|
||||
ZONE_COUNT = 16
|
||||
nodes = {}
|
||||
while len(nodes) < NODE_COUNT:
|
||||
zone = 0
|
||||
while zone < ZONE_COUNT and len(nodes) < NODE_COUNT:
|
||||
node_id = len(nodes)
|
||||
nodes[node_id] = {'id': node_id, 'zone': zone}
|
||||
zone += 1
|
||||
ring = build_ring(nodes, PARTITION_POWER, REPLICAS)
|
||||
test_ring(ring)
|
||||
|
||||
::
|
||||
|
||||
0.06s to build ring
|
||||
82s to test ring
|
||||
117186: Desired data ids per node
|
||||
118773: Most data ids on one node, 1.35% over
|
||||
115801: Least data ids on one node, 1.18% under
|
||||
1875000: Desired data ids per zone
|
||||
1878339: Most data ids in one zone, 0.18% over
|
||||
1869914: Least data ids in one zone, 0.27% under
|
||||
|
||||
It takes a bit longer to test our ring, but that’s mostly because of
|
||||
the switch to dictionaries from arrays for various items. Having node
|
||||
dictionaries is nice because you can attach any node information you
|
||||
want directly there (ip addresses, tcp ports, drive paths, etc.). But
|
||||
we’re still on track for further testing; our distribution is still good.
|
||||
|
||||
Now, let’s add our one last feature to our ring: the concept of weights.
|
||||
Weights are useful because the nodes you add later in a ring’s life are
|
||||
likely to have more capacity than those you have at the outset. For this
|
||||
test, we’ll make half our nodes have twice the weight. We’ll have to
|
||||
change build_ring to give more partitions to the nodes with more weight
|
||||
and we’ll change test_ring to take into account these weights. Since
|
||||
we’ve changed so much I’ll just post the entire module again:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from array import array
|
||||
from hashlib import md5
|
||||
from random import shuffle
|
||||
from struct import unpack_from
|
||||
from time import time
|
||||
|
||||
class Ring(object):
|
||||
|
||||
def __init__(self, nodes, part2node, replicas):
|
||||
self.nodes = nodes
|
||||
self.part2node = part2node
|
||||
self.replicas = replicas
|
||||
partition_power = 1
|
||||
while 2 ** partition_power < len(part2node):
|
||||
partition_power += 1
|
||||
if len(part2node) != 2 ** partition_power:
|
||||
raise Exception("part2node's length is not an "
|
||||
"exact power of 2")
|
||||
self.partition_shift = 32 - partition_power
|
||||
|
||||
def get_nodes(self, data_id):
|
||||
data_id = str(data_id)
|
||||
part = unpack_from('>I',
|
||||
md5(data_id).digest())[0] >> self.partition_shift
|
||||
node_ids = [self.part2node[part]]
|
||||
zones = [self.nodes[node_ids[0]]]
|
||||
for replica in xrange(1, self.replicas):
|
||||
while self.part2node[part] in node_ids and \
|
||||
self.nodes[self.part2node[part]] in zones:
|
||||
part += 1
|
||||
if part >= len(self.part2node):
|
||||
part = 0
|
||||
node_ids.append(self.part2node[part])
|
||||
zones.append(self.nodes[node_ids[-1]])
|
||||
return [self.nodes[n] for n in node_ids]
|
||||
|
||||
def build_ring(nodes, partition_power, replicas):
|
||||
begin = time()
|
||||
parts = 2 ** partition_power
|
||||
total_weight = \
|
||||
float(sum(n['weight'] for n in nodes.itervalues()))
|
||||
for node in nodes.itervalues():
|
||||
node['desired_parts'] = \
|
||||
parts / total_weight * node['weight']
|
||||
part2node = array('H')
|
||||
for part in xrange(2 ** partition_power):
|
||||
for node in nodes.itervalues():
|
||||
if node['desired_parts'] >= 1:
|
||||
node['desired_parts'] -= 1
|
||||
part2node.append(node['id'])
|
||||
break
|
||||
else:
|
||||
for node in nodes.itervalues():
|
||||
if node['desired_parts'] >= 0:
|
||||
node['desired_parts'] -= 1
|
||||
part2node.append(node['id'])
|
||||
break
|
||||
shuffle(part2node)
|
||||
ring = Ring(nodes, part2node, replicas)
|
||||
print '%.02fs to build ring' % (time() - begin)
|
||||
return ring
|
||||
|
||||
def test_ring(ring):
|
||||
begin = time()
|
||||
DATA_ID_COUNT = 10000000
|
||||
node_counts = {}
|
||||
zone_counts = {}
|
||||
for data_id in xrange(DATA_ID_COUNT):
|
||||
for node in ring.get_nodes(data_id):
|
||||
node_counts[node['id']] = \
|
||||
node_counts.get(node['id'], 0) + 1
|
||||
zone_counts[node['zone']] = \
|
||||
zone_counts.get(node['zone'], 0) + 1
|
||||
print '%ds to test ring' % (time() - begin)
|
||||
total_weight = float(sum(n['weight'] for n in
|
||||
ring.nodes.itervalues()))
|
||||
max_over = 0
|
||||
max_under = 0
|
||||
for node in ring.nodes.itervalues():
|
||||
desired = DATA_ID_COUNT * REPLICAS * \
|
||||
node['weight'] / total_weight
|
||||
diff = node_counts[node['id']] - desired
|
||||
if diff > 0:
|
||||
over = 100.0 * diff / desired
|
||||
if over > max_over:
|
||||
max_over = over
|
||||
else:
|
||||
under = 100.0 * (-diff) / desired
|
||||
if under > max_under:
|
||||
max_under = under
|
||||
print '%.02f%% max node over' % max_over
|
||||
print '%.02f%% max node under' % max_under
|
||||
max_over = 0
|
||||
max_under = 0
|
||||
for zone in set(n['zone'] for n in
|
||||
ring.nodes.itervalues()):
|
||||
zone_weight = sum(n['weight'] for n in
|
||||
ring.nodes.itervalues() if n['zone'] == zone)
|
||||
desired = DATA_ID_COUNT * REPLICAS * \
|
||||
zone_weight / total_weight
|
||||
diff = zone_counts[zone] - desired
|
||||
if diff > 0:
|
||||
over = 100.0 * diff / desired
|
||||
if over > max_over:
|
||||
max_over = over
|
||||
else:
|
||||
under = 100.0 * (-diff) / desired
|
||||
if under > max_under:
|
||||
max_under = under
|
||||
print '%.02f%% max zone over' % max_over
|
||||
print '%.02f%% max zone under' % max_under
|
||||
|
||||
if __name__ == '__main__':
|
||||
PARTITION_POWER = 16
|
||||
REPLICAS = 3
|
||||
NODE_COUNT = 256
|
||||
ZONE_COUNT = 16
|
||||
nodes = {}
|
||||
while len(nodes) < NODE_COUNT:
|
||||
zone = 0
|
||||
while zone < ZONE_COUNT and len(nodes) < NODE_COUNT:
|
||||
node_id = len(nodes)
|
||||
nodes[node_id] = {'id': node_id, 'zone': zone,
|
||||
'weight': 1.0 + (node_id % 2)}
|
||||
zone += 1
|
||||
ring = build_ring(nodes, PARTITION_POWER, REPLICAS)
|
||||
test_ring(ring)
|
||||
|
||||
::
|
||||
|
||||
0.88s to build ring
|
||||
86s to test ring
|
||||
1.66% max over
|
||||
1.46% max under
|
||||
0.28% max zone over
|
||||
0.23% max zone under
|
||||
|
||||
So things are still good, even though we have differently weighted nodes.
|
||||
I ran another test with this code using random weights from 1 to 100 and
|
||||
got over/under values for nodes of 7.35%/18.12% and zones of 0.24%/0.22%,
|
||||
still pretty good considering the crazy weight ranges.
|
||||
|
||||
Summary
|
||||
=======
|
||||
Hopefully this series has been a good introduction to building a ring.
|
||||
This code is essentially how the OpenStack Swift ring works, except that
|
||||
Swift’s ring has lots of additional optimizations, such as storing each
|
||||
replica assignment separately, and lots of extra features for building,
|
||||
validating, and otherwise working with rings.
|
|
@ -1,6 +1,6 @@
|
|||
[DEFAULT]
|
||||
# bind_ip = 0.0.0.0
|
||||
bind_port = 6002
|
||||
bind_port = 6202
|
||||
# bind_timeout = 30
|
||||
# backlog = 4096
|
||||
# user = swift
|
||||
|
@ -47,9 +47,10 @@ bind_port = 6002
|
|||
#
|
||||
# eventlet_debug = false
|
||||
#
|
||||
# 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.
|
||||
# fallocate_reserve = 0
|
||||
# You can set fallocate_reserve to the number of bytes or percentage of disk
|
||||
# space you'd like fallocate to reserve, whether there is space for the given
|
||||
# file size or not. Percentage will be used if the value ends with a '%'.
|
||||
# fallocate_reserve = 1%
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = healthcheck recon account-server
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[DEFAULT]
|
||||
# bind_ip = 0.0.0.0
|
||||
bind_port = 6001
|
||||
bind_port = 6201
|
||||
# bind_timeout = 30
|
||||
# backlog = 4096
|
||||
# user = swift
|
||||
|
@ -53,9 +53,10 @@ bind_port = 6001
|
|||
#
|
||||
# eventlet_debug = false
|
||||
#
|
||||
# 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.
|
||||
# fallocate_reserve = 0
|
||||
# You can set fallocate_reserve to the number of bytes or percentage of disk
|
||||
# space you'd like fallocate to reserve, whether there is space for the given
|
||||
# file size or not. Percentage will be used if the value ends with a '%'.
|
||||
# fallocate_reserve = 1%
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = healthcheck recon container-server
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
# The endpoint is what the container sync daemon will use when sending out
|
||||
# requests to that cluster. Keep in mind this endpoint must be reachable by all
|
||||
# container servers, since that is where the container sync daemon runs. Note
|
||||
# the the endpoint ends with /v1/ and that the container sync daemon will then
|
||||
# that the endpoint ends with /v1/ and that the container sync daemon will then
|
||||
# add the account/container/obj name after that.
|
||||
#
|
||||
# Distribute this container-sync-realms.conf file to all your proxy servers
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[DEFAULT]
|
||||
# bind_ip = 0.0.0.0
|
||||
bind_port = 6000
|
||||
bind_port = 6200
|
||||
# bind_timeout = 30
|
||||
# backlog = 4096
|
||||
# user = swift
|
||||
|
@ -16,10 +16,9 @@ bind_port = 6000
|
|||
# ignored.
|
||||
# workers = auto
|
||||
#
|
||||
# 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.
|
||||
# Make object-server run this many worker processes per unique port of "local"
|
||||
# ring devices across all storage policies. The default value of 0 disables this
|
||||
# feature.
|
||||
# servers_per_port = 0
|
||||
#
|
||||
# Maximum concurrent requests per worker
|
||||
|
@ -52,9 +51,10 @@ bind_port = 6000
|
|||
#
|
||||
# eventlet_debug = false
|
||||
#
|
||||
# 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.
|
||||
# fallocate_reserve = 0
|
||||
# You can set fallocate_reserve to the number of bytes or percentage of disk
|
||||
# space you'd like fallocate to reserve, whether there is space for the given
|
||||
# file size or not. Percentage will be used if the value ends with a '%'.
|
||||
# fallocate_reserve = 1%
|
||||
#
|
||||
# Time to wait while attempting to connect to another backend node.
|
||||
# conn_timeout = 0.5
|
||||
|
@ -106,10 +106,6 @@ use = egg:swift#object
|
|||
#
|
||||
# auto_create_account_prefix = .
|
||||
#
|
||||
# A value of 0 means "don't use thread pools". A reasonable starting point is
|
||||
# 4.
|
||||
# threads_per_disk = 0
|
||||
#
|
||||
# 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,
|
||||
|
@ -118,14 +114,15 @@ use = egg:swift#object
|
|||
# should not specify any value for "replication_server".
|
||||
# replication_server = false
|
||||
#
|
||||
# Set to restrict the number of concurrent incoming REPLICATION requests
|
||||
# Set to restrict the number of concurrent incoming SSYNC requests
|
||||
# Set to 0 for unlimited
|
||||
# Note that REPLICATION is currently an ssync only item
|
||||
# Note that SSYNC requests are only used by the object reconstructor or the
|
||||
# object replicator when configured to use ssync.
|
||||
# replication_concurrency = 4
|
||||
#
|
||||
# Restricts incoming REPLICATION requests to one per device,
|
||||
# Restricts incoming SSYNC 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
|
||||
# device, but you may wish to set this to False to allow multiple SSYNC
|
||||
# requests (up to the above replication_concurrency setting) per device.
|
||||
# replication_one_per_device = True
|
||||
#
|
||||
|
@ -133,8 +130,8 @@ use = egg:swift#object
|
|||
# giving up.
|
||||
# replication_lock_timeout = 15
|
||||
#
|
||||
# These next two settings control when the REPLICATION subrequest handler will
|
||||
# abort an incoming REPLICATION attempt. An abort will occur if there are at
|
||||
# These next two settings control when the SSYNC subrequest handler will
|
||||
# abort an incoming SSYNC 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
|
||||
|
@ -305,6 +302,13 @@ use = egg:swift#recon
|
|||
# points and report the result after a full scan.
|
||||
# object_size_stats =
|
||||
|
||||
# The auditor will cleanup old rsync tempfiles after they are "old
|
||||
# enough" to delete. You can configure the time elapsed in seconds
|
||||
# before rsync tempfiles will be unlinked, or the default value of
|
||||
# "auto" try to use object-replicator's rsync_timeout + 900 and fallback
|
||||
# to 86400 (1 day).
|
||||
# rsync_tempfile_timeout = auto
|
||||
|
||||
# Note: Put it at the beginning of the pipleline to profile all middleware. But
|
||||
# it is safer to put this after healthcheck.
|
||||
[filter:xprofile]
|
||||
|
|
|
@ -79,12 +79,12 @@ bind_port = 8080
|
|||
[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 copy 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
|
||||
#pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit authtoken keystoneauth copy container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
|
@ -129,12 +129,6 @@ use = egg:swift#proxy
|
|||
# 'false' no one, even authorized, can.
|
||||
# allow_account_management = false
|
||||
#
|
||||
# Set object_post_as_copy = false to turn on fast posts where only the metadata
|
||||
# changes are stored anew and the original data file is kept in place. This
|
||||
# makes for quicker posts; but since the container metadata isn't updated in
|
||||
# this mode, features like container sync won't be able to sync posts.
|
||||
# object_post_as_copy = true
|
||||
#
|
||||
# If set to 'true' authorized accounts that do not yet exist within the Swift
|
||||
# cluster will be automatically created.
|
||||
# account_autocreate = false
|
||||
|
@ -164,13 +158,28 @@ use = egg:swift#proxy
|
|||
# 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".
|
||||
# The valid values for sorting_method are "affinity", "shuffle", or "timing".
|
||||
# sorting_method = shuffle
|
||||
#
|
||||
# If the "timing" sorting_method is used, the timings will only be valid for
|
||||
# the number of seconds configured by timing_expiry.
|
||||
# timing_expiry = 300
|
||||
#
|
||||
# By default on a GET/HEAD swift will connect to a storage node one at a time
|
||||
# in a single thread. There is smarts in the order they are hit however. If you
|
||||
# turn on concurrent_gets below, then replica count threads will be used.
|
||||
# With addition of the concurrency_timeout option this will allow swift to send
|
||||
# out GET/HEAD requests to the storage nodes concurrently and answer with the
|
||||
# first to respond. With an EC policy the parameter only affects HEAD requests.
|
||||
# concurrent_gets = off
|
||||
#
|
||||
# This parameter controls how long to wait before firing off the next
|
||||
# concurrent_get thread. A value of 0 would be fully concurrent, any other
|
||||
# number will stagger the firing of the threads. This number should be
|
||||
# between 0 and node_timeout. The default is what ever you set for the
|
||||
# conn_timeout parameter.
|
||||
# concurrency_timeout = 0.5
|
||||
#
|
||||
# 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.
|
||||
|
@ -342,12 +351,6 @@ user_test5_tester5 = testing5 service
|
|||
# you can set this to false.
|
||||
# allow_overrides = true
|
||||
#
|
||||
# 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.
|
||||
# is_admin = false
|
||||
#
|
||||
# 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.
|
||||
|
@ -615,19 +618,23 @@ use = egg:swift#bulk
|
|||
# max_failed_extractions = 1000
|
||||
# max_deletes_per_request = 10000
|
||||
# max_failed_deletes = 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.
|
||||
# yield_frequency = 10
|
||||
|
||||
#
|
||||
# Note: The following 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
|
||||
|
||||
# delete_container_retry_count = 0
|
||||
#
|
||||
# To speed up the bulk delete process, multiple deletes may be executed in
|
||||
# parallel. Avoid setting this too high, as it gives clients a force multiplier
|
||||
# which may be used in DoS attacks. The suggested range is between 2 and 10.
|
||||
# delete_concurrency = 2
|
||||
|
||||
# Note: Put after auth and staticweb in the pipeline.
|
||||
[filter:slo]
|
||||
|
@ -648,6 +655,12 @@ use = egg:swift#slo
|
|||
#
|
||||
# Time limit on GET requests (seconds)
|
||||
# max_get_time = 86400
|
||||
#
|
||||
# When deleting with ?multipart-manifest=delete, multiple deletes may be
|
||||
# executed in parallel. Avoid setting this too high, as it gives clients a
|
||||
# force multiplier which may be used in DoS attacks. The suggested range is
|
||||
# between 2 and 10.
|
||||
# delete_concurrency = 2
|
||||
|
||||
# Note: Put after auth and staticweb in the pipeline.
|
||||
# If you don't put it in the pipeline, it will be inserted for you.
|
||||
|
@ -674,6 +687,12 @@ use = egg:swift#account_quotas
|
|||
|
||||
[filter:gatekeeper]
|
||||
use = egg:swift#gatekeeper
|
||||
# Set this to false if you want to allow clients to set arbitrary X-Timestamps
|
||||
# on uploaded objects. This may be used to preserve timestamps when migrating
|
||||
# from a previous storage system, but risks allowing users to upload
|
||||
# difficult-to-delete data.
|
||||
# shunt_inbound_x_timestamp = true
|
||||
#
|
||||
# You can override the default log routing for this filter here:
|
||||
# set log_name = gatekeeper
|
||||
# set log_facility = LOG_LOCAL0
|
||||
|
@ -735,3 +754,14 @@ use = egg:swift#versioned_writes
|
|||
# in the container configuration file, which will be eventually
|
||||
# deprecated. See documentation for more details.
|
||||
# allow_versioned_writes = false
|
||||
|
||||
# Note: Put after auth and before dlo and slo middlewares.
|
||||
# If you don't put it in the pipeline, it will be inserted for you.
|
||||
[filter:copy]
|
||||
use = egg:swift#copy
|
||||
# Set object_post_as_copy = false to turn on fast posts where only the metadata
|
||||
# changes are stored anew and the original data file is kept in place. This
|
||||
# makes for quicker posts.
|
||||
# When object_post_as_copy is set to True, a POST request will be transformed
|
||||
# into a COPY request where source and destination objects are the same.
|
||||
# object_post_as_copy = true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[swift-hash]
|
||||
|
||||
# swift_hash_path_suffix and swift_hash_path_prefix are used as part of the
|
||||
# the hashing algorithm when determining data placement in the cluster.
|
||||
# hashing algorithm when determining data placement in the cluster.
|
||||
# These values should remain secret and MUST NOT change
|
||||
# once a cluster has been deployed.
|
||||
# Use only printable chars (python -c "import string; print(string.printable)")
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# This is a cross-platform list tracking distribution packages needed by tests;
|
||||
# see http://docs.openstack.org/infra/bindep/ for additional information.
|
||||
|
||||
build-essential [platform:dpkg]
|
||||
gcc [platform:rpm]
|
||||
gettext
|
||||
liberasurecode-dev [platform:dpkg]
|
||||
liberasurecode-devel [platform:rpm]
|
||||
libffi-dev [platform:dpkg]
|
||||
libffi-devel [platform:rpm]
|
||||
memcached
|
||||
python-dev [platform:dpkg]
|
||||
python-devel [platform:rpm]
|
||||
rsync
|
||||
xfsprogs
|
|
@ -2,7 +2,7 @@
|
|||
name = swift
|
||||
summary = OpenStack Object Storage
|
||||
description-file =
|
||||
README.md
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
|
@ -96,6 +96,7 @@ paste.filter_factory =
|
|||
container_sync = swift.common.middleware.container_sync:filter_factory
|
||||
xprofile = swift.common.middleware.xprofile:filter_factory
|
||||
versioned_writes = swift.common.middleware.versioned_writes:filter_factory
|
||||
copy = swift.common.middleware.copy:filter_factory
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
|
|
|
@ -145,7 +145,8 @@ class AccountAuditor(Daemon):
|
|||
self.logger.increment('failures')
|
||||
self.account_failures += 1
|
||||
self.logger.error(
|
||||
_('Audit Failed for %s: %s'), path, str(e))
|
||||
_('Audit Failed for %(path)s: %(err)s'),
|
||||
{'path': path, 'err': str(e)})
|
||||
except (Exception, Timeout):
|
||||
self.logger.increment('failures')
|
||||
self.account_failures += 1
|
||||
|
|
|
@ -73,7 +73,7 @@ class AccountReaper(Daemon):
|
|||
self.node_timeout = float(conf.get('node_timeout', 10))
|
||||
self.conn_timeout = float(conf.get('conn_timeout', 0.5))
|
||||
self.myips = whataremyips(conf.get('bind_ip', '0.0.0.0'))
|
||||
self.bind_port = int(conf.get('bind_port', 6002))
|
||||
self.bind_port = int(conf.get('bind_port', 6202))
|
||||
self.concurrency = int(conf.get('concurrency', 25))
|
||||
self.container_concurrency = self.object_concurrency = \
|
||||
sqrt(self.concurrency)
|
||||
|
@ -171,7 +171,9 @@ class AccountReaper(Daemon):
|
|||
container_shard = None
|
||||
for container_shard, node in enumerate(nodes):
|
||||
if is_local_device(self.myips, None, node['ip'], None) and \
|
||||
(not self.bind_port or self.bind_port == node['port']):
|
||||
(not self.bind_port or
|
||||
self.bind_port == node['port']) and \
|
||||
(device == node['device']):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
|
@ -311,8 +313,9 @@ class AccountReaper(Daemon):
|
|||
delete_timestamp = Timestamp(info['delete_timestamp'])
|
||||
if self.stats_containers_remaining and \
|
||||
begin - float(delete_timestamp) >= self.reap_not_done_after:
|
||||
self.logger.warning(_('Account %s has not been reaped since %s') %
|
||||
(account, delete_timestamp.isoformat))
|
||||
self.logger.warning(
|
||||
_('Account %(account)s has not been reaped since %(time)s') %
|
||||
{'account': account, 'time': delete_timestamp.isoformat})
|
||||
return True
|
||||
|
||||
def reap_container(self, account, account_partition, account_nodes,
|
||||
|
|
|
@ -21,4 +21,4 @@ class AccountReplicator(db_replicator.Replicator):
|
|||
server_type = 'account'
|
||||
brokerclass = AccountBroker
|
||||
datadir = DATADIR
|
||||
default_port = 6002
|
||||
default_port = 6202
|
||||
|
|
|
@ -308,7 +308,7 @@ def print_obj_metadata(metadata):
|
|||
print_metadata('Other Metadata:', other_metadata)
|
||||
|
||||
|
||||
def print_info(db_type, db_file, swift_dir='/etc/swift'):
|
||||
def print_info(db_type, db_file, swift_dir='/etc/swift', stale_reads_ok=False):
|
||||
if db_type not in ('account', 'container'):
|
||||
print("Unrecognized DB type: internal error")
|
||||
raise InfoSystemExit()
|
||||
|
@ -318,10 +318,10 @@ def print_info(db_type, db_file, swift_dir='/etc/swift'):
|
|||
if not db_file.startswith(('/', './')):
|
||||
db_file = './' + db_file # don't break if the bare db file is given
|
||||
if db_type == 'account':
|
||||
broker = AccountBroker(db_file)
|
||||
broker = AccountBroker(db_file, stale_reads_ok=stale_reads_ok)
|
||||
datadir = ABDATADIR
|
||||
else:
|
||||
broker = ContainerBroker(db_file)
|
||||
broker = ContainerBroker(db_file, stale_reads_ok=stale_reads_ok)
|
||||
datadir = CBDATADIR
|
||||
try:
|
||||
info = broker.get_info()
|
||||
|
|
|
@ -72,7 +72,7 @@ class Scout(object):
|
|||
Perform the actual HTTP request to obtain swift recon telemtry.
|
||||
|
||||
:param base_url: the base url of the host you wish to check. str of the
|
||||
format 'http://127.0.0.1:6000/recon/'
|
||||
format 'http://127.0.0.1:6200/recon/'
|
||||
:param recon_type: the swift recon check to request.
|
||||
:returns: tuple of (recon url used, response body, and status)
|
||||
"""
|
||||
|
|
|
@ -37,26 +37,26 @@ addition::
|
|||
|
||||
"rounds": [
|
||||
[
|
||||
["add", "r1z2-10.20.30.40:6000/sda", 8000],
|
||||
["add", "r1z2-10.20.30.40:6000/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.40:6000/sdc", 8000],
|
||||
["add", "r1z2-10.20.30.40:6000/sdd", 8000],
|
||||
["add", "r1z2-10.20.30.40:6200/sda", 8000],
|
||||
["add", "r1z2-10.20.30.40:6200/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.40:6200/sdc", 8000],
|
||||
["add", "r1z2-10.20.30.40:6200/sdd", 8000],
|
||||
|
||||
["add", "r1z2-10.20.30.41:6000/sda", 8000],
|
||||
["add", "r1z2-10.20.30.41:6000/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.41:6000/sdc", 8000],
|
||||
["add", "r1z2-10.20.30.41:6000/sdd", 8000],
|
||||
["add", "r1z2-10.20.30.41:6200/sda", 8000],
|
||||
["add", "r1z2-10.20.30.41:6200/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.41:6200/sdc", 8000],
|
||||
["add", "r1z2-10.20.30.41:6200/sdd", 8000],
|
||||
|
||||
["add", "r1z2-10.20.30.43:6000/sda", 8000],
|
||||
["add", "r1z2-10.20.30.43:6000/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.43:6000/sdc", 8000],
|
||||
["add", "r1z2-10.20.30.43:6000/sdd", 8000],
|
||||
["add", "r1z2-10.20.30.43:6200/sda", 8000],
|
||||
["add", "r1z2-10.20.30.43:6200/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.43:6200/sdc", 8000],
|
||||
["add", "r1z2-10.20.30.43:6200/sdd", 8000],
|
||||
|
||||
["add", "r1z2-10.20.30.44:6000/sda", 8000],
|
||||
["add", "r1z2-10.20.30.44:6000/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.44:6000/sdc", 8000]
|
||||
["add", "r1z2-10.20.30.44:6200/sda", 8000],
|
||||
["add", "r1z2-10.20.30.44:6200/sdb", 8000],
|
||||
["add", "r1z2-10.20.30.44:6200/sdc", 8000]
|
||||
], [
|
||||
["add", "r1z2-10.20.30.44:6000/sdd", 1000]
|
||||
["add", "r1z2-10.20.30.44:6200/sdd", 1000]
|
||||
], [
|
||||
["set_weight", 15, 2000]
|
||||
], [
|
||||
|
|
|
@ -722,7 +722,7 @@ swift-ring-builder <builder_file> remove <search-value> [search-value ...]
|
|||
|
||||
or
|
||||
|
||||
swift-ring-builder <builder_file> search
|
||||
swift-ring-builder <builder_file> remove
|
||||
--region <region> --zone <zone> --ip <ip or hostname> --port <port>
|
||||
--replication-ip <r_ip or r_hostname> --replication-port <r_port>
|
||||
--device <device_name> --meta <meta> --weight <weight>
|
||||
|
@ -1183,7 +1183,7 @@ swift-ring-builder <builder_file> set_overload <overload>[%]
|
|||
|
||||
def main(arguments=None):
|
||||
global argv, backup_dir, builder, builder_file, ring_file
|
||||
if arguments:
|
||||
if arguments is not None:
|
||||
argv = arguments
|
||||
else:
|
||||
argv = sys_argv
|
||||
|
|
|
@ -20,7 +20,6 @@ import time
|
|||
import six
|
||||
from six.moves.configparser import ConfigParser, NoSectionError, NoOptionError
|
||||
from six.moves import urllib
|
||||
from six.moves.urllib.parse import unquote
|
||||
|
||||
from swift.common import utils, exceptions
|
||||
from swift.common.swob import HTTPBadRequest, HTTPLengthRequired, \
|
||||
|
@ -129,7 +128,8 @@ def check_metadata(req, target_type):
|
|||
which type the target storage for the metadata is
|
||||
:returns: HTTPBadRequest with bad metadata otherwise None
|
||||
"""
|
||||
prefix = 'x-%s-meta-' % target_type.lower()
|
||||
target_type = target_type.lower()
|
||||
prefix = 'x-%s-meta-' % target_type
|
||||
meta_count = 0
|
||||
meta_size = 0
|
||||
for key, value in req.headers.items():
|
||||
|
@ -145,6 +145,11 @@ def check_metadata(req, target_type):
|
|||
if not key:
|
||||
return HTTPBadRequest(body='Metadata name cannot be empty',
|
||||
request=req, content_type='text/plain')
|
||||
bad_key = not check_utf8(key)
|
||||
bad_value = value and not check_utf8(value)
|
||||
if target_type in ('account', 'container') and (bad_key or bad_value):
|
||||
return HTTPBadRequest(body='Metadata must be valid UTF-8',
|
||||
request=req, content_type='text/plain')
|
||||
meta_count += 1
|
||||
meta_size += len(key) + len(value)
|
||||
if len(key) > MAX_META_NAME_LENGTH:
|
||||
|
@ -199,10 +204,6 @@ def check_object_creation(req, object_name):
|
|||
request=req,
|
||||
content_type='text/plain')
|
||||
|
||||
if 'X-Copy-From' in req.headers and req.content_length:
|
||||
return HTTPBadRequest(body='Copy requests require a zero byte body',
|
||||
request=req, content_type='text/plain')
|
||||
|
||||
if len(object_name) > MAX_OBJECT_NAME_LENGTH:
|
||||
return HTTPBadRequest(body='Object name length of %d longer than %d' %
|
||||
(len(object_name), MAX_OBJECT_NAME_LENGTH),
|
||||
|
@ -353,63 +354,6 @@ def check_utf8(string):
|
|||
return False
|
||||
|
||||
|
||||
def check_path_header(req, name, length, error_msg):
|
||||
"""
|
||||
Validate that the value of path-like header is
|
||||
well formatted. We assume the caller ensures that
|
||||
specific header is present in req.headers.
|
||||
|
||||
:param req: HTTP request object
|
||||
:param name: header name
|
||||
:param length: length of path segment check
|
||||
:param error_msg: error message for client
|
||||
:returns: A tuple with path parts according to length
|
||||
:raise: HTTPPreconditionFailed if header value
|
||||
is not well formatted.
|
||||
"""
|
||||
src_header = unquote(req.headers.get(name))
|
||||
if not src_header.startswith('/'):
|
||||
src_header = '/' + src_header
|
||||
try:
|
||||
return utils.split_path(src_header, length, length, True)
|
||||
except ValueError:
|
||||
raise HTTPPreconditionFailed(
|
||||
request=req,
|
||||
body=error_msg)
|
||||
|
||||
|
||||
def check_copy_from_header(req):
|
||||
"""
|
||||
Validate that the value from x-copy-from header is
|
||||
well formatted. We assume the caller ensures that
|
||||
x-copy-from header is present in req.headers.
|
||||
|
||||
:param req: HTTP request object
|
||||
:returns: A tuple with container name and object name
|
||||
:raise: HTTPPreconditionFailed if x-copy-from value
|
||||
is not well formatted.
|
||||
"""
|
||||
return check_path_header(req, 'X-Copy-From', 2,
|
||||
'X-Copy-From header must be of the form '
|
||||
'<container name>/<object name>')
|
||||
|
||||
|
||||
def check_destination_header(req):
|
||||
"""
|
||||
Validate that the value from destination header is
|
||||
well formatted. We assume the caller ensures that
|
||||
destination header is present in req.headers.
|
||||
|
||||
:param req: HTTP request object
|
||||
:returns: A tuple with container name and object name
|
||||
:raise: HTTPPreconditionFailed if destination value
|
||||
is not well formatted.
|
||||
"""
|
||||
return check_path_header(req, 'Destination', 2,
|
||||
'Destination header must be of the form '
|
||||
'<container name>/<object name>')
|
||||
|
||||
|
||||
def check_name_format(req, name, target_type):
|
||||
"""
|
||||
Validate that the header contains valid account or container name.
|
||||
|
|
|
@ -57,7 +57,8 @@ class ContainerSyncRealms(object):
|
|||
log_func = self.logger.debug
|
||||
else:
|
||||
log_func = self.logger.error
|
||||
log_func(_('Could not load %r: %s'), self.conf_path, err)
|
||||
log_func(_('Could not load %(conf)r: %(error)s') % {
|
||||
'conf': self.conf_path, 'error': err})
|
||||
else:
|
||||
if mtime != self.conf_path_mtime:
|
||||
self.conf_path_mtime = mtime
|
||||
|
@ -66,7 +67,8 @@ class ContainerSyncRealms(object):
|
|||
conf.read(self.conf_path)
|
||||
except configparser.ParsingError as err:
|
||||
self.logger.error(
|
||||
_('Could not load %r: %s'), self.conf_path, err)
|
||||
_('Could not load %(conf)r: %(error)s')
|
||||
% {'conf': self.conf_path, 'error': err})
|
||||
else:
|
||||
try:
|
||||
self.mtime_check_interval = conf.getint(
|
||||
|
@ -79,8 +81,9 @@ class ContainerSyncRealms(object):
|
|||
now + self.mtime_check_interval
|
||||
except (configparser.ParsingError, ValueError) as err:
|
||||
self.logger.error(
|
||||
_('Error in %r with mtime_check_interval: %s'),
|
||||
self.conf_path, err)
|
||||
_('Error in %(conf)r with '
|
||||
'mtime_check_interval: %(error)s')
|
||||
% {'conf': self.conf_path, 'error': err})
|
||||
realms = {}
|
||||
for section in conf.sections():
|
||||
realm = {}
|
||||
|
|
|
@ -92,9 +92,8 @@ def run_daemon(klass, conf_file, section_name='', once=False, **kwargs):
|
|||
if utils.config_true_value(conf.get('disable_fallocate', 'no')):
|
||||
utils.disable_fallocate()
|
||||
# set utils.FALLOCATE_RESERVE if desired
|
||||
reserve = int(conf.get('fallocate_reserve', 0))
|
||||
if reserve > 0:
|
||||
utils.FALLOCATE_RESERVE = reserve
|
||||
utils.FALLOCATE_RESERVE, utils.FALLOCATE_IS_PERCENT = \
|
||||
utils.config_fallocate_value(conf.get('fallocate_reserve', '1%'))
|
||||
|
||||
# By default, disable eventlet printing stacktraces
|
||||
eventlet_debug = utils.config_true_value(conf.get('eventlet_debug', 'no'))
|
||||
|
|
|
@ -32,7 +32,8 @@ from tempfile import mkstemp
|
|||
from eventlet import sleep, Timeout
|
||||
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, \
|
||||
check_utf8
|
||||
from swift.common.utils import Timestamp, renamer, \
|
||||
mkdirs, lock_parent_directory, fallocate
|
||||
from swift.common.exceptions import LockTimeout
|
||||
|
@ -45,7 +46,8 @@ DB_PREALLOCATION = False
|
|||
BROKER_TIMEOUT = 25
|
||||
#: Pickle protocol to use
|
||||
PICKLE_PROTOCOL = 2
|
||||
#: Max number of pending entries
|
||||
#: Max size of .pending file in bytes. When this is exceeded, the pending
|
||||
# records will be merged.
|
||||
PENDING_CAP = 131072
|
||||
|
||||
|
||||
|
@ -349,8 +351,10 @@ class DatabaseBroker(object):
|
|||
raise
|
||||
quar_path = "%s-%s" % (quar_path, uuid4().hex)
|
||||
renamer(self.db_dir, quar_path, fsync=False)
|
||||
detail = _('Quarantined %s to %s due to %s database') % \
|
||||
(self.db_dir, quar_path, exc_hint)
|
||||
detail = _('Quarantined %(db_dir)s to %(quar_path)s due to '
|
||||
'%(exc_hint)s database') % {'db_dir': self.db_dir,
|
||||
'quar_path': quar_path,
|
||||
'exc_hint': exc_hint}
|
||||
self.logger.error(detail)
|
||||
raise sqlite3.DatabaseError(detail)
|
||||
|
||||
|
@ -628,7 +632,7 @@ class DatabaseBroker(object):
|
|||
with lock_parent_directory(self.pending_file,
|
||||
self.pending_timeout):
|
||||
self._commit_puts()
|
||||
except LockTimeout:
|
||||
except (LockTimeout, sqlite3.OperationalError):
|
||||
if not self.stale_reads_ok:
|
||||
raise
|
||||
|
||||
|
@ -729,11 +733,11 @@ class DatabaseBroker(object):
|
|||
@staticmethod
|
||||
def validate_metadata(metadata):
|
||||
"""
|
||||
Validates that metadata_falls within acceptable limits.
|
||||
Validates that metadata falls within acceptable limits.
|
||||
|
||||
:param metadata: to be validated
|
||||
:raises: HTTPBadRequest if MAX_META_COUNT or MAX_META_OVERALL_SIZE
|
||||
is exceeded
|
||||
is exceeded, or if metadata contains non-UTF-8 data
|
||||
"""
|
||||
meta_count = 0
|
||||
meta_size = 0
|
||||
|
@ -747,6 +751,10 @@ class DatabaseBroker(object):
|
|||
key = key[len(prefix):]
|
||||
meta_count = meta_count + 1
|
||||
meta_size = meta_size + len(key) + len(value)
|
||||
bad_key = key and not check_utf8(key)
|
||||
bad_value = value and not check_utf8(value)
|
||||
if bad_key or bad_value:
|
||||
raise HTTPBadRequest('Metadata must be valid UTF-8')
|
||||
if meta_count > MAX_META_COUNT:
|
||||
raise HTTPBadRequest('Too many metadata items; max %d'
|
||||
% MAX_META_COUNT)
|
||||
|
|
|
@ -525,10 +525,13 @@ class Replicator(Daemon):
|
|||
if shouldbehere:
|
||||
shouldbehere = bool([n for n in nodes if n['id'] == node_id])
|
||||
# See Footnote [1] for an explanation of the repl_nodes assignment.
|
||||
i = 0
|
||||
while i < len(nodes) and nodes[i]['id'] != node_id:
|
||||
i += 1
|
||||
repl_nodes = nodes[i + 1:] + nodes[:i]
|
||||
if len(nodes) > 1:
|
||||
i = 0
|
||||
while i < len(nodes) and nodes[i]['id'] != node_id:
|
||||
i += 1
|
||||
repl_nodes = nodes[i + 1:] + nodes[:i]
|
||||
else: # Special case if using only a single replica
|
||||
repl_nodes = nodes
|
||||
more_nodes = self.ring.get_more_nodes(int(partition))
|
||||
if not local_dev:
|
||||
# Check further if local device is a handoff node
|
||||
|
@ -563,7 +566,7 @@ class Replicator(Daemon):
|
|||
except (Exception, Timeout):
|
||||
self.logger.exception('UNHANDLED EXCEPTION: in post replicate '
|
||||
'hook for %s', broker.db_file)
|
||||
if not shouldbehere and all(responses):
|
||||
if not shouldbehere and responses and all(responses):
|
||||
# If the db shouldn't be on this node and has been successfully
|
||||
# synced to all of its peers, it can be removed.
|
||||
if not self.delete_db(broker):
|
||||
|
|
|
@ -33,19 +33,22 @@ from swift.common.exceptions import ClientException
|
|||
from swift.common.utils import Timestamp, FileLikeIter
|
||||
from swift.common.http import HTTP_NO_CONTENT, HTTP_INSUFFICIENT_STORAGE, \
|
||||
is_success, is_server_error
|
||||
from swift.common.swob import HeaderKeyDict
|
||||
from swift.common.header_key_dict import HeaderKeyDict
|
||||
from swift.common.utils import quote
|
||||
|
||||
|
||||
class DirectClientException(ClientException):
|
||||
|
||||
def __init__(self, stype, method, node, part, path, resp):
|
||||
def __init__(self, stype, method, node, part, path, resp, host=None):
|
||||
# host can be used to override the node ip and port reported in
|
||||
# the exception
|
||||
host = host if host is not None else node
|
||||
full_path = quote('/%s/%s%s' % (node['device'], part, path))
|
||||
msg = '%s server %s:%s direct %s %r gave status %s' % (
|
||||
stype, node['ip'], node['port'], method, full_path, resp.status)
|
||||
stype, host['ip'], host['port'], method, full_path, resp.status)
|
||||
headers = HeaderKeyDict(resp.getheaders())
|
||||
super(DirectClientException, self).__init__(
|
||||
msg, http_host=node['ip'], http_port=node['port'],
|
||||
msg, http_host=host['ip'], http_port=host['port'],
|
||||
http_device=node['device'], http_status=resp.status,
|
||||
http_reason=resp.reason, http_headers=headers)
|
||||
|
||||
|
@ -482,13 +485,17 @@ def direct_get_suffix_hashes(node, part, suffixes, conn_timeout=5,
|
|||
|
||||
path = '/%s' % '-'.join(suffixes)
|
||||
with Timeout(conn_timeout):
|
||||
conn = http_connect(node['ip'], node['port'], node['device'], part,
|
||||
'REPLICATE', path, headers=gen_headers(headers))
|
||||
conn = http_connect(node['replication_ip'], node['replication_port'],
|
||||
node['device'], part, 'REPLICATE', path,
|
||||
headers=gen_headers(headers))
|
||||
with Timeout(response_timeout):
|
||||
resp = conn.getresponse()
|
||||
if not is_success(resp.status):
|
||||
raise DirectClientException('Object', 'REPLICATE',
|
||||
node, part, path, resp)
|
||||
node, part, path, resp,
|
||||
host={'ip': node['replication_ip'],
|
||||
'port': node['replication_port']}
|
||||
)
|
||||
return pickle.loads(resp.read())
|
||||
|
||||
|
||||
|
|
|
@ -145,10 +145,6 @@ class LockTimeout(MessageTimeout):
|
|||
pass
|
||||
|
||||
|
||||
class ThreadPoolDead(SwiftException):
|
||||
pass
|
||||
|
||||
|
||||
class RingBuilderError(SwiftException):
|
||||
pass
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# Copyright (c) 2010-2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class HeaderKeyDict(dict):
|
||||
"""
|
||||
A dict that title-cases all keys on the way in, so as to be
|
||||
case-insensitive.
|
||||
"""
|
||||
def __init__(self, base_headers=None, **kwargs):
|
||||
if base_headers:
|
||||
self.update(base_headers)
|
||||
self.update(kwargs)
|
||||
|
||||
def update(self, other):
|
||||
if hasattr(other, 'keys'):
|
||||
for key in other.keys():
|
||||
self[key.title()] = other[key]
|
||||
else:
|
||||
for key, value in other:
|
||||
self[key.title()] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return dict.get(self, key.title())
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if value is None:
|
||||
self.pop(key.title(), None)
|
||||
elif isinstance(value, six.text_type):
|
||||
return dict.__setitem__(self, key.title(), value.encode('utf-8'))
|
||||
else:
|
||||
return dict.__setitem__(self, key.title(), str(value))
|
||||
|
||||
def __contains__(self, key):
|
||||
return dict.__contains__(self, key.title())
|
||||
|
||||
def __delitem__(self, key):
|
||||
return dict.__delitem__(self, key.title())
|
||||
|
||||
def get(self, key, default=None):
|
||||
return dict.get(self, key.title(), default)
|
||||
|
||||
def setdefault(self, key, value=None):
|
||||
if key not in self:
|
||||
self[key] = value
|
||||
return self[key]
|
||||
|
||||
def pop(self, key, default=None):
|
||||
return dict.pop(self, key.title(), default)
|
|
@ -741,11 +741,25 @@ class SimpleClient(object):
|
|||
def base_request(self, method, container=None, name=None, prefix=None,
|
||||
headers=None, proxy=None, contents=None,
|
||||
full_listing=None, logger=None, additional_info=None,
|
||||
timeout=None):
|
||||
timeout=None, marker=None):
|
||||
# Common request method
|
||||
trans_start = time()
|
||||
url = self.url
|
||||
|
||||
if full_listing:
|
||||
info, body_data = self.base_request(
|
||||
method, container, name, prefix, headers, proxy,
|
||||
timeout=timeout, marker=marker)
|
||||
listing = body_data
|
||||
while listing:
|
||||
marker = listing[-1]['name']
|
||||
info, listing = self.base_request(
|
||||
method, container, name, prefix, headers, proxy,
|
||||
timeout=timeout, marker=marker)
|
||||
if listing:
|
||||
body_data.extend(listing)
|
||||
return [info, body_data]
|
||||
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
|
@ -762,6 +776,9 @@ class SimpleClient(object):
|
|||
if prefix:
|
||||
url += '&prefix=%s' % prefix
|
||||
|
||||
if marker:
|
||||
url += '&marker=%s' % quote(marker)
|
||||
|
||||
req = urllib2.Request(url, headers=headers, data=contents)
|
||||
if proxy:
|
||||
proxy = urllib.parse.urlparse(proxy)
|
||||
|
@ -769,6 +786,7 @@ class SimpleClient(object):
|
|||
req.get_method = lambda: method
|
||||
conn = urllib2.urlopen(req, timeout=timeout)
|
||||
body = conn.read()
|
||||
info = conn.info()
|
||||
try:
|
||||
body_data = json.loads(body)
|
||||
except ValueError:
|
||||
|
@ -792,13 +810,13 @@ class SimpleClient(object):
|
|||
url,
|
||||
conn.getcode(),
|
||||
sent_content_length,
|
||||
conn.info()['content-length'],
|
||||
info['content-length'],
|
||||
trans_start,
|
||||
trans_stop,
|
||||
trans_stop - trans_start,
|
||||
additional_info
|
||||
)))
|
||||
return [None, body_data]
|
||||
return [info, body_data]
|
||||
|
||||
def retry_request(self, method, **kwargs):
|
||||
retries = kwargs.pop('retries', self.retries)
|
||||
|
@ -837,6 +855,12 @@ class SimpleClient(object):
|
|||
contents=contents.read(), **kwargs)
|
||||
|
||||
|
||||
def head_object(url, **kwargs):
|
||||
"""For usage with container sync """
|
||||
client = SimpleClient(url=url)
|
||||
return client.retry_request('HEAD', **kwargs)
|
||||
|
||||
|
||||
def put_object(url, **kwargs):
|
||||
"""For usage with container sync """
|
||||
client = SimpleClient(url=url)
|
||||
|
|
|
@ -55,7 +55,7 @@ WARNING_WAIT = 3 # seconds to wait after message that may just be a warning
|
|||
|
||||
MAX_DESCRIPTORS = 32768
|
||||
MAX_MEMORY = (1024 * 1024 * 1024) * 2 # 2 GB
|
||||
MAX_PROCS = 8192 # workers * disks * threads_per_disk, can get high
|
||||
MAX_PROCS = 8192 # workers * disks, can get high
|
||||
|
||||
|
||||
def setup_env():
|
||||
|
@ -288,7 +288,8 @@ class Manager(object):
|
|||
for server, killed_pid in watch_server_pids(server_pids,
|
||||
interval=kill_wait,
|
||||
**kwargs):
|
||||
print(_("%s (%s) appears to have stopped") % (server, killed_pid))
|
||||
print(_("%(server)s (%(pid)s) appears to have stopped") %
|
||||
{'server': server, 'pid': killed_pid})
|
||||
killed_pids.add(killed_pid)
|
||||
if not killed_pids.symmetric_difference(signaled_pids):
|
||||
# all processes have been stopped
|
||||
|
@ -300,12 +301,15 @@ class Manager(object):
|
|||
if not killed_pids.issuperset(pids):
|
||||
# some pids of this server were not killed
|
||||
if kill_after_timeout:
|
||||
print(_('Waited %s seconds for %s to die; killing') % (
|
||||
kill_wait, server))
|
||||
print(_('Waited %(kill_wait)s seconds for %(server)s '
|
||||
'to die; killing') %
|
||||
{'kill_wait': kill_wait, 'server': server})
|
||||
# Send SIGKILL to all remaining pids
|
||||
for pid in set(pids.keys()) - killed_pids:
|
||||
print(_('Signal %s pid: %s signal: %s') % (
|
||||
server, pid, signal.SIGKILL))
|
||||
print(_('Signal %(server)s pid: %(pid)s signal: '
|
||||
'%(signal)s') % {'server': server,
|
||||
'pid': pid,
|
||||
'signal': signal.SIGKILL})
|
||||
# Send SIGKILL to process group
|
||||
try:
|
||||
kill_group(pid, signal.SIGKILL)
|
||||
|
@ -314,8 +318,9 @@ class Manager(object):
|
|||
if e.errno != errno.ESRCH:
|
||||
raise e
|
||||
else:
|
||||
print(_('Waited %s seconds for %s to die; giving up') % (
|
||||
kill_wait, server))
|
||||
print(_('Waited %(kill_wait)s seconds for %(server)s '
|
||||
'to die; giving up') %
|
||||
{'kill_wait': kill_wait, 'server': server})
|
||||
return 1
|
||||
|
||||
@command
|
||||
|
@ -498,8 +503,9 @@ class Server(object):
|
|||
# maybe there's a config file(s) out there, but I couldn't find it!
|
||||
if not kwargs.get('quiet'):
|
||||
if number:
|
||||
print(_('Unable to locate config number %s for %s')
|
||||
% (number, self.server))
|
||||
print(_('Unable to locate config number %(number)s for'
|
||||
' %(server)s') %
|
||||
{'number': number, 'server': self.server})
|
||||
else:
|
||||
print(_('Unable to locate config for %s') % self.server)
|
||||
if kwargs.get('verbose') and not kwargs.get('quiet'):
|
||||
|
@ -556,13 +562,14 @@ class Server(object):
|
|||
continue
|
||||
try:
|
||||
if sig != signal.SIG_DFL:
|
||||
print(_('Signal %s pid: %s signal: %s') % (self.server,
|
||||
pid, sig))
|
||||
print(_('Signal %(server)s pid: %(pid)s signal: '
|
||||
'%(signal)s') %
|
||||
{'server': self.server, 'pid': pid, 'signal': sig})
|
||||
safe_kill(pid, sig, 'swift-%s' % self.server)
|
||||
except InvalidPidFileException as e:
|
||||
if kwargs.get('verbose'):
|
||||
print(_('Removing pid file %s with wrong pid %d') % (
|
||||
pid_file, pid))
|
||||
print(_('Removing pid file %(pid_file)s with wrong pid '
|
||||
'%(pid)d'), {'pid_file': pid_file, 'pid': pid})
|
||||
remove_file(pid_file)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ESRCH:
|
||||
|
@ -616,14 +623,16 @@ class Server(object):
|
|||
kwargs['quiet'] = True
|
||||
conf_files = self.conf_files(**kwargs)
|
||||
if conf_files:
|
||||
print(_("%s #%d not running (%s)") % (self.server, number,
|
||||
conf_files[0]))
|
||||
print(_("%(server)s #%(number)d not running (%(conf)s)") %
|
||||
{'server': self.server, 'number': number,
|
||||
'conf': conf_files[0]})
|
||||
else:
|
||||
print(_("No %s running") % self.server)
|
||||
return 1
|
||||
for pid, pid_file in pids.items():
|
||||
conf_file = self.get_conf_file_name(pid_file)
|
||||
print(_("%s running (%s - %s)") % (self.server, pid, conf_file))
|
||||
print(_("%(server)s running (%(pid)s - %(conf)s)") %
|
||||
{'server': self.server, 'pid': pid, 'conf': conf_file})
|
||||
return 0
|
||||
|
||||
def spawn(self, conf_file, once=False, wait=True, daemon=True, **kwargs):
|
||||
|
@ -716,11 +725,13 @@ class Server(object):
|
|||
# any unstarted instances
|
||||
if conf_file in conf_files:
|
||||
already_started = True
|
||||
print(_("%s running (%s - %s)") %
|
||||
(self.server, pid, conf_file))
|
||||
print(_("%(server)s running (%(pid)s - %(conf)s)") %
|
||||
{'server': self.server, 'pid': pid, 'conf': conf_file})
|
||||
elif not kwargs.get('number', 0):
|
||||
already_started = True
|
||||
print(_("%s running (%s - %s)") % (self.server, pid, pid_file))
|
||||
print(_("%(server)s running (%(pid)s - %(pid_file)s)") %
|
||||
{'server': self.server, 'pid': pid,
|
||||
'pid_file': pid_file})
|
||||
|
||||
if already_started:
|
||||
print(_("%s already started...") % self.server)
|
||||
|
|
|
@ -52,11 +52,10 @@ Due to the eventual consistency further uploads might be possible until the
|
|||
account size has been updated.
|
||||
"""
|
||||
|
||||
from swift.common.constraints import check_copy_from_header
|
||||
from swift.common.swob import HTTPForbidden, HTTPBadRequest, \
|
||||
HTTPRequestEntityTooLarge, wsgify
|
||||
from swift.common.utils import register_swift_info
|
||||
from swift.proxy.controllers.base import get_account_info, get_object_info
|
||||
from swift.proxy.controllers.base import get_account_info
|
||||
|
||||
|
||||
class AccountQuotaMiddleware(object):
|
||||
|
@ -71,7 +70,7 @@ class AccountQuotaMiddleware(object):
|
|||
@wsgify
|
||||
def __call__(self, request):
|
||||
|
||||
if request.method not in ("POST", "PUT", "COPY"):
|
||||
if request.method not in ("POST", "PUT"):
|
||||
return self.app
|
||||
|
||||
try:
|
||||
|
@ -106,15 +105,6 @@ class AccountQuotaMiddleware(object):
|
|||
if request.method == "POST" or not obj:
|
||||
return self.app
|
||||
|
||||
if request.method == 'COPY':
|
||||
copy_from = container + '/' + obj
|
||||
else:
|
||||
if 'x-copy-from' in request.headers:
|
||||
src_cont, src_obj = check_copy_from_header(request)
|
||||
copy_from = "%s/%s" % (src_cont, src_obj)
|
||||
else:
|
||||
copy_from = None
|
||||
|
||||
content_length = (request.content_length or 0)
|
||||
|
||||
account_info = get_account_info(request.environ, self.app)
|
||||
|
@ -127,14 +117,6 @@ class AccountQuotaMiddleware(object):
|
|||
if quota < 0:
|
||||
return self.app
|
||||
|
||||
if copy_from:
|
||||
path = '/' + ver + '/' + account + '/' + copy_from
|
||||
object_info = get_object_info(request.environ, self.app, path)
|
||||
if not object_info or not object_info['length']:
|
||||
content_length = 0
|
||||
else:
|
||||
content_length = int(object_info['length'])
|
||||
|
||||
new_size = int(account_info['bytes']) + content_length
|
||||
if quota < new_size:
|
||||
resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
|
||||
|
|
|
@ -201,7 +201,8 @@ from swift.common.swob import Request, HTTPBadGateway, \
|
|||
HTTPCreated, HTTPBadRequest, HTTPNotFound, HTTPUnauthorized, HTTPOk, \
|
||||
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPNotAcceptable, \
|
||||
HTTPLengthRequired, HTTPException, HTTPServerError, wsgify
|
||||
from swift.common.utils import get_logger, register_swift_info
|
||||
from swift.common.utils import get_logger, register_swift_info, \
|
||||
StreamingPile
|
||||
from swift.common import constraints
|
||||
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND, HTTP_CONFLICT
|
||||
|
||||
|
@ -274,8 +275,9 @@ class Bulk(object):
|
|||
|
||||
def __init__(self, app, conf, max_containers_per_extraction=10000,
|
||||
max_failed_extractions=1000, max_deletes_per_request=10000,
|
||||
max_failed_deletes=1000, yield_frequency=10, retry_count=0,
|
||||
retry_interval=1.5, logger=None):
|
||||
max_failed_deletes=1000, yield_frequency=10,
|
||||
delete_concurrency=2, retry_count=0, retry_interval=1.5,
|
||||
logger=None):
|
||||
self.app = app
|
||||
self.logger = logger or get_logger(conf, log_route='bulk')
|
||||
self.max_containers = max_containers_per_extraction
|
||||
|
@ -283,6 +285,7 @@ class Bulk(object):
|
|||
self.max_failed_deletes = max_failed_deletes
|
||||
self.max_deletes_per_request = max_deletes_per_request
|
||||
self.yield_frequency = yield_frequency
|
||||
self.delete_concurrency = min(1000, max(1, delete_concurrency))
|
||||
self.retry_count = retry_count
|
||||
self.retry_interval = retry_interval
|
||||
self.max_path_length = constraints.MAX_OBJECT_NAME_LENGTH \
|
||||
|
@ -397,39 +400,74 @@ class Bulk(object):
|
|||
objs_to_delete = self.get_objs_to_delete(req)
|
||||
failed_file_response = {'type': HTTPBadRequest}
|
||||
req.environ['eventlet.minimum_write_chunk_size'] = 0
|
||||
for obj_to_delete in objs_to_delete:
|
||||
if last_yield + self.yield_frequency < time():
|
||||
separator = '\r\n\r\n'
|
||||
last_yield = time()
|
||||
yield ' '
|
||||
obj_name = obj_to_delete['name']
|
||||
if not obj_name:
|
||||
continue
|
||||
if len(failed_files) >= self.max_failed_deletes:
|
||||
raise HTTPBadRequest('Max delete failures exceeded')
|
||||
if obj_to_delete.get('error'):
|
||||
if obj_to_delete['error']['code'] == HTTP_NOT_FOUND:
|
||||
resp_dict['Number Not Found'] += 1
|
||||
else:
|
||||
|
||||
def delete_filter(predicate, objs_to_delete):
|
||||
for obj_to_delete in objs_to_delete:
|
||||
obj_name = obj_to_delete['name']
|
||||
if not obj_name:
|
||||
continue
|
||||
if not predicate(obj_name):
|
||||
continue
|
||||
if obj_to_delete.get('error'):
|
||||
if obj_to_delete['error']['code'] == HTTP_NOT_FOUND:
|
||||
resp_dict['Number Not Found'] += 1
|
||||
else:
|
||||
failed_files.append([
|
||||
quote(obj_name),
|
||||
obj_to_delete['error']['message']])
|
||||
continue
|
||||
delete_path = '/'.join(['', vrs, account,
|
||||
obj_name.lstrip('/')])
|
||||
if not constraints.check_utf8(delete_path):
|
||||
failed_files.append([quote(obj_name),
|
||||
obj_to_delete['error']['message']])
|
||||
continue
|
||||
delete_path = '/'.join(['', vrs, account,
|
||||
obj_name.lstrip('/')])
|
||||
if not constraints.check_utf8(delete_path):
|
||||
failed_files.append([quote(obj_name),
|
||||
HTTPPreconditionFailed().status])
|
||||
continue
|
||||
HTTPPreconditionFailed().status])
|
||||
continue
|
||||
yield (obj_name, delete_path)
|
||||
|
||||
def objs_then_containers(objs_to_delete):
|
||||
# process all objects first
|
||||
yield delete_filter(lambda name: '/' in name.strip('/'),
|
||||
objs_to_delete)
|
||||
# followed by containers
|
||||
yield delete_filter(lambda name: '/' not in name.strip('/'),
|
||||
objs_to_delete)
|
||||
|
||||
def do_delete(obj_name, delete_path):
|
||||
new_env = req.environ.copy()
|
||||
new_env['PATH_INFO'] = delete_path
|
||||
del(new_env['wsgi.input'])
|
||||
new_env['CONTENT_LENGTH'] = 0
|
||||
new_env['REQUEST_METHOD'] = 'DELETE'
|
||||
new_env['HTTP_USER_AGENT'] = \
|
||||
'%s %s' % (req.environ.get('HTTP_USER_AGENT'), user_agent)
|
||||
new_env['HTTP_USER_AGENT'] = '%s %s' % (
|
||||
req.environ.get('HTTP_USER_AGENT'), user_agent)
|
||||
new_env['swift.source'] = swift_source
|
||||
self._process_delete(delete_path, obj_name, new_env, resp_dict,
|
||||
failed_files, failed_file_response)
|
||||
delete_obj_req = Request.blank(delete_path, new_env)
|
||||
return (delete_obj_req.get_response(self.app), obj_name, 0)
|
||||
|
||||
with StreamingPile(self.delete_concurrency) as pile:
|
||||
for names_to_delete in objs_then_containers(objs_to_delete):
|
||||
for resp, obj_name, retry in pile.asyncstarmap(
|
||||
do_delete, names_to_delete):
|
||||
if last_yield + self.yield_frequency < time():
|
||||
separator = '\r\n\r\n'
|
||||
last_yield = time()
|
||||
yield ' '
|
||||
self._process_delete(resp, pile, obj_name,
|
||||
resp_dict, failed_files,
|
||||
failed_file_response, retry)
|
||||
if len(failed_files) >= self.max_failed_deletes:
|
||||
# Abort, but drain off the in-progress deletes
|
||||
for resp, obj_name, retry in pile:
|
||||
if last_yield + self.yield_frequency < time():
|
||||
separator = '\r\n\r\n'
|
||||
last_yield = time()
|
||||
yield ' '
|
||||
# Don't pass in the pile, as we shouldn't retry
|
||||
self._process_delete(
|
||||
resp, None, obj_name, resp_dict,
|
||||
failed_files, failed_file_response, retry)
|
||||
msg = 'Max delete failures exceeded'
|
||||
raise HTTPBadRequest(msg)
|
||||
|
||||
if failed_files:
|
||||
resp_dict['Response Status'] = \
|
||||
|
@ -603,10 +641,8 @@ class Bulk(object):
|
|||
yield separator + get_response_body(
|
||||
out_content_type, resp_dict, failed_files)
|
||||
|
||||
def _process_delete(self, delete_path, obj_name, env, resp_dict,
|
||||
def _process_delete(self, resp, pile, obj_name, resp_dict,
|
||||
failed_files, failed_file_response, retry=0):
|
||||
delete_obj_req = Request.blank(delete_path, env)
|
||||
resp = delete_obj_req.get_response(self.app)
|
||||
if resp.status_int // 100 == 2:
|
||||
resp_dict['Number Deleted'] += 1
|
||||
elif resp.status_int == HTTP_NOT_FOUND:
|
||||
|
@ -614,13 +650,16 @@ class Bulk(object):
|
|||
elif resp.status_int == HTTP_UNAUTHORIZED:
|
||||
failed_files.append([quote(obj_name),
|
||||
HTTPUnauthorized().status])
|
||||
elif resp.status_int == HTTP_CONFLICT and \
|
||||
elif resp.status_int == HTTP_CONFLICT and pile and \
|
||||
self.retry_count > 0 and self.retry_count > retry:
|
||||
retry += 1
|
||||
sleep(self.retry_interval ** retry)
|
||||
self._process_delete(delete_path, obj_name, env, resp_dict,
|
||||
failed_files, failed_file_response,
|
||||
retry)
|
||||
delete_obj_req = Request.blank(resp.environ['PATH_INFO'],
|
||||
resp.environ)
|
||||
|
||||
def _retry(req, app, obj_name, retry):
|
||||
return req.get_response(app), obj_name, retry
|
||||
pile.spawn(_retry, delete_obj_req, self.app, obj_name, retry)
|
||||
else:
|
||||
if resp.status_int // 100 == 5:
|
||||
failed_file_response['type'] = HTTPBadGateway
|
||||
|
@ -664,6 +703,8 @@ def filter_factory(global_conf, **local_conf):
|
|||
max_deletes_per_request = int(conf.get('max_deletes_per_request', 10000))
|
||||
max_failed_deletes = int(conf.get('max_failed_deletes', 1000))
|
||||
yield_frequency = int(conf.get('yield_frequency', 10))
|
||||
delete_concurrency = min(1000, max(1, int(
|
||||
conf.get('delete_concurrency', 2))))
|
||||
retry_count = int(conf.get('delete_container_retry_count', 0))
|
||||
retry_interval = 1.5
|
||||
|
||||
|
@ -684,6 +725,7 @@ def filter_factory(global_conf, **local_conf):
|
|||
max_deletes_per_request=max_deletes_per_request,
|
||||
max_failed_deletes=max_failed_deletes,
|
||||
yield_frequency=yield_frequency,
|
||||
delete_concurrency=delete_concurrency,
|
||||
retry_count=retry_count,
|
||||
retry_interval=retry_interval)
|
||||
return bulk_filter
|
||||
|
|
|
@ -51,13 +51,11 @@ For example::
|
|||
[filter:container_quotas]
|
||||
use = egg:swift#container_quotas
|
||||
"""
|
||||
from swift.common.constraints import check_copy_from_header, \
|
||||
check_account_format, check_destination_header
|
||||
from swift.common.http import is_success
|
||||
from swift.common.swob import HTTPRequestEntityTooLarge, HTTPBadRequest, \
|
||||
wsgify
|
||||
from swift.common.utils import register_swift_info
|
||||
from swift.proxy.controllers.base import get_container_info, get_object_info
|
||||
from swift.proxy.controllers.base import get_container_info
|
||||
|
||||
|
||||
class ContainerQuotaMiddleware(object):
|
||||
|
@ -91,25 +89,9 @@ class ContainerQuotaMiddleware(object):
|
|||
return HTTPBadRequest(body='Invalid count quota.')
|
||||
|
||||
# check user uploads against quotas
|
||||
elif obj and req.method in ('PUT', 'COPY'):
|
||||
container_info = None
|
||||
if req.method == 'PUT':
|
||||
container_info = get_container_info(
|
||||
req.environ, self.app, swift_source='CQ')
|
||||
if req.method == 'COPY' and 'Destination' in req.headers:
|
||||
dest_account = account
|
||||
if 'Destination-Account' in req.headers:
|
||||
dest_account = req.headers.get('Destination-Account')
|
||||
dest_account = check_account_format(req, dest_account)
|
||||
dest_container, dest_object = check_destination_header(req)
|
||||
path_info = req.environ['PATH_INFO']
|
||||
req.environ['PATH_INFO'] = "/%s/%s/%s/%s" % (
|
||||
version, dest_account, dest_container, dest_object)
|
||||
try:
|
||||
container_info = get_container_info(
|
||||
req.environ, self.app, swift_source='CQ')
|
||||
finally:
|
||||
req.environ['PATH_INFO'] = path_info
|
||||
elif obj and req.method in ('PUT'):
|
||||
container_info = get_container_info(
|
||||
req.environ, self.app, swift_source='CQ')
|
||||
if not container_info or not is_success(container_info['status']):
|
||||
# this will hopefully 404 later
|
||||
return self.app
|
||||
|
@ -118,16 +100,6 @@ class ContainerQuotaMiddleware(object):
|
|||
'bytes' in container_info and \
|
||||
container_info['meta']['quota-bytes'].isdigit():
|
||||
content_length = (req.content_length or 0)
|
||||
if 'x-copy-from' in req.headers or req.method == 'COPY':
|
||||
if 'x-copy-from' in req.headers:
|
||||
container, obj = check_copy_from_header(req)
|
||||
path = '/%s/%s/%s/%s' % (version, account,
|
||||
container, obj)
|
||||
object_info = get_object_info(req.environ, self.app, path)
|
||||
if not object_info or not object_info['length']:
|
||||
content_length = 0
|
||||
else:
|
||||
content_length = int(object_info['length'])
|
||||
new_size = int(container_info['bytes']) + content_length
|
||||
if int(container_info['meta']['quota-bytes']) < new_size:
|
||||
return self.bad_response(req, container_info)
|
||||
|
|
|
@ -97,6 +97,11 @@ class ContainerSync(object):
|
|||
req.environ.setdefault('swift.log_info', []).append(
|
||||
'cs:no-local-user-key')
|
||||
else:
|
||||
# x-timestamp headers get shunted by gatekeeper
|
||||
if 'x-backend-inbound-x-timestamp' in req.headers:
|
||||
req.headers['x-timestamp'] = req.headers.pop(
|
||||
'x-backend-inbound-x-timestamp')
|
||||
|
||||
expected = self.realms_conf.get_sig(
|
||||
req.method, req.path,
|
||||
req.headers.get('x-timestamp', '0'), nonce,
|
||||
|
|
|
@ -0,0 +1,522 @@
|
|||
# Copyright (c) 2015 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""
|
||||
Server side copy is a feature that enables users/clients to COPY objects
|
||||
between accounts and containers without the need to download and then
|
||||
re-upload objects, thus eliminating additional bandwidth consumption and
|
||||
also saving time. This may be used when renaming/moving an object which
|
||||
in Swift is a (COPY + DELETE) operation.
|
||||
|
||||
The server side copy middleware should be inserted in the pipeline after auth
|
||||
and before the quotas and large object middlewares. If it is not present in the
|
||||
pipeline in the proxy-server configuration file, it will be inserted
|
||||
automatically. There is no configurable option provided to turn off server
|
||||
side copy.
|
||||
|
||||
--------
|
||||
Metadata
|
||||
--------
|
||||
* All metadata of source object is preserved during object copy.
|
||||
* One can also provide additional metadata during PUT/COPY request. This will
|
||||
over-write any existing conflicting keys.
|
||||
* Server side copy can also be used to change content-type of an existing
|
||||
object.
|
||||
|
||||
-----------
|
||||
Object Copy
|
||||
-----------
|
||||
* The destination container must exist before requesting copy of the object.
|
||||
* When several replicas exist, the system copies from the most recent replica.
|
||||
That is, the copy operation behaves as though the X-Newest header is in the
|
||||
request.
|
||||
* The request to copy an object should have no body (i.e. content-length of the
|
||||
request must be zero).
|
||||
|
||||
There are two ways in which an object can be copied:
|
||||
|
||||
1. Send a PUT request to the new object (destination/target) with an additional
|
||||
header named ``X-Copy-From`` specifying the source object
|
||||
(in '/container/object' format). Example::
|
||||
|
||||
curl -i -X PUT http://<storage_url>/container1/destination_obj
|
||||
-H 'X-Auth-Token: <token>'
|
||||
-H 'X-Copy-From: /container2/source_obj'
|
||||
-H 'Content-Length: 0'
|
||||
|
||||
2. Send a COPY request with an existing object in URL with an additional header
|
||||
named ``Destination`` specifying the destination/target object
|
||||
(in '/container/object' format). Example::
|
||||
|
||||
curl -i -X COPY http://<storage_url>/container2/source_obj
|
||||
-H 'X-Auth-Token: <token>'
|
||||
-H 'Destination: /container1/destination_obj'
|
||||
-H 'Content-Length: 0'
|
||||
|
||||
Note that if the incoming request has some conditional headers (e.g. ``Range``,
|
||||
``If-Match``), the *source* object will be evaluated for these headers (i.e. if
|
||||
PUT with both ``X-Copy-From`` and ``Range``, Swift will make a partial copy to
|
||||
the destination object).
|
||||
|
||||
-------------------------
|
||||
Cross Account Object Copy
|
||||
-------------------------
|
||||
Objects can also be copied from one account to another account if the user
|
||||
has the necessary permissions (i.e. permission to read from container
|
||||
in source account and permission to write to container in destination account).
|
||||
|
||||
Similar to examples mentioned above, there are two ways to copy objects across
|
||||
accounts:
|
||||
|
||||
1. Like the example above, send PUT request to copy object but with an
|
||||
additional header named ``X-Copy-From-Account`` specifying the source
|
||||
account. Example::
|
||||
|
||||
curl -i -X PUT http://<host>:<port>/v1/AUTH_test1/container/destination_obj
|
||||
-H 'X-Auth-Token: <token>'
|
||||
-H 'X-Copy-From: /container/source_obj'
|
||||
-H 'X-Copy-From-Account: AUTH_test2'
|
||||
-H 'Content-Length: 0'
|
||||
|
||||
2. Like the previous example, send a COPY request but with an additional header
|
||||
named ``Destination-Account`` specifying the name of destination account.
|
||||
Example::
|
||||
|
||||
curl -i -X COPY http://<host>:<port>/v1/AUTH_test2/container/source_obj
|
||||
-H 'X-Auth-Token: <token>'
|
||||
-H 'Destination: /container/destination_obj'
|
||||
-H 'Destination-Account: AUTH_test1'
|
||||
-H 'Content-Length: 0'
|
||||
|
||||
-------------------
|
||||
Large Object Copy
|
||||
-------------------
|
||||
The best option to copy a large object is to copy segments individually.
|
||||
To copy the manifest object of a large object, add the query parameter to
|
||||
the copy request::
|
||||
|
||||
?multipart-manifest=get
|
||||
|
||||
If a request is sent without the query parameter, an attempt will be made to
|
||||
copy the whole object but will fail if the object size is
|
||||
greater than 5GB.
|
||||
|
||||
-------------------
|
||||
Object Post as Copy
|
||||
-------------------
|
||||
Historically, this has been a feature (and a configurable option with default
|
||||
set to True) in proxy server configuration. This has been moved to server side
|
||||
copy middleware.
|
||||
|
||||
When ``object_post_as_copy`` is set to ``true`` (default value), an incoming
|
||||
POST request is morphed into a COPY request where source and destination
|
||||
objects are same.
|
||||
|
||||
This feature was necessary because of a previous behavior where POSTS would
|
||||
update the metadata on the object but not on the container. As a result,
|
||||
features like container sync would not work correctly. This is no longer the
|
||||
case and the plan is to deprecate this option. It is being kept now for
|
||||
backwards compatibility. At first chance, set ``object_post_as_copy`` to
|
||||
``false``.
|
||||
"""
|
||||
|
||||
import os
|
||||
from urllib import quote
|
||||
from ConfigParser import ConfigParser, NoSectionError, NoOptionError
|
||||
from six.moves.urllib.parse import unquote
|
||||
|
||||
from swift.common import utils
|
||||
from swift.common.utils import get_logger, \
|
||||
config_true_value, FileLikeIter, read_conf_dir, close_if_possible
|
||||
from swift.common.swob import Request, HTTPPreconditionFailed, \
|
||||
HTTPRequestEntityTooLarge, HTTPBadRequest
|
||||
from swift.common.http import HTTP_MULTIPLE_CHOICES, HTTP_CREATED, \
|
||||
is_success
|
||||
from swift.common.constraints import check_account_format, MAX_FILE_SIZE
|
||||
from swift.common.request_helpers import copy_header_subset, remove_items, \
|
||||
is_sys_meta, is_sys_or_user_meta
|
||||
from swift.common.wsgi import WSGIContext, make_subrequest
|
||||
|
||||
|
||||
def _check_path_header(req, name, length, error_msg):
|
||||
"""
|
||||
Validate that the value of path-like header is
|
||||
well formatted. We assume the caller ensures that
|
||||
specific header is present in req.headers.
|
||||
|
||||
:param req: HTTP request object
|
||||
:param name: header name
|
||||
:param length: length of path segment check
|
||||
:param error_msg: error message for client
|
||||
:returns: A tuple with path parts according to length
|
||||
:raise: HTTPPreconditionFailed if header value
|
||||
is not well formatted.
|
||||
"""
|
||||
src_header = unquote(req.headers.get(name))
|
||||
if not src_header.startswith('/'):
|
||||
src_header = '/' + src_header
|
||||
try:
|
||||
return utils.split_path(src_header, length, length, True)
|
||||
except ValueError:
|
||||
raise HTTPPreconditionFailed(
|
||||
request=req,
|
||||
body=error_msg)
|
||||
|
||||
|
||||
def _check_copy_from_header(req):
|
||||
"""
|
||||
Validate that the value from x-copy-from header is
|
||||
well formatted. We assume the caller ensures that
|
||||
x-copy-from header is present in req.headers.
|
||||
|
||||
:param req: HTTP request object
|
||||
:returns: A tuple with container name and object name
|
||||
:raise: HTTPPreconditionFailed if x-copy-from value
|
||||
is not well formatted.
|
||||
"""
|
||||
return _check_path_header(req, 'X-Copy-From', 2,
|
||||
'X-Copy-From header must be of the form '
|
||||
'<container name>/<object name>')
|
||||
|
||||
|
||||
def _check_destination_header(req):
|
||||
"""
|
||||
Validate that the value from destination header is
|
||||
well formatted. We assume the caller ensures that
|
||||
destination header is present in req.headers.
|
||||
|
||||
:param req: HTTP request object
|
||||
:returns: A tuple with container name and object name
|
||||
:raise: HTTPPreconditionFailed if destination value
|
||||
is not well formatted.
|
||||
"""
|
||||
return _check_path_header(req, 'Destination', 2,
|
||||
'Destination header must be of the form '
|
||||
'<container name>/<object name>')
|
||||
|
||||
|
||||
def _copy_headers_into(from_r, to_r):
|
||||
"""
|
||||
Will copy desired headers from from_r to to_r
|
||||
:params from_r: a swob Request or Response
|
||||
:params to_r: a swob Request or Response
|
||||
"""
|
||||
pass_headers = ['x-delete-at']
|
||||
for k, v in from_r.headers.items():
|
||||
if is_sys_or_user_meta('object', k) or k.lower() in pass_headers:
|
||||
to_r.headers[k] = v
|
||||
|
||||
|
||||
class ServerSideCopyWebContext(WSGIContext):
|
||||
|
||||
def __init__(self, app, logger):
|
||||
super(ServerSideCopyWebContext, self).__init__(app)
|
||||
self.app = app
|
||||
self.logger = logger
|
||||
|
||||
def get_source_resp(self, req):
|
||||
sub_req = make_subrequest(
|
||||
req.environ, path=req.path_info, headers=req.headers,
|
||||
swift_source='SSC')
|
||||
return sub_req.get_response(self.app)
|
||||
|
||||
def send_put_req(self, req, additional_resp_headers, start_response):
|
||||
app_resp = self._app_call(req.environ)
|
||||
self._adjust_put_response(req, additional_resp_headers)
|
||||
start_response(self._response_status,
|
||||
self._response_headers,
|
||||
self._response_exc_info)
|
||||
return app_resp
|
||||
|
||||
def _adjust_put_response(self, req, additional_resp_headers):
|
||||
if 'swift.post_as_copy' in req.environ:
|
||||
# Older editions returned 202 Accepted on object POSTs, so we'll
|
||||
# convert any 201 Created responses to that for compatibility with
|
||||
# picky clients.
|
||||
if self._get_status_int() == HTTP_CREATED:
|
||||
self._response_status = '202 Accepted'
|
||||
elif is_success(self._get_status_int()):
|
||||
for header, value in additional_resp_headers.items():
|
||||
self._response_headers.append((header, value))
|
||||
|
||||
def handle_OPTIONS_request(self, req, start_response):
|
||||
app_resp = self._app_call(req.environ)
|
||||
if is_success(self._get_status_int()):
|
||||
for i, (header, value) in enumerate(self._response_headers):
|
||||
if header.lower() == 'allow' and 'COPY' not in value:
|
||||
self._response_headers[i] = ('Allow', value + ', COPY')
|
||||
if header.lower() == 'access-control-allow-methods' and \
|
||||
'COPY' not in value:
|
||||
self._response_headers[i] = \
|
||||
('Access-Control-Allow-Methods', value + ', COPY')
|
||||
start_response(self._response_status,
|
||||
self._response_headers,
|
||||
self._response_exc_info)
|
||||
return app_resp
|
||||
|
||||
|
||||
class ServerSideCopyMiddleware(object):
|
||||
|
||||
def __init__(self, app, conf):
|
||||
self.app = app
|
||||
self.logger = get_logger(conf, log_route="copy")
|
||||
# Read the old object_post_as_copy option from Proxy app just in case
|
||||
# someone has set it to false (non default). This wouldn't cause
|
||||
# problems during upgrade.
|
||||
self._load_object_post_as_copy_conf(conf)
|
||||
self.object_post_as_copy = \
|
||||
config_true_value(conf.get('object_post_as_copy', 'true'))
|
||||
|
||||
def _load_object_post_as_copy_conf(self, conf):
|
||||
if ('object_post_as_copy' in conf or '__file__' not in conf):
|
||||
# Option is explicitly set in middleware conf. In that case,
|
||||
# we assume operator knows what he's doing.
|
||||
# This takes preference over the one set in proxy app
|
||||
return
|
||||
|
||||
cp = ConfigParser()
|
||||
if os.path.isdir(conf['__file__']):
|
||||
read_conf_dir(cp, conf['__file__'])
|
||||
else:
|
||||
cp.read(conf['__file__'])
|
||||
|
||||
try:
|
||||
pipe = cp.get("pipeline:main", "pipeline")
|
||||
except (NoSectionError, NoOptionError):
|
||||
return
|
||||
|
||||
proxy_name = pipe.rsplit(None, 1)[-1]
|
||||
proxy_section = "app:" + proxy_name
|
||||
|
||||
try:
|
||||
conf['object_post_as_copy'] = cp.get(proxy_section,
|
||||
'object_post_as_copy')
|
||||
except (NoSectionError, NoOptionError):
|
||||
pass
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
req = Request(env)
|
||||
try:
|
||||
(version, account, container, obj) = req.split_path(4, 4, True)
|
||||
except ValueError:
|
||||
# If obj component is not present in req, do not proceed further.
|
||||
return self.app(env, start_response)
|
||||
|
||||
self.account_name = account
|
||||
self.container_name = container
|
||||
self.object_name = obj
|
||||
|
||||
# Save off original request method (COPY/POST) in case it gets mutated
|
||||
# into PUT during handling. This way logging can display the method
|
||||
# the client actually sent.
|
||||
req.environ['swift.orig_req_method'] = req.method
|
||||
|
||||
if req.method == 'PUT' and req.headers.get('X-Copy-From'):
|
||||
return self.handle_PUT(req, start_response)
|
||||
elif req.method == 'COPY':
|
||||
return self.handle_COPY(req, start_response)
|
||||
elif req.method == 'POST' and self.object_post_as_copy:
|
||||
return self.handle_object_post_as_copy(req, start_response)
|
||||
elif req.method == 'OPTIONS':
|
||||
# Does not interfere with OPTIONS response from (account,container)
|
||||
# servers and /info response.
|
||||
return self.handle_OPTIONS(req, start_response)
|
||||
|
||||
return self.app(env, start_response)
|
||||
|
||||
def handle_object_post_as_copy(self, req, start_response):
|
||||
req.method = 'PUT'
|
||||
req.path_info = '/v1/%s/%s/%s' % (
|
||||
self.account_name, self.container_name, self.object_name)
|
||||
req.headers['Content-Length'] = 0
|
||||
req.headers.pop('Range', None)
|
||||
req.headers['X-Copy-From'] = quote('/%s/%s' % (self.container_name,
|
||||
self.object_name))
|
||||
req.environ['swift.post_as_copy'] = True
|
||||
params = req.params
|
||||
# for post-as-copy always copy the manifest itself if source is *LO
|
||||
params['multipart-manifest'] = 'get'
|
||||
req.params = params
|
||||
return self.handle_PUT(req, start_response)
|
||||
|
||||
def handle_COPY(self, req, start_response):
|
||||
if not req.headers.get('Destination'):
|
||||
return HTTPPreconditionFailed(request=req,
|
||||
body='Destination header required'
|
||||
)(req.environ, start_response)
|
||||
dest_account = self.account_name
|
||||
if 'Destination-Account' in req.headers:
|
||||
dest_account = req.headers.get('Destination-Account')
|
||||
dest_account = check_account_format(req, dest_account)
|
||||
req.headers['X-Copy-From-Account'] = self.account_name
|
||||
self.account_name = dest_account
|
||||
del req.headers['Destination-Account']
|
||||
dest_container, dest_object = _check_destination_header(req)
|
||||
source = '/%s/%s' % (self.container_name, self.object_name)
|
||||
self.container_name = dest_container
|
||||
self.object_name = dest_object
|
||||
# re-write the existing request as a PUT instead of creating a new one
|
||||
req.method = 'PUT'
|
||||
# As this the path info is updated with destination container,
|
||||
# the proxy server app will use the right object controller
|
||||
# implementation corresponding to the container's policy type.
|
||||
ver, _junk = req.split_path(1, 2, rest_with_last=True)
|
||||
req.path_info = '/%s/%s/%s/%s' % \
|
||||
(ver, dest_account, dest_container, dest_object)
|
||||
req.headers['Content-Length'] = 0
|
||||
req.headers['X-Copy-From'] = quote(source)
|
||||
del req.headers['Destination']
|
||||
return self.handle_PUT(req, start_response)
|
||||
|
||||
def _get_source_object(self, ssc_ctx, source_path, req):
|
||||
source_req = req.copy_get()
|
||||
|
||||
# make sure the source request uses it's container_info
|
||||
source_req.headers.pop('X-Backend-Storage-Policy-Index', None)
|
||||
source_req.path_info = quote(source_path)
|
||||
source_req.headers['X-Newest'] = 'true'
|
||||
if 'swift.post_as_copy' in req.environ:
|
||||
# We're COPYing one object over itself because of a POST; rely on
|
||||
# the PUT for write authorization, don't require read authorization
|
||||
source_req.environ['swift.authorize'] = lambda req: None
|
||||
source_req.environ['swift.authorize_override'] = True
|
||||
|
||||
# in case we are copying an SLO manifest, set format=raw parameter
|
||||
params = source_req.params
|
||||
if params.get('multipart-manifest') == 'get':
|
||||
params['format'] = 'raw'
|
||||
source_req.params = params
|
||||
|
||||
source_resp = ssc_ctx.get_source_resp(source_req)
|
||||
|
||||
if source_resp.content_length is None:
|
||||
# This indicates a transfer-encoding: chunked source object,
|
||||
# which currently only happens because there are more than
|
||||
# CONTAINER_LISTING_LIMIT segments in a segmented object. In
|
||||
# this case, we're going to refuse to do the server-side copy.
|
||||
return HTTPRequestEntityTooLarge(request=req)
|
||||
|
||||
if source_resp.content_length > MAX_FILE_SIZE:
|
||||
return HTTPRequestEntityTooLarge(request=req)
|
||||
|
||||
return source_resp
|
||||
|
||||
def _create_response_headers(self, source_path, source_resp, sink_req):
|
||||
resp_headers = dict()
|
||||
acct, path = source_path.split('/', 3)[2:4]
|
||||
resp_headers['X-Copied-From-Account'] = quote(acct)
|
||||
resp_headers['X-Copied-From'] = quote(path)
|
||||
if 'last-modified' in source_resp.headers:
|
||||
resp_headers['X-Copied-From-Last-Modified'] = \
|
||||
source_resp.headers['last-modified']
|
||||
# Existing sys and user meta of source object is added to response
|
||||
# headers in addition to the new ones.
|
||||
for k, v in sink_req.headers.items():
|
||||
if is_sys_or_user_meta('object', k) or k.lower() == 'x-delete-at':
|
||||
resp_headers[k] = v
|
||||
return resp_headers
|
||||
|
||||
def handle_PUT(self, req, start_response):
|
||||
if req.content_length:
|
||||
return HTTPBadRequest(body='Copy requests require a zero byte '
|
||||
'body', request=req,
|
||||
content_type='text/plain')(req.environ,
|
||||
start_response)
|
||||
|
||||
# Form the path of source object to be fetched
|
||||
ver, acct, _rest = req.split_path(2, 3, True)
|
||||
src_account_name = req.headers.get('X-Copy-From-Account')
|
||||
if src_account_name:
|
||||
src_account_name = check_account_format(req, src_account_name)
|
||||
else:
|
||||
src_account_name = acct
|
||||
src_container_name, src_obj_name = _check_copy_from_header(req)
|
||||
source_path = '/%s/%s/%s/%s' % (ver, src_account_name,
|
||||
src_container_name, src_obj_name)
|
||||
|
||||
if req.environ.get('swift.orig_req_method', req.method) != 'POST':
|
||||
self.logger.info("Copying object from %s to %s" %
|
||||
(source_path, req.path))
|
||||
|
||||
# GET the source object, bail out on error
|
||||
ssc_ctx = ServerSideCopyWebContext(self.app, self.logger)
|
||||
source_resp = self._get_source_object(ssc_ctx, source_path, req)
|
||||
if source_resp.status_int >= HTTP_MULTIPLE_CHOICES:
|
||||
close_if_possible(source_resp.app_iter)
|
||||
return source_resp(source_resp.environ, start_response)
|
||||
|
||||
# Create a new Request object based on the original req instance.
|
||||
# This will preserve env and headers.
|
||||
sink_req = Request.blank(req.path_info,
|
||||
environ=req.environ, headers=req.headers)
|
||||
|
||||
params = sink_req.params
|
||||
if params.get('multipart-manifest') == 'get':
|
||||
if 'X-Static-Large-Object' in source_resp.headers:
|
||||
params['multipart-manifest'] = 'put'
|
||||
if 'X-Object-Manifest' in source_resp.headers:
|
||||
del params['multipart-manifest']
|
||||
sink_req.headers['X-Object-Manifest'] = \
|
||||
source_resp.headers['X-Object-Manifest']
|
||||
sink_req.params = params
|
||||
|
||||
# Set data source, content length and etag for the PUT request
|
||||
sink_req.environ['wsgi.input'] = FileLikeIter(source_resp.app_iter)
|
||||
sink_req.content_length = source_resp.content_length
|
||||
sink_req.etag = source_resp.etag
|
||||
|
||||
# We no longer need these headers
|
||||
sink_req.headers.pop('X-Copy-From', None)
|
||||
sink_req.headers.pop('X-Copy-From-Account', None)
|
||||
# If the copy request does not explicitly override content-type,
|
||||
# use the one present in the source object.
|
||||
if not req.headers.get('content-type'):
|
||||
sink_req.headers['Content-Type'] = \
|
||||
source_resp.headers['Content-Type']
|
||||
|
||||
fresh_meta_flag = config_true_value(
|
||||
sink_req.headers.get('x-fresh-metadata', 'false'))
|
||||
|
||||
if fresh_meta_flag or 'swift.post_as_copy' in sink_req.environ:
|
||||
# Post-as-copy: ignore new sysmeta, copy existing sysmeta
|
||||
condition = lambda k: is_sys_meta('object', k)
|
||||
remove_items(sink_req.headers, condition)
|
||||
copy_header_subset(source_resp, sink_req, condition)
|
||||
else:
|
||||
# Copy/update existing sysmeta and user meta
|
||||
_copy_headers_into(source_resp, sink_req)
|
||||
# Copy/update new metadata provided in request if any
|
||||
_copy_headers_into(req, sink_req)
|
||||
|
||||
# Create response headers for PUT response
|
||||
resp_headers = self._create_response_headers(source_path,
|
||||
source_resp, sink_req)
|
||||
|
||||
put_resp = ssc_ctx.send_put_req(sink_req, resp_headers, start_response)
|
||||
close_if_possible(source_resp.app_iter)
|
||||
return put_resp
|
||||
|
||||
def handle_OPTIONS(self, req, start_response):
|
||||
return ServerSideCopyWebContext(self.app, self.logger).\
|
||||
handle_OPTIONS_request(req, start_response)
|
||||
|
||||
|
||||
def filter_factory(global_conf, **local_conf):
|
||||
conf = global_conf.copy()
|
||||
conf.update(local_conf)
|
||||
|
||||
def copy_filter(app):
|
||||
return ServerSideCopyMiddleware(app, conf)
|
||||
|
||||
return copy_filter
|
|
@ -344,12 +344,11 @@ class GetContext(WSGIContext):
|
|||
close_if_possible(resp_iter)
|
||||
response = self.get_or_head_response(req, value)
|
||||
return response(req.environ, start_response)
|
||||
else:
|
||||
# Not a dynamic large object manifest; just pass it through.
|
||||
start_response(self._response_status,
|
||||
self._response_headers,
|
||||
self._response_exc_info)
|
||||
return resp_iter
|
||||
# Not a dynamic large object manifest; just pass it through.
|
||||
start_response(self._response_status,
|
||||
self._response_headers,
|
||||
self._response_exc_info)
|
||||
return resp_iter
|
||||
|
||||
|
||||
class DynamicLargeObject(object):
|
||||
|
@ -406,11 +405,6 @@ class DynamicLargeObject(object):
|
|||
except ValueError:
|
||||
return self.app(env, start_response)
|
||||
|
||||
# install our COPY-callback hook
|
||||
env['swift.copy_hook'] = self.copy_hook(
|
||||
env.get('swift.copy_hook',
|
||||
lambda src_req, src_resp, sink_req: src_resp))
|
||||
|
||||
if ((req.method == 'GET' or req.method == 'HEAD') and
|
||||
req.params.get('multipart-manifest') != 'get'):
|
||||
return GetContext(self, self.logger).\
|
||||
|
@ -439,24 +433,6 @@ class DynamicLargeObject(object):
|
|||
body=('X-Object-Manifest must be in the '
|
||||
'format container/prefix'))
|
||||
|
||||
def copy_hook(self, inner_hook):
|
||||
|
||||
def dlo_copy_hook(source_req, source_resp, sink_req):
|
||||
x_o_m = source_resp.headers.get('X-Object-Manifest')
|
||||
if x_o_m:
|
||||
if source_req.params.get('multipart-manifest') == 'get':
|
||||
# To copy the manifest, we let the copy proceed as normal,
|
||||
# but ensure that X-Object-Manifest is set on the new
|
||||
# object.
|
||||
sink_req.headers['X-Object-Manifest'] = x_o_m
|
||||
else:
|
||||
ctx = GetContext(self, self.logger)
|
||||
source_resp = ctx.get_or_head_response(
|
||||
source_req, x_o_m, source_resp.headers.items())
|
||||
return inner_hook(source_req, source_resp, sink_req)
|
||||
|
||||
return dlo_copy_hook
|
||||
|
||||
|
||||
def filter_factory(global_conf, **local_conf):
|
||||
conf = global_conf.copy()
|
||||
|
|
|
@ -32,7 +32,7 @@ automatically inserted close to the start of the pipeline by the proxy server.
|
|||
|
||||
|
||||
from swift.common.swob import Request
|
||||
from swift.common.utils import get_logger
|
||||
from swift.common.utils import get_logger, config_true_value
|
||||
from swift.common.request_helpers import remove_items, get_sys_meta_prefix
|
||||
import re
|
||||
|
||||
|
@ -69,6 +69,8 @@ class GatekeeperMiddleware(object):
|
|||
self.logger = get_logger(conf, log_route='gatekeeper')
|
||||
self.inbound_condition = make_exclusion_test(inbound_exclusions)
|
||||
self.outbound_condition = make_exclusion_test(outbound_exclusions)
|
||||
self.shunt_x_timestamp = config_true_value(
|
||||
conf.get('shunt_inbound_x_timestamp', 'true'))
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
req = Request(env)
|
||||
|
@ -76,6 +78,13 @@ class GatekeeperMiddleware(object):
|
|||
if removed:
|
||||
self.logger.debug('removed request headers: %s' % removed)
|
||||
|
||||
if 'X-Timestamp' in req.headers and self.shunt_x_timestamp:
|
||||
ts = req.headers.pop('X-Timestamp')
|
||||
req.headers['X-Backend-Inbound-X-Timestamp'] = ts
|
||||
# log in a similar format as the removed headers
|
||||
self.logger.debug('shunted request headers: %s' %
|
||||
[('X-Timestamp', ts)])
|
||||
|
||||
def gatekeeper_response(status, response_headers, exc_info=None):
|
||||
removed = filter(
|
||||
lambda h: self.outbound_condition(h[0]),
|
||||
|
|
|
@ -75,12 +75,6 @@ class KeystoneAuth(object):
|
|||
id.. For example, if the project id is ``1234``, the path is
|
||||
``/v1/AUTH_1234``.
|
||||
|
||||
If the ``is_admin`` option 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 one of the
|
||||
``operator_roles``. Note that the condition compares names rather than
|
||||
UUIDs. This option is deprecated. It is ``false`` by default.
|
||||
|
||||
If you need to have a different reseller_prefix to be able to
|
||||
mix different auth servers you can configure the option
|
||||
``reseller_prefix`` in your keystoneauth entry like this::
|
||||
|
@ -188,7 +182,11 @@ class KeystoneAuth(object):
|
|||
self.reseller_admin_role = conf.get('reseller_admin_role',
|
||||
'ResellerAdmin').lower()
|
||||
config_is_admin = conf.get('is_admin', "false").lower()
|
||||
self.is_admin = swift_utils.config_true_value(config_is_admin)
|
||||
if swift_utils.config_true_value(config_is_admin):
|
||||
self.logger.warning("The 'is_admin' option for keystoneauth is no "
|
||||
"longer supported. Remove the 'is_admin' "
|
||||
"option from your keystoneauth config")
|
||||
|
||||
config_overrides = conf.get('allow_overrides', 't').lower()
|
||||
self.allow_overrides = swift_utils.config_true_value(config_overrides)
|
||||
self.default_domain_id = conf.get('default_domain_id', 'default')
|
||||
|
@ -289,7 +287,8 @@ class KeystoneAuth(object):
|
|||
def _get_project_domain_id(self, environ):
|
||||
info = get_account_info(environ, self.app, 'KS')
|
||||
domain_id = info.get('sysmeta', {}).get('project-domain-id')
|
||||
exists = is_success(info.get('status', 0))
|
||||
exists = (is_success(info.get('status', 0))
|
||||
and info.get('account_really_exists', True))
|
||||
return exists, domain_id
|
||||
|
||||
def _set_project_domain_id(self, req, path_parts, env_identity):
|
||||
|
@ -484,14 +483,6 @@ class KeystoneAuth(object):
|
|||
req.environ['swift_owner'] = True
|
||||
return
|
||||
|
||||
# If user is of the same name of the tenant then make owner of it.
|
||||
if self.is_admin and user_name == tenant_name:
|
||||
self.logger.warning("the is_admin feature has been deprecated "
|
||||
"and will be removed in the future "
|
||||
"update your config file")
|
||||
req.environ['swift_owner'] = True
|
||||
return
|
||||
|
||||
if acl_authorized is not None:
|
||||
return self.denied_response(req)
|
||||
|
||||
|
|
|
@ -37,9 +37,9 @@ with a JSON-encoded list of endpoints of the form::
|
|||
|
||||
correspondingly, e.g.::
|
||||
|
||||
http://10.1.1.1:6000/sda1/2/a/c2/o1
|
||||
http://10.1.1.1:6000/sda1/2/a/c2
|
||||
http://10.1.1.1:6000/sda1/2/a
|
||||
http://10.1.1.1:6200/sda1/2/a/c2/o1
|
||||
http://10.1.1.1:6200/sda1/2/a/c2
|
||||
http://10.1.1.1:6200/sda1/2/a
|
||||
|
||||
Using the v2 API, answers requests of the form::
|
||||
|
||||
|
|
|
@ -206,6 +206,9 @@ class ReconMiddleware(object):
|
|||
"""list unmounted (failed?) devices"""
|
||||
mountlist = []
|
||||
for entry in os.listdir(self.devices):
|
||||
if not os.path.isdir(os.path.join(self.devices, entry)):
|
||||
continue
|
||||
|
||||
try:
|
||||
mounted = check_mount(self.devices, entry)
|
||||
except OSError as err:
|
||||
|
@ -219,6 +222,9 @@ class ReconMiddleware(object):
|
|||
"""get disk utilization statistics"""
|
||||
devices = []
|
||||
for entry in os.listdir(self.devices):
|
||||
if not os.path.isdir(os.path.join(self.devices, entry)):
|
||||
continue
|
||||
|
||||
try:
|
||||
mounted = check_mount(self.devices, entry)
|
||||
except OSError as err:
|
||||
|
|
|
@ -149,9 +149,15 @@ A GET request with the query parameter::
|
|||
|
||||
?multipart-manifest=get
|
||||
|
||||
Will return the actual manifest file itself. This is generated json and does
|
||||
not match the data sent from the original multipart-manifest=put. This call's
|
||||
main purpose is for debugging.
|
||||
will return a transformed version of the original manifest, containing
|
||||
additional fields and different key names.
|
||||
|
||||
A GET request with the query parameters::
|
||||
|
||||
?multipart-manifest=get&format=raw
|
||||
|
||||
will return the contents of the original manifest as it was sent by the client.
|
||||
The main purpose for both calls is solely debugging.
|
||||
|
||||
When the manifest object is uploaded you are more or less guaranteed that
|
||||
every segment in the manifest exists and matched the specifications.
|
||||
|
@ -393,7 +399,7 @@ class SloGetContext(WSGIContext):
|
|||
req.environ, path='/'.join(['', version, acc, con, obj]),
|
||||
method='GET',
|
||||
headers={'x-auth-token': req.headers.get('x-auth-token')},
|
||||
agent=('%(orig)s ' + 'SLO MultipartGET'), swift_source='SLO')
|
||||
agent='%(orig)s SLO MultipartGET', swift_source='SLO')
|
||||
sub_resp = sub_req.get_response(self.slo.app)
|
||||
|
||||
if not is_success(sub_resp.status_int):
|
||||
|
@ -573,14 +579,18 @@ class SloGetContext(WSGIContext):
|
|||
|
||||
# Handle pass-through request for the manifest itself
|
||||
if req.params.get('multipart-manifest') == 'get':
|
||||
new_headers = []
|
||||
for header, value in self._response_headers:
|
||||
if header.lower() == 'content-type':
|
||||
new_headers.append(('Content-Type',
|
||||
'application/json; charset=utf-8'))
|
||||
else:
|
||||
new_headers.append((header, value))
|
||||
self._response_headers = new_headers
|
||||
if req.params.get('format') == 'raw':
|
||||
resp_iter = self.convert_segment_listing(
|
||||
self._response_headers, resp_iter)
|
||||
else:
|
||||
new_headers = []
|
||||
for header, value in self._response_headers:
|
||||
if header.lower() == 'content-type':
|
||||
new_headers.append(('Content-Type',
|
||||
'application/json; charset=utf-8'))
|
||||
else:
|
||||
new_headers.append((header, value))
|
||||
self._response_headers = new_headers
|
||||
start_response(self._response_status,
|
||||
self._response_headers,
|
||||
self._response_exc_info)
|
||||
|
@ -594,7 +604,7 @@ class SloGetContext(WSGIContext):
|
|||
get_req = make_subrequest(
|
||||
req.environ, method='GET',
|
||||
headers={'x-auth-token': req.headers.get('x-auth-token')},
|
||||
agent=('%(orig)s ' + 'SLO MultipartGET'), swift_source='SLO')
|
||||
agent='%(orig)s SLO MultipartGET', swift_source='SLO')
|
||||
resp_iter = self._app_call(get_req.environ)
|
||||
|
||||
# Any Content-Range from a manifest is almost certainly wrong for the
|
||||
|
@ -606,7 +616,40 @@ class SloGetContext(WSGIContext):
|
|||
req, resp_headers, resp_iter)
|
||||
return response(req.environ, start_response)
|
||||
|
||||
def get_or_head_response(self, req, resp_headers, resp_iter):
|
||||
def convert_segment_listing(self, resp_headers, resp_iter):
|
||||
"""
|
||||
Converts the manifest data to match with the format
|
||||
that was put in through ?multipart-manifest=put
|
||||
|
||||
:param resp_headers: response headers
|
||||
:param resp_iter: a response iterable
|
||||
"""
|
||||
segments = self._get_manifest_read(resp_iter)
|
||||
|
||||
for seg_dict in segments:
|
||||
seg_dict.pop('content_type', None)
|
||||
seg_dict.pop('last_modified', None)
|
||||
seg_dict.pop('sub_slo', None)
|
||||
seg_dict['path'] = seg_dict.pop('name', None)
|
||||
seg_dict['size_bytes'] = seg_dict.pop('bytes', None)
|
||||
seg_dict['etag'] = seg_dict.pop('hash', None)
|
||||
|
||||
json_data = json.dumps(segments) # convert to string
|
||||
if six.PY3:
|
||||
json_data = json_data.encode('utf-8')
|
||||
|
||||
new_headers = []
|
||||
for header, value in resp_headers:
|
||||
if header.lower() == 'content-length':
|
||||
new_headers.append(('Content-Length',
|
||||
len(json_data)))
|
||||
else:
|
||||
new_headers.append((header, value))
|
||||
self._response_headers = new_headers
|
||||
|
||||
return [json_data]
|
||||
|
||||
def _get_manifest_read(self, resp_iter):
|
||||
with closing_if_possible(resp_iter):
|
||||
resp_body = ''.join(resp_iter)
|
||||
try:
|
||||
|
@ -614,6 +657,11 @@ class SloGetContext(WSGIContext):
|
|||
except ValueError:
|
||||
segments = []
|
||||
|
||||
return segments
|
||||
|
||||
def get_or_head_response(self, req, resp_headers, resp_iter):
|
||||
segments = self._get_manifest_read(resp_iter)
|
||||
|
||||
etag = md5()
|
||||
content_length = 0
|
||||
for seg_dict in segments:
|
||||
|
@ -737,7 +785,9 @@ class StaticLargeObject(object):
|
|||
'rate_limit_after_segment', '10'))
|
||||
self.rate_limit_segments_per_sec = int(self.conf.get(
|
||||
'rate_limit_segments_per_sec', '1'))
|
||||
self.bulk_deleter = Bulk(app, {}, logger=self.logger)
|
||||
delete_concurrency = int(self.conf.get('delete_concurrency', '2'))
|
||||
self.bulk_deleter = Bulk(
|
||||
app, {}, delete_concurrency=delete_concurrency, logger=self.logger)
|
||||
|
||||
def handle_multipart_get_or_head(self, req, start_response):
|
||||
"""
|
||||
|
@ -751,20 +801,6 @@ class StaticLargeObject(object):
|
|||
"""
|
||||
return SloGetContext(self).handle_slo_get_or_head(req, start_response)
|
||||
|
||||
def copy_hook(self, inner_hook):
|
||||
|
||||
def slo_hook(source_req, source_resp, sink_req):
|
||||
x_slo = source_resp.headers.get('X-Static-Large-Object')
|
||||
if (config_true_value(x_slo)
|
||||
and source_req.params.get('multipart-manifest') != 'get'
|
||||
and 'swift.post_as_copy' not in source_req.environ):
|
||||
source_resp = SloGetContext(self).get_or_head_response(
|
||||
source_req, source_resp.headers.items(),
|
||||
source_resp.app_iter)
|
||||
return inner_hook(source_req, source_resp, sink_req)
|
||||
|
||||
return slo_hook
|
||||
|
||||
def handle_multipart_put(self, req, start_response):
|
||||
"""
|
||||
Will handle the PUT of a SLO manifest.
|
||||
|
@ -810,20 +846,14 @@ class StaticLargeObject(object):
|
|||
obj_name = obj_name.encode('utf-8')
|
||||
obj_path = '/'.join(['', vrs, account, obj_name.lstrip('/')])
|
||||
|
||||
new_env = req.environ.copy()
|
||||
new_env['PATH_INFO'] = obj_path
|
||||
new_env['REQUEST_METHOD'] = 'HEAD'
|
||||
new_env['swift.source'] = 'SLO'
|
||||
del(new_env['wsgi.input'])
|
||||
del(new_env['QUERY_STRING'])
|
||||
new_env['CONTENT_LENGTH'] = 0
|
||||
new_env['HTTP_USER_AGENT'] = \
|
||||
'%s MultipartPUT' % req.environ.get('HTTP_USER_AGENT')
|
||||
|
||||
if obj_path != last_obj_path:
|
||||
last_obj_path = obj_path
|
||||
head_seg_resp = \
|
||||
Request.blank(obj_path, new_env).get_response(self)
|
||||
sub_req = make_subrequest(
|
||||
req.environ, path=obj_path + '?', # kill the query string
|
||||
method='HEAD',
|
||||
headers={'x-auth-token': req.headers.get('x-auth-token')},
|
||||
agent='%(orig)s SLO MultipartPUT', swift_source='SLO')
|
||||
head_seg_resp = sub_req.get_response(self)
|
||||
|
||||
if head_seg_resp.is_success:
|
||||
segment_length = head_seg_resp.content_length
|
||||
|
@ -1017,11 +1047,6 @@ class StaticLargeObject(object):
|
|||
except ValueError:
|
||||
return self.app(env, start_response)
|
||||
|
||||
# install our COPY-callback hook
|
||||
env['swift.copy_hook'] = self.copy_hook(
|
||||
env.get('swift.copy_hook',
|
||||
lambda src_req, src_resp, sink_req: src_resp))
|
||||
|
||||
try:
|
||||
if req.method == 'PUT' and \
|
||||
req.params.get('multipart-manifest') == 'put':
|
||||
|
|
|
@ -131,7 +131,8 @@ from swift.common.utils import human_readable, split_path, config_true_value, \
|
|||
quote, register_swift_info, get_logger
|
||||
from swift.common.wsgi import make_env, WSGIContext
|
||||
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, \
|
||||
Request
|
||||
from swift.proxy.controllers.base import get_container_info
|
||||
|
||||
|
||||
|
@ -196,10 +197,12 @@ class _StaticWebContext(WSGIContext):
|
|||
self._error, self._listings, self._listings_css and self._dir_type.
|
||||
|
||||
:param env: The WSGI environment dict.
|
||||
:return container_info: The container_info dict.
|
||||
"""
|
||||
self._index = self._error = self._listings = self._listings_css = \
|
||||
self._dir_type = None
|
||||
container_info = get_container_info(env, self.app, swift_source='SW')
|
||||
container_info = get_container_info(
|
||||
env, self.app, swift_source='SW')
|
||||
if is_success(container_info['status']):
|
||||
meta = container_info.get('meta', {})
|
||||
self._index = meta.get('web-index', '').strip()
|
||||
|
@ -208,6 +211,7 @@ class _StaticWebContext(WSGIContext):
|
|||
self._listings_label = meta.get('web-listings-label', '').strip()
|
||||
self._listings_css = meta.get('web-listings-css', '').strip()
|
||||
self._dir_type = meta.get('web-directory-type', '').strip()
|
||||
return container_info
|
||||
|
||||
def _listing(self, env, start_response, prefix=None):
|
||||
"""
|
||||
|
@ -356,7 +360,15 @@ class _StaticWebContext(WSGIContext):
|
|||
:param env: The original WSGI environment dict.
|
||||
:param start_response: The original WSGI start_response hook.
|
||||
"""
|
||||
self._get_container_info(env)
|
||||
container_info = self._get_container_info(env)
|
||||
req = Request(env)
|
||||
req.acl = container_info['read_acl']
|
||||
# we checked earlier that swift.authorize is set in env
|
||||
aresp = env['swift.authorize'](req)
|
||||
if aresp:
|
||||
resp = aresp(env, self._start_response)
|
||||
return self._error_response(resp, env, start_response)
|
||||
|
||||
if not self._listings and not self._index:
|
||||
if config_true_value(env.get('HTTP_X_WEB_MODE', 'f')):
|
||||
return HTTPNotFound()(env, start_response)
|
||||
|
|
|
@ -169,8 +169,9 @@ from six.moves.urllib.parse import parse_qs
|
|||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
from swift.proxy.controllers.base import get_account_info, get_container_info
|
||||
from swift.common.swob import HeaderKeyDict, header_to_environ_key, \
|
||||
HTTPUnauthorized, HTTPBadRequest
|
||||
from swift.common.header_key_dict import HeaderKeyDict
|
||||
from swift.common.swob import header_to_environ_key, HTTPUnauthorized, \
|
||||
HTTPBadRequest
|
||||
from swift.common.utils import split_path, get_valid_utf8_str, \
|
||||
register_swift_info, get_hmac, streq_const_time, quote
|
||||
|
||||
|
@ -399,7 +400,7 @@ class TempURL(object):
|
|||
|
||||
def _start_response(status, headers, exc_info=None):
|
||||
headers = self._clean_outgoing_headers(headers)
|
||||
if env['REQUEST_METHOD'] == 'GET' and status[0] == '2':
|
||||
if env['REQUEST_METHOD'] in ('GET', 'HEAD') and status[0] == '2':
|
||||
# figure out the right value for content-disposition
|
||||
# 1) use the value from the query string
|
||||
# 2) use the value from the object metadata
|
||||
|
|
|
@ -117,16 +117,17 @@ Disable versioning from a container (x is any value except empty)::
|
|||
|
||||
import calendar
|
||||
import json
|
||||
import six
|
||||
from six.moves.urllib.parse import quote, unquote
|
||||
import time
|
||||
|
||||
from swift.common.utils import get_logger, Timestamp, \
|
||||
register_swift_info, config_true_value
|
||||
from swift.common.request_helpers import get_sys_meta_prefix
|
||||
register_swift_info, config_true_value, close_if_possible, FileLikeIter
|
||||
from swift.common.request_helpers import get_sys_meta_prefix, \
|
||||
copy_header_subset
|
||||
from swift.common.wsgi import WSGIContext, make_pre_authed_request
|
||||
from swift.common.swob import Request, HTTPException
|
||||
from swift.common.constraints import (
|
||||
check_account_format, check_container_format, check_destination_header)
|
||||
from swift.common.swob import (
|
||||
Request, HTTPException, HTTPRequestEntityTooLarge)
|
||||
from swift.common.constraints import check_container_format, MAX_FILE_SIZE
|
||||
from swift.proxy.controllers.base import get_container_info
|
||||
from swift.common.http import (
|
||||
is_success, is_client_error, HTTP_NOT_FOUND)
|
||||
|
@ -155,15 +156,74 @@ class VersionedWritesContext(WSGIContext):
|
|||
except ListingIterError:
|
||||
raise HTTPServerError(request=req)
|
||||
|
||||
def _listing_pages_iter(self, account_name, lcontainer, lprefix, env):
|
||||
marker = ''
|
||||
def _in_proxy_reverse_listing(self, account_name, lcontainer, lprefix,
|
||||
env, failed_marker, failed_listing):
|
||||
'''Get the complete prefix listing and reverse it on the proxy.
|
||||
|
||||
This is only necessary if we encounter a response from a
|
||||
container-server that does not respect the ``reverse`` param
|
||||
included by default in ``_listing_pages_iter``. This may happen
|
||||
during rolling upgrades from pre-2.6.0 swift.
|
||||
|
||||
:param failed_marker: the marker that was used when we encountered
|
||||
the non-reversed listing
|
||||
:param failed_listing: the non-reversed listing that was encountered.
|
||||
If ``failed_marker`` is blank, we can use this
|
||||
to save ourselves a request
|
||||
:returns: an iterator over all objects starting with ``lprefix`` (up
|
||||
to but not including the failed marker) in reverse order
|
||||
'''
|
||||
complete_listing = []
|
||||
if not failed_marker:
|
||||
# We've never gotten a reversed listing. So save a request and
|
||||
# use the failed listing.
|
||||
complete_listing.extend(failed_listing)
|
||||
marker = complete_listing[-1]['name'].encode('utf8')
|
||||
else:
|
||||
# We've gotten at least one reversed listing. Have to start at
|
||||
# the beginning.
|
||||
marker = ''
|
||||
|
||||
# First, take the *entire* prefix listing into memory
|
||||
try:
|
||||
for page in self._listing_pages_iter(
|
||||
account_name, lcontainer, lprefix,
|
||||
env, marker, end_marker=failed_marker, reverse=False):
|
||||
complete_listing.extend(page)
|
||||
except ListingIterNotFound:
|
||||
pass
|
||||
|
||||
# Now that we've got everything, return the whole listing as one giant
|
||||
# reversed page
|
||||
return reversed(complete_listing)
|
||||
|
||||
def _listing_pages_iter(self, account_name, lcontainer, lprefix,
|
||||
env, marker='', end_marker='', reverse=True):
|
||||
'''Get "pages" worth of objects that start with a prefix.
|
||||
|
||||
The optional keyword arguments ``marker``, ``end_marker``, and
|
||||
``reverse`` are used similar to how they are for containers. We're
|
||||
either coming:
|
||||
|
||||
- directly from ``_listing_iter``, in which case none of the
|
||||
optional args are specified, or
|
||||
|
||||
- from ``_in_proxy_reverse_listing``, in which case ``reverse``
|
||||
is ``False`` and both ``marker`` and ``end_marker`` are specified
|
||||
(although they may still be blank).
|
||||
'''
|
||||
while True:
|
||||
lreq = make_pre_authed_request(
|
||||
env, method='GET', swift_source='VW',
|
||||
path='/v1/%s/%s' % (account_name, lcontainer))
|
||||
lreq.environ['QUERY_STRING'] = \
|
||||
'format=json&prefix=%s&reverse=on&marker=%s' % (
|
||||
'format=json&prefix=%s&marker=%s' % (
|
||||
quote(lprefix), quote(marker))
|
||||
if end_marker:
|
||||
lreq.environ['QUERY_STRING'] += '&end_marker=%s' % (
|
||||
quote(end_marker))
|
||||
if reverse:
|
||||
lreq.environ['QUERY_STRING'] += '&reverse=on'
|
||||
lresp = lreq.get_response(self.app)
|
||||
if not is_success(lresp.status_int):
|
||||
if lresp.status_int == HTTP_NOT_FOUND:
|
||||
|
@ -179,90 +239,138 @@ class VersionedWritesContext(WSGIContext):
|
|||
sublisting = json.loads(lresp.body)
|
||||
if not sublisting:
|
||||
break
|
||||
marker = sublisting[-1]['name'].encode('utf-8')
|
||||
|
||||
# When using the ``reverse`` param, check that the listing is
|
||||
# actually reversed
|
||||
first_item = sublisting[0]['name'].encode('utf-8')
|
||||
last_item = sublisting[-1]['name'].encode('utf-8')
|
||||
page_is_after_marker = marker and first_item > marker
|
||||
if reverse and (first_item < last_item or page_is_after_marker):
|
||||
# Apparently there's at least one pre-2.6.0 container server
|
||||
yield self._in_proxy_reverse_listing(
|
||||
account_name, lcontainer, lprefix,
|
||||
env, marker, sublisting)
|
||||
return
|
||||
|
||||
marker = last_item
|
||||
yield sublisting
|
||||
|
||||
def handle_obj_versions_put(self, req, object_versions,
|
||||
object_name, policy_index):
|
||||
ret = None
|
||||
|
||||
# do a HEAD request to check object versions
|
||||
def _get_source_object(self, req, path_info):
|
||||
# make a GET request to check object versions
|
||||
_headers = {'X-Newest': 'True',
|
||||
'X-Backend-Storage-Policy-Index': policy_index,
|
||||
'x-auth-token': req.headers.get('x-auth-token')}
|
||||
|
||||
# make a pre_auth request in case the user has write access
|
||||
# to container, but not READ. This was allowed in previous version
|
||||
# (i.e., before middleware) so keeping the same behavior here
|
||||
head_req = make_pre_authed_request(
|
||||
req.environ, path=req.path_info,
|
||||
headers=_headers, method='HEAD', swift_source='VW')
|
||||
hresp = head_req.get_response(self.app)
|
||||
get_req = make_pre_authed_request(
|
||||
req.environ, path=path_info,
|
||||
headers=_headers, method='GET', swift_source='VW')
|
||||
source_resp = get_req.get_response(self.app)
|
||||
|
||||
is_dlo_manifest = 'X-Object-Manifest' in req.headers or \
|
||||
'X-Object-Manifest' in hresp.headers
|
||||
if source_resp.content_length is None or \
|
||||
source_resp.content_length > MAX_FILE_SIZE:
|
||||
return HTTPRequestEntityTooLarge(request=req)
|
||||
|
||||
return source_resp
|
||||
|
||||
def _put_versioned_obj(self, req, put_path_info, source_resp):
|
||||
# Create a new Request object to PUT to the versions container, copying
|
||||
# all headers from the source object apart from x-timestamp.
|
||||
put_req = make_pre_authed_request(
|
||||
req.environ, path=put_path_info, method='PUT',
|
||||
swift_source='VW')
|
||||
copy_header_subset(source_resp, put_req,
|
||||
lambda k: k.lower() != 'x-timestamp')
|
||||
put_req.headers['x-auth-token'] = req.headers.get('x-auth-token')
|
||||
put_req.environ['wsgi.input'] = FileLikeIter(source_resp.app_iter)
|
||||
return put_req.get_response(self.app)
|
||||
|
||||
def _check_response_error(self, req, resp):
|
||||
"""
|
||||
Raise Error Response in case of error
|
||||
"""
|
||||
if is_success(resp.status_int):
|
||||
return
|
||||
if is_client_error(resp.status_int):
|
||||
# missing container or bad permissions
|
||||
raise HTTPPreconditionFailed(request=req)
|
||||
# could not version the data, bail
|
||||
raise HTTPServiceUnavailable(request=req)
|
||||
|
||||
def handle_obj_versions_put(self, req, versions_cont, api_version,
|
||||
account_name, object_name):
|
||||
"""
|
||||
Copy current version of object to versions_container before proceding
|
||||
with original request.
|
||||
|
||||
:param req: original request.
|
||||
:param versions_cont: container where previous versions of the object
|
||||
are stored.
|
||||
:param api_version: api version.
|
||||
:param account_name: account name.
|
||||
:param object_name: name of object of original request
|
||||
"""
|
||||
if 'X-Object-Manifest' in req.headers:
|
||||
# do not version DLO manifest, proceed with original request
|
||||
return self.app
|
||||
|
||||
get_resp = self._get_source_object(req, req.path_info)
|
||||
|
||||
if 'X-Object-Manifest' in get_resp.headers:
|
||||
# do not version DLO manifest, proceed with original request
|
||||
close_if_possible(get_resp.app_iter)
|
||||
return self.app
|
||||
if get_resp.status_int == HTTP_NOT_FOUND:
|
||||
# nothing to version, proceed with original request
|
||||
close_if_possible(get_resp.app_iter)
|
||||
return self.app
|
||||
|
||||
# check for any other errors
|
||||
self._check_response_error(req, get_resp)
|
||||
|
||||
# if there's an existing object, then copy it to
|
||||
# X-Versions-Location
|
||||
if is_success(hresp.status_int) and not is_dlo_manifest:
|
||||
lcontainer = object_versions.split('/')[0]
|
||||
prefix_len = '%03x' % len(object_name)
|
||||
lprefix = prefix_len + object_name + '/'
|
||||
ts_source = hresp.environ.get('swift_x_timestamp')
|
||||
if ts_source is None:
|
||||
ts_source = calendar.timegm(time.strptime(
|
||||
hresp.headers['last-modified'],
|
||||
'%a, %d %b %Y %H:%M:%S GMT'))
|
||||
new_ts = Timestamp(ts_source).internal
|
||||
vers_obj_name = lprefix + new_ts
|
||||
copy_headers = {
|
||||
'Destination': '%s/%s' % (lcontainer, vers_obj_name),
|
||||
'x-auth-token': req.headers.get('x-auth-token')}
|
||||
prefix_len = '%03x' % len(object_name)
|
||||
lprefix = prefix_len + object_name + '/'
|
||||
ts_source = get_resp.headers.get(
|
||||
'x-timestamp',
|
||||
calendar.timegm(time.strptime(
|
||||
get_resp.headers['last-modified'],
|
||||
'%a, %d %b %Y %H:%M:%S GMT')))
|
||||
vers_obj_name = lprefix + Timestamp(ts_source).internal
|
||||
|
||||
# COPY implementation sets X-Newest to True when it internally
|
||||
# does a GET on source object. So, we don't have to explicity
|
||||
# set it in request headers here.
|
||||
copy_req = make_pre_authed_request(
|
||||
req.environ, path=req.path_info,
|
||||
headers=copy_headers, method='COPY', swift_source='VW')
|
||||
copy_resp = copy_req.get_response(self.app)
|
||||
put_path_info = "/%s/%s/%s/%s" % (
|
||||
api_version, account_name, versions_cont, vers_obj_name)
|
||||
put_resp = self._put_versioned_obj(req, put_path_info, get_resp)
|
||||
|
||||
if is_success(copy_resp.status_int):
|
||||
# success versioning previous existing object
|
||||
# return None and handle original request
|
||||
ret = None
|
||||
else:
|
||||
if is_client_error(copy_resp.status_int):
|
||||
# missing container or bad permissions
|
||||
ret = HTTPPreconditionFailed(request=req)
|
||||
else:
|
||||
# could not copy the data, bail
|
||||
ret = HTTPServiceUnavailable(request=req)
|
||||
self._check_response_error(req, put_resp)
|
||||
return self.app
|
||||
|
||||
else:
|
||||
if hresp.status_int == HTTP_NOT_FOUND or is_dlo_manifest:
|
||||
# nothing to version
|
||||
# return None and handle original request
|
||||
ret = None
|
||||
else:
|
||||
# if not HTTP_NOT_FOUND, return error immediately
|
||||
ret = hresp
|
||||
|
||||
return ret
|
||||
|
||||
def handle_obj_versions_delete(self, req, object_versions,
|
||||
def handle_obj_versions_delete(self, req, versions_cont, api_version,
|
||||
account_name, container_name, object_name):
|
||||
lcontainer = object_versions.split('/')[0]
|
||||
"""
|
||||
Delete current version of object and pop previous version in its place.
|
||||
|
||||
:param req: original request.
|
||||
:param versions_cont: container where previous versions of the object
|
||||
are stored.
|
||||
:param api_version: api version.
|
||||
:param account_name: account name.
|
||||
:param container_name: container name.
|
||||
:param object_name: object name.
|
||||
"""
|
||||
prefix_len = '%03x' % len(object_name)
|
||||
lprefix = prefix_len + object_name + '/'
|
||||
|
||||
item_iter = self._listing_iter(account_name, lcontainer, lprefix, req)
|
||||
item_iter = self._listing_iter(account_name, versions_cont, lprefix,
|
||||
req)
|
||||
|
||||
authed = False
|
||||
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
|
||||
# validate the write access to the versioned container before
|
||||
# making any backend requests
|
||||
if 'swift.authorize' in req.environ:
|
||||
container_info = get_container_info(
|
||||
req.environ, self.app)
|
||||
|
@ -276,35 +384,29 @@ class VersionedWritesContext(WSGIContext):
|
|||
# current object and delete the previous version
|
||||
prev_obj_name = previous_version['name'].encode('utf-8')
|
||||
|
||||
copy_path = '/v1/' + account_name + '/' + \
|
||||
lcontainer + '/' + prev_obj_name
|
||||
get_path = "/%s/%s/%s/%s" % (
|
||||
api_version, account_name, versions_cont, prev_obj_name)
|
||||
|
||||
copy_headers = {'X-Newest': 'True',
|
||||
'Destination': container_name + '/' + object_name,
|
||||
'x-auth-token': req.headers.get('x-auth-token')}
|
||||
|
||||
copy_req = make_pre_authed_request(
|
||||
req.environ, path=copy_path,
|
||||
headers=copy_headers, method='COPY', swift_source='VW')
|
||||
copy_resp = copy_req.get_response(self.app)
|
||||
get_resp = self._get_source_object(req, get_path)
|
||||
|
||||
# if the version isn't there, keep trying with previous version
|
||||
if copy_resp.status_int == HTTP_NOT_FOUND:
|
||||
if get_resp.status_int == HTTP_NOT_FOUND:
|
||||
continue
|
||||
|
||||
if not is_success(copy_resp.status_int):
|
||||
if is_client_error(copy_resp.status_int):
|
||||
# some user error, maybe permissions
|
||||
return HTTPPreconditionFailed(request=req)
|
||||
else:
|
||||
# could not copy the data, bail
|
||||
return HTTPServiceUnavailable(request=req)
|
||||
self._check_response_error(req, get_resp)
|
||||
|
||||
# reset these because the COPY changed them
|
||||
new_del_req = make_pre_authed_request(
|
||||
req.environ, path=copy_path, method='DELETE',
|
||||
put_path_info = "/%s/%s/%s/%s" % (
|
||||
api_version, account_name, container_name, object_name)
|
||||
put_resp = self._put_versioned_obj(req, put_path_info, get_resp)
|
||||
|
||||
self._check_response_error(req, put_resp)
|
||||
|
||||
# redirect the original DELETE to the source of the reinstated
|
||||
# version object - we already auth'd original req so make a
|
||||
# pre-authed request
|
||||
req = make_pre_authed_request(
|
||||
req.environ, path=get_path, method='DELETE',
|
||||
swift_source='VW')
|
||||
req = new_del_req
|
||||
|
||||
# remove 'X-If-Delete-At', since it is not for the older copy
|
||||
if 'X-If-Delete-At' in req.headers:
|
||||
|
@ -366,7 +468,7 @@ class VersionedWritesMiddleware(object):
|
|||
req.headers['X-Versions-Location'] = ''
|
||||
|
||||
# if both headers are in the same request
|
||||
# adding location takes precendence over removing
|
||||
# adding location takes precedence over removing
|
||||
if 'X-Remove-Versions-Location' in req.headers:
|
||||
del req.headers['X-Remove-Versions-Location']
|
||||
else:
|
||||
|
@ -384,59 +486,41 @@ class VersionedWritesMiddleware(object):
|
|||
vw_ctx = VersionedWritesContext(self.app, self.logger)
|
||||
return vw_ctx.handle_container_request(req.environ, start_response)
|
||||
|
||||
def object_request(self, req, version, account, container, obj,
|
||||
def object_request(self, req, api_version, account, container, obj,
|
||||
allow_versioned_writes):
|
||||
account_name = unquote(account)
|
||||
container_name = unquote(container)
|
||||
object_name = unquote(obj)
|
||||
container_info = None
|
||||
resp = None
|
||||
is_enabled = config_true_value(allow_versioned_writes)
|
||||
if req.method in ('PUT', 'DELETE'):
|
||||
container_info = get_container_info(
|
||||
req.environ, self.app)
|
||||
elif req.method == 'COPY' and 'Destination' in req.headers:
|
||||
if 'Destination-Account' in req.headers:
|
||||
account_name = req.headers.get('Destination-Account')
|
||||
account_name = check_account_format(req, account_name)
|
||||
container_name, object_name = check_destination_header(req)
|
||||
req.environ['PATH_INFO'] = "/%s/%s/%s/%s" % (
|
||||
version, account_name, container_name, object_name)
|
||||
container_info = get_container_info(
|
||||
req.environ, self.app)
|
||||
|
||||
if not container_info:
|
||||
return self.app
|
||||
container_info = get_container_info(
|
||||
req.environ, self.app)
|
||||
|
||||
# To maintain backwards compatibility, container version
|
||||
# location could be stored as sysmeta or not, need to check both.
|
||||
# If stored as sysmeta, check if middleware is enabled. If sysmeta
|
||||
# is not set, but versions property is set in container_info, then
|
||||
# for backwards compatibility feature is enabled.
|
||||
object_versions = container_info.get(
|
||||
versions_cont = container_info.get(
|
||||
'sysmeta', {}).get('versions-location')
|
||||
if object_versions and isinstance(object_versions, six.text_type):
|
||||
object_versions = object_versions.encode('utf-8')
|
||||
elif not object_versions:
|
||||
object_versions = container_info.get('versions')
|
||||
if not versions_cont:
|
||||
versions_cont = container_info.get('versions')
|
||||
# if allow_versioned_writes is not set in the configuration files
|
||||
# but 'versions' is configured, enable feature to maintain
|
||||
# backwards compatibility
|
||||
if not allow_versioned_writes and object_versions:
|
||||
if not allow_versioned_writes and versions_cont:
|
||||
is_enabled = True
|
||||
|
||||
if is_enabled and object_versions:
|
||||
object_versions = unquote(object_versions)
|
||||
if is_enabled and versions_cont:
|
||||
versions_cont = unquote(versions_cont).split('/')[0]
|
||||
vw_ctx = VersionedWritesContext(self.app, self.logger)
|
||||
if req.method in ('PUT', 'COPY'):
|
||||
policy_idx = req.headers.get(
|
||||
'X-Backend-Storage-Policy-Index',
|
||||
container_info['storage_policy'])
|
||||
if req.method == 'PUT':
|
||||
resp = vw_ctx.handle_obj_versions_put(
|
||||
req, object_versions, object_name, policy_idx)
|
||||
req, versions_cont, api_version, account_name,
|
||||
object_name)
|
||||
else: # handle DELETE
|
||||
resp = vw_ctx.handle_obj_versions_delete(
|
||||
req, object_versions, account_name,
|
||||
req, versions_cont, api_version, account_name,
|
||||
container_name, object_name)
|
||||
|
||||
if resp:
|
||||
|
@ -445,12 +529,9 @@ class VersionedWritesMiddleware(object):
|
|||
return self.app
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
# making a duplicate, because if this is a COPY request, we will
|
||||
# modify the PATH_INFO to find out if the 'Destination' is in a
|
||||
# versioned container
|
||||
req = Request(env.copy())
|
||||
req = Request(env)
|
||||
try:
|
||||
(version, account, container, obj) = req.split_path(3, 4, True)
|
||||
(api_version, account, container, obj) = req.split_path(3, 4, True)
|
||||
except ValueError:
|
||||
return self.app(env, start_response)
|
||||
|
||||
|
@ -476,10 +557,11 @@ class VersionedWritesMiddleware(object):
|
|||
allow_versioned_writes)
|
||||
except HTTPException as error_response:
|
||||
return error_response(env, start_response)
|
||||
elif obj and req.method in ('PUT', 'COPY', 'DELETE'):
|
||||
elif (obj and req.method in ('PUT', 'DELETE') and
|
||||
not req.environ.get('swift.post_as_copy')):
|
||||
try:
|
||||
return self.object_request(
|
||||
req, version, account, container, obj,
|
||||
req, api_version, account, container, obj,
|
||||
allow_versioned_writes)(env, start_response)
|
||||
except HTTPException as error_response:
|
||||
return error_response(env, start_response)
|
||||
|
|
|
@ -54,9 +54,9 @@ Retrieve metrics from specific function in json format::
|
|||
A list of URL examples:
|
||||
|
||||
http://localhost:8080/__profile__ (proxy server)
|
||||
http://localhost:6000/__profile__/all (object server)
|
||||
http://localhost:6001/__profile__/current (container server)
|
||||
http://localhost:6002/__profile__/12345?format=json (account server)
|
||||
http://localhost:6200/__profile__/all (object server)
|
||||
http://localhost:6201/__profile__/current (container server)
|
||||
http://localhost:6202/__profile__/12345?format=json (account server)
|
||||
|
||||
The profiling middleware can be configured in paste file for WSGI servers such
|
||||
as proxy, account, container and object servers. Please refer to the sample
|
||||
|
|
|
@ -443,10 +443,10 @@ class RingBuilder(object):
|
|||
self._set_parts_wanted(replica_plan)
|
||||
|
||||
assign_parts = defaultdict(list)
|
||||
# gather parts from failed devices
|
||||
removed_devs = self._gather_parts_from_failed_devices(assign_parts)
|
||||
# gather parts from replica count adjustment
|
||||
self._adjust_replica2part2dev_size(assign_parts)
|
||||
# gather parts from failed devices
|
||||
removed_devs = self._gather_parts_from_failed_devices(assign_parts)
|
||||
# gather parts for dispersion (N.B. this only picks up parts that
|
||||
# *must* disperse according to the replica plan)
|
||||
self._gather_parts_for_dispersion(assign_parts, replica_plan)
|
||||
|
@ -1688,3 +1688,38 @@ class RingBuilder(object):
|
|||
if matched:
|
||||
matched_devs.append(dev)
|
||||
return matched_devs
|
||||
|
||||
def increase_partition_power(self):
|
||||
""" Increases ring partition power by one.
|
||||
|
||||
Devices will be assigned to partitions like this:
|
||||
|
||||
OLD: 0, 3, 7, 5, 2, 1, ...
|
||||
NEW: 0, 0, 3, 3, 7, 7, 5, 5, 2, 2, 1, 1, ...
|
||||
|
||||
"""
|
||||
|
||||
new_replica2part2dev = []
|
||||
for replica in self._replica2part2dev:
|
||||
new_replica = array('H')
|
||||
for device in replica:
|
||||
new_replica.append(device)
|
||||
new_replica.append(device) # append device a second time
|
||||
new_replica2part2dev.append(new_replica)
|
||||
self._replica2part2dev = new_replica2part2dev
|
||||
|
||||
for device in self._iter_devs():
|
||||
device['parts'] *= 2
|
||||
|
||||
# We need to update the time when a partition has been moved the last
|
||||
# time. Since this is an array of all partitions, we need to double it
|
||||
# two
|
||||
new_last_part_moves = []
|
||||
for partition in self._last_part_moves:
|
||||
new_last_part_moves.append(partition)
|
||||
new_last_part_moves.append(partition)
|
||||
self._last_part_moves = new_last_part_moves
|
||||
|
||||
self.part_power += 1
|
||||
self.parts *= 2
|
||||
self.version += 1
|
||||
|
|
|
@ -170,16 +170,13 @@ class BaseStoragePolicy(object):
|
|||
if self.idx < 0:
|
||||
raise PolicyError('Invalid index', idx)
|
||||
self.alias_list = []
|
||||
if not name or not self._validate_policy_name(name):
|
||||
raise PolicyError('Invalid name %r' % name, idx)
|
||||
self.alias_list.append(name)
|
||||
self.add_name(name)
|
||||
if aliases:
|
||||
names_list = list_from_csv(aliases)
|
||||
for alias in names_list:
|
||||
if alias == name:
|
||||
continue
|
||||
self._validate_policy_name(alias)
|
||||
self.alias_list.append(alias)
|
||||
self.add_name(alias)
|
||||
self.is_deprecated = config_true_value(is_deprecated)
|
||||
self.is_default = config_true_value(is_default)
|
||||
if self.policy_type not in BaseStoragePolicy.policy_type_to_policy_cls:
|
||||
|
@ -288,14 +285,16 @@ class BaseStoragePolicy(object):
|
|||
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.
|
||||
"""
|
||||
if not name:
|
||||
raise PolicyError('Invalid name %r' % name, self.idx)
|
||||
# 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))
|
||||
msg = 'Names are used as HTTP headers, and can not ' \
|
||||
'reliably contain any characters not in %r. ' \
|
||||
'Invalid name %r' % (VALID_CHARS, name)
|
||||
raise PolicyError(msg, self.idx)
|
||||
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)
|
||||
|
@ -305,8 +304,6 @@ class BaseStoragePolicy(object):
|
|||
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
|
||||
|
@ -316,8 +313,8 @@ class BaseStoragePolicy(object):
|
|||
|
||||
:param name: a new alias for the storage policy
|
||||
"""
|
||||
if self._validate_policy_name(name):
|
||||
self.alias_list.append(name)
|
||||
self._validate_policy_name(name)
|
||||
self.alias_list.append(name)
|
||||
|
||||
def remove_name(self, name):
|
||||
"""
|
||||
|
|
|
@ -50,6 +50,7 @@ from six import BytesIO
|
|||
from six import StringIO
|
||||
from six.moves import urllib
|
||||
|
||||
from swift.common.header_key_dict import HeaderKeyDict
|
||||
from swift.common.utils import reiterate, split_path, Timestamp, pairs, \
|
||||
close_if_possible
|
||||
from swift.common.exceptions import InvalidTimestamp
|
||||
|
@ -271,53 +272,6 @@ class HeaderEnvironProxy(MutableMapping):
|
|||
return keys
|
||||
|
||||
|
||||
class HeaderKeyDict(dict):
|
||||
"""
|
||||
A dict that title-cases all keys on the way in, so as to be
|
||||
case-insensitive.
|
||||
"""
|
||||
def __init__(self, base_headers=None, **kwargs):
|
||||
if base_headers:
|
||||
self.update(base_headers)
|
||||
self.update(kwargs)
|
||||
|
||||
def update(self, other):
|
||||
if hasattr(other, 'keys'):
|
||||
for key in other.keys():
|
||||
self[key.title()] = other[key]
|
||||
else:
|
||||
for key, value in other:
|
||||
self[key.title()] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return dict.get(self, key.title())
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if value is None:
|
||||
self.pop(key.title(), None)
|
||||
elif isinstance(value, six.text_type):
|
||||
return dict.__setitem__(self, key.title(), value.encode('utf-8'))
|
||||
else:
|
||||
return dict.__setitem__(self, key.title(), str(value))
|
||||
|
||||
def __contains__(self, key):
|
||||
return dict.__contains__(self, key.title())
|
||||
|
||||
def __delitem__(self, key):
|
||||
return dict.__delitem__(self, key.title())
|
||||
|
||||
def get(self, key, default=None):
|
||||
return dict.get(self, key.title(), default)
|
||||
|
||||
def setdefault(self, key, value=None):
|
||||
if key not in self:
|
||||
self[key] = value
|
||||
return self[key]
|
||||
|
||||
def pop(self, key, default=None):
|
||||
return dict.pop(self, key.title(), default)
|
||||
|
||||
|
||||
def _resp_status_property():
|
||||
"""
|
||||
Set and retrieve the value of Response.status
|
||||
|
@ -532,7 +486,9 @@ class Range(object):
|
|||
# when end contains non numeric value, this also causes
|
||||
# ValueError
|
||||
end = int(end)
|
||||
if start is not None and end < start:
|
||||
if end < 0:
|
||||
raise ValueError('Invalid Range header: %s' % headerval)
|
||||
elif start is not None and end < start:
|
||||
raise ValueError('Invalid Range header: %s' % headerval)
|
||||
else:
|
||||
end = None
|
||||
|
@ -932,6 +888,11 @@ class Request(object):
|
|||
return self._params_cache
|
||||
str_params = params
|
||||
|
||||
@params.setter
|
||||
def params(self, param_pairs):
|
||||
self._params_cache = None
|
||||
self.query_string = urllib.parse.urlencode(param_pairs)
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
"""
|
||||
|
@ -1125,6 +1086,7 @@ class Response(object):
|
|||
content_range = _header_property('content-range')
|
||||
etag = _resp_etag_property()
|
||||
status = _resp_status_property()
|
||||
status_int = None
|
||||
body = _resp_body_property()
|
||||
host_url = _host_url_property()
|
||||
last_modified = _datetime_property('last-modified')
|
||||
|
@ -1345,7 +1307,7 @@ class Response(object):
|
|||
object length and body or app_iter to reset the content_length
|
||||
properties on the request.
|
||||
|
||||
It is ok to not call this method, the conditional resposne will be
|
||||
It is ok to not call this method, the conditional response will be
|
||||
maintained for you when you __call__ the response.
|
||||
"""
|
||||
self.response_iter = self._response_iter(self.app_iter, self._body)
|
||||
|
|
|
@ -47,8 +47,7 @@ import datetime
|
|||
|
||||
import eventlet
|
||||
import eventlet.semaphore
|
||||
from eventlet import GreenPool, sleep, Timeout, tpool, greenthread, \
|
||||
greenio, event
|
||||
from eventlet import GreenPool, sleep, Timeout, tpool
|
||||
from eventlet.green import socket, threading
|
||||
import eventlet.queue
|
||||
import netifaces
|
||||
|
@ -68,6 +67,7 @@ from swift import gettext_ as _
|
|||
import swift.common.exceptions
|
||||
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND, \
|
||||
HTTP_PRECONDITION_FAILED, HTTP_REQUESTED_RANGE_NOT_SATISFIABLE
|
||||
from swift.common.header_key_dict import HeaderKeyDict
|
||||
|
||||
if six.PY3:
|
||||
stdlib_queue = eventlet.patcher.original('queue')
|
||||
|
@ -96,6 +96,9 @@ _libc_accept = None
|
|||
# If set to non-zero, fallocate routines will fail based on free space
|
||||
# available being at or below this amount, in bytes.
|
||||
FALLOCATE_RESERVE = 0
|
||||
# Indicates if FALLOCATE_RESERVE is the percentage of free space (True) or
|
||||
# the number of bytes (False).
|
||||
FALLOCATE_IS_PERCENT = False
|
||||
|
||||
# Used by hash_path to offer a bit more security when generating hashes for
|
||||
# paths. It simply appends this value to all paths; guessing the hash a path
|
||||
|
@ -452,6 +455,25 @@ def get_trans_id_time(trans_id):
|
|||
return None
|
||||
|
||||
|
||||
def config_fallocate_value(reserve_value):
|
||||
"""
|
||||
Returns fallocate reserve_value as an int or float.
|
||||
Returns is_percent as a boolean.
|
||||
Returns a ValueError on invalid fallocate value.
|
||||
"""
|
||||
try:
|
||||
if str(reserve_value[-1:]) == '%':
|
||||
reserve_value = float(reserve_value[:-1])
|
||||
is_percent = True
|
||||
else:
|
||||
reserve_value = int(reserve_value)
|
||||
is_percent = False
|
||||
except ValueError:
|
||||
raise ValueError('Error: %s is an invalid value for fallocate'
|
||||
'_reserve.' % reserve_value)
|
||||
return reserve_value, is_percent
|
||||
|
||||
|
||||
class FileLikeIter(object):
|
||||
|
||||
def __init__(self, iterable):
|
||||
|
@ -574,7 +596,8 @@ class FileLikeIter(object):
|
|||
class FallocateWrapper(object):
|
||||
|
||||
def __init__(self, noop=False):
|
||||
if noop:
|
||||
self.noop = noop
|
||||
if self.noop:
|
||||
self.func_name = 'posix_fallocate'
|
||||
self.fallocate = noop_libc_function
|
||||
return
|
||||
|
@ -592,12 +615,18 @@ class FallocateWrapper(object):
|
|||
|
||||
def __call__(self, fd, mode, offset, length):
|
||||
"""The length parameter must be a ctypes.c_uint64."""
|
||||
if FALLOCATE_RESERVE > 0:
|
||||
st = os.fstatvfs(fd)
|
||||
free = st.f_frsize * st.f_bavail - length.value
|
||||
if free <= FALLOCATE_RESERVE:
|
||||
raise OSError('FALLOCATE_RESERVE fail %s <= %s' % (
|
||||
free, FALLOCATE_RESERVE))
|
||||
if not self.noop:
|
||||
if FALLOCATE_RESERVE > 0:
|
||||
st = os.fstatvfs(fd)
|
||||
free = st.f_frsize * st.f_bavail - length.value
|
||||
if FALLOCATE_IS_PERCENT:
|
||||
free = \
|
||||
(float(free) / float(st.f_frsize * st.f_blocks)) * 100
|
||||
if float(free) <= float(FALLOCATE_RESERVE):
|
||||
raise OSError(
|
||||
errno.ENOSPC,
|
||||
'FALLOCATE_RESERVE fail %s <= %s' %
|
||||
(free, FALLOCATE_RESERVE))
|
||||
args = {
|
||||
'fallocate': (fd, mode, offset, length),
|
||||
'posix_fallocate': (fd, offset, length)
|
||||
|
@ -671,8 +700,9 @@ def fsync_dir(dirpath):
|
|||
if err.errno == errno.ENOTDIR:
|
||||
# Raise error if someone calls fsync_dir on a non-directory
|
||||
raise
|
||||
logging.warning(_("Unable to perform fsync() on directory %s: %s"),
|
||||
dirpath, os.strerror(err.errno))
|
||||
logging.warning(_('Unable to perform fsync() on directory %(dir)s:'
|
||||
' %(err)s'),
|
||||
{'dir': dirpath, 'err': os.strerror(err.errno)})
|
||||
finally:
|
||||
if dirfd:
|
||||
os.close(dirfd)
|
||||
|
@ -1231,21 +1261,59 @@ class NullLogger(object):
|
|||
|
||||
class LoggerFileObject(object):
|
||||
|
||||
# Note: this is greenthread-local storage
|
||||
_cls_thread_local = threading.local()
|
||||
|
||||
def __init__(self, logger, log_type='STDOUT'):
|
||||
self.logger = logger
|
||||
self.log_type = log_type
|
||||
|
||||
def write(self, value):
|
||||
value = value.strip()
|
||||
if value:
|
||||
if 'Connection reset by peer' in value:
|
||||
self.logger.error(
|
||||
_('%s: Connection reset by peer'), self.log_type)
|
||||
else:
|
||||
self.logger.error(_('%s: %s'), self.log_type, value)
|
||||
# We can get into a nasty situation when logs are going to syslog
|
||||
# and syslog dies.
|
||||
#
|
||||
# It's something like this:
|
||||
#
|
||||
# (A) someone logs something
|
||||
#
|
||||
# (B) there's an exception in sending to /dev/log since syslog is
|
||||
# not working
|
||||
#
|
||||
# (C) logging takes that exception and writes it to stderr (see
|
||||
# logging.Handler.handleError)
|
||||
#
|
||||
# (D) stderr was replaced with a LoggerFileObject at process start,
|
||||
# so the LoggerFileObject takes the provided string and tells
|
||||
# its logger to log it (to syslog, naturally).
|
||||
#
|
||||
# Then, steps B through D repeat until we run out of stack.
|
||||
if getattr(self._cls_thread_local, 'already_called_write', False):
|
||||
return
|
||||
|
||||
self._cls_thread_local.already_called_write = True
|
||||
try:
|
||||
value = value.strip()
|
||||
if value:
|
||||
if 'Connection reset by peer' in value:
|
||||
self.logger.error(
|
||||
_('%s: Connection reset by peer'), self.log_type)
|
||||
else:
|
||||
self.logger.error(_('%(type)s: %(value)s'),
|
||||
{'type': self.log_type, 'value': value})
|
||||
finally:
|
||||
self._cls_thread_local.already_called_write = False
|
||||
|
||||
def writelines(self, values):
|
||||
self.logger.error(_('%s: %s'), self.log_type, '#012'.join(values))
|
||||
if getattr(self._cls_thread_local, 'already_called_writelines', False):
|
||||
return
|
||||
|
||||
self._cls_thread_local.already_called_writelines = True
|
||||
try:
|
||||
self.logger.error(_('%(type)s: %(value)s'),
|
||||
{'type': self.log_type,
|
||||
'value': '#012'.join(values)})
|
||||
finally:
|
||||
self._cls_thread_local.already_called_writelines = False
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
@ -1583,7 +1651,6 @@ class SwiftLogFormatter(logging.Formatter):
|
|||
msg = msg + record.exc_text
|
||||
|
||||
if (hasattr(record, 'txn_id') and record.txn_id and
|
||||
record.levelno != logging.INFO and
|
||||
record.txn_id not in msg):
|
||||
msg = "%s (txn: %s)" % (msg, record.txn_id)
|
||||
if (hasattr(record, 'client_ip') and record.client_ip and
|
||||
|
@ -2121,10 +2188,21 @@ def unlink_older_than(path, mtime):
|
|||
Remove any file in a given path that that was last modified before mtime.
|
||||
|
||||
:param path: path to remove file from
|
||||
:mtime: timestamp of oldest file to keep
|
||||
:param mtime: timestamp of oldest file to keep
|
||||
"""
|
||||
for fname in listdir(path):
|
||||
fpath = os.path.join(path, fname)
|
||||
filepaths = map(functools.partial(os.path.join, path), listdir(path))
|
||||
return unlink_paths_older_than(filepaths, mtime)
|
||||
|
||||
|
||||
def unlink_paths_older_than(filepaths, mtime):
|
||||
"""
|
||||
Remove any files from the given list that that were
|
||||
last modified before mtime.
|
||||
|
||||
:param filepaths: a list of strings, the full paths of files to check
|
||||
:param mtime: timestamp of oldest file to keep
|
||||
"""
|
||||
for fpath in filepaths:
|
||||
try:
|
||||
if os.path.getmtime(fpath) < mtime:
|
||||
os.unlink(fpath)
|
||||
|
@ -2202,8 +2280,8 @@ def readconf(conf_path, section_name=None, log_name=None, defaults=None,
|
|||
if c.has_section(section_name):
|
||||
conf = dict(c.items(section_name))
|
||||
else:
|
||||
print(_("Unable to find %s config section in %s") %
|
||||
(section_name, conf_path))
|
||||
print(_("Unable to find %(section)s config section in %(conf)s") %
|
||||
{'section': section_name, 'conf': conf_path})
|
||||
sys.exit(1)
|
||||
if "log_name" not in conf:
|
||||
if log_name is not None:
|
||||
|
@ -2470,6 +2548,10 @@ class GreenAsyncPile(object):
|
|||
finally:
|
||||
self._inflight -= 1
|
||||
|
||||
@property
|
||||
def inflight(self):
|
||||
return self._inflight
|
||||
|
||||
def spawn(self, func, *args, **kwargs):
|
||||
"""
|
||||
Spawn a job in a green thread on the pile.
|
||||
|
@ -2478,6 +2560,16 @@ class GreenAsyncPile(object):
|
|||
self._inflight += 1
|
||||
self._pool.spawn(self._run_func, func, args, kwargs)
|
||||
|
||||
def waitfirst(self, timeout):
|
||||
"""
|
||||
Wait up to timeout seconds for first result to come in.
|
||||
|
||||
:param timeout: seconds to wait for results
|
||||
:returns: first item to come back, or None
|
||||
"""
|
||||
for result in self._wait(timeout, first_n=1):
|
||||
return result
|
||||
|
||||
def waitall(self, timeout):
|
||||
"""
|
||||
Wait timeout seconds for any results to come in.
|
||||
|
@ -2485,11 +2577,16 @@ class GreenAsyncPile(object):
|
|||
:param timeout: seconds to wait for results
|
||||
:returns: list of results accrued in that time
|
||||
"""
|
||||
return self._wait(timeout)
|
||||
|
||||
def _wait(self, timeout, first_n=None):
|
||||
results = []
|
||||
try:
|
||||
with GreenAsyncPileWaitallTimeout(timeout):
|
||||
while True:
|
||||
results.append(next(self))
|
||||
if first_n and len(results) >= first_n:
|
||||
break
|
||||
except (GreenAsyncPileWaitallTimeout, StopIteration):
|
||||
pass
|
||||
return results
|
||||
|
@ -2509,6 +2606,48 @@ class GreenAsyncPile(object):
|
|||
__next__ = next
|
||||
|
||||
|
||||
class StreamingPile(GreenAsyncPile):
|
||||
"""
|
||||
Runs jobs in a pool of green threads, spawning more jobs as results are
|
||||
retrieved and worker threads become available.
|
||||
|
||||
When used as a context manager, has the same worker-killing properties as
|
||||
:class:`ContextPool`.
|
||||
"""
|
||||
def __init__(self, size):
|
||||
""":param size: number of worker threads to use"""
|
||||
self.pool = ContextPool(size)
|
||||
super(StreamingPile, self).__init__(self.pool)
|
||||
|
||||
def asyncstarmap(self, func, args_iter):
|
||||
"""
|
||||
This is the same as :func:`itertools.starmap`, except that *func* is
|
||||
executed in a separate green thread for each item, and results won't
|
||||
necessarily have the same order as inputs.
|
||||
"""
|
||||
args_iter = iter(args_iter)
|
||||
|
||||
# Initialize the pile
|
||||
for args in itertools.islice(args_iter, self.pool.size):
|
||||
self.spawn(func, *args)
|
||||
|
||||
# Keep populating the pile as greenthreads become available
|
||||
for args in args_iter:
|
||||
yield next(self)
|
||||
self.spawn(func, *args)
|
||||
|
||||
# Drain the pile
|
||||
for result in self:
|
||||
yield result
|
||||
|
||||
def __enter__(self):
|
||||
self.pool.__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.pool.__exit__(type, value, traceback)
|
||||
|
||||
|
||||
class ModifiedParseResult(ParseResult):
|
||||
"Parse results class for urlparse."
|
||||
|
||||
|
@ -2577,7 +2716,8 @@ def validate_sync_to(value, allowed_sync_hosts, realms_conf):
|
|||
endpoint = realms_conf.endpoint(realm, cluster)
|
||||
if not endpoint:
|
||||
return (
|
||||
_('No cluster endpoint for %r %r') % (realm, cluster),
|
||||
_('No cluster endpoint for %(realm)r %(cluster)r')
|
||||
% {'realm': realm, 'cluster': cluster},
|
||||
None, None, None)
|
||||
return (
|
||||
None,
|
||||
|
@ -2865,6 +3005,10 @@ def public(func):
|
|||
return func
|
||||
|
||||
|
||||
def majority_size(n):
|
||||
return (n // 2) + 1
|
||||
|
||||
|
||||
def quorum_size(n):
|
||||
"""
|
||||
quorum size as it applies to services that use 'replication' for data
|
||||
|
@ -2874,7 +3018,7 @@ def quorum_size(n):
|
|||
Number of successful backend requests needed for the proxy to consider
|
||||
the client request successful.
|
||||
"""
|
||||
return (n // 2) + 1
|
||||
return (n + 1) // 2
|
||||
|
||||
|
||||
def rsync_ip(ip):
|
||||
|
@ -3138,205 +3282,6 @@ def tpool_reraise(func, *args, **kwargs):
|
|||
return resp
|
||||
|
||||
|
||||
class ThreadPool(object):
|
||||
"""
|
||||
Perform blocking operations in background threads.
|
||||
|
||||
Call its methods from within greenlets to green-wait for results without
|
||||
blocking the eventlet reactor (hopefully).
|
||||
"""
|
||||
|
||||
BYTE = 'a'.encode('utf-8')
|
||||
|
||||
def __init__(self, nthreads=2):
|
||||
self.nthreads = nthreads
|
||||
self._run_queue = stdlib_queue.Queue()
|
||||
self._result_queue = stdlib_queue.Queue()
|
||||
self._threads = []
|
||||
self._alive = True
|
||||
|
||||
if nthreads <= 0:
|
||||
return
|
||||
|
||||
# We spawn a greenthread whose job it is to pull results from the
|
||||
# worker threads via a real Queue and send them to eventlet Events so
|
||||
# that the calling greenthreads can be awoken.
|
||||
#
|
||||
# Since each OS thread has its own collection of greenthreads, it
|
||||
# doesn't work to have the worker thread send stuff to the event, as
|
||||
# it then notifies its own thread-local eventlet hub to wake up, which
|
||||
# doesn't do anything to help out the actual calling greenthread over
|
||||
# in the main thread.
|
||||
#
|
||||
# Thus, each worker sticks its results into a result queue and then
|
||||
# writes a byte to a pipe, signaling the result-consuming greenlet (in
|
||||
# the main thread) to wake up and consume results.
|
||||
#
|
||||
# This is all stuff that eventlet.tpool does, but that code can't have
|
||||
# multiple instances instantiated. Since the object server uses one
|
||||
# pool per disk, we have to reimplement this stuff.
|
||||
_raw_rpipe, self.wpipe = os.pipe()
|
||||
self.rpipe = greenio.GreenPipe(_raw_rpipe, 'rb')
|
||||
|
||||
for _junk in range(nthreads):
|
||||
thr = stdlib_threading.Thread(
|
||||
target=self._worker,
|
||||
args=(self._run_queue, self._result_queue))
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
self._threads.append(thr)
|
||||
|
||||
# This is the result-consuming greenthread that runs in the main OS
|
||||
# thread, as described above.
|
||||
self._consumer_coro = greenthread.spawn_n(self._consume_results,
|
||||
self._result_queue)
|
||||
|
||||
def _worker(self, work_queue, result_queue):
|
||||
"""
|
||||
Pulls an item from the queue and runs it, then puts the result into
|
||||
the result queue. Repeats forever.
|
||||
|
||||
:param work_queue: queue from which to pull work
|
||||
:param result_queue: queue into which to place results
|
||||
"""
|
||||
while True:
|
||||
item = work_queue.get()
|
||||
if item is None:
|
||||
break
|
||||
ev, func, args, kwargs = item
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
result_queue.put((ev, True, result))
|
||||
except BaseException:
|
||||
result_queue.put((ev, False, sys.exc_info()))
|
||||
finally:
|
||||
work_queue.task_done()
|
||||
os.write(self.wpipe, self.BYTE)
|
||||
|
||||
def _consume_results(self, queue):
|
||||
"""
|
||||
Runs as a greenthread in the same OS thread as callers of
|
||||
run_in_thread().
|
||||
|
||||
Takes results from the worker OS threads and sends them to the waiting
|
||||
greenthreads.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
self.rpipe.read(1)
|
||||
except ValueError:
|
||||
# can happen at process shutdown when pipe is closed
|
||||
break
|
||||
|
||||
while True:
|
||||
try:
|
||||
ev, success, result = queue.get(block=False)
|
||||
except stdlib_queue.Empty:
|
||||
break
|
||||
|
||||
try:
|
||||
if success:
|
||||
ev.send(result)
|
||||
else:
|
||||
ev.send_exception(*result)
|
||||
finally:
|
||||
queue.task_done()
|
||||
|
||||
def run_in_thread(self, func, *args, **kwargs):
|
||||
"""
|
||||
Runs ``func(*args, **kwargs)`` in a thread. Blocks the current greenlet
|
||||
until results are available.
|
||||
|
||||
Exceptions thrown will be reraised in the calling thread.
|
||||
|
||||
If the threadpool was initialized with nthreads=0, it invokes
|
||||
``func(*args, **kwargs)`` directly, followed by eventlet.sleep() to
|
||||
ensure the eventlet hub has a chance to execute. It is more likely the
|
||||
hub will be invoked when queuing operations to an external thread.
|
||||
|
||||
:returns: result of calling func
|
||||
:raises: whatever func raises
|
||||
"""
|
||||
if not self._alive:
|
||||
raise swift.common.exceptions.ThreadPoolDead()
|
||||
|
||||
if self.nthreads <= 0:
|
||||
result = func(*args, **kwargs)
|
||||
sleep()
|
||||
return result
|
||||
|
||||
ev = event.Event()
|
||||
self._run_queue.put((ev, func, args, kwargs), block=False)
|
||||
|
||||
# blocks this greenlet (and only *this* greenlet) until the real
|
||||
# thread calls ev.send().
|
||||
result = ev.wait()
|
||||
return result
|
||||
|
||||
def _run_in_eventlet_tpool(self, func, *args, **kwargs):
|
||||
"""
|
||||
Really run something in an external thread, even if we haven't got any
|
||||
threads of our own.
|
||||
"""
|
||||
def inner():
|
||||
try:
|
||||
return (True, func(*args, **kwargs))
|
||||
except (Timeout, BaseException) as err:
|
||||
return (False, err)
|
||||
|
||||
success, result = tpool.execute(inner)
|
||||
if success:
|
||||
return result
|
||||
else:
|
||||
raise result
|
||||
|
||||
def force_run_in_thread(self, func, *args, **kwargs):
|
||||
"""
|
||||
Runs ``func(*args, **kwargs)`` in a thread. Blocks the current greenlet
|
||||
until results are available.
|
||||
|
||||
Exceptions thrown will be reraised in the calling thread.
|
||||
|
||||
If the threadpool was initialized with nthreads=0, uses eventlet.tpool
|
||||
to run the function. This is in contrast to run_in_thread(), which
|
||||
will (in that case) simply execute func in the calling thread.
|
||||
|
||||
:returns: result of calling func
|
||||
:raises: whatever func raises
|
||||
"""
|
||||
if not self._alive:
|
||||
raise swift.common.exceptions.ThreadPoolDead()
|
||||
|
||||
if self.nthreads <= 0:
|
||||
return self._run_in_eventlet_tpool(func, *args, **kwargs)
|
||||
else:
|
||||
return self.run_in_thread(func, *args, **kwargs)
|
||||
|
||||
def terminate(self):
|
||||
"""
|
||||
Releases the threadpool's resources (OS threads, greenthreads, pipes,
|
||||
etc.) and renders it unusable.
|
||||
|
||||
Don't call run_in_thread() or force_run_in_thread() after calling
|
||||
terminate().
|
||||
"""
|
||||
self._alive = False
|
||||
if self.nthreads <= 0:
|
||||
return
|
||||
|
||||
for _junk in range(self.nthreads):
|
||||
self._run_queue.put(None)
|
||||
for thr in self._threads:
|
||||
thr.join()
|
||||
self._threads = []
|
||||
self.nthreads = 0
|
||||
|
||||
greenthread.kill(self._consumer_coro)
|
||||
|
||||
self.rpipe.close()
|
||||
os.close(self.wpipe)
|
||||
|
||||
|
||||
def ismount(path):
|
||||
"""
|
||||
Test whether a path is a mount point. This will catch any
|
||||
|
@ -3648,7 +3593,6 @@ def parse_mime_headers(doc_file):
|
|||
:param doc_file: binary file-like object containing a MIME document
|
||||
:returns: a swift.common.swob.HeaderKeyDict containing the headers
|
||||
"""
|
||||
from swift.common.swob import HeaderKeyDict # avoid circular import
|
||||
headers = []
|
||||
while True:
|
||||
line = doc_file.readline()
|
||||
|
|
|
@ -196,10 +196,10 @@ def get_socket(conf):
|
|||
raise
|
||||
sleep(0.1)
|
||||
if not sock:
|
||||
raise Exception(_('Could not bind to %s:%s '
|
||||
'after trying for %s seconds') % (
|
||||
bind_addr[0], bind_addr[1], bind_timeout))
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
raise Exception(_('Could not bind to %(addr)s:%(port)s '
|
||||
'after trying for %(timeout)s seconds') % {
|
||||
'addr': bind_addr[0], 'port': bind_addr[1],
|
||||
'timeout': bind_timeout})
|
||||
# in my experience, sockets can hang around forever without keepalive
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
|
@ -407,8 +407,12 @@ def run_server(conf, logger, sock, global_conf=None):
|
|||
wsgi.WRITE_TIMEOUT = int(conf.get('client_timeout') or 60)
|
||||
|
||||
eventlet.hubs.use_hub(get_hub())
|
||||
# NOTE(sileht): monkey-patching thread is required by python-keystoneclient
|
||||
eventlet.patcher.monkey_patch(all=False, socket=True, thread=True)
|
||||
# NOTE(sileht):
|
||||
# monkey-patching thread is required by python-keystoneclient;
|
||||
# monkey-patching select is required by oslo.messaging pika driver
|
||||
# if thread is monkey-patched.
|
||||
eventlet.patcher.monkey_patch(all=False, socket=True, select=True,
|
||||
thread=True)
|
||||
eventlet_debug = config_true_value(conf.get('eventlet_debug', 'no'))
|
||||
eventlet.debug.hub_exceptions(eventlet_debug)
|
||||
wsgi_logger = NullLogger()
|
||||
|
@ -893,9 +897,9 @@ def run_wsgi(conf_path, app_section, *args, **kwargs):
|
|||
loadapp(conf_path, global_conf=global_conf)
|
||||
|
||||
# set utils.FALLOCATE_RESERVE if desired
|
||||
reserve = int(conf.get('fallocate_reserve', 0))
|
||||
if reserve > 0:
|
||||
utils.FALLOCATE_RESERVE = reserve
|
||||
utils.FALLOCATE_RESERVE, utils.FALLOCATE_IS_PERCENT = \
|
||||
utils.config_fallocate_value(conf.get('fallocate_reserve', '1%'))
|
||||
|
||||
# redirect errors to logger and close stdio
|
||||
capture_stdio(logger)
|
||||
|
||||
|
@ -1096,7 +1100,8 @@ def make_env(env, method=None, path=None, agent='Swift', query_string=None,
|
|||
'SERVER_PROTOCOL', 'swift.cache', 'swift.source',
|
||||
'swift.trans_id', 'swift.authorize_override',
|
||||
'swift.authorize', 'HTTP_X_USER_ID', 'HTTP_X_PROJECT_ID',
|
||||
'HTTP_REFERER'):
|
||||
'HTTP_REFERER', 'swift.orig_req_method', 'swift.log_info',
|
||||
'swift.infocache'):
|
||||
if name in env:
|
||||
newenv[name] = env[name]
|
||||
if method:
|
||||
|
|
|
@ -146,7 +146,7 @@ def update_new_item_from_existing(new_item, existing):
|
|||
their timestamps are newer.
|
||||
|
||||
The multiple timestamps are encoded into a single string for storing
|
||||
in the 'created_at' column of the the objects db table.
|
||||
in the 'created_at' column of the objects db table.
|
||||
|
||||
:param new_item: A dict of object update attributes
|
||||
:param existing: A dict of existing object attributes
|
||||
|
@ -776,7 +776,7 @@ class ContainerBroker(DatabaseBroker):
|
|||
marker = name[:end] + chr(ord(delimiter) + 1)
|
||||
curs.close()
|
||||
break
|
||||
elif end > 0:
|
||||
elif end >= 0:
|
||||
if reverse:
|
||||
end_marker = name[:end + 1]
|
||||
else:
|
||||
|
|
|
@ -25,7 +25,7 @@ from swift.common.direct_client import (
|
|||
direct_head_container, direct_delete_container_object,
|
||||
direct_put_container_object, ClientException)
|
||||
from swift.common.internal_client import InternalClient, UnexpectedResponse
|
||||
from swift.common.utils import get_logger, split_path, quorum_size, \
|
||||
from swift.common.utils import get_logger, split_path, majority_size, \
|
||||
FileLikeIter, Timestamp, last_modified_date_to_timestamp, \
|
||||
LRUCache, decode_timestamps
|
||||
|
||||
|
@ -194,7 +194,7 @@ def add_to_reconciler_queue(container_ring, account, container, obj,
|
|||
server
|
||||
|
||||
:returns: .misplaced_object container name, False on failure. "Success"
|
||||
means a quorum of containers got the update.
|
||||
means a majority of containers got the update.
|
||||
"""
|
||||
container_name = get_reconciler_container_name(obj_timestamp)
|
||||
object_name = get_reconciler_obj_name(obj_policy_index, account,
|
||||
|
@ -232,7 +232,7 @@ def add_to_reconciler_queue(container_ring, account, container, obj,
|
|||
response_timeout=response_timeout)
|
||||
|
||||
successes = sum(pile)
|
||||
if successes >= quorum_size(len(nodes)):
|
||||
if successes >= majority_size(len(nodes)):
|
||||
return container_name
|
||||
else:
|
||||
return False
|
||||
|
@ -289,7 +289,7 @@ def direct_get_container_policy_index(container_ring, account_name,
|
|||
:param container_ring: ring in which to look up the container locations
|
||||
:param account_name: name of the container's account
|
||||
:param container_name: name of the container
|
||||
:returns: storage policy index, or None if it couldn't get a quorum
|
||||
:returns: storage policy index, or None if it couldn't get a majority
|
||||
"""
|
||||
def _eat_client_exception(*args):
|
||||
try:
|
||||
|
@ -307,7 +307,7 @@ def direct_get_container_policy_index(container_ring, account_name,
|
|||
container_name)
|
||||
|
||||
headers = [x for x in pile if x is not None]
|
||||
if len(headers) < quorum_size(len(nodes)):
|
||||
if len(headers) < majority_size(len(nodes)):
|
||||
return
|
||||
return best_policy_index(headers)
|
||||
|
||||
|
|
|
@ -31,14 +31,14 @@ from swift.common.exceptions import DeviceUnavailable
|
|||
from swift.common.http import is_success
|
||||
from swift.common.db import DatabaseAlreadyExists
|
||||
from swift.common.utils import (Timestamp, hash_path,
|
||||
storage_directory, quorum_size)
|
||||
storage_directory, majority_size)
|
||||
|
||||
|
||||
class ContainerReplicator(db_replicator.Replicator):
|
||||
server_type = 'container'
|
||||
brokerclass = ContainerBroker
|
||||
datadir = DATADIR
|
||||
default_port = 6001
|
||||
default_port = 6201
|
||||
|
||||
def report_up_to_date(self, full_info):
|
||||
reported_key_map = {
|
||||
|
@ -202,9 +202,9 @@ class ContainerReplicator(db_replicator.Replicator):
|
|||
broker.update_reconciler_sync(info['max_row'])
|
||||
return
|
||||
max_sync = self.dump_to_reconciler(broker, point)
|
||||
success = responses.count(True) >= quorum_size(len(responses))
|
||||
success = responses.count(True) >= majority_size(len(responses))
|
||||
if max_sync > point and success:
|
||||
# to be safe, only slide up the sync point with a quorum on
|
||||
# to be safe, only slide up the sync point with a majority on
|
||||
# replication
|
||||
broker.update_reconciler_sync(max_sync)
|
||||
|
||||
|
|
|
@ -41,10 +41,11 @@ from swift.common.exceptions import ConnectionTimeout
|
|||
from swift.common.http import HTTP_NOT_FOUND, is_success
|
||||
from swift.common.storage_policy import POLICIES
|
||||
from swift.common.base_storage_server import BaseStorageServer
|
||||
from swift.common.header_key_dict import HeaderKeyDict
|
||||
from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPConflict, \
|
||||
HTTPCreated, HTTPInternalServerError, HTTPNoContent, HTTPNotFound, \
|
||||
HTTPPreconditionFailed, HTTPMethodNotAllowed, Request, Response, \
|
||||
HTTPInsufficientStorage, HTTPException, HeaderKeyDict
|
||||
HTTPInsufficientStorage, HTTPException
|
||||
|
||||
|
||||
def gen_resp_headers(info, is_deleted=False):
|
||||
|
@ -182,11 +183,12 @@ class ContainerController(BaseStorageServer):
|
|||
if len(account_hosts) != len(account_devices):
|
||||
# This shouldn't happen unless there's a bug in the proxy,
|
||||
# but if there is, we want to know about it.
|
||||
self.logger.error(_('ERROR Account update failed: different '
|
||||
'numbers of hosts and devices in request: '
|
||||
'"%s" vs "%s"') %
|
||||
(req.headers.get('X-Account-Host', ''),
|
||||
req.headers.get('X-Account-Device', '')))
|
||||
self.logger.error(_(
|
||||
'ERROR Account update failed: different '
|
||||
'numbers of hosts and devices in request: '
|
||||
'"%(hosts)s" vs "%(devices)s"') % {
|
||||
'hosts': req.headers.get('X-Account-Host', ''),
|
||||
'devices': req.headers.get('X-Account-Device', '')})
|
||||
return HTTPBadRequest(req=req)
|
||||
|
||||
if account_partition:
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import collections
|
||||
import errno
|
||||
import os
|
||||
import uuid
|
||||
from swift import gettext_ as _
|
||||
from time import ctime, time
|
||||
from random import choice, random, shuffle
|
||||
from random import choice, random
|
||||
from struct import unpack_from
|
||||
|
||||
from eventlet import sleep, Timeout
|
||||
|
@ -29,7 +30,8 @@ from swift.container.backend import ContainerBroker
|
|||
from swift.container.sync_store import ContainerSyncStore
|
||||
from swift.common.container_sync_realms import ContainerSyncRealms
|
||||
from swift.common.internal_client import (
|
||||
delete_object, put_object, InternalClient, UnexpectedResponse)
|
||||
delete_object, put_object, head_object,
|
||||
InternalClient, UnexpectedResponse)
|
||||
from swift.common.exceptions import ClientException
|
||||
from swift.common.ring import Ring
|
||||
from swift.common.ring.utils import is_local_device
|
||||
|
@ -39,7 +41,6 @@ from swift.common.utils import (
|
|||
whataremyips, Timestamp, decode_timestamps)
|
||||
from swift.common.daemon import Daemon
|
||||
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND
|
||||
from swift.common.storage_policy import POLICIES
|
||||
from swift.common.wsgi import ConfigString
|
||||
|
||||
|
||||
|
@ -100,13 +101,6 @@ class ContainerSync(Daemon):
|
|||
If they exist, newer rows since the last sync will trigger PUTs or DELETEs
|
||||
to the other container.
|
||||
|
||||
.. note::
|
||||
|
||||
Container sync will sync object POSTs only if the proxy server is set
|
||||
to use "object_post_as_copy = true" which is the default. So-called
|
||||
fast object posts, "object_post_as_copy = false" do not update the
|
||||
container listings and therefore can't be detected for synchronization.
|
||||
|
||||
The actual syncing is slightly more complicated to make use of the three
|
||||
(or number-of-replicas) main nodes for a container without each trying to
|
||||
do the exact same work but also without missing work if one node happens to
|
||||
|
@ -205,6 +199,14 @@ class ContainerSync(Daemon):
|
|||
self.container_skips = 0
|
||||
#: Number of containers that had a failure of some type.
|
||||
self.container_failures = 0
|
||||
|
||||
#: Per container stats. These are collected per container.
|
||||
#: puts - the number of puts that were done for the container
|
||||
#: deletes - the number of deletes that were fot the container
|
||||
#: bytes - the total number of bytes transferred per the container
|
||||
self.container_stats = collections.defaultdict(int)
|
||||
self.container_stats.clear()
|
||||
|
||||
#: Time of last stats report.
|
||||
self.reported = time()
|
||||
self.swift_dir = conf.get('swift_dir', '/etc/swift')
|
||||
|
@ -213,7 +215,7 @@ class ContainerSync(Daemon):
|
|||
ring_name='container')
|
||||
bind_ip = conf.get('bind_ip', '0.0.0.0')
|
||||
self._myips = whataremyips(bind_ip)
|
||||
self._myport = int(conf.get('bind_port', 6001))
|
||||
self._myport = int(conf.get('bind_port', 6201))
|
||||
swift.common.db.DB_PREALLOCATION = \
|
||||
config_true_value(conf.get('db_preallocation', 'f'))
|
||||
self.conn_timeout = float(conf.get('conn_timeout', 5))
|
||||
|
@ -235,17 +237,9 @@ class ContainerSync(Daemon):
|
|||
if err.errno != errno.ENOENT:
|
||||
raise
|
||||
raise SystemExit(
|
||||
_('Unable to load internal client from config: %r (%s)') %
|
||||
(internal_client_conf_path, err))
|
||||
|
||||
def get_object_ring(self, policy_idx):
|
||||
"""
|
||||
Get the ring object to use based on its policy.
|
||||
|
||||
:policy_idx: policy index as defined in swift.conf
|
||||
:returns: appropriate ring object
|
||||
"""
|
||||
return POLICIES.get_object_ring(policy_idx, self.swift_dir)
|
||||
_('Unable to load internal client from config: '
|
||||
'%(conf)r (%(error)s)')
|
||||
% {'conf': internal_client_conf_path, 'error': err})
|
||||
|
||||
def run_forever(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -255,6 +249,7 @@ class ContainerSync(Daemon):
|
|||
while True:
|
||||
begin = time()
|
||||
for path in self.sync_store.synced_containers_generator():
|
||||
self.container_stats.clear()
|
||||
self.container_sync(path)
|
||||
if time() - self.reported >= 3600: # once an hour
|
||||
self.report()
|
||||
|
@ -298,6 +293,30 @@ class ContainerSync(Daemon):
|
|||
self.container_skips = 0
|
||||
self.container_failures = 0
|
||||
|
||||
def container_report(self, start, end, sync_point1, sync_point2, info,
|
||||
max_row):
|
||||
self.logger.info(_('Container sync report: %(container)s, '
|
||||
'time window start: %(start)s, '
|
||||
'time window end: %(end)s, '
|
||||
'puts: %(puts)s, '
|
||||
'posts: %(posts)s, '
|
||||
'deletes: %(deletes)s, '
|
||||
'bytes: %(bytes)s, '
|
||||
'sync_point1: %(point1)s, '
|
||||
'sync_point2: %(point2)s, '
|
||||
'total_rows: %(total)s'),
|
||||
{'container': '%s/%s' % (info['account'],
|
||||
info['container']),
|
||||
'start': start,
|
||||
'end': end,
|
||||
'puts': self.container_stats['puts'],
|
||||
'posts': 0,
|
||||
'deletes': self.container_stats['deletes'],
|
||||
'bytes': self.container_stats['bytes'],
|
||||
'point1': sync_point1,
|
||||
'point2': sync_point2,
|
||||
'total': max_row})
|
||||
|
||||
def container_sync(self, path):
|
||||
"""
|
||||
Checks the given path for a container database, determines if syncing
|
||||
|
@ -355,63 +374,152 @@ class ContainerSync(Daemon):
|
|||
self.container_failures += 1
|
||||
self.logger.increment('failures')
|
||||
return
|
||||
stop_at = time() + self.container_time
|
||||
start_at = time()
|
||||
stop_at = start_at + self.container_time
|
||||
next_sync_point = None
|
||||
while time() < stop_at and sync_point2 < sync_point1:
|
||||
rows = broker.get_items_since(sync_point2, 1)
|
||||
if not rows:
|
||||
break
|
||||
row = rows[0]
|
||||
if row['ROWID'] > sync_point1:
|
||||
break
|
||||
key = hash_path(info['account'], info['container'],
|
||||
row['name'], raw_digest=True)
|
||||
# This node will only initially sync out one third of the
|
||||
# objects (if 3 replicas, 1/4 if 4, etc.) and will skip
|
||||
# problematic rows as needed in case of faults.
|
||||
# This section will attempt to sync previously skipped
|
||||
# rows in case the previous attempts by any of the nodes
|
||||
# didn't succeed.
|
||||
if not self.container_sync_row(
|
||||
row, sync_to, user_key, broker, info, realm,
|
||||
realm_key):
|
||||
if not next_sync_point:
|
||||
next_sync_point = sync_point2
|
||||
sync_point2 = row['ROWID']
|
||||
broker.set_x_container_sync_points(None, sync_point2)
|
||||
if next_sync_point:
|
||||
broker.set_x_container_sync_points(None, next_sync_point)
|
||||
while time() < stop_at:
|
||||
rows = broker.get_items_since(sync_point1, 1)
|
||||
if not rows:
|
||||
break
|
||||
row = rows[0]
|
||||
key = hash_path(info['account'], info['container'],
|
||||
row['name'], raw_digest=True)
|
||||
# This node will only initially sync out one third of the
|
||||
# objects (if 3 replicas, 1/4 if 4, etc.). It'll come back
|
||||
# around to the section above and attempt to sync
|
||||
# previously skipped rows in case the other nodes didn't
|
||||
# succeed or in case it failed to do so the first time.
|
||||
if unpack_from('>I', key)[0] % \
|
||||
len(nodes) == ordinal:
|
||||
self.container_sync_row(
|
||||
row, sync_to, user_key, broker, info, realm,
|
||||
realm_key)
|
||||
sync_point1 = row['ROWID']
|
||||
broker.set_x_container_sync_points(sync_point1, None)
|
||||
self.container_syncs += 1
|
||||
self.logger.increment('syncs')
|
||||
sync_stage_time = start_at
|
||||
try:
|
||||
while time() < stop_at and sync_point2 < sync_point1:
|
||||
rows = broker.get_items_since(sync_point2, 1)
|
||||
if not rows:
|
||||
break
|
||||
row = rows[0]
|
||||
if row['ROWID'] > sync_point1:
|
||||
break
|
||||
# This node will only initially sync out one third
|
||||
# of the objects (if 3 replicas, 1/4 if 4, etc.)
|
||||
# and will skip problematic rows as needed in case of
|
||||
# faults.
|
||||
# This section will attempt to sync previously skipped
|
||||
# rows in case the previous attempts by any of the
|
||||
# nodes didn't succeed.
|
||||
if not self.container_sync_row(
|
||||
row, sync_to, user_key, broker, info, realm,
|
||||
realm_key):
|
||||
if not next_sync_point:
|
||||
next_sync_point = sync_point2
|
||||
sync_point2 = row['ROWID']
|
||||
broker.set_x_container_sync_points(None, sync_point2)
|
||||
if next_sync_point:
|
||||
broker.set_x_container_sync_points(None,
|
||||
next_sync_point)
|
||||
else:
|
||||
next_sync_point = sync_point2
|
||||
sync_stage_time = time()
|
||||
while sync_stage_time < stop_at:
|
||||
rows = broker.get_items_since(sync_point1, 1)
|
||||
if not rows:
|
||||
break
|
||||
row = rows[0]
|
||||
key = hash_path(info['account'], info['container'],
|
||||
row['name'], raw_digest=True)
|
||||
# This node will only initially sync out one third of
|
||||
# the objects (if 3 replicas, 1/4 if 4, etc.).
|
||||
# It'll come back around to the section above
|
||||
# and attempt to sync previously skipped rows in case
|
||||
# the other nodes didn't succeed or in case it failed
|
||||
# to do so the first time.
|
||||
if unpack_from('>I', key)[0] % \
|
||||
len(nodes) == ordinal:
|
||||
self.container_sync_row(
|
||||
row, sync_to, user_key, broker, info, realm,
|
||||
realm_key)
|
||||
sync_point1 = row['ROWID']
|
||||
broker.set_x_container_sync_points(sync_point1, None)
|
||||
sync_stage_time = time()
|
||||
self.container_syncs += 1
|
||||
self.logger.increment('syncs')
|
||||
except Exception as ex:
|
||||
raise ex
|
||||
finally:
|
||||
self.container_report(start_at, sync_stage_time,
|
||||
sync_point1,
|
||||
next_sync_point,
|
||||
info, broker.get_max_row())
|
||||
except (Exception, Timeout):
|
||||
self.container_failures += 1
|
||||
self.logger.increment('failures')
|
||||
self.logger.exception(_('ERROR Syncing %s'),
|
||||
broker if broker else path)
|
||||
|
||||
def _update_sync_to_headers(self, name, sync_to, user_key,
|
||||
realm, realm_key, method, headers):
|
||||
"""
|
||||
Updates container sync headers
|
||||
|
||||
:param name: The name of the object
|
||||
:param sync_to: The URL to the remote container.
|
||||
:param user_key: The X-Container-Sync-Key to use when sending requests
|
||||
to the other container.
|
||||
:param realm: The realm from self.realms_conf, if there is one.
|
||||
If None, fallback to using the older allowed_sync_hosts
|
||||
way of syncing.
|
||||
:param realm_key: The realm key from self.realms_conf, if there
|
||||
is one. If None, fallback to using the older
|
||||
allowed_sync_hosts way of syncing.
|
||||
:param method: HTTP method to create sig with
|
||||
:param headers: headers to update with container sync headers
|
||||
"""
|
||||
if realm and realm_key:
|
||||
nonce = uuid.uuid4().hex
|
||||
path = urlparse(sync_to).path + '/' + quote(name)
|
||||
sig = self.realms_conf.get_sig(method, path,
|
||||
headers.get('x-timestamp', 0),
|
||||
nonce, realm_key,
|
||||
user_key)
|
||||
headers['x-container-sync-auth'] = '%s %s %s' % (realm,
|
||||
nonce,
|
||||
sig)
|
||||
else:
|
||||
headers['x-container-sync-key'] = user_key
|
||||
|
||||
def _object_in_remote_container(self, name, sync_to, user_key,
|
||||
realm, realm_key, timestamp):
|
||||
"""
|
||||
Performs head object on remote to eliminate extra remote put and
|
||||
local get object calls
|
||||
|
||||
:param name: The name of the object in the updated row in the local
|
||||
database triggering the sync update.
|
||||
:param sync_to: The URL to the remote container.
|
||||
:param user_key: The X-Container-Sync-Key to use when sending requests
|
||||
to the other container.
|
||||
:param realm: The realm from self.realms_conf, if there is one.
|
||||
If None, fallback to using the older allowed_sync_hosts
|
||||
way of syncing.
|
||||
:param realm_key: The realm key from self.realms_conf, if there
|
||||
is one. If None, fallback to using the older
|
||||
allowed_sync_hosts way of syncing.
|
||||
:param timestamp: last modified date of local object
|
||||
:returns: True if object already exists in remote
|
||||
"""
|
||||
headers = {'x-timestamp': timestamp.internal}
|
||||
self._update_sync_to_headers(name, sync_to, user_key, realm,
|
||||
realm_key, 'HEAD', headers)
|
||||
try:
|
||||
metadata, _ = head_object(sync_to, name=name,
|
||||
headers=headers,
|
||||
proxy=self.select_http_proxy(),
|
||||
logger=self.logger,
|
||||
retries=0)
|
||||
remote_ts = Timestamp(metadata.get('x-timestamp', 0))
|
||||
self.logger.debug("remote obj timestamp %s local obj %s" %
|
||||
(timestamp.internal, remote_ts.internal))
|
||||
if timestamp <= remote_ts:
|
||||
return True
|
||||
# Object in remote should be updated
|
||||
return False
|
||||
except ClientException as http_err:
|
||||
# Object not in remote
|
||||
if http_err.http_status == 404:
|
||||
return False
|
||||
raise http_err
|
||||
|
||||
def container_sync_row(self, row, sync_to, user_key, broker, info,
|
||||
realm, realm_key):
|
||||
"""
|
||||
Sends the update the row indicates to the sync_to container.
|
||||
Update can be either delete or put.
|
||||
|
||||
:param row: The updated row in the local database triggering the sync
|
||||
update.
|
||||
|
@ -439,17 +547,9 @@ class ContainerSync(Daemon):
|
|||
# timestamp of the source tombstone
|
||||
try:
|
||||
headers = {'x-timestamp': ts_data.internal}
|
||||
if realm and realm_key:
|
||||
nonce = uuid.uuid4().hex
|
||||
path = urlparse(sync_to).path + '/' + quote(
|
||||
row['name'])
|
||||
sig = self.realms_conf.get_sig(
|
||||
'DELETE', path, headers['x-timestamp'], nonce,
|
||||
realm_key, user_key)
|
||||
headers['x-container-sync-auth'] = '%s %s %s' % (
|
||||
realm, nonce, sig)
|
||||
else:
|
||||
headers['x-container-sync-key'] = user_key
|
||||
self._update_sync_to_headers(row['name'], sync_to,
|
||||
user_key, realm, realm_key,
|
||||
'DELETE', headers)
|
||||
delete_object(sync_to, name=row['name'], headers=headers,
|
||||
proxy=self.select_http_proxy(),
|
||||
logger=self.logger,
|
||||
|
@ -458,16 +558,16 @@ class ContainerSync(Daemon):
|
|||
if err.http_status != HTTP_NOT_FOUND:
|
||||
raise
|
||||
self.container_deletes += 1
|
||||
self.container_stats['deletes'] += 1
|
||||
self.logger.increment('deletes')
|
||||
self.logger.timing_since('deletes.timing', start_time)
|
||||
else:
|
||||
# when sync'ing a live object, use ts_meta - this is the time
|
||||
# at which the source object was last modified by a PUT or POST
|
||||
part, nodes = \
|
||||
self.get_object_ring(info['storage_policy_index']). \
|
||||
get_nodes(info['account'], info['container'],
|
||||
row['name'])
|
||||
shuffle(nodes)
|
||||
if self._object_in_remote_container(row['name'],
|
||||
sync_to, user_key, realm,
|
||||
realm_key, ts_meta):
|
||||
return True
|
||||
exc = None
|
||||
# look up for the newest one
|
||||
headers_out = {'X-Newest': True,
|
||||
|
@ -502,21 +602,15 @@ class ContainerSync(Daemon):
|
|||
if 'content-type' in headers:
|
||||
headers['content-type'] = clean_content_type(
|
||||
headers['content-type'])
|
||||
if realm and realm_key:
|
||||
nonce = uuid.uuid4().hex
|
||||
path = urlparse(sync_to).path + '/' + quote(row['name'])
|
||||
sig = self.realms_conf.get_sig(
|
||||
'PUT', path, headers['x-timestamp'], nonce, realm_key,
|
||||
user_key)
|
||||
headers['x-container-sync-auth'] = '%s %s %s' % (
|
||||
realm, nonce, sig)
|
||||
else:
|
||||
headers['x-container-sync-key'] = user_key
|
||||
self._update_sync_to_headers(row['name'], sync_to, user_key,
|
||||
realm, realm_key, 'PUT', headers)
|
||||
put_object(sync_to, name=row['name'], headers=headers,
|
||||
contents=FileLikeIter(body),
|
||||
proxy=self.select_http_proxy(), logger=self.logger,
|
||||
timeout=self.conn_timeout)
|
||||
self.container_puts += 1
|
||||
self.container_stats['puts'] += 1
|
||||
self.container_stats['bytes'] += row['size']
|
||||
self.logger.increment('puts')
|
||||
self.logger.timing_since('puts.timing', start_time)
|
||||
except ClientException as err:
|
||||
|
|
|
@ -31,7 +31,7 @@ from swift.common.bufferedhttp import http_connect
|
|||
from swift.common.exceptions import ConnectionTimeout
|
||||
from swift.common.ring import Ring
|
||||
from swift.common.utils import get_logger, config_true_value, ismount, \
|
||||
dump_recon_cache, quorum_size, Timestamp
|
||||
dump_recon_cache, majority_size, Timestamp
|
||||
from swift.common.daemon import Daemon
|
||||
from swift.common.http import is_success, HTTP_INTERNAL_SERVER_ERROR
|
||||
|
||||
|
@ -143,7 +143,8 @@ class ContainerUpdater(Daemon):
|
|||
pid2filename[pid] = tmpfilename
|
||||
else:
|
||||
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||
patcher.monkey_patch(all=False, socket=True, thread=True)
|
||||
patcher.monkey_patch(all=False, socket=True, select=True,
|
||||
thread=True)
|
||||
self.no_changes = 0
|
||||
self.successes = 0
|
||||
self.failures = 0
|
||||
|
@ -177,7 +178,7 @@ class ContainerUpdater(Daemon):
|
|||
"""
|
||||
Run the updater once.
|
||||
"""
|
||||
patcher.monkey_patch(all=False, socket=True, thread=True)
|
||||
patcher.monkey_patch(all=False, socket=True, select=True, thread=True)
|
||||
self.logger.info(_('Begin container update single threaded sweep'))
|
||||
begin = time.time()
|
||||
self.no_changes = 0
|
||||
|
@ -237,7 +238,7 @@ class ContainerUpdater(Daemon):
|
|||
for event in events:
|
||||
if is_success(event.wait()):
|
||||
successes += 1
|
||||
if successes >= quorum_size(len(events)):
|
||||
if successes >= majority_size(len(events)):
|
||||
self.logger.increment('successes')
|
||||
self.successes += 1
|
||||
self.logger.debug(
|
||||
|
|
|
@ -6,18 +6,16 @@
|
|||
# Andreas Jaeger <jaegerandi@gmail.com>, 2014
|
||||
# Ettore Atalan <atalanttore@googlemail.com>, 2014-2015
|
||||
# Jonas John <jonas.john@e-werkzeug.eu>, 2015
|
||||
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
|
||||
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
|
||||
# Monika Wolf <vcomas3@de.ibm.com>, 2016. #zanata
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: swift 2.6.1.dev176\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2016-03-08 04:09+0000\n"
|
||||
"Project-Id-Version: swift 2.7.1.dev50\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-04-17 21:20+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-03-07 06:04+0000\n"
|
||||
"PO-Revision-Date: 2016-03-24 03:15+0000\n"
|
||||
"Last-Translator: Monika Wolf <vcomas3@de.ibm.com>\n"
|
||||
"Language: de\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
@ -55,6 +53,16 @@ msgstr "%(ip)s/%(device)s zurückgemeldet als ausgehängt"
|
|||
msgid "%(msg)s %(ip)s:%(port)s/%(device)s"
|
||||
msgstr "%(msg)s %(ip)s:%(port)s/%(device)s"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(reconstructed)d/%(total)d (%(percentage).2f%%) partitions of %(device)d/"
|
||||
"%(dtotal)d (%(dpercentage).2f%%) devices reconstructed in %(time).2fs "
|
||||
"(%(rate).2f/sec, %(remaining)s remaining)"
|
||||
msgstr ""
|
||||
"%(reconstructed)d/%(total)d (%(percentage).2f%%) Partitionen von %(device)d/"
|
||||
"%(dtotal)d (%(dpercentage).2f%%) Geräten rekonstruiert in %(time).2fs "
|
||||
"(%(rate).2f/sec, %(remaining)s verbleibend)"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(replicated)d/%(total)d (%(percentage).2f%%) partitions replicated in "
|
||||
|
@ -71,14 +79,6 @@ msgstr "%(success)s Erfolge, %(failure)s Fehlschläge"
|
|||
msgid "%(type)s returning 503 for %(statuses)s"
|
||||
msgstr "%(type)s gab 503 für %(statuses)s zurück"
|
||||
|
||||
#, python-format
|
||||
msgid "%s #%d not running (%s)"
|
||||
msgstr "%s #%d läuft nicht (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "%s (%s) appears to have stopped"
|
||||
msgstr "%s (%s) scheinbar gestoppt"
|
||||
|
||||
#, python-format
|
||||
msgid "%s already started..."
|
||||
msgstr "%s bereits gestartet..."
|
||||
|
@ -95,14 +95,6 @@ msgstr "%s ist nicht eingehängt"
|
|||
msgid "%s responded as unmounted"
|
||||
msgstr "%s zurückgemeldet als ausgehängt"
|
||||
|
||||
#, python-format
|
||||
msgid "%s running (%s - %s)"
|
||||
msgstr "%s läuft (%s - %s)"
|
||||
|
||||
#, python-format
|
||||
msgid "%s: %s"
|
||||
msgstr "%s: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "%s: Connection reset by peer"
|
||||
msgstr "%s: Verbindung zurückgesetzt durch Peer"
|
||||
|
@ -141,10 +133,6 @@ msgstr ", Rückgabecodes: "
|
|||
msgid "Account"
|
||||
msgstr "Konto"
|
||||
|
||||
#, python-format
|
||||
msgid "Account %s has not been reaped since %s"
|
||||
msgstr "Konto %s wurde nicht aufgeräumt seit %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Account audit \"once\" mode completed: %.02fs"
|
||||
msgstr "Kontoprüfungsmodus \"once\" abgeschlossen: %.02fs"
|
||||
|
@ -160,10 +148,6 @@ msgstr ""
|
|||
"Versuch, %(count)d Datenbanken in %(time).5f Sekunden zu replizieren "
|
||||
"(%(rate).5f/s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Audit Failed for %s: %s"
|
||||
msgstr "Prüfung fehlgeschlagen für %s: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Bad rsync return code: %(ret)d <- %(args)s"
|
||||
msgstr "Falscher rsync-Rückgabecode: %(ret)d <- %(args)s"
|
||||
|
@ -189,10 +173,6 @@ msgstr "Einzelthread-Scanvorgang für Containeraktualisierung wird gestartet"
|
|||
msgid "Begin container update sweep"
|
||||
msgstr "Scanvorgang für Containeraktualisierung wird gestartet"
|
||||
|
||||
#, python-format
|
||||
msgid "Begin object audit \"%s\" mode (%s%s)"
|
||||
msgstr "Objektprüfung mit \"%s\"-Modus wird gestartet (%s%s)"
|
||||
|
||||
msgid "Begin object update single threaded sweep"
|
||||
msgstr "Einzelthread-Scanvorgang für Objektaktualisierung wird gestartet"
|
||||
|
||||
|
@ -229,6 +209,11 @@ msgstr "Client beim Lesen getrennt"
|
|||
msgid "Client disconnected without sending enough data"
|
||||
msgstr "Client getrennt ohne dem Senden von genügend Daten"
|
||||
|
||||
msgid "Client disconnected without sending last chunk"
|
||||
msgstr ""
|
||||
"Die Verbindung zum Client wurde getrennt, bevor der letzte Chunk gesendet "
|
||||
"wurde. "
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Client path %(client)s does not match path stored in object metadata %(meta)s"
|
||||
|
@ -236,7 +221,6 @@ msgstr ""
|
|||
"Clientpfad %(client)s entspricht nicht dem in den Objektmetadaten "
|
||||
"gespeicherten Pfad %(meta)s"
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Configuration option internal_client_conf_path not defined. Using default "
|
||||
"configuration, See internal-client.conf-sample for options"
|
||||
|
@ -304,6 +288,11 @@ msgstr "Fehler beim Downloaden von Daten: %s"
|
|||
msgid "Devices pass completed: %.02fs"
|
||||
msgstr "Gerätedurchgang abgeschlossen: %.02fs"
|
||||
|
||||
#, python-format
|
||||
msgid "Directory %r does not map to a valid policy (%s)"
|
||||
msgstr ""
|
||||
"Das Verzeichnis %r kann keiner gültigen Richtlinie (%s) zugeordnet werden."
|
||||
|
||||
#, python-format
|
||||
msgid "ERROR %(db_file)s: %(validate_sync_to_err)s"
|
||||
msgstr "FEHLER %(db_file)s: %(validate_sync_to_err)s"
|
||||
|
@ -383,6 +372,10 @@ msgid "ERROR Exception causing client disconnect"
|
|||
msgstr ""
|
||||
"FEHLER Ausnahme, die zu einer Unterbrechung der Verbindung zum Client führt"
|
||||
|
||||
#, python-format
|
||||
msgid "ERROR Exception transferring data to object servers %s"
|
||||
msgstr "FEHLER: Ausnahme bei der Übertragung von Daten an die Ojektserver %s"
|
||||
|
||||
msgid "ERROR Failed to get my own IPs?"
|
||||
msgstr "FEHLER Eigene IPs konnten nicht abgerufen werden?"
|
||||
|
||||
|
@ -475,7 +468,7 @@ msgstr ""
|
|||
"FEHLER beim Synchronisieren von %(file)s Dateien mit dem Knoten %(node)s"
|
||||
|
||||
msgid "ERROR trying to replicate"
|
||||
msgstr "FEHLER beim Versuch, zu replizieren"
|
||||
msgstr "FEHLER beim Versuch zu replizieren"
|
||||
|
||||
#, python-format
|
||||
msgid "ERROR while trying to clean up %s"
|
||||
|
@ -536,10 +529,10 @@ msgid "Error on render profiling results: %s"
|
|||
msgstr "Fehler beim Wiedergeben der Profilerstellungsergebnisse: %s"
|
||||
|
||||
msgid "Error parsing recon cache file"
|
||||
msgstr "Fehler beim Analysieren von recon-Cachedatei"
|
||||
msgstr "Fehler beim Analysieren von recon-Zwischenspeicherdatei"
|
||||
|
||||
msgid "Error reading recon cache file"
|
||||
msgstr "Fehler beim Lesen von recon-Cachedatei"
|
||||
msgstr "Fehler beim Lesen von recon-Zwischenspeicherdatei"
|
||||
|
||||
msgid "Error reading ringfile"
|
||||
msgstr "Fehler beim Lesen der Ringdatei"
|
||||
|
@ -560,6 +553,12 @@ msgstr "Fehler beim Syncen der Partition"
|
|||
msgid "Error syncing with node: %s"
|
||||
msgstr "Fehler beim Synchronisieren mit Knoten: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Error trying to rebuild %(path)s policy#%(policy)d frag#%(frag_index)s"
|
||||
msgstr ""
|
||||
"Fehler bei Versuch, erneuten Build zu erstellen für %(path)s policy#"
|
||||
"%(policy)d frag#%(frag_index)s"
|
||||
|
||||
msgid "Error: An error occurred"
|
||||
msgstr "Fehler: Ein Fehler ist aufgetreten"
|
||||
|
||||
|
@ -579,13 +578,8 @@ msgstr "Ausnahme in Reaper-Loop für Konto der höchsten Ebene"
|
|||
msgid "Exception in top-level replication loop"
|
||||
msgstr "Ausnahme in Replizierungsloop der höchsten Ebene"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception while deleting container %s %s"
|
||||
msgstr "Ausnahme beim Löschen von Container %s %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception while deleting object %s %s %s"
|
||||
msgstr "Ausnahme beim Löschen von Objekt %s %s %s"
|
||||
msgid "Exception in top-levelreconstruction loop"
|
||||
msgstr "Ausnahme in Rekonstruktionsloop der höchsten Ebene"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception with %(ip)s:%(port)s/%(device)s"
|
||||
|
@ -616,6 +610,13 @@ msgstr "CNAME-Kette für %(given_domain)s bis %(found_domain)s wird gefolgt"
|
|||
msgid "Found configs:"
|
||||
msgstr "Gefundene Konfigurationen:"
|
||||
|
||||
msgid ""
|
||||
"Handoffs first mode still has handoffs remaining. Aborting current "
|
||||
"replication pass."
|
||||
msgstr ""
|
||||
"Der Modus 'handoffs_first' ist noch nicht abgeschlossen. Der aktuelle "
|
||||
"Replikationsdurchgang wird abgebrochen."
|
||||
|
||||
msgid "Host unreachable"
|
||||
msgstr "Host nicht erreichbar"
|
||||
|
||||
|
@ -686,6 +687,10 @@ msgstr "Kein Bereichsschlüssel für %r"
|
|||
msgid "Node error limited %(ip)s:%(port)s (%(device)s)"
|
||||
msgstr "Knotenfehler begrenzt %(ip)s:%(port)s (%(device)s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Not enough object servers ack'ed (got %d)"
|
||||
msgstr "Es wurden nicht genügend Objektserver bestätigt (got %d)."
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Not found %(sync_from)r => %(sync_to)r - object "
|
||||
|
@ -730,6 +735,18 @@ msgstr ""
|
|||
"%(errors)d, Dateien/s insgesamt: %(frate).2f, Bytes/s insgesamt: "
|
||||
"%(brate).2f, Prüfungszeit: %(audit).2f, Geschwindigkeit: %(audit_rate).2f"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Object audit (%(type)s). Since %(start_time)s: Locally: %(passes)d passed, "
|
||||
"%(quars)d quarantined, %(errors)d errors, files/sec: %(frate).2f, bytes/sec: "
|
||||
"%(brate).2f, Total time: %(total).2f, Auditing time: %(audit).2f, Rate: "
|
||||
"%(audit_rate).2f"
|
||||
msgstr ""
|
||||
"Objektprüfung (%(type)s). Seit %(start_time)s: Lokal: %(passes)d übergeben, "
|
||||
"%(quars)d unter Quarantäne gestellt, %(errors)d Fehler, Dateien/s: "
|
||||
"%(frate).2f, Bytes/s: %(brate).2f, Zeit insgesamt: %(total).2f, "
|
||||
"Prüfungszeit: %(audit).2f, Geschwindigkeit: %(audit_rate).2f"
|
||||
|
||||
#, python-format
|
||||
msgid "Object audit stats: %s"
|
||||
msgstr "Objektprüfungsstatistik: %s"
|
||||
|
@ -819,10 +836,6 @@ msgstr ""
|
|||
"%(object_path)s bis %(quar_path)s wurden unter Quarantäne gestellt, da es "
|
||||
"sich nicht um ein Verzeichnis handelt"
|
||||
|
||||
#, python-format
|
||||
msgid "Quarantined %s to %s due to %s database"
|
||||
msgstr "%s unter Quarantäne gestellt in %s aufgrund von %s-Datenbank"
|
||||
|
||||
#, python-format
|
||||
msgid "Quarantining DB %s"
|
||||
msgstr "Datenbank %s wird unter Quarantäne gestellt"
|
||||
|
@ -846,12 +859,12 @@ msgid "Removing partition: %s"
|
|||
msgstr "Partition wird entfernt: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing pid file %s with invalid pid"
|
||||
msgstr "PID-Datei %s mit ungültiger PID wird entfernt."
|
||||
msgid "Removing pid file %(pid_file)s with wrong pid %(pid)d"
|
||||
msgstr "PID-Datei %(pid_file)s mit falscher PID %(pid)d wird entfernt"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing pid file %s with wrong pid %d"
|
||||
msgstr "PID-Datei %s mit falscher PID %d wird entfernt."
|
||||
msgid "Removing pid file %s with invalid pid"
|
||||
msgstr "PID-Datei %s mit ungültiger PID wird entfernt."
|
||||
|
||||
#, python-format
|
||||
msgid "Removing stale pid file %s"
|
||||
|
@ -890,10 +903,6 @@ msgstr "Objektrekonstruktor läuft im Skriptmodus."
|
|||
msgid "Running object replicator in script mode."
|
||||
msgstr "Objektreplikator läuft im Skriptmodus."
|
||||
|
||||
#, python-format
|
||||
msgid "Signal %s pid: %s signal: %s"
|
||||
msgstr "Signal %s PID: %s Signal: %s"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Since %(time)s: %(sync)s synced [%(delete)s deletes, %(put)s puts], %(skip)s "
|
||||
|
@ -961,10 +970,18 @@ msgstr ""
|
|||
msgid "Timeout %(action)s to memcached: %(server)s"
|
||||
msgstr "Zeitlimit %(action)s für memcached: %(server)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Timeout Exception with %(ip)s:%(port)s/%(device)s"
|
||||
msgstr "Zeitüberschreitungsausnahme bei %(ip)s:%(port)s/%(device)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to %(method)s %(path)s"
|
||||
msgstr "Versuch, %(method)s %(path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to GET %(full_path)s"
|
||||
msgstr "Versuch, %(full_path)s mit GET abzurufen"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to get final status of PUT to %s"
|
||||
msgstr "Versuch, den finalen Status von PUT für %s abzurufen"
|
||||
|
@ -978,6 +995,10 @@ msgstr "Versuch, während des GET-Vorgangs zu lesen (Wiederholung)"
|
|||
msgid "Trying to send to client"
|
||||
msgstr "Versuch, an den Client zu senden"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to sync suffixes with %s"
|
||||
msgstr "Es wird versucht, Suffixe mit %s zu synchronisieren."
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to write to %s"
|
||||
msgstr "Versuch, an %s zu schreiben"
|
||||
|
@ -986,8 +1007,9 @@ msgid "UNCAUGHT EXCEPTION"
|
|||
msgstr "NICHT ABGEFANGENE AUSNAHME"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to find %s config section in %s"
|
||||
msgstr "%s-Konfigurationsabschnitt in %s kann nicht gefunden werden"
|
||||
msgid "Unable to load internal client from config: %r (%s)"
|
||||
msgstr ""
|
||||
"Interner Client konnte nicht aus der Konfiguration geladen werden: %r (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to locate %s in libc. Leaving as a no-op."
|
||||
|
@ -1004,10 +1026,6 @@ msgstr ""
|
|||
"fallocate, posix_fallocate konnte nicht in libc gefunden werden. Wird als "
|
||||
"Nullbefehl verlassen."
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to perform fsync() on directory %s: %s"
|
||||
msgstr "fsync() kann für Verzeichnis %s nicht ausgeführt werden: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to read config from %s"
|
||||
msgstr "Konfiguration aus %s kann nicht gelesen werden"
|
||||
|
@ -1023,6 +1041,11 @@ msgstr "Unerwartete Antwort: %s"
|
|||
msgid "Unhandled exception"
|
||||
msgstr "Nicht behandelte Exception"
|
||||
|
||||
#, python-format
|
||||
msgid "Unknown exception trying to GET: %(account)r %(container)r %(object)r"
|
||||
msgstr ""
|
||||
"Unbekannte Ausnahme bei GET-Versuch: %(account)r %(container)r %(object)r"
|
||||
|
||||
#, python-format
|
||||
msgid "Update report failed for %(container)s %(dbfile)s"
|
||||
msgstr "Aktualisierungsbericht fehlgeschlagen für %(container)s %(dbfile)s"
|
||||
|
@ -1053,14 +1076,6 @@ msgstr ""
|
|||
"WARNUNG: Grenzwert für Speicher kann nicht geändert werden. Wird nicht als "
|
||||
"Root ausgeführt?"
|
||||
|
||||
#, python-format
|
||||
msgid "Waited %s seconds for %s to die; giving up"
|
||||
msgstr "Hat %s Sekunden für %s zum Erlöschen gewartet; Gibt auf"
|
||||
|
||||
#, python-format
|
||||
msgid "Waited %s seconds for %s to die; killing"
|
||||
msgstr "Hat %s Sekunden für %s zum Erlöschen gewartet. Wird abgebrochen."
|
||||
|
||||
msgid "Warning: Cannot ratelimit without a memcached client"
|
||||
msgstr ""
|
||||
"Warnung: Geschwindigkeitsbegrenzung kann nicht ohne memcached-Client "
|
||||
|
|
|
@ -3,19 +3,17 @@
|
|||
# This file is distributed under the same license as the swift project.
|
||||
#
|
||||
# Translators:
|
||||
# Carlos A. Muñoz <camunoz@redhat.com>, 2015. #zanata
|
||||
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
|
||||
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: swift 2.6.1.dev176\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2016-03-08 04:09+0000\n"
|
||||
"Project-Id-Version: swift 2.7.1.dev50\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-04-17 21:20+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2015-09-09 05:36+0000\n"
|
||||
"Last-Translator: Carlos A. Muñoz <camunoz@redhat.com>\n"
|
||||
"PO-Revision-Date: 2016-03-29 11:20+0000\n"
|
||||
"Last-Translator: Eugènia Torrella <tester03@es.ibm.com>\n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Generated-By: Babel 2.0\n"
|
||||
|
@ -52,6 +50,16 @@ msgstr "%(ip)s/%(device)s han respondido como desmontados"
|
|||
msgid "%(msg)s %(ip)s:%(port)s/%(device)s"
|
||||
msgstr "%(msg)s %(ip)s:%(port)s/%(device)s"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(reconstructed)d/%(total)d (%(percentage).2f%%) partitions of %(device)d/"
|
||||
"%(dtotal)d (%(dpercentage).2f%%) devices reconstructed in %(time).2fs "
|
||||
"(%(rate).2f/sec, %(remaining)s remaining)"
|
||||
msgstr ""
|
||||
"%(reconstructed)d/%(total)d (%(percentage).2f%%) particiones de %(device)d/"
|
||||
"%(dtotal)d (%(dpercentage).2f%%) dispositivos reconstruidas en %(time).2fs "
|
||||
"(%(rate).2f/sec, %(remaining)s remaining)"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(replicated)d/%(total)d (%(percentage).2f%%) partitions replicated in "
|
||||
|
@ -68,14 +76,6 @@ msgstr "%(success)s éxitos, %(failure)s fallos"
|
|||
msgid "%(type)s returning 503 for %(statuses)s"
|
||||
msgstr "%(type)s devuelve 503 para %(statuses)s"
|
||||
|
||||
#, python-format
|
||||
msgid "%s #%d not running (%s)"
|
||||
msgstr "%s #%d no está en ejecución (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "%s (%s) appears to have stopped"
|
||||
msgstr "%s (%s) parece haberse detenido"
|
||||
|
||||
#, python-format
|
||||
msgid "%s already started..."
|
||||
msgstr "%s ya está iniciado..."
|
||||
|
@ -89,12 +89,8 @@ msgid "%s is not mounted"
|
|||
msgstr "%s no está montado"
|
||||
|
||||
#, python-format
|
||||
msgid "%s running (%s - %s)"
|
||||
msgstr "%s en ejecución (%s - %s)"
|
||||
|
||||
#, python-format
|
||||
msgid "%s: %s"
|
||||
msgstr "%s: %s"
|
||||
msgid "%s responded as unmounted"
|
||||
msgstr "%s ha respondido como desmontado"
|
||||
|
||||
#, python-format
|
||||
msgid "%s: Connection reset by peer"
|
||||
|
@ -135,10 +131,6 @@ msgstr ", códigos de retorno:"
|
|||
msgid "Account"
|
||||
msgstr "Cuenta"
|
||||
|
||||
#, python-format
|
||||
msgid "Account %s has not been reaped since %s"
|
||||
msgstr "La cuenta %s no se ha cosechado desde %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Account audit \"once\" mode completed: %.02fs"
|
||||
msgstr "Auditoría de cuenta en modalidad de \"una vez\" finalizada: %.02fs"
|
||||
|
@ -154,10 +146,6 @@ msgstr ""
|
|||
"Se han intentado replicar %(count)d bases de datos en %(time).5f segundos "
|
||||
"(%(rate).5f/s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Audit Failed for %s: %s"
|
||||
msgstr "La auditoría ha fallado para %s: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Bad rsync return code: %(ret)d <- %(args)s"
|
||||
msgstr "Código de retorno de resincronización erróneo: %(ret)d <- %(args)s"
|
||||
|
@ -183,10 +171,6 @@ msgstr "Comenzar el barrido de hebra única de actualización del contenedor"
|
|||
msgid "Begin container update sweep"
|
||||
msgstr "Comenzar el barrido de actualización del contenedor"
|
||||
|
||||
#, python-format
|
||||
msgid "Begin object audit \"%s\" mode (%s%s)"
|
||||
msgstr "Comenzar auditoría de objetos en modalidad \"%s\" (%s%s)"
|
||||
|
||||
msgid "Begin object update single threaded sweep"
|
||||
msgstr "Comenzar el barrido de hebra única de actualización del objeto"
|
||||
|
||||
|
@ -221,6 +205,9 @@ msgstr "El cliente se ha desconectado de la lectura"
|
|||
msgid "Client disconnected without sending enough data"
|
||||
msgstr "El cliente se ha desconectado sin enviar suficientes datos"
|
||||
|
||||
msgid "Client disconnected without sending last chunk"
|
||||
msgstr "El cliente se ha desconectado sin enviar el último fragmento"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Client path %(client)s does not match path stored in object metadata %(meta)s"
|
||||
|
@ -228,6 +215,14 @@ msgstr ""
|
|||
"La vía de acceso de cliente %(client)s no coincide con la vía de acceso "
|
||||
"almacenada en los metadatos de objeto %(meta)s"
|
||||
|
||||
msgid ""
|
||||
"Configuration option internal_client_conf_path not defined. Using default "
|
||||
"configuration, See internal-client.conf-sample for options"
|
||||
msgstr ""
|
||||
"La opción de configuración internal_client_conf_path no está definida. Se "
|
||||
"utilizará la configuración predeterminada, Consulte internal-client.conf-"
|
||||
"sample para ver las opciones"
|
||||
|
||||
msgid "Connection refused"
|
||||
msgstr "Conexión rechazada"
|
||||
|
||||
|
@ -288,6 +283,10 @@ msgstr "Error de descarga de datos: %s"
|
|||
msgid "Devices pass completed: %.02fs"
|
||||
msgstr "Paso de dispositivos finalizado: %.02fs"
|
||||
|
||||
#, python-format
|
||||
msgid "Directory %r does not map to a valid policy (%s)"
|
||||
msgstr "El directory %r no está correlacionado con una política válida (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "ERROR %(db_file)s: %(validate_sync_to_err)s"
|
||||
msgstr "ERROR %(db_file)s: %(validate_sync_to_err)s"
|
||||
|
@ -367,6 +366,10 @@ msgstr ""
|
|||
msgid "ERROR Exception causing client disconnect"
|
||||
msgstr "ERROR Excepción que provoca la desconexión del cliente"
|
||||
|
||||
#, python-format
|
||||
msgid "ERROR Exception transferring data to object servers %s"
|
||||
msgstr "ERROR Excepción al transferir datos a los servidores de objetos %s"
|
||||
|
||||
msgid "ERROR Failed to get my own IPs?"
|
||||
msgstr "ERROR ¿No puedo obtener mis propias IP?"
|
||||
|
||||
|
@ -545,6 +548,11 @@ msgstr "Error al sincronizar la partición"
|
|||
msgid "Error syncing with node: %s"
|
||||
msgstr "Error en la sincronización con el nodo: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Error trying to rebuild %(path)s policy#%(policy)d frag#%(frag_index)s"
|
||||
msgstr ""
|
||||
"Error al intentar reconstruir %(path)s policy#%(policy)d frag#%(frag_index)s"
|
||||
|
||||
msgid "Error: An error occurred"
|
||||
msgstr "Error: se ha producido un error"
|
||||
|
||||
|
@ -564,13 +572,8 @@ msgstr "Excepción en el bucle cosechador de cuenta de nivel superior"
|
|||
msgid "Exception in top-level replication loop"
|
||||
msgstr "Excepción en el bucle de réplica de nivel superior"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception while deleting container %s %s"
|
||||
msgstr "Excepción al suprimir el contenedor %s %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception while deleting object %s %s %s"
|
||||
msgstr "Excepción al suprimir el objeto %s %s %s"
|
||||
msgid "Exception in top-levelreconstruction loop"
|
||||
msgstr "Excepción en el bucle de reconstrucción de nivel superior"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception with %(ip)s:%(port)s/%(device)s"
|
||||
|
@ -602,6 +605,13 @@ msgstr "Siguiente cadena CNAME de %(given_domain)s a %(found_domain)s"
|
|||
msgid "Found configs:"
|
||||
msgstr "Configuraciones encontradas:"
|
||||
|
||||
msgid ""
|
||||
"Handoffs first mode still has handoffs remaining. Aborting current "
|
||||
"replication pass."
|
||||
msgstr ""
|
||||
"El modo de transferencias primero aún tiene transferencias restantes. "
|
||||
"Abortando el pase de réplica actual."
|
||||
|
||||
msgid "Host unreachable"
|
||||
msgstr "Host no alcanzable"
|
||||
|
||||
|
@ -621,6 +631,10 @@ msgstr "Host no válido %r en X-Container-Sync-To"
|
|||
msgid "Invalid pending entry %(file)s: %(entry)s"
|
||||
msgstr "Entrada pendiente no válida %(file)s: %(entry)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Invalid response %(resp)s from %(full_path)s"
|
||||
msgstr "Respuesta no válida %(resp)s de %(full_path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Invalid response %(resp)s from %(ip)s"
|
||||
msgstr "Respuesta no válida %(resp)s desde %(ip)s"
|
||||
|
@ -656,6 +670,10 @@ msgstr "No hay punto final de clúster para %r %r"
|
|||
msgid "No permission to signal PID %d"
|
||||
msgstr "No hay permiso para señalar el PID %d"
|
||||
|
||||
#, python-format
|
||||
msgid "No policy with index %s"
|
||||
msgstr "No hay ninguna política que tenga el índice %s"
|
||||
|
||||
#, python-format
|
||||
msgid "No realm key for %r"
|
||||
msgstr "No hay clave de dominio para %r"
|
||||
|
@ -664,6 +682,10 @@ msgstr "No hay clave de dominio para %r"
|
|||
msgid "Node error limited %(ip)s:%(port)s (%(device)s)"
|
||||
msgstr "Error de nodo limitado %(ip)s:%(port)s (%(device)s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Not enough object servers ack'ed (got %d)"
|
||||
msgstr "No hay suficientes servidores de objetos reconocidos (constan %d)"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Not found %(sync_from)r => %(sync_to)r - object "
|
||||
|
@ -672,6 +694,10 @@ msgstr ""
|
|||
"No se ha encontrado %(sync_from)r => %(sync_to)r - "
|
||||
"objeto %(obj_name)rd"
|
||||
|
||||
#, python-format
|
||||
msgid "Nothing reconstructed for %s seconds."
|
||||
msgstr "No se ha reconstruido nada durante %s segundos."
|
||||
|
||||
#, python-format
|
||||
msgid "Nothing replicated for %s seconds."
|
||||
msgstr "No se ha replicado nada durante %s segundos."
|
||||
|
@ -704,10 +730,30 @@ msgstr ""
|
|||
"segundo: %(brate).2f, Tiempo de auditoría: %(audit).2f, Velocidad: "
|
||||
"%(audit_rate).2f"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Object audit (%(type)s). Since %(start_time)s: Locally: %(passes)d passed, "
|
||||
"%(quars)d quarantined, %(errors)d errors, files/sec: %(frate).2f, bytes/sec: "
|
||||
"%(brate).2f, Total time: %(total).2f, Auditing time: %(audit).2f, Rate: "
|
||||
"%(audit_rate).2f"
|
||||
msgstr ""
|
||||
"Auditoría de objetos (%(type)s). Desde %(start_time)s: Localmente: "
|
||||
"%(passes)d han pasado, %(quars)d en cuarentena, %(errors)d errores, archivos "
|
||||
"por segundo: %(frate).2f , bytes por segundo: %(brate).2f, Tiempo total: "
|
||||
"%(total).2f, Tiempo de auditoría: %(audit).2f, Velocidad: %(audit_rate).2f"
|
||||
|
||||
#, python-format
|
||||
msgid "Object audit stats: %s"
|
||||
msgstr "Estadísticas de auditoría de objetos: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Object reconstruction complete (once). (%.02f minutes)"
|
||||
msgstr "Reconstrucción de objeto finalizada (una vez). (%.02f minutos)"
|
||||
|
||||
#, python-format
|
||||
msgid "Object reconstruction complete. (%.02f minutes)"
|
||||
msgstr "Reconstrucción de objeto finalizada. (%.02f minutos)"
|
||||
|
||||
#, python-format
|
||||
msgid "Object replication complete (once). (%.02f minutes)"
|
||||
msgstr "Réplica de objeto finalizada (una vez). (%.02f minutos)"
|
||||
|
@ -786,10 +832,6 @@ msgstr ""
|
|||
"Se ha puesto en cuarentena %(object_path)s en %(quar_path)s debido a que no "
|
||||
"es un directorio"
|
||||
|
||||
#, python-format
|
||||
msgid "Quarantined %s to %s due to %s database"
|
||||
msgstr "%s de %s en cuarentena debido a la base de datos %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Quarantining DB %s"
|
||||
msgstr "Poniendo en cuarentena la base de datos %s"
|
||||
|
@ -812,6 +854,15 @@ msgstr "Eliminando %s objetos"
|
|||
msgid "Removing partition: %s"
|
||||
msgstr "Eliminando partición: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing pid file %(pid_file)s with wrong pid %(pid)d"
|
||||
msgstr ""
|
||||
"Eliminando el archivo PID %(pid_file)s que tiene el PID no válido %(pid)d"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing pid file %s with invalid pid"
|
||||
msgstr "Eliminando el archivo PID %s, que tiene un PID no válido"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing stale pid file %s"
|
||||
msgstr "Eliminando fichero de identificación positiva obsoleto %s"
|
||||
|
@ -831,6 +882,10 @@ msgstr ""
|
|||
"Se devuelven 498 de %(meth)s a %(acc)s/%(cont)s/%(obj)s. Ajuste de límite "
|
||||
"(suspensión máxima) %(e)s"
|
||||
|
||||
msgid "Ring change detected. Aborting current reconstruction pass."
|
||||
msgstr ""
|
||||
"Cambio de anillo detectado. Abortando el pase de reconstrucción actual."
|
||||
|
||||
msgid "Ring change detected. Aborting current replication pass."
|
||||
msgstr "Cambio de anillo detectado. Abortando el pase de réplica actual."
|
||||
|
||||
|
@ -838,13 +893,12 @@ msgstr "Cambio de anillo detectado. Abortando el pase de réplica actual."
|
|||
msgid "Running %s once"
|
||||
msgstr "Ejecutando %s una vez"
|
||||
|
||||
msgid "Running object reconstructor in script mode."
|
||||
msgstr "Ejecutando reconstructor de objeto en modo script."
|
||||
|
||||
msgid "Running object replicator in script mode."
|
||||
msgstr "Ejecutando replicador de objeto en modalidad de script."
|
||||
|
||||
#, python-format
|
||||
msgid "Signal %s pid: %s signal: %s"
|
||||
msgstr "Señal %s pid: %s señal: %s"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Since %(time)s: %(sync)s synced [%(delete)s deletes, %(put)s puts], %(skip)s "
|
||||
|
@ -881,6 +935,12 @@ msgstr "Omitiendo %s, ya que no está montado"
|
|||
msgid "Starting %s"
|
||||
msgstr "Iniciando %s"
|
||||
|
||||
msgid "Starting object reconstruction pass."
|
||||
msgstr "Iniciando el paso de reconstrucción de objeto."
|
||||
|
||||
msgid "Starting object reconstructor in daemon mode."
|
||||
msgstr "Iniciando reconstructor de objeto en modo daemon."
|
||||
|
||||
msgid "Starting object replication pass."
|
||||
msgstr "Iniciando el paso de réplica de objeto."
|
||||
|
||||
|
@ -907,10 +967,18 @@ msgstr ""
|
|||
msgid "Timeout %(action)s to memcached: %(server)s"
|
||||
msgstr "%(action)s de tiempo de espera para memcached: %(server)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Timeout Exception with %(ip)s:%(port)s/%(device)s"
|
||||
msgstr "Excepción de tiempo de espera superado con %(ip)s:%(port)s/%(device)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to %(method)s %(path)s"
|
||||
msgstr "Intentando %(method)s %(path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to GET %(full_path)s"
|
||||
msgstr "Intentando hacer un GET de %(full_path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to get final status of PUT to %s"
|
||||
msgstr "Intentando obtener el estado final de PUT en %s"
|
||||
|
@ -924,6 +992,10 @@ msgstr "Intentando leer durante GET (reintento)"
|
|||
msgid "Trying to send to client"
|
||||
msgstr "Intentando enviar al cliente"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to sync suffixes with %s"
|
||||
msgstr "Intentando sincronizar los sufijos con %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to write to %s"
|
||||
msgstr "Intentando escribir en %s"
|
||||
|
@ -932,23 +1004,24 @@ msgid "UNCAUGHT EXCEPTION"
|
|||
msgstr "UNCAUGHT EXCEPTION"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to find %s config section in %s"
|
||||
msgstr "No se ha podido encontrar la sección de configuración %s en %s"
|
||||
msgid "Unable to load internal client from config: %r (%s)"
|
||||
msgstr ""
|
||||
"No se puede cargar el cliente interno a partir de la configuración: %r (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to locate %s in libc. Leaving as a no-op."
|
||||
msgstr "No se ha podido localizar %s en libc. Se dejará como no operativo."
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to locate config for %s"
|
||||
msgstr "No se ha podido encontrar el número de configuración de %s"
|
||||
|
||||
msgid ""
|
||||
"Unable to locate fallocate, posix_fallocate in libc. Leaving as a no-op."
|
||||
msgstr ""
|
||||
"No se ha podido localizar fallocate, posix_fallocate en libc. Se dejará como "
|
||||
"no operativo."
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to perform fsync() on directory %s: %s"
|
||||
msgstr "No se puede realizar fsync() en el directorio %s: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to read config from %s"
|
||||
msgstr "No se ha podido leer la configuración de %s"
|
||||
|
@ -964,6 +1037,12 @@ msgstr "Respuesta inesperada : %s "
|
|||
msgid "Unhandled exception"
|
||||
msgstr "Excepción no controlada"
|
||||
|
||||
#, python-format
|
||||
msgid "Unknown exception trying to GET: %(account)r %(container)r %(object)r"
|
||||
msgstr ""
|
||||
"Se ha producido una excepción desconocida al intentar hacer un GET de: "
|
||||
"%(account)r %(container)r %(object)r"
|
||||
|
||||
#, python-format
|
||||
msgid "Update report failed for %(container)s %(dbfile)s"
|
||||
msgstr "Informe de actualización fallido para %(container)s %(dbfile)s"
|
||||
|
@ -994,10 +1073,6 @@ msgstr ""
|
|||
"AVISO: no se ha podido modificar el límite de memoria. ¿Está en ejecución "
|
||||
"como no root?"
|
||||
|
||||
#, python-format
|
||||
msgid "Waited %s seconds for %s to die; giving up"
|
||||
msgstr "Se han esperado %s segundos a que muriera %s; abandonando"
|
||||
|
||||
msgid "Warning: Cannot ratelimit without a memcached client"
|
||||
msgstr ""
|
||||
"Aviso: no se puede ajustar el límite sin un cliente almacenado en memoria "
|
||||
|
|
|
@ -4,18 +4,17 @@
|
|||
#
|
||||
# Translators:
|
||||
# Maxime COQUEREL <max.coquerel@gmail.com>, 2014
|
||||
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
|
||||
# Tom Cocozzello <tjcocozz@us.ibm.com>, 2015. #zanata
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: swift 2.6.1.dev176\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2016-03-08 04:09+0000\n"
|
||||
"Project-Id-Version: swift 2.7.1.dev50\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-04-17 21:20+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2015-08-11 11:22+0000\n"
|
||||
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
|
||||
"PO-Revision-Date: 2016-03-25 03:29+0000\n"
|
||||
"Last-Translator: Angelique Pillal <pillal@fr.ibm.com>\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"Generated-By: Babel 2.0\n"
|
||||
|
@ -52,6 +51,16 @@ msgstr "%(ip)s/%(device)s démonté (d'après la réponse)"
|
|||
msgid "%(msg)s %(ip)s:%(port)s/%(device)s"
|
||||
msgstr "%(msg)s %(ip)s:%(port)s/%(device)s"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(reconstructed)d/%(total)d (%(percentage).2f%%) partitions of %(device)d/"
|
||||
"%(dtotal)d (%(dpercentage).2f%%) devices reconstructed in %(time).2fs "
|
||||
"(%(rate).2f/sec, %(remaining)s remaining)"
|
||||
msgstr ""
|
||||
"%(reconstructed)d/%(total)d (%(percentage).2f%%) partitions sur %(device)d/"
|
||||
"%(dtotal)d (%(dpercentage).2f%%) périphériques reconstruites en %(time).2fs "
|
||||
"(%(rate).2f/sec, %(remaining)s remaining)"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(replicated)d/%(total)d (%(percentage).2f%%) partitions replicated in "
|
||||
|
@ -68,14 +77,6 @@ msgstr "%(success)s succès, %(failure)s échec(s)"
|
|||
msgid "%(type)s returning 503 for %(statuses)s"
|
||||
msgstr "%(type)s : renvoi de l'erreur 503 pour %(statuses)s"
|
||||
|
||||
#, python-format
|
||||
msgid "%s #%d not running (%s)"
|
||||
msgstr "%s #%d n'est pas demarré (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "%s (%s) appears to have stopped"
|
||||
msgstr "%s (%s) semble s'être arrêté"
|
||||
|
||||
#, python-format
|
||||
msgid "%s already started..."
|
||||
msgstr "%s déjà démarré..."
|
||||
|
@ -89,12 +90,8 @@ msgid "%s is not mounted"
|
|||
msgstr "%s n'est pas monté"
|
||||
|
||||
#, python-format
|
||||
msgid "%s running (%s - %s)"
|
||||
msgstr "%s en cours d'exécution (%s - %s)"
|
||||
|
||||
#, python-format
|
||||
msgid "%s: %s"
|
||||
msgstr "%s : %s"
|
||||
msgid "%s responded as unmounted"
|
||||
msgstr "%s ont été identifié(es) comme étant démonté(es)"
|
||||
|
||||
#, python-format
|
||||
msgid "%s: Connection reset by peer"
|
||||
|
@ -134,10 +131,6 @@ msgstr ", return codes: "
|
|||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#, python-format
|
||||
msgid "Account %s has not been reaped since %s"
|
||||
msgstr "Le compte %s n'a pas été collecté depuis %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Account audit \"once\" mode completed: %.02fs"
|
||||
msgstr "Audit de compte en mode \"Once\" terminé : %.02fs"
|
||||
|
@ -153,10 +146,6 @@ msgstr ""
|
|||
"Tentative de réplication de %(count)d bases de données en %(time).5f "
|
||||
"secondes (%(rate).5f/s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Audit Failed for %s: %s"
|
||||
msgstr "Echec de l'audit pour %s : %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Bad rsync return code: %(ret)d <- %(args)s"
|
||||
msgstr "Code retour Rsync non valide : %(ret)d <- %(args)s"
|
||||
|
@ -183,10 +172,6 @@ msgstr ""
|
|||
msgid "Begin container update sweep"
|
||||
msgstr "Démarrer le balayage des mises à jour du conteneur"
|
||||
|
||||
#, python-format
|
||||
msgid "Begin object audit \"%s\" mode (%s%s)"
|
||||
msgstr "Démarrer l'audit d'objet en mode \"%s\" (%s%s)"
|
||||
|
||||
msgid "Begin object update single threaded sweep"
|
||||
msgstr ""
|
||||
"Démarrer le balayage des mises à jour d'objet (unité d'exécution unique)"
|
||||
|
@ -224,6 +209,9 @@ msgstr "Client déconnecté lors de la lecture"
|
|||
msgid "Client disconnected without sending enough data"
|
||||
msgstr "Client déconnecté avant l'envoi de toutes les données requises"
|
||||
|
||||
msgid "Client disconnected without sending last chunk"
|
||||
msgstr "Le client a été déconnecté avant l'envoi du dernier bloc"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Client path %(client)s does not match path stored in object metadata %(meta)s"
|
||||
|
@ -231,14 +219,22 @@ msgstr ""
|
|||
"Le chemin d'accès au client %(client)s ne correspond pas au chemin stocké "
|
||||
"dans les métadonnées d'objet %(meta)s"
|
||||
|
||||
msgid ""
|
||||
"Configuration option internal_client_conf_path not defined. Using default "
|
||||
"configuration, See internal-client.conf-sample for options"
|
||||
msgstr ""
|
||||
"L'option de configuration internal_client_conf_path n'a pas été définie. La "
|
||||
"configuration par défaut est utilisée. Consultez les options dans internal-"
|
||||
"client.conf-sample."
|
||||
|
||||
msgid "Connection refused"
|
||||
msgstr "Connexion refusé"
|
||||
msgstr "Connexion refusée"
|
||||
|
||||
msgid "Connection timeout"
|
||||
msgstr "Connexion timeout"
|
||||
msgstr "Dépassement du délai d'attente de connexion"
|
||||
|
||||
msgid "Container"
|
||||
msgstr "Containeur"
|
||||
msgstr "Conteneur"
|
||||
|
||||
#, python-format
|
||||
msgid "Container audit \"once\" mode completed: %.02fs"
|
||||
|
@ -280,7 +276,7 @@ msgstr "Liaison impossible à %s:%s après une tentative de %s secondes"
|
|||
|
||||
#, python-format
|
||||
msgid "Could not load %r: %s"
|
||||
msgstr "Ne peut pas etre charger %r: %s"
|
||||
msgstr "Impossible de charger %r: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Data download error: %s"
|
||||
|
@ -290,6 +286,10 @@ msgstr "Erreur de téléchargement des données: %s"
|
|||
msgid "Devices pass completed: %.02fs"
|
||||
msgstr "Session d'audit d'unité terminée : %.02fs"
|
||||
|
||||
#, python-format
|
||||
msgid "Directory %r does not map to a valid policy (%s)"
|
||||
msgstr "Le répertoire %r n'est pas mappé à une stratégie valide (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "ERROR %(db_file)s: %(validate_sync_to_err)s"
|
||||
msgstr "ERREUR %(db_file)s : %(validate_sync_to_err)s"
|
||||
|
@ -372,6 +372,11 @@ msgstr ""
|
|||
msgid "ERROR Exception causing client disconnect"
|
||||
msgstr "ERREUR Exception entraînant la déconnexion du client"
|
||||
|
||||
#, python-format
|
||||
msgid "ERROR Exception transferring data to object servers %s"
|
||||
msgstr ""
|
||||
"ERREUR Exception lors du transfert de données vers des serveurs d'objets %s"
|
||||
|
||||
msgid "ERROR Failed to get my own IPs?"
|
||||
msgstr "ERREUR Obtention impossible de mes propres adresses IP ?"
|
||||
|
||||
|
@ -550,6 +555,12 @@ msgstr "Erreur de synchronisation de la partition"
|
|||
msgid "Error syncing with node: %s"
|
||||
msgstr "Erreur de synchronisation avec le noeud : %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Error trying to rebuild %(path)s policy#%(policy)d frag#%(frag_index)s"
|
||||
msgstr ""
|
||||
"Une erreur est survenue lors de la tentative de régénération de %(path)s "
|
||||
"policy#%(policy)d frag#%(frag_index)s"
|
||||
|
||||
msgid "Error: An error occurred"
|
||||
msgstr "Erreur : une erreur s'est produite"
|
||||
|
||||
|
@ -569,13 +580,8 @@ msgstr "Exception dans la boucle de collecteur de compte de niveau supérieur"
|
|||
msgid "Exception in top-level replication loop"
|
||||
msgstr "Exception dans la boucle de réplication de niveau supérieur"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception while deleting container %s %s"
|
||||
msgstr "Exception lors de la suppression du conteneur %s %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception while deleting object %s %s %s"
|
||||
msgstr "Exception lors de la suppression de l'objet %s %s %s"
|
||||
msgid "Exception in top-levelreconstruction loop"
|
||||
msgstr "Exception dans la boucle de reconstruction de niveau supérieur"
|
||||
|
||||
#, python-format
|
||||
msgid "Exception with %(ip)s:%(port)s/%(device)s"
|
||||
|
@ -606,7 +612,14 @@ msgstr ""
|
|||
"Suivi de la chaîne CNAME pour %(given_domain)s jusqu'à %(found_domain)s"
|
||||
|
||||
msgid "Found configs:"
|
||||
msgstr "Configurations trouvés:"
|
||||
msgstr "Configurations trouvées :"
|
||||
|
||||
msgid ""
|
||||
"Handoffs first mode still has handoffs remaining. Aborting current "
|
||||
"replication pass."
|
||||
msgstr ""
|
||||
"Le premier mode de transferts contient d'autres transferts. Abandon de la "
|
||||
"session de réplication en cours."
|
||||
|
||||
msgid "Host unreachable"
|
||||
msgstr "Hôte inaccessible"
|
||||
|
@ -627,6 +640,10 @@ msgstr "Hôte %r non valide dans X-Container-Sync-To"
|
|||
msgid "Invalid pending entry %(file)s: %(entry)s"
|
||||
msgstr "Entrée en attente non valide %(file)s : %(entry)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Invalid response %(resp)s from %(full_path)s"
|
||||
msgstr "Réponse %(resp)s non valide de %(full_path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Invalid response %(resp)s from %(ip)s"
|
||||
msgstr "Réponse %(resp)s non valide de %(ip)s"
|
||||
|
@ -662,6 +679,10 @@ msgstr "Aucun noeud final de cluster pour %r %r"
|
|||
msgid "No permission to signal PID %d"
|
||||
msgstr "Aucun droit pour signaler le PID %d"
|
||||
|
||||
#, python-format
|
||||
msgid "No policy with index %s"
|
||||
msgstr "Aucune statégie avec un index de type %s"
|
||||
|
||||
#, python-format
|
||||
msgid "No realm key for %r"
|
||||
msgstr "Aucune clé de domaine pour %r"
|
||||
|
@ -672,6 +693,11 @@ msgstr ""
|
|||
"Noeud marqué avec limite d'erreurs (error_limited) %(ip)s:%(port)s "
|
||||
"(%(device)s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Not enough object servers ack'ed (got %d)"
|
||||
msgstr ""
|
||||
"Le nombre de serveurs d'objets reconnus n'est pas suffisant (%d obtenus)"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Not found %(sync_from)r => %(sync_to)r - object "
|
||||
|
@ -680,6 +706,10 @@ msgstr ""
|
|||
"Introuvable : %(sync_from)r => %(sync_to)r - objet "
|
||||
"%(obj_name)r"
|
||||
|
||||
#, python-format
|
||||
msgid "Nothing reconstructed for %s seconds."
|
||||
msgstr "Aucun élément reconstruit pendant %s secondes."
|
||||
|
||||
#, python-format
|
||||
msgid "Nothing replicated for %s seconds."
|
||||
msgstr "Aucun élément répliqué pendant %s secondes."
|
||||
|
@ -714,10 +744,32 @@ msgstr ""
|
|||
"total d'octets/sec : %(brate).2f. Durée d'audit : %(audit).2f. Taux : "
|
||||
"%(audit_rate).2f"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Object audit (%(type)s). Since %(start_time)s: Locally: %(passes)d passed, "
|
||||
"%(quars)d quarantined, %(errors)d errors, files/sec: %(frate).2f, bytes/sec: "
|
||||
"%(brate).2f, Total time: %(total).2f, Auditing time: %(audit).2f, Rate: "
|
||||
"%(audit_rate).2f"
|
||||
msgstr ""
|
||||
"Audit d'objet (%(type)s). Depuis %(start_time)s, localement : %(passes)d "
|
||||
"succès. %(quars)d en quarantaine. %(errors)d erreurs. Fichiers/sec : "
|
||||
"%(frate).2f. Octets/sec : %(brate).2f. Durée totale : %(total).2f. Durée "
|
||||
"d'audit : %(audit).2f. Taux : %(audit_rate).2f"
|
||||
|
||||
#, python-format
|
||||
msgid "Object audit stats: %s"
|
||||
msgstr "Statistiques de l'audit d'objet : %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Object reconstruction complete (once). (%.02f minutes)"
|
||||
msgstr ""
|
||||
"La reconstruction d'objet en mode Once (une fois) est terminée. (%.02f "
|
||||
"minutes)"
|
||||
|
||||
#, python-format
|
||||
msgid "Object reconstruction complete. (%.02f minutes)"
|
||||
msgstr "Reconstruction d'objet terminée. (%.02f minutes)"
|
||||
|
||||
#, python-format
|
||||
msgid "Object replication complete (once). (%.02f minutes)"
|
||||
msgstr ""
|
||||
|
@ -796,10 +848,6 @@ msgstr ""
|
|||
"%(object_path)s n'est pas un répertoire et a donc été mis en quarantaine "
|
||||
"dans %(quar_path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Quarantined %s to %s due to %s database"
|
||||
msgstr "En quarantaine de %s à %s en raison de la base de données %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Quarantining DB %s"
|
||||
msgstr "Mise en quarantaine de la base de données %s"
|
||||
|
@ -822,6 +870,15 @@ msgstr "Suppression de %s objets"
|
|||
msgid "Removing partition: %s"
|
||||
msgstr "Suppression partition: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing pid file %(pid_file)s with wrong pid %(pid)d"
|
||||
msgstr ""
|
||||
"Supression du fichier PID %(pid_file)s, comportant un PID incorrect %(pid)d"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing pid file %s with invalid pid"
|
||||
msgstr "Suppression du fichier pid %s comportant un pid non valide"
|
||||
|
||||
#, python-format
|
||||
msgid "Removing stale pid file %s"
|
||||
msgstr "Suppression du fichier PID %s périmé"
|
||||
|
@ -841,6 +898,11 @@ msgstr ""
|
|||
"Renvoi de 498 pour %(meth)s jusqu'à %(acc)s/%(cont)s/%(obj)s . Ratelimit "
|
||||
"(Max Sleep) %(e)s"
|
||||
|
||||
msgid "Ring change detected. Aborting current reconstruction pass."
|
||||
msgstr ""
|
||||
"Changement d'anneau détecté. Abandon de la session de reconstruction en "
|
||||
"cours."
|
||||
|
||||
msgid "Ring change detected. Aborting current replication pass."
|
||||
msgstr ""
|
||||
"Changement d'anneau détecté. Abandon de la session de réplication en cours."
|
||||
|
@ -849,13 +911,12 @@ msgstr ""
|
|||
msgid "Running %s once"
|
||||
msgstr "Exécution unique de %s"
|
||||
|
||||
msgid "Running object reconstructor in script mode."
|
||||
msgstr "Exécution du reconstructeur d'objet en mode script."
|
||||
|
||||
msgid "Running object replicator in script mode."
|
||||
msgstr "Exécution du réplicateur d'objet en mode script."
|
||||
|
||||
#, python-format
|
||||
msgid "Signal %s pid: %s signal: %s"
|
||||
msgstr "Signal %s pid: %s signal: %s"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Since %(time)s: %(sync)s synced [%(delete)s deletes, %(put)s puts], %(skip)s "
|
||||
|
@ -890,6 +951,12 @@ msgstr "%s est ignoré car il n'est pas monté"
|
|||
msgid "Starting %s"
|
||||
msgstr "Démarrage %s"
|
||||
|
||||
msgid "Starting object reconstruction pass."
|
||||
msgstr "Démarrage de la session de reconstruction d'objet."
|
||||
|
||||
msgid "Starting object reconstructor in daemon mode."
|
||||
msgstr "Démarrage du reconstructeur d'objet en mode démon."
|
||||
|
||||
msgid "Starting object replication pass."
|
||||
msgstr "Démarrage de la session de réplication d'objet."
|
||||
|
||||
|
@ -915,10 +982,20 @@ msgstr ""
|
|||
msgid "Timeout %(action)s to memcached: %(server)s"
|
||||
msgstr "Délai d'attente de %(action)s dans memcached : %(server)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Timeout Exception with %(ip)s:%(port)s/%(device)s"
|
||||
msgstr ""
|
||||
"Exception liée à un dépassement de délai concernant %(ip)s:%(port)s/"
|
||||
"%(device)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to %(method)s %(path)s"
|
||||
msgstr "Tentative d'exécution de %(method)s %(path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to GET %(full_path)s"
|
||||
msgstr "Tentative de lecture de %(full_path)s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to get final status of PUT to %s"
|
||||
msgstr "Tentative d'obtention du statut final de l'opération PUT sur %s"
|
||||
|
@ -932,6 +1009,10 @@ msgstr "Tentative de lecture pendant une opération GET (nouvelle tentative)"
|
|||
msgid "Trying to send to client"
|
||||
msgstr "Tentative d'envoi au client"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to sync suffixes with %s"
|
||||
msgstr "Tentative de synchronisation de suffixes à l'aide de %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Trying to write to %s"
|
||||
msgstr "Tentative d'écriture sur %s"
|
||||
|
@ -940,24 +1021,25 @@ msgid "UNCAUGHT EXCEPTION"
|
|||
msgstr "EXCEPTION NON INTERCEPTEE"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to find %s config section in %s"
|
||||
msgstr "Impossuble de trouvé la section configuration %s dans %s"
|
||||
msgid "Unable to load internal client from config: %r (%s)"
|
||||
msgstr ""
|
||||
"Impossible de charger le client interne depuis la configuration : %r (%s)"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to locate %s in libc. Leaving as a no-op."
|
||||
msgstr ""
|
||||
"Impossible de localiser %s dans libc. Laissé comme action nulle (no-op)."
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to locate config for %s"
|
||||
msgstr "Impossible de trouver la configuration pour %s"
|
||||
|
||||
msgid ""
|
||||
"Unable to locate fallocate, posix_fallocate in libc. Leaving as a no-op."
|
||||
msgstr ""
|
||||
"Impossible de localiser fallocate, posix_fallocate dans libc. Laissé comme "
|
||||
"action nulle (no-op)."
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to perform fsync() on directory %s: %s"
|
||||
msgstr "Impossible d'exécuter fsync() dans le répertoire %s : %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to read config from %s"
|
||||
msgstr "Impossible de lire le fichier de configuration depuis %s"
|
||||
|
@ -973,6 +1055,12 @@ msgstr "Réponse inattendue : %s"
|
|||
msgid "Unhandled exception"
|
||||
msgstr "Exception non prise en charge"
|
||||
|
||||
#, python-format
|
||||
msgid "Unknown exception trying to GET: %(account)r %(container)r %(object)r"
|
||||
msgstr ""
|
||||
"Une exception inconnue s'est produite pendant une opération GET: %(account)r "
|
||||
"%(container)r %(object)r"
|
||||
|
||||
#, python-format
|
||||
msgid "Update report failed for %(container)s %(dbfile)s"
|
||||
msgstr "Echec du rapport de mise à jour pour %(container)s %(dbfile)s"
|
||||
|
@ -1003,10 +1091,6 @@ msgstr ""
|
|||
"AVERTISSEMENT : Impossible de modifier la limite de mémoire. Exécution en "
|
||||
"tant que non root ?"
|
||||
|
||||
#, python-format
|
||||
msgid "Waited %s seconds for %s to die; giving up"
|
||||
msgstr "Attente de %s secondes pour la fin de %s ; abandon"
|
||||
|
||||
msgid "Warning: Cannot ratelimit without a memcached client"
|
||||
msgstr "Avertissement : impossible d'appliquer Ratelimit sans client memcached"
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue