Merge branch 'master' into feature/hummingbird

Change-Id: Ie90e2bc680570395791b2fb1e3bdc58466bb21a2
This commit is contained in:
John Dickinson 2016-05-31 14:42:07 -07:00
commit 0330478b70
192 changed files with 14734 additions and 7725 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ ChangeLog
.coverage
*.egg
*.egg-info
.eggs/*
.DS_Store
.tox
pycscope.*

View File

@ -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
View File

@ -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
View File

@ -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

View File

@ -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.

182
CONTRIBUTING.rst Normal file
View File

@ -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.

View File

@ -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

View File

@ -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

145
README.rst Normal file
View File

@ -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

387
REVIEW_GUIDELINES.rst Normal file
View File

@ -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.

View File

@ -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))

View File

@ -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))

View File

@ -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()

View File

@ -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.
"""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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/"

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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. |
+------------------------------------------------+------------------------------+

View File

@ -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]

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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
-------------------------------------------------------

View File

@ -58,6 +58,7 @@ Overview and Concepts
crossdomain
overview_erasure_code
overview_backing_store
ring_background
associated_projects
Developer Documentation

View File

@ -103,6 +103,7 @@ LE :ref:`list_endpoints`
KS :ref:`keystoneauth`
RL :ref:`ratelimit`
VW :ref:`versioned_writes`
SSC :ref:`copy`
======================= =============================

View File

@ -187,6 +187,15 @@ Recon
:members:
:show-inheritance:
.. _copy:
Server Side Copy
================
.. automodule:: swift.common.middleware.copy
:members:
:show-inheritance:
Static Large Objects
====================

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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`.

View File

@ -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 thats not bad at all; less than a percent over/under for distribution
per node. In the next part of this series well 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 thats 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 lets imagine we have a 100 node system up and running using our
previous algorithm, but its 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.
Were going to have to shuffle a ton of data around our system to get
it all into place again.
Lets examine whats 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.
Lets 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, thats severe. Wed 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, lets say our ids range from 0
to 999. We have two nodes and well assign data ids 0499 to node 0 and
500999 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.
Lets 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 well see what
is an “accordion effect”. We shrunk node 0s range a bit to give to the
new node, but that shifted all the other nodes ranges by the same amount.
We can minimize the change to a nodes 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. Lets 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. Its values
are calculated and never change for the lifetime of the cluster, so lets
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
algorithms 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 cant 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 nodes 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.
Lets continue with this assumption that changing the virtual node
count is more work than its 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 wont 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 wed 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 were 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 wed
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 isnt that much to use these days.
Even with all the overhead of flexible data types, things arent 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, were going to start calling these virtual
nodes “partitions”. This will make it a bit easier to discern between the
two types of nodes weve been talking about so far. Also, it makes sense
to talk about partitions as they are really just unchanging sections
of the hash space.
Were 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 isnt much faster, but it is a little.
So, heres our updated ring code, using 8,388,608 (2 ** 23) partitions
and 65,536 nodes. Weve upped the sample data id set and checked the
distribution to make sure we havent 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 its just that our sample size (100m) is
too small for our number of partitions (8m). Itll take way too long
to run experiments with an even larger sample size, so lets 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, well 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, lets 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, youd 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 nodes
capacity.
Instead, lets 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.
Heres 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
Thats pretty good; less than 1% over/under. While this works well,
there are a couple of problems.
First, because of how weve 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. Itd 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 weve 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.
Theres a completely alternate, and quite common, way of accomplishing
these same requirements. This alternate method doesnt 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 well just stick with that.
In the next part of this series, well 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 well package
the code up into a useable Python module and then add one last feature.
First, lets 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 thats 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
were still on track for further testing; our distribution is still good.
Now, lets add our one last feature to our ring: the concept of weights.
Weights are useful because the nodes you add later in a rings life are
likely to have more capacity than those you have at the outset. For this
test, well make half our nodes have twice the weight. Well have to
change build_ring to give more partitions to the nodes with more weight
and well change test_ring to take into account these weights. Since
weve changed so much Ill 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
Swifts 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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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)")

15
other-requirements.txt Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -21,4 +21,4 @@ class AccountReplicator(db_replicator.Replicator):
server_type = 'account'
brokerclass = AccountBroker
datadir = DATADIR
default_port = 6002
default_port = 6202

View File

@ -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()

View File

@ -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)
"""

View File

@ -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]
], [

View File

@ -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

View File

@ -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.

View File

@ -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 = {}

View File

@ -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'))

View File

@ -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)

View File

@ -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):

View File

@ -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())

View File

@ -145,10 +145,6 @@ class LockTimeout(MessageTimeout):
pass
class ThreadPoolDead(SwiftException):
pass
class RingBuilderError(SwiftException):
pass

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.')

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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()

View File

@ -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]),

View File

@ -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)

View File

@ -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::

View File

@ -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:

View File

@ -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':

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

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

@ -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):
"""

View File

@ -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)

View File

@ -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()

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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(

View File

@ -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 "

View File

@ -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 "

View File

@ -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