Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: I521e44397eaf2841c255de75600895c3f6ec6f7b
This commit is contained in:
Tony Breeds 2017-09-12 16:11:54 -06:00
parent 3db6ddd6e4
commit 61c3872483
67 changed files with 14 additions and 23522 deletions

View File

@ -1,6 +0,0 @@
[run]
branch = True
source = swiftclient
[report]
ignore_errors = True

View File

@ -1,10 +0,0 @@
#!/bin/bash
set -e
export OS_TEST_PATH='tests.functional'
python setup.py testr --coverage --testr-args="--concurrency=1"
RET=$?
coverage report -m
rm -f .coverage
exit $RET

18
.gitignore vendored
View File

@ -1,18 +0,0 @@
*.sw?
dist/
.tox
*.egg
*.egg-info
*.py[co]
.DS_Store
*.log
.testrepository
subunit.log
build
swiftclient/versioninfo
.autogenerated
.coverage
cover/
coverage.xml
doc/build
doc/source/api/

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/python-swiftclient.git

View File

@ -1,96 +0,0 @@
Greg Holt <gholt@rackspace.com> gholt <gholt@brim.net>
Greg Holt <gholt@rackspace.com> gholt <devnull@brim.net>
Greg Holt <gholt@rackspace.com> gholt <z-github@brim.net>
Greg Holt <gholt@rackspace.com> gholt <z-launchpad@brim.net>
Greg Holt <gholt@rackspace.com> <gregory.holt+launchpad.net@gmail.com>
Greg Holt <gholt@rackspace.com>
John Dickinson <me@not.mn> <john.dickinson@rackspace.com>
Michael Barton <mike@weirdlooking.com> <michael.barton@rackspace.com>
Michael Barton <mike@weirdlooking.com> <mike-launchpad@weirdlooking.com>
Michael Barton <mike@weirdlooking.com> Mike Barton
Clay Gerrard <clay.gerrard@gmail.com> <clayg@clayg-desktop>
Clay Gerrard <clay.gerrard@gmail.com> <clay.gerrard@rackspace.com>
Clay Gerrard <clay.gerrard@gmail.com> <clay@swiftstack.com>
Clay Gerrard <clay.gerrard@gmail.com> clayg <clay.gerrard@gmail.com>
David Goetz <david.goetz@rackspace.com> <david.goetz@gmail.com>
David Goetz <david.goetz@rackspace.com> <dpgoetz@gmail.com>
Anne Gentle <anne@openstack.org> <anne.gentle@rackspace.com>
Anne Gentle <anne@openstack.org> annegentle
Fujita Tomonori <fujita.tomonori@lab.ntt.co.jp>
Greg Lange <greglange@gmail.com> <glange@rackspace.com>
Greg Lange <greglange@gmail.com> <greglange+launchpad@gmail.com>
Chmouel Boudjnah <chmouel@enovance.com> <chmouel@chmouel.com>
Gaurav B. Gangalwar <gaurav@gluster.com> gaurav@gluster.com <>
Joe Arnold <joe@swiftstack.com> <joe@cloudscaling.com>
Kapil Thangavelu <kapil.foss@gmail.com> kapil.foss@gmail.com <>
Samuel Merritt <sam@swiftstack.com> <spam@andcheese.org>
Morita Kazutaka <morita.kazutaka@gmail.com>
Zhongyue Luo <zhongyue.nah@intel.com> <lzyeval@gmail.com>
Russ Nelson <russ@crynwr.com> <nelson@nelson-laptop>
Marcelo Martins <btorch@gmail.com> <marcelo.martins@rackspace.com>
Andrew Clay Shafer <acs@parvuscaptus.com> <andrew@cloudscaling.com>
Soren Hansen <soren@linux2go.dk> <soren.hansen@rackspace.com>
Soren Hansen <soren@linux2go.dk> <sorhanse@cisco.com>
Ye Jia Xu <xyj.asmy@gmail.com> monsterxx03 <xyj.asmy@gmail.com>
Victor Rodionov <victor.rodionov@nexenta.com> <vito.ordaz@gmail.com>
Florian Hines <syn@ronin.io> <florian.hines@gmail.com>
Jay Payne <letterj@gmail.com> <letterj@racklabs.com>
Doug Weimer <dweimer@gmail.com> <dougw@sdsc.edu>
Li Riqiang <lrqrun@gmail.com> lrqrun <lrqrun@gmail.com>
Cory Wright <cory.wright@rackspace.com> <corywright@gmail.com>
Julien Danjou <julien@danjou.info> <julien.danjou@enovance.com>
David Hadas <davidh@il.ibm.com> <david.hadas@gmail.com>
Yaguang Wang <yaguang.wang@intel.com> ywang19 <yaguang.wang@intel.com>
Liu Siqi <meizu647@gmail.com> dk647 <meizu647@gmail.com>
James E. Blair <jeblair@openstack.org> <james.blair@rackspace.com>
Kun Huang <gareth@unitedstack.com> <academicgareth@gmail.com>
Michael Shuler <mshuler@gmail.com> <mshuler@rackspace.com>
Ilya Kharin <ikharin@mirantis.com> <akscram@gmail.com>
Dmitry Ukov <dukov@mirantis.com> Ukov Dmitry <dukov@mirantis.com>
Tom Fifield <tom@openstack.org> Tom Fifield <fifieldt@unimelb.edu.au>
Sascha Peilicke <saschpe@gmx.de> Sascha Peilicke <saschpe@suse.de>
Zhenguo Niu <zhenguo@unitedstack.com> <Niu.ZGlinux@gmail.com>
Peter Portante <peter.portante@redhat.com> <peter.a.portante@gmail.com>
Christian Schwede <cschwede@redhat.com> <info@cschwede.de>
Christian Schwede <cschwede@redhat.com> <christian.schwede@enovance.com>
Constantine Peresypkin <constantine.peresypk@rackspace.com> <constantine@litestack.com>
Madhuri Kumari <madhuri.rai07@gmail.com> madhuri <madhuri@madhuri-VirtualBox.(none)>
Morgan Fainberg <morgan.fainberg@gmail.com> <m@metacloud.com>
Hua Zhang <zhuadl@cn.ibm.com> <zhuadl@cn.ibm.com>
Yummy Bian <yummy.bian@gmail.com> <yummy.bian@gmail.com>
Alistair Coles <alistair.coles@hpe.com> <alistair.coles@hp.com>
Tong Li <litong01@us.ibm.com> <litong01@us.ibm.com>
Paul Luse <paul.e.luse@intel.com> <paul.e.luse@intel.com>
Yuan Zhou <yuan.zhou@intel.com> <yuan.zhou@intel.com>
Jola Mirecka <jola.mirecka@hp.com> <jola.mirecka@hp.com>
Ning Zhang <ning@zmanda.com> <ning@zmanda.com>
Mauro Stettler <mauro.stettler@gmail.com> <mauro.stettler@gmail.com>
Pawel Palucki <pawel.palucki@gmail.com> <pawel.palucki@gmail.com>
Guang Yee <guang.yee@hp.com> <guang.yee@hp.com>
Jing Liuqing <jing.liuqing@99cloud.net> <jing.liuqing@99cloud.net>
Lorcan Browne <lorcan.browne@hp.com> <lorcan.browne@hp.com>
Eohyung Lee <liquidnuker@gmail.com> <liquid@kt.com>
Harshit Chitalia <harshit@acelio.com> <harshit@acelio.com>
Richard Hawkins <richard.hawkins@rackspace.com>
Sarvesh Ranjan <saranjan@cisco.com>
Minwoo Bae <minwoob@us.ibm.com> Minwoo B
Jaivish Kothari <jaivish.kothari@nectechnologies.in> <janonymous.codevulture@gmail.com>
Michael Matur <michael.matur@gmail.com>
Kazuhiro Miyahara <miyahara.kazuhiro@lab.ntt.co.jp>
Alexandra Settle <alexandra.settle@rackspace.com>
Mark Seger <mark.seger@hpe.com> <mark.seger@hp.com>
Donagh McCabe <donagh.mccabe@hpe.com> <donagh.mccabe@hp.com>
Stuart McLaren <stuart.mclaren@hpe.com> <stuart.mclaren@hp.com>
Alexis Lee <lxsli@hpe.com> <alexisl@hp.com>
Stanislaw Pitucha <stanislaw.pitucha@hpe.com> <stanislaw.pitucha@hp.com>
Mahati Chamarthy <mahati.chamarthy@gmail.com>
Peter Lisak <peter.lisak@firma.seznam.cz>
Doug Hellmann <doug@doughellmann.com> <doug.hellmann@dreamhost.com>
Ondrej Novy <ondrej.novy@firma.seznam.cz>
James Nzomo <james@tdt.rocks> <kazikubwa@gmail.com>
Alessandro Pilotti <ap@pilotti.it> <apilotti@cloudbasesolutions.com>
Marek Kaleta <marek.kaleta@firma.seznam.cz> <Marek.Kaleta@firma.seznam.cz>
Andreas Jaeger <aj@suse.de> <aj@suse.com>
Shashi Kant <shashi.kant@nectechnologies.in>
Nandini Tata <nandini.tata@intel.com> <nandini.tata.15@gmail.com>
Flavio Percoco <flaper87@gmail.com>

View File

@ -1,18 +0,0 @@
#!/bin/sh
RET=0
for MAN in doc/manpages/* ; do
OUTPUT=$(LC_ALL=en_US.UTF-8 MANROFFSEQ='' MANWIDTH=80 man --warnings -E UTF-8 -l \
-Tutf8 -Z "$MAN" 2>&1 >/dev/null)
if [ -n "$OUTPUT" ] ; then
RET=1
echo "$MAN:"
echo "$OUTPUT"
fi
done
if [ "$RET" -eq "0" ] ; then
echo "All manpages are fine"
fi
exit "$RET"

View File

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

View File

@ -1,8 +0,0 @@
#!/bin/bash
set -e
python setup.py testr --coverage --testr-args="tests.unit"
RET=$?
coverage report -m
rm -f .coverage
exit $RET

143
AUTHORS
View File

@ -1,143 +0,0 @@
Alessandro Pilotti (ap@pilotti.it)
Alex Gaynor (alex.gaynor@gmail.com)
Alexandra Settle (alexandra.settle@rackspace.com)
Alexis Lee (lxsli@hpe.com)
Alistair Coles (alistair.coles@hpe.com)
Andreas Jaeger (aj@suse.de)
Andrew Welleck (awellec@us.ibm.com)
Andy McCrae (andy.mccrae@gmail.com)
Anh Tran (anhtt@vn.fujitsu.com)
Anne Gentle (anne@openstack.org)
Ben McCann (ben@benmccann.com)
Cedric Brandily (zzelle@gmail.com)
Chaozhe.Chen (chaozhe.chen@easystack.cn)
Charles Hsu (charles0126@gmail.com)
Cheng Li (shcli@cn.ibm.com)
Chmouel Boudjnah (chmouel@enovance.com)
Chris Buccella (chris.buccella@antallagon.com)
Christian Berendt (berendt@b1-systems.de)
Christian Schwede (cschwede@redhat.com)
Christopher Bartz (bartz@dkrz.de)
Chuck Short (chuck.short@canonical.com)
Clark Boylan (clark.boylan@gmail.com)
Claudiu Belu (cbelu@cloudbasesolutions.com)
Clay Gerrard (clay.gerrard@gmail.com)
Clint Byrum (clint@fewbar.com)
Dan Prince (dprince@redhat.com)
Daniel Wakefield (daniel.wakefield@hp.com)
Darrell Bishop (darrell@swiftstack.com)
David Goetz (david.goetz@rackspace.com)
David Kranz (david.kranz@qrclab.com)
David Shrewsbury (shrewsbury.dave@gmail.com)
Davide Guerri (davide.guerri@hp.com)
Dean Troyer (dtroyer@gmail.com)
Dirk Mueller (dirk@dmllr.de)
Donagh McCabe (donagh.mccabe@hpe.com)
Doug Hellmann (doug@doughellmann.com)
EdLeafe (ed@leafe.com)
Fabien Boucher (fabien.boucher@enovance.com)
Feng Liu (mefengliu23@gmail.com)
Flavio Percoco (flaper87@gmail.com)
Florent Flament (florent.flament-ext@cloudwatt.com)
Greg Holt (gholt@rackspace.com)
Greg Lange (greglange@gmail.com)
groqez (groqez@yopmail.net)
Hemanth Makkapati (hemanth.makkapati@mailtrust.com)
hgangwx (hgangwx@cn.ibm.com)
Hirokazu Sakata (h.sakata@staff.east.ntt.co.jp)
Hiroshi Miura (miurahr@nttdata.co.jp)
howardlee (lihongweibj@inspur.com)
Hu Bing (hubingsh@cn.ibm.com)
Ian Cordasco (ian.cordasco@rackspace.com)
Jaivish Kothari (jaivish.kothari@nectechnologies.in)
Jakub Krajcovic (jakub.krajcovic@gmail.com)
James Nzomo (james@tdt.rocks)
Jamie Lennox (jamielennox@gmail.com)
Jeremy Stanley (fungi@yuggoth.org)
Ji-Wei (ji.wei3@zte.com.cn)
Jian Zhang (jian.zhang@intel.com)
Jing Liuqing (jing.liuqing@99cloud.net)
Jiří Suchomel (jsuchome@suse.cz)
Joel Wright (joel.wright@sohonet.com)
John Dickinson (me@not.mn)
Jola Mirecka (jola.mirecka@hp.com)
Josh Gachnang (josh@pcsforeducation.com)
Juan J. Martinez (juan@memset.com)
Jude Job (judeopenstack@gmail.com)
Julien Danjou (julien@danjou.info)
Kota Tsuyuzaki (tsuyuzaki.kota@lab.ntt.co.jp)
Kun Huang (gareth@unitedstack.com)
Leah Klearman (lklrmn@gmail.com)
Li Riqiang (lrqrun@gmail.com)
Luis de Bethencourt (luis@debethencourt.com)
Mahati Chamarthy (mahati.chamarthy@gmail.com)
Marek Kaleta (marek.kaleta@firma.seznam.cz)
Mark Seger (mark.seger@hpe.com)
Mark Washenberger (mark.washenberger@rackspace.com)
Martin Geisler (martin@geisler.net)
Matthew Oliver (matt@oliver.net.au)
Matthieu Huin (mhu@enovance.com)
Mike Widman (mwidman@endurancewindpower.com)
Min Min Ren (rminmin@cn.ibm.com)
Mohit Motiani (mohit.motiani@intel.com)
Monty Taylor (mordred@inaugust.com)
Nandini Tata (nandini.tata@intel.com)
Nguyen Hung Phuong (phuongnh@vn.fujitsu.com)
Nick Craig-Wood (nick@craig-wood.com)
Ondrej Novy (ondrej.novy@firma.seznam.cz)
Pallavi (pallavi.s@nectechnologies.in)
Paul Belanger (pabelanger@redhat.com)
Paulo Ewerton (pauloewerton@lsd.ufcg.edu.br)
Pete Zaitcev (zaitcev@kotori.zaitcev.us)
Peter Lisak (peter.lisak@firma.seznam.cz)
Pradeep Kumar Singh (pradeep.singh@nectechnologies.in)
Pratik Mallya (pratik.mallya@gmail.com)
Qiu Yu (qiuyu@ebaysf.com)
Ray Chen (oldsharp@163.com)
ricolin (rico.l@inwinstack.com)
Romain Hardouin (romain_hardouin@yahoo.fr)
Sahid Orentino Ferdjaoui (sahid.ferdjaoui@cloudwatt.com)
SaiKiran (saikiranveeravarapu@gmail.com)
Sam Morrison (sorrison@gmail.com)
Samuel Merritt (sam@swiftstack.com)
Sean Dague (sean@dague.net)
Sergey Gotliv (sgotliv@redhat.com)
Sergio Cazzolato (sergio.j.cazzolato@intel.com)
Shane Wang (shane.wang@intel.com)
Shashi Kant (shashi.kant@nectechnologies.in)
Shashirekha Gundur (shashirekha.j.gundur@intel.com)
shu-mutou (shu-mutou@rf.jp.nec.com)
Stanislav Vitkovskiy (stas.vitkovsky@gmail.com)
Stanislaw Pitucha (stanislaw.pitucha@hpe.com)
Steve Martinelli (stevemar@ca.ibm.com)
Steven Hardy (shardy@redhat.com)
Stuart McLaren (stuart.mclaren@hpe.com)
Sushil Kumar (sushil.kumar2@globallogic.com)
tanlin (lin.tan@intel.com)
Taurus Cheung (Taurus.Cheung@harmonicinc.com)
TheSriram (sriram@klusterkloud.com)
Thiago da Silva (thiago@redhat.com)
Thomas Goirand (thomas@goirand.fr)
Tihomir Trifonov (t.trifonov@gmail.com)
Tim Burke (tim.burke@gmail.com)
Tong Li (litong01@us.ibm.com)
Tony Breeds (tony@bakeyournoodle.com)
Tristan Cacqueray (tristan.cacqueray@enovance.com)
Vasyl Khomenko (vasiliyk@yahoo-inc.com)
venkatamahesh (venkatamaheshkotha@gmail.com)
Victor Stinner (victor.stinner@enovance.com)
wangxiyuan (wangxiyuan@huawei.com)
Wu Wenxiang (wu.wenxiang@99cloud.net)
YangLei (yanglyy@cn.ibm.com)
yangxurong (yangxurong@huawei.com)
You Yamagata (bi.yamagata@gmail.com)
Yuan Zhou (yuan.zhou@intel.com)
Yushiro FURUKAWA (y.furukawa_2@jp.fujitsu.com)
yuxcer (yuxcer@126.com)
yuyafei (yu.yafei@zte.com.cn)
YUZAWA Takahiko (yuzawataka@intellilink.co.jp)
Zack M. Davis (zdavis@swiftstack.com)
zhang-jinnan (ben.os@99cloud.net)
zhangyanxian (zhangyanxianmail@163.com)
zheng yin (yin.zheng@easystack.cn)
Zhenguo Niu (zhenguo@unitedstack.com)

View File

@ -1,18 +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
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, not Github:
https://bugs.launchpad.net/python-swiftclient

562
ChangeLog
View File

@ -1,562 +0,0 @@
3.3.0
-----
* Added support for prefix-based tempurls. This allows you to create a
tempurl that is valid for all objects which share a given prefix and
matches the feature in Swift 2.12.0 and later.
* In the SDK, we previously only accepted iterables of strings like
'Header: Value'. Now, we'll also accept lists of tuples like
('Header', 'Value') as well as dictionaries like {'Header': 'Value'}.
* Improved help message strings
* Various other minor bug fixes and improvements.
3.2.0
-----
* Added Keystone session support and a "v1password" plugin for Keystone.
This plugin provides a way for Keystone sessions (and clients that
use them, like python-openstackclient) to communicate with old auth
endpoints that still use this mechanism.
* HEAD, GET, and DELETE now support sending additional headers to match
existing functionality on PUT requests.
* Various other minor bug fixes and improvements.
3.1.0
-----
* Added a copy object method.
* Arbitrary query strings can now be passed into container functions.
* Client certificate and key can now be specified via CLI
options (--os-cert/--os-key) or environment variables ($OS_CERT/$OS_KEY).
* A new CLI option `--ignore-checksum` can be specified to turn off
checksum validation. In the SDK, the new `checksum=True` parameter can
be used for the same purpose.
* Added --json option to `swift capabilities` / `swift info`
* Default to v3 auth if we find a (user|project)-domain-(name|id) option.
* Added a Python version constraint of >= Py27.
* `client.py` will now retry on a 401 (auth error) even if `retries` is
set to zero.
* Fixed `swift download` when `marker` was specified.
* Object segments uploaded via swiftclient are now given the content type
"application/swiftclient-segment".
* "Directory marker" objects are now given a "application/directory"
content type to match both Swift's `staticweb` feature and other
ecosystem tools.
* Strip leading/trailing whitespace from headers (otherwise, new versions
of the requests library will raise an InvalidHeader error). Additionally,
header values with standard types (integer, float, or bool) are coerced
to strings before being sent to a socket.
* Non-python dependencies are now specified in bindep.txt. Currently this
only lists a single dependency for testing (PyPy), but if future
dependencies are added, they will be included in this file.
* Client exceptions now include response headers. One benefit is that
this allows clients to see transaction IDs without needing to turn on
debug logging.
* Client connections now accept gzip-encoded responses.
* Various other minor bug fixes and improvements.
3.0.0
-----
* Python 2.6 and Python 3.3 support has been removed. Currently
supported and tested versions of Python are Python 2.7 and Python 3.4.
* Do not reveal sensitive headers in swiftclient log messages by default.
This is controlled by the client.logger_settings dictionary. Setting the
`redact_sensitive_headers` key to False prevents the information hiding. If
the value is True (the default), the `reveal_sensitive_prefix` controls
the maximum length of any sensitive header value logged. The default is
16 to match the default in Swift.
* Object downloads that fail partway through will now retry with a Range
request to read the rest of the object.
* Object uploads will be retried if the source supports seek/tell or has a
reset() method.
* Delete requests will use the cluster's bulk delete feature, if available,
for requests that would require a lot of individual deletes.
* The delete CLI option now accepts a --prefix option to delete objects that
start with the given prefix (similar to the same-named option for list).
* Add support for the auth-version to be specified using
--os-identity-api-version or OS_IDENTITY_API_VERSION
for compatibility with other openstack client command
line options.
* --debug and --info command-line options now work anywhere in the command.
* Objects can now be uploaded to pseudo-directories with the CLI.
* Fixed an issue with uploading a large object that includes a unicode path.
* swiftclient can now auth against Keystone using only a project (tenant)
and a token. This is useful when the client doesn't have access to the
password for a user but otherwise has been granted access.
* Various other minor bug fixes and improvements.
2.7.0
-----
* This is the very last release to support Python 2.6. Any further
development on the 2.7.x release series will only be for security bugfixes.
* Added content type to CLI object list long-form output
* client.get_container() and client.head_object now accept a headers parameter
* Fixed bug when setting Content-Type on upload from CLI
* Fixed bug when deleting DLOs with unicode characters
* Updated man pages and docstrings
* Suppress iso8601 logging in --debug output
* Various other minor bug fixes and improvements.
2.6.0
-----
* Several CLI options have learned short options. The usage strings have
been updated to reflect this.
* Added --no-shuffle option to the CLI download command.
* Added --absolute option for CLI TempURL generation and the corresponding
parameter to utils.generate_temp_url(). This allows for an exact, specific
time to be used for the TempURL expiry time.
* CLI arguments are now always decoded as UTF-8.
* Stop Connection class modifying os_options parameter.
* Reduce memory usage for download/delete.
* The swift service API now logs and reports the traceback
on failed operations.
* Increase httplib._MAXHEADERS to 256 to work around header limits in recent
Python releases.
* Added minimal working service token support to client.py.
* Various other minor bug fixes and improvements.
2.5.0
-----
* The CLI learned an "auth" subcommand which returns bash environment
snippets for auth credentials.
* The CLI --version option is now more explicit by calling itself
"python-swiftclient" rather than the name of the binary.
* Now validates the checksum of each chunk of a large object as it is
uploaded.
* Fixes uploading an object with a relative path.
* Added the ability to download objects to a particular folder.
* Now correctly removes all old segments of an object when replacing a
Dynamic Large Object (DLO).
* The --skip-identical option now works properly when downloading
large objects.
* The client.get_object() response learned a .read([length]) method.
* Fixed an issue where an intermediate caching/proxy service could cause
object content to be improperly decoded.
* Added a timeout parameter to HTTPConnection objects for socket-level
read timeouts.
* Removed a dependency on simplejson.
* Various other minor bug fixes and improvements.
2.4.0
-----
* Mention --segment-size option after 413 response
* Add improvements to MD5 validation
* Unindent a chunk of st_list
* Release connection after consuming the content
* Verify MD5 of uploaded objects
* Fix crash with -l, -d /, and pseudo folders
* add functional tox target
* Fix crash when stat'ing objects with non-ascii names
* Add help message for "<subcommand> --help"
* Fix missing ca-certificate parameter to get_auth
* Fix deleting SLO segments on overwrite
* This patch fixes downloading files to stdout
* Fix environment sanitization for TestServiceUtils
* Fix cross account upload using --os-storage-url
* Change tests to use CaptureOutput class
* Print info message about incorrect --totals usage when neither -l nor --lh is provided. Added test coverage for --totals
* Make preauth params work
* Fix misplaced check for None in SwiftUploadObject
* Fix misnamed dictionary key
* Change tests to use new CaptureOutput class
* Workflow documentation is now in infra-manual
* Show warning when auth_version >= 2 and keystoneclient is missing
* Capture test output better
* Suppress 'No handlers...' message from keystoneclient logger
* Add unit tests for _encode_meta_headers
* Fix misnamed variable in SwiftReader
* Check that content_type header exists before using
* Adds user friendly message when --segment-size is a non-integer
* Make swift post output an error message when failing
* Replaces Stacktraces with useful error messages
* Fix KeyError raised from client Connection
* Fix race in shell when testing for errors to raise SysExit
* Fix race between container create jobs during upload
* Fix the info command with --insecure
* Allow segment size to be specified in a human readable way
* Use skipTest from testtools instead of inherited Exception
* Add tests for account listing using --lh switch
* Do not crash with "swift list --lh" for Ceph RadosGW
2.3.1
-----
* Remove a debugging print statement
* Fix unit tests failing when OS_ env vars are set
* Fix bug with some OS options not being passed to client
* Add per policy container count to account stat output
* Stop creating extraneous directories
2.3.0
-----
* Work toward Python 3.4 support and testing
* Add importable SwiftService incorporating shell.py logic
* Adds console script entry point
* Do not create an empty directory 'pseudo/'
* fixed unit tests when env vars are set
* Fix crash when downloading a pseudo-directory
* Clean up raw policy stats in account stat
* Update theme for docs
* Add a tox job for generating docs
* Add keystone v3 auth support
2.2.0
-----
* Fix context sensitive help for info and tempurl
* Allow to specify storage policy when uploading objects
* Adding Swift Temporary URL support
* Add CONTRIBUTING.md
* Add context sensitive help
* Relax requirement for tenant_name in get_auth()
* replace string format arguments with function parameters
* Removed now unnecessary workaround for PyPy
* Use Emacs-friendly coding line
* Remove extra double quote from docstring
* Fix wrong assertions in unit tests
* fixed several pep8 issues
2.1.0
-----
* Fix Python3 bugs
* Remove testtools.main() call from tests
* Move test_shell.py under tests/unit/
* Mark swiftclient as being a universal wheel
* change assert_ to assertTrue
* change assertEquals to assertEqual
* Provide a link to the documentation to the README
* fixed typos found by RETF rules
* Fix running the unittests under py3
* Add "." for help strings
* Declare that we support Python 3
* Make the function tests Python3-import friendly
* Only encode metadata for user customed headers
* Add functional tests for python-swiftclient
* Removed a duplicate word in a docstring
* Mock auth_end_time in test_shell.test_download
* Don't utf8 encode urls
* Fixed several shell tests on Python3
* Fix up StringIO use in tests for py3
* Updated test_shell for Python3
* Fix test_raw_upload test
* Remove validate_headers
* Use quote/unquote from six module for py3
* Makes use of requests.Session
* Fix test_multithreading on Python 3
* Add tests for bin/swift
* Fix swiftclient.client.quote() for Python 3
* Add requests related unit-tests
* Update help message to specify unit of --segment-size option
* Python 3: fix tests on HTTP headers
* Updated from global requirements
* Use the standard library's copy of mock when it's available
* Replaced print statements with print function
* Removed usage of tuple unpacking in parameters
* don't use mutable defaults in kwargs
* set user-agent header
* Python 3: Get compatible types from six
* Python 3: Fix module names in import
* Python 3: Add six dependency
* Replace dict.iteritems() with dict.items()
* Python 3: Replace iter.next() with six.next(iter)
* Make bin/swift testable part 2
* Make bin/swift testable part 1
* Python 3: Fix tests using temporary text files
* Python 3: cast map() result to list
* Fix temporary pypy gate issue with setuptools
* Decode HTTP responses, fixes bug #1282861
* Copy Swift's .mailmap to swiftclient repo
* Improve help strings
* TCP port is appended two time in ClientException
* add "info" as an alias to "capabilities"
* Use six.StringIO instead of StringIO.StringIO
2.0.3
-----
* Help string format persistent
* Make the help strings constant
* Add LengthWrapper in put_object to honor content_length param
* Updated from global requirements
* Remove useless statement
* swift.1 manpage fix for groff warnings
2.0.2
-----
* Remove multipart/form-data file upload
2.0.1
-----
* Fix --insecure option on auth
* Only run flake8 on swiftclient code
2.0
---
1.9.0
-----
* Remove extraneous vim configuration comments
* Rename Openstack to OpenStack
* Port to python-requests
* Add option to skip downloading/uploading identical files
* Remove tox locale overrides
* Fix swiftclient help
* Fix misspellings in python swiftclient
* changed things because reasons
* Add missing backslash
* match hacking rules in swift
* Updated from global requirements
* Install manpage in share/man/man1 instead of man/man1
* assertEquals is deprecated, use assertEqual
* Add capabilities option
* Install swiftclient manpage
* Replace xrange in for loop with range
* Add --object-name
* retry on ratelimit
* Fix help of some optional arguments
* Updates tox.ini to use new features
* Fix Sphinx version issue
* Enable usage of proxies defined in environment (http(s)_proxy)
* Don't crash when header is value of None
* Fix download bandwidth for swift command
* Updates .gitignore
* Allow custom headers when using swift download (CLI)
* Replaced two references to Cloud Files with Swift
* Fix a typo in help text: "downlad"
* Add close to swiftclient.client.Connection
* enhance swiftclient logging
* Clarify main help for post subcommand
* Fixes python-swiftclient debugging message
1.8.0
-----
* Make pbr only a build-time dependency
* Add verbose output to all stat commands
* assertEquals is deprecated, use assertEqual (H602)
* Skip sniffing and resetting if retry is disabled
* user defined headers added to swift post queries
1.7.0
-----
* Sync with global requirements
* fix bug with replace old *LOs
* Extend usage message for `swift download`
1.6.0
-----
* Added support for running the tests under PyPy with tox
* Remove redundant unit suffix
* Reformat help outputs
* Add a NullHandler when setting up library logging
* Assignment to reserved built-in symbol "file"
* Added headers argument support to get_object()
* Move multi-threading code to a library
* fix(gitignore) : Ignore *.egg files
* python3: Start of adding basic python3 support
* Added log statements in swift client
* Update docstring for swiftclient.Connection.__init__
* Refuse carriage return in header value
* Adds max-backoff for retries in Connection
* Allow setting # of retries in the binary
1.5.0
-----
* Note '-V 2' is necessary for auth 2.0
* Allow storage url override for both auth vers
* Add *.swp into .gitignore
* Add -p option to download command
* add -t for totals to list command and --lh to stat
* add optional 'response_dict' parameters to many calls into which they'll return a dictionary of the response status, reason and headers
* Fixes re-auth flow with expired tokens
* Remove explicit distribute depend
* Add -l and --lh switches to swift 'list' command
* Changed the call to set_tunnel to work in python 2.6 or python 2.7 since its name changed between versions
* Add option to disable SSL compression
* python3: Introduce py33 to tox.ini
* Rename requires files to standard names
* remove busy-wait so that swift client won't use up all CPU cycles
* log get_auth request url instead of x-storage-url
* Update the man page
* Add .coveragerc file to show correct code coverage
* do not warn about etag for slo
* Eradicate eventlet and fix bug lp:959221
* Add end_marker and path query parameters
* Switch to pbr for setup
* Switch to flake8
* Improve Python 3.x compatibility
* Confirm we have auth creds before clearing preauth
1.4.0
-----
* Improve auth option help
* Static large object support
* Fixed pep8 errors in test directory
* Allow user to specify headers at the command line
* Enhance put_object to inform when chunk is ignored
* Allow v2 to use storage_url/storage_token directly
* Add client man page swift.1
* Allow to specify segment container
* Added "/" check when list containers
* Print useful message when keystoneclient is not installed
* Fix reporting version
1.3.0
-----
* Use testr instead of nose
* Update to latest oslo version/setup
* Add generated files to .gitignore
* Add env[SWIFTCLIENT_INSECURE]
* Fix debug feature and add --debug to swift
* Use testtools as base class for test cases
* Add --os-cacert
* Add --insecure option to fix bug #1077869
* Don't segment objects smaller than --segment-size
* Don't add trailing slash to auth URL
* Adding segment size as another x-object-manifest component
* Stop loss of precision when writing 'x-object-meta-mtime'
* Remove unused json_request
* fixed inconsistencies in parameter descriptions
* tell nose to explicity test the 'tests' directory
* Fixes setup compatibility issue on Windows
* Force utf-8 encode of HTTPConnection params
* swiftclient Connection : default optional arguments to None
* Add OpenStack trove classifier for PyPI
* Resolves issue with empty os_options for swift-bench & swift-dispersion-report
* Catch authorization failures
* Do not use dictionaries as default parameters
1.2.0
-----
* Add region_name support
* Allow endpoint type to be specified
* PEP8 cleanup
* PEP8 issues fixed
* Add ability to download without writing to disk
* Fix PEP8 issues
* Change '_' to '-' in options
* Fix swiftclient 400 error when OS_AUTH_URL is set
* Add nosehtmloutput as a test dependency
* Shuffle download order (of containers and objects)
* Add timing stats to verbose download output
* Ensure Content-Length header when PUT/POST a container
* Make python-keystoneclient optional
* Fix container delete throughput and 409 retries
* Consume version info from pkg_resources
* Use keystoneclient for authentication
* Removes the title "Swift Web" from landing page
1.1.1
-----
* Now url encodes/decodes x-object-manifest values
* Configurable concurrency for swift client
* Allow specify tenant:user in user
* Make swift exit on ctrl-c
* Add post-tag versioning
* Don't suppress openstack auth options
* Make swift not hang on error
* Fix pep8 errors w/pep8==1.3
* Add missing test/tools files to the tarball
* Add build_sphinx options
* Make CLI exit nonzero on error
* Add doc and version in swiftclient.__init__.py
* Raise ClientException for invalid auth version
* Version bump after pypi release
1.1.0
-----
* Removed now-unused .cache.bundle references
* Added setup.cfg for verbose test output
* Add run_tests.sh script here
* Adding fake_http_connect to test.utils
* Add openstack project infrastructure
* Add logging
* Defined version to 1.0
* Add CHANGELOG LICENSE and MANIFEST.in
* Delete old test_client and add a gitignore
* Rename client to swiftclient
* Fix links
* Import script from swift to run unittests
* Add test_client from original swift repository
* Add AUTHORS file
* Make sure we get a header StorageURL with 1.0
* Allow specify the tenant in user
* First commit

175
LICENSE
View File

@ -1,175 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,8 +0,0 @@
include AUTHORS
include ChangeLog
include LICENSE
include README.rst
include run_tests.sh tox.ini
recursive-include doc *
recursive-include tests *
recursive-include tools *

14
README Normal file
View File

@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,56 +0,0 @@
========================
Team and repository tags
========================
.. image:: https://governance.openstack.org/badges/python-swiftclient.svg
:target: https://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
Python bindings to the OpenStack Object Storage API
===================================================
.. image:: https://img.shields.io/pypi/v/python-swiftclient.svg
:target: https://pypi.python.org/pypi/python-swiftclient/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/python-swiftclient.svg
:target: https://pypi.python.org/pypi/python-swiftclient/
:alt: Downloads
This is a python client for the Swift API. There's a Python API (the
``swiftclient`` module), and a command-line script (``swift``).
Development takes place via the usual OpenStack processes as outlined
in the `OpenStack wiki`__.
__ http://docs.openstack.org/infra/manual/developers.html
This code is based on the original client previously included with
`OpenStack's Swift`__ The python-swiftclient is licensed under the
Apache License like the rest of OpenStack.
__ http://github.com/openstack/swift
* Free software: Apache license
* `PyPI`_ - package installation
* `Online Documentation`_
* `Launchpad project`_ - release management
* `Blueprints`_ - feature specifications
* `Bugs`_ - issue tracking
* `Source`_
* `Specs`_
* `How to Contribute`_
.. _PyPI: https://pypi.python.org/pypi/python-swiftclient
.. _Online Documentation: https://docs.openstack.org/python-swiftclient/latest/
.. _Launchpad project: https://launchpad.net/python-swiftclient
.. _Blueprints: https://blueprints.launchpad.net/python-swiftclient
.. _Bugs: https://bugs.launchpad.net/python-swiftclient
.. _Source: https://git.openstack.org/cgit/openstack/python-swiftclient
.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html
.. _Specs: http://specs.openstack.org/openstack/swift-specs/
.. contents:: Contents:
:local:

View File

@ -1,24 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2014 Christian Schwede <christian.schwede@enovance.com>
#
# 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 sys
from swiftclient.shell import main
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,6 +0,0 @@
# This is a cross-platform list tracking distribution packages needed by tests;
# see http://docs.openstack.org/infra/bindep/ for additional information.
curl
pypy [test]
pypy-dev [test]

View File

@ -1,90 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXSOURCE = source
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE)
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-swiftclient.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-swiftclient.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@ -1,211 +0,0 @@
.\"
.\" Author: Joao Marcelo Martins <marcelo.martins@rackspace.com> or <btorch@gmail.com>
.\" Copyright (c) 2010-2011 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.
.\"
.TH swift 1 "8/26/2011" "Linux" "OpenStack Swift"
.SH NAME
.LP
.B swift
\- OpenStack Swift client tool
.SH SYNOPSIS
.LP
.B swift
[options] <command> [args]
.SH DESCRIPTION
.PP
The \fBswift\fR tool is a command line utility for communicating with
an OpenStack Object Storage (Swift) environment. It allows one to perform
several types of operations.
.SH COMMANDS
.PP
\fBstat\fR [\fIcommand-options\fR] [\fIcontainer\fR] [\fIobject\fR]
.RS 4
Displays information for the account, container, or object depending on the args given (if any).
In verbose mode, the Storage URL and the authentication token are displayed
as well. Option \-\-lh reports sizes in human readable format similar to ls \-lh.
.RE
\fBlist\fR [\fIcommand-options\fR] [\fIcontainer\fR]
.RS 4
Lists the containers for the account or the objects for a container.
The \-p <prefix> or \-\-prefix <prefix> is an option that will only list items beginning
with that prefix. The \-d <delim> or \-\-delimiter <delim> is option
(for container listings only) that will roll up items with the given
delimiter (see OpenStack Swift general documentation for what this means).
The \-l or \-\-long and \-\-lh options provide more detail, similar to ls \-l and ls \-lh, the latter
providing sizes in human readable format (eg 3K, 12M, etc). These latter 2 switches
use more overhead to get those details, which is directly proportional to the number
of container or objects being listed. With the \-t or \-\-total option they only report totals.
.RE
\fBupload\fR [\fIcommand-options\fR] container file_or_directory [\fIfile_or_directory\fR] [...]
.RS 4
Uploads to the given container the files and directories specified by the
remaining args. The \-c or \-\-changed is an option that will only upload files
that have changed since the last upload. The \-\-object\-name <object\-name> is
an option that will upload file and name object to <object\-name> or upload dir
and use <object\-name> as object prefix. The \-S <size> or \-\-segment\-size <size>
and \-\-leave\-segments and others are options as well (see swift upload \-\-help for more).
.RE
\fBpost\fR [\fIcommand-options\fR] [\fIcontainer\fR] [\fIobject\fR]
.RS 4
Updates meta information for the account, container, or object depending
on the args given. If the container is not found, it will be created
automatically; but this is not true for accounts and objects. Containers
also allow the \-r (or \-\-read\-acl) and \-w (or \-\-write\-acl) options. The \-m
or \-\-meta option is allowed on all and used to define the user meta data
items to set in the form Name:Value. This option can be repeated.
For more details and options see swift post \-\-help.
\fBExample\fR: post \-m Color:Blue \-m Size:Large
.RE
\fBcopy\fR [\fIcommand-options\fR] \fIcontainer\fR \fIobject\fR
.RS 4
Copies an object to a new destination or adds user metadata to the object (current
user metadata will be preserved, in contrast with the post command) depending
on the args given. The \-\-destination option sets the destination in the form
/container/object. If not set, the object will be copied onto itself which is useful
for adding metadata. The \-M or \-\-fresh\-metadata option copies the object without
the existing user metadata. The \-m or \-\-meta option is always allowed and is used
to define the user metadata items to set in the form Name:Value (this option
can be repeated).
For more details and options see swift copy \-\-help.
.RE
\fBdownload\fR [\fIcommand-options\fR] [\fIcontainer\fR] [\fIobject\fR] [\fIobject\fR] [...]
.RS 4
Downloads everything in the account (with \-\-all), or everything in a
container, or a list of objects depending on the args given. For a single
object download, you may use the \-o [\-\-output] <filename> option to
redirect the output to a specific file or if "-" then just redirect to stdout or
with \-\-no-download actually not to write anything to disk.
The \-\-ignore-checksum is an option that turns off checksum validation.
You can specify optional headers with the repeatable cURL-like option
\-H [\-\-header]. For more details and options see swift download \-\-help.
The \-\-ignore\-mtime option ignores the x\-object\-meta\-mtime metadata entry
on the object (if present) and instead creates the downloaded files with
fresh atime and mtime values.
.RE
\fBdelete\fR [\fIcommand-options\fR] [\fIcontainer\fR] [\fIobject\fR] [\fIobject\fR] [...]
.RS 4
Deletes everything in the account (with \-\-all), or everything in a container,
or all objects in a container that start with a given string (given by \-\-prefix),
or a list of objects depending on the args given. Segments of manifest objects
will be deleted as well, unless you specify the \-\-leave\-segments option.
For more details and options see swift delete \-\-help.
.RE
\fBcapabilities\fR [\fIcommand-options\fR] [\fIproxy-url\fR]
.RS 4
Displays cluster capabilities. If the proxy-url option is not provided the
storage-url retrieved after authentication is used as proxy-url.
By default, the output includes the list of the activated Swift middlewares as
well as relevant options for each one. Additionally the command displays
relevant options for the Swift core.
The \-\-json option will print a json representation of the cluster
capabilities. This is typically more suitable for consumption by other
programs, such as jq.
\fBExample\fR: capabilities https://swift.example.com
capabilities \-\-json
.RE
\fBtempurl\fR [\fIcommand-option\fR] \fImethod\fR \fItime\fR \fIpath\fR \fIkey\fR
.RS 4
Generates a temporary URL allowing unauthenticated access to the Swift object
at the given path, using the given HTTP method, for the given time,
using the given TempURL key.
The time can be specified either as an integer
denoting the amount of seconds the temporary URL is valid, or as an ISO 8601
timestamp in one of following formats: Complete date: YYYY\-MM\-DD (eg 1997\-07\-16),
complete date plus hours, minutes and seconds: YYYY\-MM\-DDThh:mm:ss
(eg 1997\-07\-16T19:20:30) or complete date plus hours, minutes and seconds with
UTC designator: YYYY\-MM\-DDThh:mm:ssZ (eg 1997\-07\-16T19:20:30Z). Be aware that
if you do not use the latter format, the timestamp is generated using your locale
timezone. If the first format is used, the time part used will equal to 00:00:00.
With the \-\-prefix\-based option a
prefix-based URL is generated.
The option \-\-iso8601 provides ISO 8601 UTC timestamps
instead of Unix timestamps inside the generated URL.
If optional \-\-absolute argument is
provided and the time argument is specified in seconds, the seconds are
interpreted as a Unix timestamp at which the URL
should expire.
\fBExample\fR: tempurl GET $(date \-d "Jan 1 2016" +%s)
/v1/AUTH_foo/bar_container/quux.md my_secret_tempurl_key \-\-absolute
.RE
\fBauth\fR
.RS 4
Display auth related authentication variables in shell friendly format.
For examples see swift auth \-\-help.
.RE
.SH OPTIONS
.PD 0
.IP "--version Show program's version number and exit"
.IP "-h, --help Show this (or any subcommand if after command) help message and exit"
.IP "-s, --snet Use SERVICENET internal network"
.IP "-v, --verbose Print more info"
.IP "-q, --quiet Suppress status output"
.IP "-A AUTH, --auth=AUTH URL for obtaining an auth token "
.IP "-U USER, --user=USER User name for obtaining an auth token"
.IP "-V 1|2, --auth-version=VERSION Authentication protocol version"
.IP "-K KEY, --key=KEY Key for obtaining an auth token"
.IP "--os-storage-url=URL Use this instead of URL returned from auth"
.IP "--os-help Show all OpenStack authentication options"
.PD
.RS 4
For more options see swift \-\-help and swift \-\-os\-help.
.RE
.SH EXAMPLE
.PP
swift \-A https://127.0.0.1:443/auth/v1.0 \-U swiftops:swiftops \-K swiftops stat
.RS 2
.PD 0
.IP " Account: AUTH_43b42dae-dc0b-4a4b-ac55-97de614d6e6e"
.IP "Containers: 1"
.IP " Objects: 1"
.IP " Bytes: 1124"
.IP "Accept-Ranges: bytes"
.IP "X-Trans-Id: txb21186a9eef64ed295a1e95896a0fc72"
.PD
.RE
.SH DOCUMENTATION
.LP
More in depth documentation about OpenStack Swift as a whole can be found at
.BI https://docs.openstack.org/swift/latest/

View File

@ -1,960 +0,0 @@
====
CLI
====
The ``swift`` tool is a command line utility for communicating with an OpenStack
Object Storage (swift) environment. It allows one to perform several types of
operations.
For help on a specific :command:`swift` command, enter:
.. code-block:: console
$ swift COMMAND --help
.. _swift_command_usage:
swift usage
~~~~~~~~~~~
.. code-block:: console
Usage: swift [--version] [--help] [--os-help] [--snet] [--verbose]
[--debug] [--info] [--quiet] [--auth <auth_url>]
[--auth-version <auth_version> |
--os-identity-api-version <auth_version> ]
[--user <username>]
[--key <api_key>] [--retries <num_retries>]
[--os-username <auth-user-name>] [--os-password <auth-password>]
[--os-user-id <auth-user-id>]
[--os-user-domain-id <auth-user-domain-id>]
[--os-user-domain-name <auth-user-domain-name>]
[--os-tenant-id <auth-tenant-id>]
[--os-tenant-name <auth-tenant-name>]
[--os-project-id <auth-project-id>]
[--os-project-name <auth-project-name>]
[--os-project-domain-id <auth-project-domain-id>]
[--os-project-domain-name <auth-project-domain-name>]
[--os-auth-url <auth-url>] [--os-auth-token <auth-token>]
[--os-storage-url <storage-url>] [--os-region-name <region-name>]
[--os-service-type <service-type>]
[--os-endpoint-type <endpoint-type>]
[--os-cacert <ca-certificate>] [--insecure]
[--os-cert <client-certificate-file>]
[--os-key <client-certificate-key-file>]
[--no-ssl-compression]
<subcommand> [--help] [<subcommand options>]
**Subcommands:**
``delete``
Delete a container or objects within a container.
``download``
Download objects from containers.
``list``
Lists the containers for the account or the objects
for a container.
``post``
Updates meta information for the account, container,
or object; creates containers if not present.
``copy``
Copies object, optionally adds meta
``stat``
Displays information for the account, container,
or object.
``upload``
Uploads files or directories to the given container.
``capabilities``
List cluster capabilities.
``tempurl``
Create a temporary URL.
``auth``
Display auth related environment variables.
.. _swift_command_options:
swift optional arguments
~~~~~~~~~~~~~~~~~~~~~~~~
``--version``
show program's version number and exit
``-h, --help``
show this help message and exit
``--os-help``
Show OpenStack authentication options.
``-s, --snet``
Use SERVICENET internal network.
``-v, --verbose``
Print more info.
``--debug``
Show the curl commands and results of all http queries
regardless of result status.
``--info``
Show the curl commands and results of all http queries
which return an error.
``-q, --quiet``
Suppress status output.
``-A AUTH, --auth=AUTH``
URL for obtaining an auth token.
``-V AUTH_VERSION, --auth-version=AUTH_VERSION, --os-identity-api-version=AUTH_VERSION``
Specify a version for authentication. Defaults to
``env[ST_AUTH_VERSION]``, ``env[OS_AUTH_VERSION]``,
``env[OS_IDENTITY_API_VERSION]`` or 1.0.
``-U USER, --user=USER``
User name for obtaining an auth token.
``-K KEY, --key=KEY``
Key for obtaining an auth token.
``-R RETRIES, --retries=RETRIES``
The number of times to retry a failed connection.
``--insecure``
Allow swiftclient to access servers without having to
verify the SSL certificate. Defaults to
``env[SWIFTCLIENT_INSECURE]`` (set to 'true' to enable).
``--no-ssl-compression``
This option is deprecated and not used anymore. SSL
compression should be disabled by default by the
system SSL library.
Authentication
~~~~~~~~~~~~~~
This section covers the options for authenticating with a swift
object store. The combinations of options required for each authentication
version are detailed below, but are just a subset of those that can be used
to successfully authenticate. These are the most common and recommended
combinations.
You should obtain the details of your authentication version and credentials
from your storage provider. These details should make it clearer which of the
authentication sections below are most likely to allow you to connect to your
storage account.
Keystone v3
-----------
.. code-block:: bash
swift --os-auth-url https://api.example.com:5000/v3 --auth-version 3 \
--os-project-name project1 --os-project-domain-name domain1 \
--os-username user --os-user-domain-name domain1 \
--os-password password list
swift --os-auth-url https://api.example.com:5000/v3 --auth-version 3 \
--os-project-id 0123456789abcdef0123456789abcdef \
--os-user-id abcdef0123456789abcdef0123456789 \
--os-password password list
Manually specifying the options above on the command line can be avoided by
setting the following combinations of environment variables:
.. code-block:: bash
ST_AUTH_VERSION=3
OS_USERNAME=user
OS_USER_DOMAIN_NAME=domain1
OS_PASSWORD=password
OS_PROJECT_NAME=project1
OS_PROJECT_DOMAIN_NAME=domain1
OS_AUTH_URL=https://api.example.com:5000/v3
ST_AUTH_VERSION=3
OS_USER_ID=abcdef0123456789abcdef0123456789
OS_PASSWORD=password
OS_PROJECT_ID=0123456789abcdef0123456789abcdef
OS_AUTH_URL=https://api.example.com:5000/v3
Keystone v2
-----------
.. code-block:: bash
swift --os-auth-url https://api.example.com:5000/v2.0 \
--os-tenant-name tenant \
--os-username user --os-password password list
Manually specifying the options above on the command line can be avoided by
setting the following environment variables:
.. code-block:: bash
ST_AUTH_VERSION=2.0
OS_USERNAME=user
OS_PASSWORD=password
OS_TENANT_NAME=tenant
OS_AUTH_URL=https://api.example.com:5000/v2.0
Legacy auth systems
-------------------
You can configure swift to work with any number of other authentication systems
that we will not cover in this document. If your storage provider is not using
Keystone to provide access tokens, please contact them for instructions on the
required options. It is likely that the options will need to be specified as
below:
.. code-block:: bash
swift -A https://api.example.com/v1.0 -U user -K api_key list
Specifying the options above manually on the command line can be avoided by
setting the following environment variables:
.. code-block:: bash
ST_AUTH_VERSION=1.0
ST_AUTH=https://api.example.com/v1.0
ST_USER=user
ST_KEY=key
It is also possible that you need to use a completely separate auth system, in which
case ``swiftclient`` cannot request a token for you. In this case you should make the
authentication request separately and access your storage using the token and
storage URL options shown below:
.. code-block:: bash
swift --os-auth-token 6ee5eb33efad4e45ab46806eac010566 \
--os-storage-url https://10.1.5.2:8080/v1/AUTH_ced809b6a4baea7aeab61a \
list
.. We need the backslash below in order to indent the note
\
.. note::
Leftover environment variables are a common source of confusion when
authorization fails.
CLI commands
~~~~~~~~~~~~
.. _swift_auth:
Auth
----
.. code-block:: console
Usage: swift auth
Display authentication variables in shell friendly format. Command to run to export storage
URL and auth token into ``OS_STORAGE_URL`` and ``OS_AUTH_TOKEN``: ``swift auth``.
Command to append to a runcom file (e.g. ``~/.bashrc``, ``/etc/profile``) for automatic
authentication: ``swift auth -v -U test:tester -K testing``.
.. _swift_stat:
swift stat
----------
.. code-block:: console
Usage: swift stat [--lh] [--header <header:value>]
[<container> [<object>]]
Displays information for the account, container, or object depending on
the arguments given (if any). In verbose mode, the storage URL and the
authentication token are displayed as well.
**Positional arguments:**
``[container]``
Name of container to stat from.
``[object]``
Name of object to stat.
**Optional arguments:**
``--lh``
Report sizes in human readable format similar to
ls -lh.
``-H, --header <header:value>``
Adds a custom request header to use for stat.
.. _swift_list:
swift list
----------
.. code-block:: console
Usage: swift list [--long] [--lh] [--totals] [--prefix <prefix>]
[--delimiter <delimiter>] [--header <header:value>]
[<container>]
Lists the containers for the account or the objects for a container.
The ``-p <prefix>`` or ``--prefix <prefix>`` is an option that will only
list items beginning with that prefix. The ``-d <delimiter>`` or
``--delimiter <delimiter>`` is an option (for container listings only)
that will roll up items with the given delimiter (see `OpenStack Swift
general documentation <http://docs.openstack.org/swift/latest/>` for
what this means).
The ``-l`` and ``--lh`` options provide more detail, similar to ``ls -l``
and ``ls -lh``, the latter providing sizes in human readable format
(For example: ``3K``, ``12M``, etc). The latter two switches use more
overhead to retrieve the displayed details, which is directly proportional
to the number of container or objects listed.
**Positional arguments:**
``[container]``
Name of container to list object in.
**Optional arguments:**
``-l, --long``
Long listing format, similar to ls -l.
``--lh``
Report sizes in human readable format similar to
ls -lh.
``-t, --totals``
Used with -l or --lh, only report totals.
``-p <prefix>, --prefix <prefix>``
Only list items beginning with the prefix.
``-d <delim>, --delimiter <delim>``
Roll up items with the given delimiter. For containers
only. See OpenStack Swift API documentation for what
this means.
``-H, --header <header:value>``
Adds a custom request header to use for listing.
.. _swift_upload:
swift upload
------------
.. code-block:: console
Usage: swift upload [--changed] [--skip-identical] [--segment-size <size>]
[--segment-container <container>] [--leave-segments]
[--object-threads <thread>] [--segment-threads <threads>]
[--header <header>] [--use-slo] [--ignore-checksum]
[--object-name <object-name>]
<container> <file_or_directory> [<file_or_directory>] [...]
Uploads the files and directories specified by the remaining arguments to the
given container. The ``-c`` or ``--changed`` is an option that will only
upload files that have changed since the last upload. The
``--object-name <object-name>`` is an option that will upload a file and
name object to ``<object-name>`` or upload a directory and use ``<object-name>``
as object prefix. The ``-S <size>`` or ``--segment-size <size>`` and
``--leave-segments`` are options as well (see ``--help`` for more).
Uploads specified files and directories to the given container.
**Positional arguments:**
``<container>``
Name of container to upload to.
``<file_or_directory>``
Name of file or directory to upload. Specify multiple
times for multiple uploads.
**Optional arguments:**
``-c, --changed``
Only upload files that have changed since the last
upload.
``--skip-identical``
Skip uploading files that are identical on both sides.
``-S, --segment-size <size>``
Upload files in segments no larger than <size> (in
Bytes) and then create a "manifest" file that will
download all the segments as if it were the original
file.
``--segment-container <container>``
Upload the segments into the specified container. If
not specified, the segments will be uploaded to a
<container>_segments container to not pollute the
main <container> listings.
``--leave-segments``
Indicates that you want the older segments of manifest
objects left alone (in the case of overwrites).
``--object-threads <threads>``
Number of threads to use for uploading full objects.
Default is 10.
``--segment-threads <threads>``
Number of threads to use for uploading object segments.
Default is 10.
``-H, --header <header:value>``
Adds a customized request header. This option may be
repeated. Example: -H "content-type:text/plain"
-H "Content-Length: 4000".
``--use-slo``
When used in conjunction with --segment-size it will
create a Static Large Object instead of the default
Dynamic Large Object.
``--object-name <object-name>``
Upload file and name object to <object-name> or upload
dir and use <object-name> as object prefix instead of
folder name.
``--ignore-checksum``
Turn off checksum validation for uploads.
.. _swift_post:
swift post
----------
.. code-block:: console
Usage: swift post [--read-acl <acl>] [--write-acl <acl>] [--sync-to]
[--sync-key <sync-key>] [--meta <name:value>]
[--header <header>]
[<container> [<object>]]
Updates meta information for the account, container, or object depending
on the arguments given. If the container is not found, the ``swiftclient``
will create it automatically, but this is not true for accounts and
objects. Containers also allow the ``-r <read-acl>`` (or ``--read-acl
<read-acl>``) and ``-w <write-acl>`` (or ``--write-acl <write-acl>``) options.
The ``-m`` or ``--meta`` option is allowed on accounts, containers and objects,
and is used to define the user metadata items to set in the form ``Name:Value``.
You can repeat this option. For example: ``post -m Color:Blue -m Size:Large``
For more information about ACL formats see the documentation:
`ACLs <http://docs.openstack.org/swift/latest/misc.html#acls>`_.
**Positional arguments:**
``[container]``
Name of container to post to.
``[object]``
Name of object to post.
**Optional arguments:**
``-r, --read-acl <acl>``
Read ACL for containers. Quick summary of ACL syntax:
``.r:*``, ``.r:-.example.com``, ``.r:www.example.com``,
``account1`` (v1.0 identity API only),
``account1:*``, ``account2:user2`` (v2.0+ identity API).
``-w, --write-acl <acl>``
Write ACL for containers. Quick summary of ACL syntax:
``account1`` (v1.0 identity API only),
``account1:*``, ``account2:user2`` (v2.0+ identity API).
``-t, --sync-to <sync-to>``
Sync To for containers, for multi-cluster replication.
``-k, --sync-key <sync-key>``
Sync Key for containers, for multi-cluster replication.
``-m, --meta <name:value>``
Sets a meta data item. This option may be repeated.
Example: -m Color:Blue -m Size:Large
``-H, --header <header:value>``
Adds a customized request header.
This option may be repeated.
Example: -H "content-type:text/plain" -H "Content-Length: 4000"
.. _swift_download:
swift download
--------------
.. code-block:: console
Usage: swift download [--all] [--marker <marker>] [--prefix <prefix>]
[--output <out_file>] [--output-dir <out_directory>]
[--object-threads <threads>] [--ignore-checksum]
[--container-threads <threads>] [--no-download]
[--skip-identical] [--remove-prefix]
[--header <header:value>] [--no-shuffle]
[<container> [<object>] [...]]
Downloads everything in the account (with ``--all``), or everything in a
container, or a list of objects depending on the arguments given. For a
single object download, you may use the ``-o <filename>`` or ``--output <filename>``
option to redirect the output to a specific file or ``-`` to
redirect to stdout. The ``--ignore-checksum`` is an option that turn off
checksum validation. You can specify optional headers with the repeatable
cURL-like option ``-H [--header <name:value>]``. ``--ignore-mtime`` ignores the
``x-object-meta-mtime`` metadata entry on the object (if present) and instead
creates the downloaded files with fresh atime and mtime values.
**Positional arguments:**
``<container>``
Name of container to download from. To download a
whole account, omit this and specify --all.
``<object>``
Name of object to download. Specify multiple times
for multiple objects. Omit this to download all
objects from the container.
**Optional arguments:**
``-a, --all``
Indicates that you really want to download
everything in the account.
``-m, --marker <marker>``
Marker to use when starting a container or account
download.
``-p, --prefix <prefix>``
Only download items beginning with <prefix>
``-r, --remove-prefix``
An optional flag for --prefix <prefix>, use this
option to download items without <prefix>
``-o, --output <out_file>``
For a single file download, stream the output to
<out_file>. Specifying "-" as <out_file> will
redirect to stdout.
``-D, --output-dir <out_directory>``
An optional directory to which to store objects.
By default, all objects are recreated in the current
directory.
``--object-threads <threads>``
Number of threads to use for downloading objects.
Default is 10.
``--container-threads <threads>``
Number of threads to use for downloading containers.
Default is 10.
``--no-download``
Perform download(s), but don't actually write anything
to disk.
``-H, --header <header:value>``
Adds a customized request header to the query, like
"Range" or "If-Match". This option may be repeated.
Example: --header "content-type:text/plain"
``--skip-identical``
Skip downloading files that are identical on both
sides.
``--ignore-checksum``
Turn off checksum validation for downloads.
``--no-shuffle``
By default, when downloading a complete account or
container, download order is randomised in order to
reduce the load on individual drives when multiple
clients are executed simultaneously to download the
same set of objects (e.g. a nightly automated download
script to multiple servers). Enable this option to
submit download jobs to the thread pool in the order
they are listed in the object store.
.. _swift_delete:
swift delete
------------
.. code-block:: console
Usage: swift delete [--all] [--leave-segments]
[--object-threads <threads>]
[--container-threads <threads>]
[--header <header:value>]
[<container> [<object>] [...]]
Deletes everything in the account (with ``--all``), or everything in a
container, or a list of objects depending on the arguments given. Segments
of manifest objects will be deleted as well, unless you specify the
``--leave-segments`` option.
**Positional arguments:**
``[<container>]``
Name of container to delete from.
``[<object>]``
Name of object to delete. Specify multiple times
for multiple objects.
**Optional arguments:**
``-a, --all``
Delete all containers and objects.
``--leave-segments``
Do not delete segments of manifest objects.
``-H, --header <header:value>``
Adds a custom request header to use for deleting
objects or an entire container.
``--object-threads <threads>``
Number of threads to use for deleting objects.
Default is 10.
``--container-threads <threads>``
Number of threads to use for deleting containers.
Default is 10.
.. _swift_copy:
swift copy
----------
.. code-block:: console
Usage: swift copy [--destination </container/object>] [--fresh-metadata]
[--meta <name:value>] [--header <header>] <container>
<object> [<object>] [...]
Copies an object to a new destination or adds user metadata to an object. Depending
on the options supplied, you can preserve existing metadata in contrast to the post
command. The ``--destination`` option sets the copy target destination in the form
``/container/object``. If not set, the object will be copied onto itself which is useful
for adding metadata. You can use the ``-M`` or ``--fresh-metadata`` option to copy
an object without existing user meta data, and the ``-m`` or ``--meta`` option
to define user meta data items to set in the form ``Name:Value``. You can repeat
this option. For example: ``copy -m Color:Blue -m Size:Large``.
**Positional arguments:**
``<container>``
Name of container to copy from.
``<object>``
Name of object to copy. Specify multiple times for multiple objects
**Optional arguments:**
``-d, --destination </container[/object]>``
The container and name of the destination object. Name
of destination object can be omitted, then will be
same as name of source object. Supplying multiple
objects and destination with object name is invalid.
``-M, --fresh-metadata``
Copy the object without any existing metadata,
If not set, metadata will be preserved or appended
``-m, --meta <name:value>``
Sets a meta data item. This option may be repeated.
Example: -m Color:Blue -m Size:Large
``-H, --header <header:value>``
Adds a customized request header. This option may be repeated.
Example: -H "content-type:text/plain" -H "Content-Length: 4000"
.. _swift_capabilities:
swift capabilities
------------------
.. code-block:: console
Usage: swift capabilities [--json] [<proxy_url>]
Displays cluster capabilities. The output includes the list of the
activated Swift middlewares as well as relevant options for each ones.
Additionally the command displays relevant options for the Swift core. If
the ``proxy-url`` option is not provided, the storage URL retrieved after
authentication is used as ``proxy-url``.
**Optional positional arguments:**
``<proxy_url>``
Proxy URL of the cluster to retrieve capabilities.
``--json``
Print the cluster capabilities in JSON format.
.. _swift_tempurl:
swift tempurl
-------------
.. code-block:: console
Usage: swift tempurl [--absolute] [--prefix-based]
<method> <seconds> <path> <key>
Generates a temporary URL for a Swift object. ``method`` option sets an HTTP method to
allow for this temporary URL that is usually ``GET`` or ``PUT``. ``time`` option sets
the amount of time the temporary URL will be valid for.
``time`` can be specified as an integer, denoting the number of seconds
from now on until the URL shall be valid; or, if ``--absolute``
is passed, the Unix timestamp when the temporary URL will expire.
But beyond that, ``time`` can also be specified as an ISO 8601 timestamp
in one of following formats:
i) Complete date: YYYY-MM-DD (eg 1997-07-16)
ii) Complete date plus hours, minutes and seconds:
YYYY-MM-DDThh:mm:ss
(eg 1997-07-16T19:20:30)
iii) Complete date plus hours, minutes and seconds with UTC designator:
YYYY-MM-DDThh:mm:ssZ
(eg 1997-07-16T19:20:30Z)
Please be aware that if you don't provide the UTC designator (i.e., Z)
the timestamp is generated using your local timezone. If only a date is
specified, the time part used will equal to ``00:00:00``.
``path`` option sets the full path to the Swift object.
Example: ``/v1/AUTH_account/c/o``. ``key`` option is
the secret temporary URL key set on the Swift cluster. To set a key, run
``swift post -m "Temp-URL-Key: <your secret key>"``. To generate a prefix-based temporary
URL use the ``--prefix-based`` option. This URL will contain the path to the prefix. Do not
forget to append the desired objectname at the end of the path portion (and before the
query portion) before sharing the URL. It is possible to use ISO 8601 UTC timestamps within the
URL by using the ``--iso8601`` option.
**Positional arguments:**
``<method>``
An HTTP method to allow for this temporary URL.
Usually 'GET' or 'PUT'.
``<seconds>``
The amount of time in seconds the temporary URL will be
valid for; or, if --absolute is passed, the Unix
timestamp when the temporary URL will expire.
``<path>``
The full path to the Swift object.
Example: /v1/AUTH_account/c/o
or: http://saio:8080/v1/AUTH_account/c/o
``<key>``
The secret temporary URL key set on the Swift cluster.
To set a key, run 'swift post -m
"Temp-URL-Key:b3968d0207b54ece87cccc06515a89d4"'
**Optional arguments:**
``--absolute``
Interpret the <seconds> positional argument as a Unix
timestamp rather than a number of seconds in the
future.
``--prefix-based``
If present, a prefix-based tempURL will be generated.
Examples
~~~~~~~~
In this section we present some example usage of the ``swift`` CLI. To keep the
examples as short as possible, these examples assume that the relevant authentication
options have been set using environment variables. You can obtain the full list of
commands and options available in the ``swift`` CLI by executing the following:
.. code-block:: bash
> swift --help
> swift <command> --help
Simple examples
---------------
List the existing swift containers:
.. code-block:: bash
> swift list
container_1
Create a new container:
.. code-block:: bash
> swift post TestContainer
Upload an object into a container:
.. code-block:: bash
> swift upload TestContainer testSwift.txt
testSwift.txt
List the contents of a container:
.. code-block:: bash
> swift list TestContainer
testSwift.txt
Copy an object to new destination:
.. code-block:: bash
> swift copy -d /DestContainer/testSwift.txt SourceContainer testSwift.txt
SourceContainer/testSwift.txt copied to /DestContainer/testSwift.txt
Delete an object from a container:
.. code-block:: bash
> swift delete TestContainer testSwift.txt
testSwift.txt
Delete a container:
.. code-block:: bash
> swift delete TestContainer
TestContainer
Display auth related authentication variables in shell friendly format:
.. code-block:: bash
> swift auth
export OS_STORAGE_URL=http://127.0.0.1:8080/v1/AUTH_bf5e63572f7a420a83fcf0aa8c72c2c7
export OS_AUTH_TOKEN=c597015ae19943a18438b52ef3762e79
Download an object from a container:
.. code-block:: bash
> swift download TestContainer testSwift.txt
testSwift.txt [auth 0.028s, headers 0.045s, total 0.045s, 0.002 MB/s]
.. We need the backslash below in order to indent the note
\
.. note::
To upload an object to a container, your current working directory must be
where the file is located or you must provide the complete path to the file.
In other words, the --object-name <object-name> is an option that will upload
file and name object to <object-name> or upload directory and use <object-name> as
object prefix. In the case that you provide the complete path of the file,
that complete path will be the name of the uploaded object.
For example:
.. code-block:: bash
> swift upload TestContainer /home/swift/testSwift/testSwift.txt
home/swift/testSwift/testSwift.txt
> swift list TestContainer
home/swift/testSwift/testSwift.txt
More complex examples
---------------------
Swift has a single object size limit of 5GiB. In order to upload files larger
than this, we must create a large object that consists of smaller segments.
The example below shows how to upload a large video file as a static large
object in 1GiB segments:
.. code-block:: bash
> swift upload videos --use-slo --segment-size 1G myvideo.mp4
myvideo.mp4 segment 8
myvideo.mp4 segment 4
myvideo.mp4 segment 2
myvideo.mp4 segment 7
myvideo.mp4 segment 0
myvideo.mp4 segment 1
myvideo.mp4 segment 3
myvideo.mp4 segment 6
myvideo.mp4 segment 5
myvideo.mp4
This command will upload segments to a container named ``videos_segments``, and
create a manifest file describing the entire object in the ``videos`` container.
For more information on large objects, see the documentation `here
<https://docs.openstack.org/swift/latest/overview_large_objects.html>`_.
.. code-block:: bash
> swift list videos
myvideo.mp4
> swift list videos_segments
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000000
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000001
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000002
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000003
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000004
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000005
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000006
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000007
myvideo.mp4/slo/1460229233.679546/9341553868/1073741824/00000008
Firstly, the key should be set, then generate a temporary URL for a Swift object:
.. code-block:: bash
> swift post -m "Temp-URL-Key:b3968d0207b54ece87cccc06515a89d4"
> swift tempurl GET 6000 /v1/AUTH_bf5e63572f7a420a83fcf0aa8c72c2c7\
/firstcontainer/clean.sh b3968d0207b54ece87cccc06515a89d4
/v1/AUTH_/firstcontainer/clean.sh?temp_url_sig=\
9218fc288cc09e5edd857b6a3d43cf2122b906dc&temp_url_expires=1472203614

View File

@ -1,179 +0,0 @@
==============================
The swiftclient.Connection API
==============================
A low level API that provides methods for authentication and methods that
correspond to the individual REST API calls described in the swift
documentation.
For usage details see the client docs: :mod:`swiftclient.client`.
Authentication
--------------
This section covers the various combinations of kwargs required when creating
an instance of the ``Connection`` object for communicating with a swift
object store. The combinations of options required for each authentication
version are detailed below, but are
just a subset of those that can be used to successfully authenticate. These
are the most common and recommended combinations.
Keystone Session
~~~~~~~~~~~~~~~~
.. code-block:: python
from keystoneauth1 import session
from keystoneauth1.identity import v3
# Create a password auth plugin
auth = v3.Password(auth_url='http://127.0.0.1:5000/v3/',
username='tester',
password='testing',
user_domain_name='Default',
project_name='Default',
project_domain_name='Default')
# Create session
keystone_session = session.Session(auth=auth)
# Create swiftclient Connection
swift_conn = Connection(session=keystone_session)
Keystone v3
~~~~~~~~~~~
.. code-block:: python
_authurl = 'http://127.0.0.1:5000/v3/'
_auth_version = '3'
_user = 'tester'
_key = 'testing'
_os_options = {
'user_domain_name': 'Default',
'project_domain_name': 'Default',
'project_name': 'Default'
}
conn = Connection(
authurl=_authurl,
user=_user,
key=_key,
os_options=_os_options,
auth_version=_auth_version
)
Keystone v2
~~~~~~~~~~~
.. code-block:: python
_authurl = 'http://127.0.0.1:5000/v2.0/'
_auth_version = '2'
_user = 'tester'
_key = 'testing'
_tenant_name = 'test'
conn = Connection(
authurl=_authurl,
user=_user,
key=_key,
tenant_name=_tenant_name,
auth_version=_auth_version
)
Legacy Auth
~~~~~~~~~~~
.. code-block:: python
_authurl = 'http://127.0.0.1:8080/'
_auth_version = '1'
_user = 'tester'
_key = 'testing'
_tenant_name = 'test'
conn = Connection(
authurl=_authurl,
user=_user,
key=_key,
tenant_name=_tenant_name,
auth_version=_auth_version
)
Examples
--------
In this section we present some simple code examples that demonstrate the usage
of the ``Connection`` API. You can find full details of the options and methods
available to the ``Connection`` API in the docstring generated documentation:
:mod:`swiftclient.client`.
List the available containers:
.. code-block:: python
resp_headers, containers = conn.get_account()
print("Response headers: %s" % resp_headers)
for container in containers:
print(container)
Create a new container:
.. code-block:: python
container = 'new-container'
conn.put_container(container)
resp_headers, containers = conn.get_account()
if container in containers:
print("The container was created")
Create a new object with the contents of a local text file:
.. code-block:: python
container = 'new-container'
with open('local.txt', 'r') as local:
conn.put_object(
container,
'local_object.txt',
contents=local,
content_type='text/plain'
)
Confirm presence of the object:
.. code-block:: python
obj = 'local_object.txt'
container = 'new-container'
try:
resp_headers = conn.head_object(container, obj)
print('The object was successfully created')
except ClientException as e:
if e.http_status = '404':
print('The object was not found')
else:
print('An error occurred checking for the existence of the object')
Download the created object:
.. code-block:: python
obj = 'local_object.txt'
container = 'new-container'
resp_headers, obj_contents = conn.get_object(container, obj)
with open('local_copy.txt', 'w') as local:
local.write(obj_contents)
Delete the created object:
.. code-block:: python
obj = 'local_object.txt'
container = 'new-container'
try:
conn.delete_object(container, obj)
print("Successfully deleted the object")
except ClientException as e:
print("Failed to delete the object with error: %s" % e)

View File

@ -1,209 +0,0 @@
# -*- coding: utf-8 -*-
#
# Swiftclient documentation build configuration file, created by
# sphinx-quickstart on Tue Apr 17 02:17:37 2012.
#
# This file is execfile()d with the current directory set to its containing
# dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
# 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
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.append(os.path.abspath('.'))
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
sys.path.insert(0, ROOT)
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
'sphinx.ext.coverage', 'oslosphinx']
autoclass_content = 'both'
autodoc_default_flags = ['members', 'undoc-members', 'show-inheritance']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Swiftclient'
copyright = u'2013-2016 OpenStack, LLC.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
import swiftclient.version
release = swiftclient.version.version_string
version = swiftclient.version.version_string
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
# unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
#html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
html_theme_options = {'show_other_versions': True}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_use_modindex = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'SwiftClientwebdoc'
# -- Options for LaTeX output -------------------------------------------------
# The paper size ('letter' or 'a4').
# latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual])
latex_documents = [
('index', 'SwiftClient.tex', u'SwiftClient Documentation',
u'OpenStack, LLC.', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# Additional stuff for the LaTeX preamble.
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_use_modindex = True

View File

@ -1,55 +0,0 @@
======================================
Welcome to the python-swiftclient Docs
======================================
Introduction
~~~~~~~~~~~~
.. toctree::
:maxdepth: 2
introduction
Developer Documentation
~~~~~~~~~~~~~~~~~~~~~~~
.. toctree::
:maxdepth: 2
cli
service-api
client-api
Code-Generated Documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. toctree::
:maxdepth: 2
swiftclient
Indices and tables
~~~~~~~~~~~~~~~~~~
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
License
~~~~~~~
Copyright 2013 OpenStack, LLC.
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.

View File

@ -1,94 +0,0 @@
============
Introduction
============
Where to Start?
~~~~~~~~~~~~~~~
The ``python-swiftclient`` project comprises a command line tool and two
separate APIs for accessing swift programmatically. Choosing the most
appropriate method for a given use case is the first problem a user needs to
solve.
Use Cases
---------
Alongside the command line tool, the ``python-swiftclient`` includes two
levels of API:
* A low level client API that provides simple Python wrappers around the
various authentication mechanisms and the individual HTTP requests.
* A high level service API that provides methods for performing common
operations in parallel on a thread pool.
Example use cases:
* Uploading and retrieving data
Use the command line tool if you are simply uploading and downloading
files and directories to and from your filesystem. The command line tool
can be integrated into a shell script to automate tasks.
* Integrating into an automated Python workflow
Use the ``SwiftService`` API to perform operations offered by the CLI
if your use case requires integration with a Python-based workflow.
This method offers greater control and flexibility over individual object
operations, such as the metadata set on each object. The ``SwiftService``
class provides methods to perform multiple sets of operations against a
swift object store using a configurable shared thread pool. A single
instance of the ``SwiftService`` class can be shared between multiple
threads in your own code.
* Developing an application in Python to access a swift object store
Use the ``SwiftService`` API to develop Python applications that use
swift to store and retrieve objects. A ``SwiftService`` instance provides
a configurable thread pool for performing all operations supported by the
CLI.
* Fine-grained control over threading or the requests being performed
Use the ``Connection`` API if your use case requires fine grained control
over advanced features or you wish to use your own existing threading
model. Examples of advanced features requiring the use of the
``Connection`` API include creating an SLO manifest that references
already existing objects, or fine grained control over the query strings
supplied with each HTTP request.
Important considerations
~~~~~~~~~~~~~~~~~~~~~~~~
This section covers some important considerations, helpful hints, and things to
avoid when integrating an object store into your workflow.
An object store is not a filesystem
-----------------------------------
It cannot be stressed enough that your usage of the object store should reflect
the proper use case, and not treat the storage like a traditional filesystem.
There are two main restrictions to bear in mind when designing an application
that uses an object store:
* You cannot rename objects. Due to fact that the name of an object is one
of the factors that determines where the object and its replicas are stored,
renaming would require multiple copies of the data to be moved between
physical storage devices. If you want to rename an object you must upload
to the new location, or make a server side copy request to the new location,
and then delete the original.
* You cannot modify objects. Objects are stored in multiple locations and
are checked for integrity based on the MD5 sum calculated during
upload. In order to modify the contents of an object, the entire desired
contents must be re-uploaded. In certain special cases it is possible to
work around this restriction using large objects, but no general
file-like access is available to modify a stored object.
Objects cannot be locked
------------------------
There is no mechanism to perform a combination of reading the
data/metadata from an object and writing an update to that data/metadata in an
atomic way. Any user with access to a container could update the contents or
metadata associated with an object at any time.
Workflows that assume that no updates have been made since the last read of an
object should be discouraged. Enabling a workflow of this type requires an
external object locking mechanism and/or cooperation between all clients
accessing the data.

View File

@ -1,898 +0,0 @@
================================
The swiftclient.SwiftService API
================================
A higher-level API aimed at allowing developers an easy way to perform multiple
operations asynchronously using a configurable thread pool. Documentation for
each service method call can be found here: :mod:`swiftclient.service`.
Authentication
--------------
This section covers the various options for authenticating with a swift
object store. The combinations of options required for each authentication
version are detailed below. Once again, these are just a subset of those that
can be used to successfully authenticate, but they are the most common and
recommended.
The relevant authentication options are presented as python dictionaries that
should be added to any other options you are supplying to your ``SwiftService``
instance. As indicated in the python code, you can also set these options as
environment variables that will be loaded automatically if the relevant option
is not specified.
The ``SwiftService`` authentication attempts to automatically select
the auth version based on the combination of options specified, but
supplying options from multiple different auth versions can cause unexpected
behaviour.
.. note::
Leftover environment variables are a common source of confusion when
authorization fails.
Keystone V3
~~~~~~~~~~~
.. code-block:: python
{
...
"auth_version": environ.get('ST_AUTH_VERSION'), # Should be '3'
"os_username": environ.get('OS_USERNAME'),
"os_password": environ.get('OS_PASSWORD'),
"os_project_name": environ.get('OS_PROJECT_NAME'),
"os_project_domain_name": environ.get('OS_PROJECT_DOMAIN_NAME'),
"os_auth_url": environ.get('OS_AUTH_URL'),
...
}
.. code-block:: python
{
...
"auth_version": environ.get('ST_AUTH_VERSION'), # Should be '3'
"os_username": environ.get('OS_USERNAME'),
"os_password": environ.get('OS_PASSWORD'),
"os_project_id": environ.get('OS_PROJECT_ID'),
"os_project_domain_id": environ.get('OS_PROJECT_DOMAIN_ID'),
"os_auth_url": environ.get('OS_AUTH_URL'),
...
}
Keystone V2
~~~~~~~~~~~
.. code-block:: python
{
...
"auth_version": environ.get('ST_AUTH_VERSION'), # Should be '2.0'
"os_username": environ.get('OS_USERNAME'),
"os_password": environ.get('OS_PASSWORD'),
"os_tenant_name": environ.get('OS_TENANT_NAME'),
"os_auth_url": environ.get('OS_AUTH_URL'),
...
}
Legacy Auth
~~~~~~~~~~~
.. code-block:: python
{
...
"auth_version": environ.get('ST_AUTH_VERSION'), # Should be '1.0'
"auth": environ.get('ST_AUTH'),
"user": environ.get('ST_USER'),
"key": environ.get('ST_KEY'),
...
}
Configuration
-------------
When you create an instance of a ``SwiftService``, you can override a collection
of default options to suit your use case. Typically, the defaults are sensible to
get us started, but depending on your needs you might want to tweak them to
improve performance (options affecting large objects and thread counts can
significantly alter performance in the right situation).
Service level defaults and some extra options can also be overridden on a
per-operation (or even in some cases per-object) basis, and you will call out
which options affect which operations later in the document.
The configuration of the service API is performed using an options dictionary
passed to the ``SwiftService`` during initialisation. The options available
in this dictionary are described below, along with their defaults:
Options
~~~~~~~
``retries``: ``5``
The number of times that the library should attempt to retry HTTP
actions before giving up and reporting a failure.
``container_threads``: ``10``
``object_dd_threads``: ``10``
``object_uu_threads``: ``10``
``segment_threads``: ``10``
The above options determine the size of the available thread pools for
performing swift operations. Container operations (such as listing a
container) operate in the container threads, and a similar pattern
applies to object and segment threads.
.. note::
Object threads are separated into two separate thread pools:
``uu`` and ``dd``. This stands for "upload/update" and "download/delete",
and the corresponding actions will be run on separate threads pools.
``segment_size``: ``None``
If specified, this option enables uploading of large objects. Should the
object being uploaded be larger than 5G in size, this option is
mandatory otherwise the upload will fail. This option should be
specified as a size in bytes.
``use_slo``: ``False``
Used in combination with the above option, ``use_slo`` will upload large
objects as static rather than dynamic. Only static large objects provide
error checking for the downloaded object, so we recommend this option.
``segment_container``: ``None``
Allows the user to select the container into which large object segments
will be uploaded. We do not recommend changing this value as it could make
locating orphaned segments more difficult in the case of errors.
``leave_segments``: ``False``
Setting this option to true means that when deleting or overwriting a large
object, its segments will be left in the object store and must be cleaned
up manually. This option can be useful when sharing large object segments
between multiple objects in more advanced scenarios, but must be treated
with care, as it could lead to ever increasing storage usage.
``changed``: ``None``
This option affects uploads and simply means that those objects which
already exist in the object store will not be overwritten if the ``mtime``
and size of the source is the same as the existing object.
``skip_identical``: ``False``
A slightly more thorough case of the above, but rather than ``mtime`` and size
uses an object's ``MD5 sum``.
``yes_all``: ``False``
This options affects only download and delete, and in each case must be
specified in order to download/delete the entire contents of an account.
This option has no effect on any other calls.
``no_download``: ``False``
This option only affects download and means that all operations proceed as
normal with the exception that no data is written to disk.
``header``: ``[]``
Used with upload and post operations to set headers on objects. Headers
are specified as colon separated strings, e.g. "content-type:text/plain".
``meta``: ``[]``
Used to set metadata on an object similarly to headers.
.. note::
Setting metadata is a destructive operation, so when updating one
of many metadata values all desired metadata for an object must be re-applied.
``long``: ``False``
Affects only list operations, and results in more metrics being made
available in the results at the expense of lower performance.
``fail_fast``: ``False``
Applies to delete and upload operations, and attempts to abort queued
tasks in the event of errors.
``prefix``: ``None``
Affects list operations; only objects with the given prefix will be
returned/affected. It is not advisable to set at the service level, as
those operations that call list to discover objects on which they should
operate will also be affected.
``delimiter``: ``None``
Affects list operations, and means that listings only contain results up
to the first instance of the delimiter in the object name. This is useful
for working with objects containing '/' in their names to simulate folder
structures.
``dir_marker``: ``False``
Affects uploads, and allows empty 'pseudofolder' objects to be created
when the source of an upload is ``None``.
``checksum``: ``True``
Affects uploads and downloads. If set check md5 sum for the transfer.
``shuffle``: ``False``
When downloading objects, the default behaviour of the CLI is to shuffle
lists of objects in order to spread the load on storage drives when multiple
clients are downloading the same files to multiple locations (e.g. in the
event of distributing an update). When using the ``SwiftService`` directly,
object downloads are scheduled in the same order as they appear in the container
listing. When combined with a single download thread this means that objects
are downloaded in lexically-sorted order. Setting this option to ``True``
gives the same shuffling behaviour as the CLI.
``destination``: ``None``
When copying objects, this specifies the destination where the object
will be copied to. The default of None means copy will be the same as
source.
``fresh_metadata``: ``None``
When copying objects, this specifies that the object metadata on the
source will *not* be applied to the destination object - the
destination object will have a new fresh set of metadata that includes
*only* the metadata specified in the meta option if any at all.
Other available options can be found in ``swiftclient/service.py`` in the
source code for ``python-swiftclient``. Each ``SwiftService`` method also allows
for an optional dictionary to override those specified at init time, and the
appropriate docstrings show which options modify each method's behaviour.
Available Operations
--------------------
Each operation provided by the service API may raise a ``SwiftError`` or
``ClientException`` for any call that fails completely (or a call which
performs only one operation at an account or container level). In the case of a
successful call an operation returns one of the following:
* A dictionary detailing the results of a single operation.
* An iterator that produces result dictionaries (for calls that perform
multiple sub-operations).
A result dictionary can indicate either the success or failure of an individual
operation (detailed in the ``success`` key), and will either contain the
successful result, or an ``error`` key detailing the error encountered
(usually an instance of Exception).
An example result dictionary is given below:
.. code-block:: python
result = {
'action': 'download_object',
'success': True,
'container': container,
'object': obj,
'path': path,
'start_time': start_time,
'finish_time': finish_time,
'headers_receipt': headers_receipt,
'auth_end_time': conn.auth_end_time,
'read_length': bytes_read,
'attempts': conn.attempts
}
All the possible ``action`` values are detailed below:
.. code-block:: python
[
'stat_account',
'stat_container',
'stat_object',
'post_account',
'post_container',
'post_object',
'list_part', # list yields zero or more 'list_part' results
'download_object',
'create_container', # from upload
'create_dir_marker', # from upload
'upload_object',
'upload_segment',
'delete_container',
'delete_object',
'delete_segment', # from delete_object operations
'capabilities',
]
Stat
~~~~
Stat can be called against an account, a container, or a list of objects to
get account stats, container stats or information about the given objects. In
the first two cases a dictionary is returned containing the results of the
operation, and in the case of a list of object names being supplied, an
iterator over the results generated for each object is returned.
Information returned includes the amount of data used by the given
object/container/account and any headers or metadata set (this includes
user set data as well as content-type and modification times).
See :mod:`swiftclient.service.SwiftService.stat` for docs generated from the
method docstring.
Valid calls for this method are as follows:
* ``stat([options])``: Returns stats for the configured account.
* ``stat(<container>, [options])``: Returns stats for the given container.
* ``stat(<container>, <object_list>, [options])``: Returns stats for each
of the given objects in the given container (through the returned
iterator).
Results from stat are dictionaries indicating the success or failure of each
operation. In the case of a successful stat against an account or container,
the method returns immediately with one of the following results:
.. code-block:: python
{
'action': 'stat_account',
'success': True,
'items': items,
'headers': headers
}
.. code-block:: python
{
'action': 'stat_container',
'container': <container>,
'success': True,
'items': items,
'headers': headers
}
In the case of stat called against a list of objects, the method returns a
generator that returns the results of individual object stat operations as they
are performed on the thread pool:
.. code-block:: python
{
'action': 'stat_object',
'object': <object_name>,
'container': <container>,
'success': True,
'items': items,
'headers': headers
}
In the case of a failure the dictionary returned will indicate that the
operation was not successful, and will include the keys below:
.. code-block:: python
{
'action': <'stat_object'|'stat_container'|'stat_account'>,
'object': <'object_name'>, # Only for stat with objects list
'container': <container>, # Only for stat with objects list or container
'success': False,
'error': <error>,
'traceback': <trace>,
'error_timestamp': <timestamp>
}
.. topic:: Example
The code below demonstrates the use of ``stat`` to retrieve the headers for
a given list of objects in a container using 20 threads. The code creates a
mapping from object name to headers which is then pretty printed to the log.
.. literalinclude:: ../../examples/stat.py
:language: python
List
~~~~
List can be called against an account or a container to retrieve the containers
or objects contained within them. Each call returns an iterator that returns
pages of results (by default, up to 10000 results in each page).
See :mod:`swiftclient.service.SwiftService.list` for docs generated from the
method docstring.
If the given container or account does not exist, the list method will raise
a ``SwiftError``, but for all other success/failures a dictionary is returned.
Each successfully listed page returns a dictionary as described below:
.. code-block:: python
{
'action': <'list_account_part'|'list_container_part'>,
'container': <container>, # Only for listing a container
'prefix': <prefix>, # The prefix of returned objects/containers
'success': True,
'listing': [Item], # A list of results
# (only in the event of success)
'marker': <marker> # The last item name in the list
# (only in the event of success)
}
Where an item contains the following keys:
.. code-block:: python
{
'name': <name>,
'bytes': 10485760,
'last_modified': '2014-12-11T12:02:38.774540',
'hash': 'fb938269cbeabe4c234e1127bbd3b74a',
'content_type': 'application/octet-stream',
'meta': <metadata> # Full metadata listing from stat'ing each object
# this key only exists if 'long' is specified in options
}
Any failure listing an account or container that exists will return a failure
dictionary as described below:
.. code-block:: python
{
'action': <'list_account_part'|'list_container_part'>,,
'container': container, # Only for listing a container
'prefix': options['prefix'],
'success': success,
'marker': marker,
'error': error,
'traceback': <trace>,
'error_timestamp': <timestamp>
}
.. topic:: Example
The code below demonstrates the use of ``list`` to list all items in a
container that are over 10MiB in size:
.. literalinclude:: ../../examples/list.py
:language: python
Post
~~~~
Post can be called against an account, container or list of objects in order to
update the metadata attached to the given items. In the first two cases a single
dictionary is returned containing the results of the operation, and in the case
of a list of objects being supplied, an iterator over the results generated for
each object post is returned.
Each element of the object list may be a plain string of the object name, or a
``SwiftPostObject`` that allows finer control over the options and metadata
applied to each of the individual post operations. When a string is given for
the object name, the options and metadata applied are a combination of those
supplied to the call to ``post()`` and the defaults of the ``SwiftService``
object.
If the given container or account does not exist, the ``post`` method will
raise a ``SwiftError``. Successful metadata update results are dictionaries as
described below:
.. code-block:: python
{
'action': <'post_account'|'post_container'|'post_object'>,
'success': True,
'container': <container>,
'object': <object>,
'headers': {},
'response_dict': <HTTP response details>
}
.. note::
Updating user metadata keys will not only add any specified keys, but
will also remove user metadata that has previously been set. This means
that each time user metadata is updated, the complete set of desired
key-value pairs must be specified.
.. topic:: Example
The code below demonstrates the use of ``post`` to set an archive folder in
a given container to expire after a 24 hour delay:
.. literalinclude:: ../../examples/post.py
:language: python
Download
~~~~~~~~
Download can be called against an entire account, a single container, or a list
of objects in a given container. Each element of the object list is a string
detailing the full name of an object to download.
In order to download the full contents of an entire account, you must set the
value of ``yes_all`` to ``True`` in the ``options`` dictionary supplied to
either the ``SwiftService`` instance or the call to ``download``.
If the given container or account does not exist, the ``download`` method will
raise a ``SwiftError``, otherwise an iterator over the results generated for
each object download is returned.
See :mod:`swiftclient.service.SwiftService.download` for docs generated from the
method docstring.
For each successfully downloaded object, the results returned by the iterator
will be a dictionary as described below (results are not returned for completed
container or object segment downloads):
.. code-block:: python
{
'action': 'download_object',
'container': <container>,
'object': <object name>,
'success': True,
'path': <local path to downloaded object>,
'pseudodir': <if true, the download created an empty directory>,
'start_time': <time download started>,
'end_time': <time download completed>,
'headers_receipt': <time the headers from the object were retrieved>,
'auth_end_time': <time authentication completed>,
'read_length': <bytes_read>,
'attempts': <attempt count>,
'response_dict': <HTTP response details>
}
Any failure uploading an object will return a failure dictionary as described
below:
.. code-block:: python
{
'action': 'download_object',
'container': <container>,
'object': <object name>,
'success': False,
'path': <local path of the failed download>,
'pseudodir': <if true, the failed download was an empty directory>,
'attempts': <attempt count>,
'error': <error>,
'traceback': <trace>,
'error_timestamp': <timestamp>,
'response_dict': <HTTP response details>
}
.. topic:: Example
The code below demonstrates the use of ``download`` to download all PNG
images from a dated archive folder in a given container:
.. literalinclude:: ../../examples/download.py
:language: python
Upload
~~~~~~
Upload is always called against an account and container and with a list of
objects to upload. Each element of the object list may be a plain string
detailing the path of the object to upload, or a ``SwiftUploadObject`` that
allows finer control over some aspects of the individual operations.
When a simple string is supplied to specify a file to upload, the name of the
object uploaded is the full path of the specified file and the options used for
the upload are those supplied to the call to ``upload``.
Constructing a ``SwiftUploadObject`` allows the user to supply an object name
for the uploaded file, and modify the options used by ``upload`` at the
granularity of individual files.
If the given container or account does not exist, the ``upload`` method will
raise a ``SwiftError``, otherwise an iterator over the results generated for
each object upload is returned.
See :mod:`swiftclient.service.SwiftService.upload` for docs generated from the
method docstring.
For each successfully uploaded object (or object segment), the results returned
by the iterator will be a dictionary as described below:
.. code-block:: python
{
'action': 'upload_object',
'container': <container>,
'object': <object name>,
'success': True,
'status': <'uploaded'|'skipped-identical'|'skipped-changed'>,
'attempts': <attempt count>,
'response_dict': <HTTP response details>
}
{
'action': 'upload_segment',
'for_container': <container>,
'for_object': <object name>,
'segment_index': <segment_index>,
'segment_size': <segment_size>,
'segment_location': <segment_path>
'segment_etag': <etag>,
'log_line': <object segment n>
'success': True,
'response_dict': <HTTP response details>,
'attempts': <attempt count>
}
Any failure uploading an object will return a failure dictionary as described
below:
.. code-block:: python
{
'action': 'upload_object',
'container': <container>,
'object': <object name>,
'success': False,
'attempts': <attempt count>,
'error': <error>,
'traceback': <trace>,
'error_timestamp': <timestamp>,
'response_dict': <HTTP response details>
}
{
'action': 'upload_segment',
'for_container': <container>,
'for_object': <object name>,
'segment_index': <segment_index>,
'segment_size': <segment_size>,
'segment_location': <segment_path>,
'log_line': <object segment n>,
'success': False,
'error': <error>,
'traceback': <trace>,
'error_timestamp': <timestamp>,
'response_dict': <HTTP response details>,
'attempts': <attempt count>
}
.. topic:: Example
The code below demonstrates the use of ``upload`` to upload all files and
folders in a given directory, and rename each object by replacing the root
directory name with 'my-<d>-objects', where <d> is the name of the uploaded
directory:
.. literalinclude:: ../../examples/upload.py
:language: python
Delete
~~~~~~
Delete can be called against an account or a container to remove the containers
or objects contained within them. Each call to ``delete`` returns an iterator
over results of each resulting sub-request.
If the number of requested delete operations is large and the target swift
cluster is running the bulk middleware, the call to ``SwiftService.delete`` will
make use of bulk operations and the returned result iterator will return
``bulk_delete`` results rather than individual ``delete_object``,
``delete_container`` or ``delete_segment`` results.
See :mod:`swiftclient.service.SwiftService.delete` for docs generated from the
method docstring.
For each successfully deleted container, object or segment, the results returned
by the iterator will be a dictionary as described below:
.. code-block:: python
{
'action': <'delete_object'|'delete_segment'>,
'container': <container>,
'object': <object name>,
'success': True,
'attempts': <attempt count>,
'response_dict': <HTTP response details>
}
{
'action': 'delete_container',
'container': <container>,
'success': True,
'response_dict': <HTTP response details>,
'attempts': <attempt count>
}
{
'action': 'bulk_delete',
'container': <container>,
'objects': <[objects]>,
'success': True,
'attempts': <attempt count>,
'response_dict': <HTTP response details>
}
Any failure in a delete operation will return a failure dictionary as described
below:
.. code-block:: python
{
'action': ('delete_object'|'delete_segment'),
'container': <container>,
'object': <object name>,
'success': False,
'attempts': <attempt count>,
'error': <error>,
'traceback': <trace>,
'error_timestamp': <timestamp>,
'response_dict': <HTTP response details>
}
{
'action': 'delete_container',
'container': <container>,
'success': False,
'error': <error>,
'traceback': <trace>,
'error_timestamp': <timestamp>,
'response_dict': <HTTP response details>,
'attempts': <attempt count>
}
{
'action': 'bulk_delete',
'container': <container>,
'objects': <[objects]>,
'success': False,
'attempts': <attempt count>,
'error': <error>,
'traceback': <trace>,
'error_timestamp': <timestamp>,
'response_dict': <HTTP response details>
}
.. topic:: Example
The code below demonstrates the use of ``delete`` to remove a given list of
objects from a specified container. As the objects are deleted the
transaction ID of the relevant request is printed along with the object name
and number of attempts required. By printing the transaction ID, the printed
operations can be easily linked to events in the swift server logs:
.. literalinclude:: ../../examples/delete.py
:language: python
Copy
~~~~
Copy can be called to copy an object or update the metadata on the given items.
Each element of the object list may be a plain string of the object name, or a
``SwiftCopyObject`` that allows finer control over the options applied to each
of the individual copy operations (destination, fresh_metadata, options).
Destination should be in format /container/object; if not set, the object will be
copied onto itself. Fresh_metadata sets mode of operation on metadata. If not set,
current object user metadata will be copied/preserved; if set, all current user
metadata will be removed.
Returns an iterator over the results generated for each object copy (and may
also include the results of creating destination containers).
When a string is given for the object name, destination and fresh metadata will
default to None and None, which result in adding metadata to existing objects.
Successful copy results are dictionaries as described below:
.. code-block:: python
{
'action': 'copy_object',
'success': True,
'container': <container>,
'object': <object>,
'destination': <destination>,
'headers': {},
'fresh_metadata': <boolean>,
'response_dict': <HTTP response details>
}
Any failure in a copy operation will return a failure dictionary as described
below:
.. code-block:: python
{
'action': 'copy_object',
'success': False,
'container': <container>,
'object': <object>,
'destination': <destination>,
'headers': {},
'fresh_metadata': <boolean>,
'response_dict': <HTTP response details>,
'error': <error>,
'traceback': <traceback>,
'error_timestamp': <timestamp>
}
.. topic:: Example
The code below demonstrates the use of ``copy`` to add new user metadata for
objects a and b, and to copy object c to d (with added metadata).
.. literalinclude:: ../../examples/copy.py
:language: python
Capabilities
~~~~~~~~~~~~
Capabilities can be called against an account or a particular proxy URL in
order to determine the capabilities of the swift cluster. These capabilities
include details about configuration options and the middlewares that are
installed in the proxy pipeline.
See :mod:`swiftclient.service.SwiftService.capabilities` for docs generated from
the method docstring.
For each successful call to list capabilities, a result dictionary will be
returned with the contents described below:
.. code-block:: python
{
'action': 'capabilities',
'timestamp': <time of the call>,
'success': True,
'capabilities': <dictionary containing capability details>
}
The contents of the capabilities dictionary contain the core swift capabilities
under the key ``swift``; all other keys show the configuration options for
additional middlewares deployed in the proxy pipeline. An example capabilities
dictionary is given below:
.. code-block:: python
{
'account_quotas': {},
'bulk_delete': {
'max_deletes_per_request': 10000,
'max_failed_deletes': 1000
},
'bulk_upload': {
'max_containers_per_extraction': 10000,
'max_failed_extractions': 1000
},
'container_quotas': {},
'container_sync': {'realms': {}},
'formpost': {},
'keystoneauth': {},
'slo': {
'max_manifest_segments': 1000,
'max_manifest_size': 2097152,
'min_segment_size': 1048576
},
'swift': {
'account_autocreate': True,
'account_listing_limit': 10000,
'allow_account_management': True,
'container_listing_limit': 10000,
'extra_header_count': 0,
'max_account_name_length': 256,
'max_container_name_length': 256,
'max_file_size': 5368709122,
'max_header_size': 8192,
'max_meta_count': 90,
'max_meta_name_length': 128,
'max_meta_overall_size': 4096,
'max_meta_value_length': 256,
'max_object_name_length': 1024,
'policies': [
{'default': True, 'name': 'Policy-0'}
],
'strict_cors_mode': False,
'version': '2.2.2'
},
'tempurl': {
'methods': ['GET', 'HEAD', 'PUT']
}
}
.. topic:: Example
The code below demonstrates the use of ``capabilities`` to determine if the
Swift cluster supports static large objects, and if so, the maximum number
of segments that can be described in a single manifest file, along with the
size restrictions on those objects:
.. literalinclude:: ../../examples/capabilities.py
:language: python

View File

@ -1,37 +0,0 @@
.. _swiftclient_package:
swiftclient
==============
.. automodule:: swiftclient
swiftclient.authv1
==================
.. automodule:: swiftclient.authv1
:inherited-members:
swiftclient.client
==================
.. automodule:: swiftclient.client
swiftclient.service
===================
.. automodule:: swiftclient.service
swiftclient.exceptions
======================
.. automodule:: swiftclient.exceptions
swiftclient.multithreading
==========================
.. automodule:: swiftclient.multithreading
swiftclient.utils
=================
.. automodule:: swiftclient.utils

View File

@ -1,20 +0,0 @@
import logging
from swiftclient.exceptions import ClientException
from swiftclient.service import SwiftService
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
with SwiftService() as swift:
try:
capabilities_result = swift.capabilities()
capabilities = capabilities_result['capabilities']
if 'slo' in capabilities:
print('SLO is supported')
else:
print('SLO is not supported')
except ClientException as e:
logger.error(e.value)

View File

@ -1,30 +0,0 @@
import logging
from swiftclient.service import SwiftService, SwiftCopyObject, SwiftError
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
with SwiftService() as swift:
try:
obj = SwiftCopyObject("c", {"Destination": "/cont/d"})
for i in swift.copy(
"cont", ["a", "b", obj],
{"meta": ["foo:bar"], "Destination": "/cc"}):
if i["success"]:
if i["action"] == "copy_object":
print(
"object %s copied from /%s/%s" %
(i["destination"], i["container"], i["object"])
)
if i["action"] == "create_container":
print(
"container %s created" % i["container"]
)
else:
if "error" in i and isinstance(i["error"], Exception):
raise i["error"]
except SwiftError as e:
logger.error(e.value)

View File

@ -1,34 +0,0 @@
import logging
from swiftclient.service import SwiftService
from sys import argv
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
_opts = {'object_dd_threads': 20}
container = argv[1]
objects = argv[2:]
with SwiftService(options=_opts) as swift:
del_iter = swift.delete(container=container, objects=objects)
for del_res in del_iter:
c = del_res.get('container', '')
o = del_res.get('object', '')
a = del_res.get('attempts')
if del_res['success'] and not del_res['action'] == 'bulk_delete':
rd = del_res.get('response_dict')
if rd is not None:
t = dict(rd.get('headers', {}))
if t:
print(
'Successfully deleted {0}/{1} in {2} attempts '
'(transaction id: {3})'.format(c, o, a, t)
)
else:
print(
'Successfully deleted {0}/{1} in {2} '
'attempts'.format(c, o, a)
)

View File

@ -1,37 +0,0 @@
import logging
from swiftclient.service import SwiftService, SwiftError
from sys import argv
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
def is_png(obj):
return (
obj["name"].lower().endswith('.png') or
obj["content_type"] == 'image/png'
)
container = argv[1]
with SwiftService() as swift:
try:
list_options = {"prefix": "archive_2016-01-01/"}
list_parts_gen = swift.list(container=container)
for page in list_parts_gen:
if page["success"]:
objects = [
obj["name"] for obj in page["listing"] if is_png(obj)
]
for down_res in swift.download(
container=container,
objects=objects):
if down_res['success']:
print("'%s' downloaded" % down_res['object'])
else:
print("'%s' download failed" % down_res['object'])
else:
raise page["error"]
except SwiftError as e:
logger.error(e.value)

View File

@ -1,32 +0,0 @@
import logging
from swiftclient.service import SwiftService, SwiftError
from sys import argv
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
container = argv[1]
minimum_size = 10*1024**2
with SwiftService() as swift:
try:
list_parts_gen = swift.list(container=container)
for page in list_parts_gen:
if page["success"]:
for item in page["listing"]:
i_size = int(item["bytes"])
if i_size > minimum_size:
i_name = item["name"]
i_etag = item["hash"]
print(
"%s [size: %s] [etag: %s]" %
(i_name, i_size, i_etag)
)
else:
raise page["error"]
except SwiftError as e:
logger.error(e.value)

View File

@ -1,31 +0,0 @@
import logging
from swiftclient.service import SwiftService, SwiftError
from sys import argv
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
container = argv[1]
with SwiftService() as swift:
try:
list_options = {"prefix": "archive_2016-01-01/"}
list_parts_gen = swift.list(container=container)
for page in list_parts_gen:
if page["success"]:
objects = [obj["name"] for obj in page["listing"]]
post_options = {"header": "X-Delete-After:86400"}
for post_res in swift.post(
container=container,
objects=objects,
options=post_options):
if post_res['success']:
print("Object '%s' POST success" % post_res['object'])
else:
print("Object '%s' POST failed" % post_res['object'])
else:
raise page["error"]
except SwiftError as e:
logger.error(e.value)

View File

@ -1,25 +0,0 @@
import logging
import pprint
from swiftclient.service import SwiftService
from sys import argv
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
_opts = {'object_dd_threads': 20}
with SwiftService(options=_opts) as swift:
container = argv[1]
objects = argv[2:]
header_data = {}
stats_it = swift.stat(container=container, objects=objects)
for stat_res in stats_it:
if stat_res['success']:
header_data[stat_res['object']] = stat_res['headers']
else:
logger.error(
'Failed to retrieve stats for %s' % stat_res['object']
)
pprint.pprint(header_data)

View File

@ -1,72 +0,0 @@
import logging
from os import walk
from os.path import join
from swiftclient.multithreading import OutputManager
from swiftclient.service import SwiftError, SwiftService, SwiftUploadObject
from sys import argv
logging.basicConfig(level=logging.ERROR)
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
_opts = {'object_uu_threads': 20}
dir = argv[1]
container = argv[2]
with SwiftService(options=_opts) as swift, OutputManager() as out_manager:
try:
# Collect all the files and folders in the given directory
objs = []
dir_markers = []
for (_dir, _ds, _fs) in walk(dir):
if not (_ds + _fs):
dir_markers.append(_dir)
else:
objs.extend([join(_dir, _f) for _f in _fs])
# Now that we've collected all the required files and dir markers
# build the ``SwiftUploadObject``s for the call to upload
objs = [
SwiftUploadObject(
o, object_name=o.replace(
dir, 'my-%s-objects' % dir, 1
)
) for o in objs
]
dir_markers = [
SwiftUploadObject(
None, object_name=d.replace(
dir, 'my-%s-objects' % dir, 1
), options={'dir_marker': True}
) for d in dir_markers
]
# Schedule uploads on the SwiftService thread pool and iterate
# over the results
for r in swift.upload(container, objs + dir_markers):
if r['success']:
if 'object' in r:
print(r['object'])
elif 'for_object' in r:
print(
'%s segment %s' % (r['for_object'],
r['segment_index'])
)
else:
error = r['error']
if r['action'] == "create_container":
logger.warning(
'Warning: failed to create container '
"'%s'%s", container, error
)
elif r['action'] == "upload_object":
logger.error(
"Failed to upload object %s to container %s: %s" %
(container, r['object'], error)
)
else:
logger.error("%s" % error)
except SwiftError as e:
logger.error(e.value)

View File

@ -1,38 +0,0 @@
---
features:
- Added a copy object method.
- Arbitrary query strings can now be passed into container functions.
- >
Client certificate and key can now be specified via CLI
options (--os-cert/--os-key) or environment variables ($OS_CERT/$OS_KEY).
- >
A new CLI option `--ignore-checksum` can be specified to turn off
checksum validation. In the SDK, the new `checksum=True` parameter can
be used for the same purpose.
- Added --json option to `swift capabilities` / `swift info`
- Default to v3 auth if we find a (user|project)-domain-(name|id) option.
- Added a Python version constraint of >= Py27.
- >
`client.py` will now retry on a 401 (auth error) even if `retries` is
set to zero.
- Fixed `swift download` when `marker` was specified.
- Object segments uploaded via swiftclient are now given the content type
"application/swiftclient-segment".
- >
"Directory marker" objects are now given a "application/directory"
content type to match both Swift's `staticweb` feature and other
ecosystem tools.
- >
Strip leading/trailing whitespace from headers (otherwise, new versions
of the requests library will raise an InvalidHeader error). Additionally,
header values with standard types (integer, float, or bool) are coerced
to strings before being sent to a socket.
- >
Non-python dependencies are now specified in bindep.txt. Currently this
only lists a single dependency for testing (PyPy), but if future
dependencies are added, they will be included in this file.
- Client exceptions now include response headers. One benefit is that
this allows clients to see transaction IDs without needing to turn on
debug logging.
- Client connections now accept gzip-encoded responses.
- Various other minor bug fixes and improvements.

View File

@ -1,12 +0,0 @@
features:
- >
Added Keystone session support and a "v1password" plugin for Keystone.
This plugin provides a way for Keystone sessions (and clients that
use them, like python-openstackclient) to communicate with old auth
endpoints that still use this mechanism.
- >
HEAD, GET, and DELETE now support sending additional headers to match
existing functionality on PUT requests.
- Various other minor bug fixes and improvements.

View File

@ -1,3 +0,0 @@
futures>=3.0;python_version=='2.7' or python_version=='2.6' # BSD
requests>=1.1
six>=1.5.2

View File

@ -1,49 +0,0 @@
#!/bin/bash
function usage {
echo "Usage: $0 [OPTION]..."
echo "Run python-swiftclient's test suite(s)"
echo ""
echo " -p, --pep8 Just run pep8"
echo " -h, --help Print this usage message"
echo ""
echo "This script is deprecated and currently retained for compatibility."
echo 'You can run the full test suite for multiple environments by running "tox".'
echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only'
echo 'the pep8 tests with "tox -e pep8".'
exit
}
command -v tox > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo 'This script requires "tox" to run.'
echo 'You can install it with "pip install tox".'
exit 1;
fi
just_pep8=0
function process_option {
case "$1" in
-h|--help) usage;;
-p|--pep8) let just_pep8=1;;
esac
}
for arg in "$@"; do
process_option $arg
done
if [ $just_pep8 -eq 1 ]; then
tox -e pep8
exit
fi
tox -e py27 $toxargs 2>&1 | tee run_tests.err.log || exit
if [ ${PIPESTATUS[0]} -ne 0 ]; then
exit ${PIPESTATUS[0]}
fi
if [ -z "$toxargs" ]; then
tox -e pep8
fi

View File

@ -1,59 +0,0 @@
[metadata]
name = python-swiftclient
summary = OpenStack Object Storage API Client Library
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = https://docs.openstack.org/python-swiftclient/latest/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Operating System :: Microsoft :: Windows
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
[global]
setup-hooks =
pbr.hooks.setup_hook
[files]
packages =
swiftclient
scripts =
bin/swift
data_files =
share/man/man1 = doc/manpages/swift.1
[extras]
keystone =
python-keystoneclient>=0.7.0
[entry_points]
console_scripts =
swift = swiftclient.shell:main
keystoneauth1.plugin =
v1password = swiftclient.authv1:PasswordLoader
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
[upload_sphinx]
upload-dir = doc/build/html
[wheel]
universal = 1
[pbr]
skip_authors = True
skip_changelog = True

View File

@ -1,26 +0,0 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools, sys
if sys.version_info < (2, 7):
sys.exit('Sorry, Python < 2.7 is not supported for'
' python-swiftclient>=3.0')
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

View File

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2012 Rackspace
#
# 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.
"""
OpenStack Swift Python client binding.
"""
from .client import * # noqa
# At setup.py time, we haven't installed anything yet, so there
# is nothing that is able to set this version property. Squelching
# that exception here should be fine- if there are problems with
# pkg_resources in a real install, that will manifest itself as
# an error still
try:
from swiftclient import version
__version__ = version.version_string
except Exception:
pass

View File

@ -1,350 +0,0 @@
# Copyright 2016 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.
"""
Authentication plugin for keystoneauth to support v1 endpoints.
Way back in the long-long ago, there was no Keystone. Swift used an auth
mechanism now known as "v1", which used only HTTP headers. Auth requests
and responses would look something like::
> GET /auth/v1.0 HTTP/1.1
> Host: <swift server>
> X-Auth-User: <tenant>:<user>
> X-Auth-Key: <password>
>
< HTTP/1.1 200 OK
< X-Storage-Url: http://<swift server>/v1/<tenant account>
< X-Auth-Token: <token>
< X-Storage-Token: <token>
<
This plugin provides a way for Keystone sessions (and clients that
use them, like python-openstackclient) to communicate with old auth
endpoints that still use this mechanism, such as tempauth, swauth,
or https://identity.api.rackspacecloud.com/v1.0
"""
import datetime
import json
import time
from six.moves.urllib.parse import urljoin
# Note that while we import keystoneauth1 here, we *don't* need to add it to
# requirements.txt -- this entire module only makes sense (and should only be
# loaded) if keystoneauth is already installed.
from keystoneauth1 import plugin
from keystoneauth1 import exceptions
from keystoneauth1 import loading
from keystoneauth1.identity import base
# stupid stdlib...
class _UTC(datetime.tzinfo):
def utcoffset(self, dt):
return datetime.timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return datetime.timedelta(0)
UTC = _UTC()
del _UTC
class ServiceCatalogV1(object):
def __init__(self, auth_url, storage_url, account):
self.auth_url = auth_url
self._storage_url = storage_url
self._account = account
@property
def storage_url(self):
if self._account:
return urljoin(self._storage_url.rstrip('/'), self._account)
return self._storage_url
@property
def catalog(self):
# openstackclient wants this for the `catalog list` and
# `catalog show` commands
endpoints = [{
'region': 'default',
'publicURL': self._storage_url,
}]
if self.storage_url != self._storage_url:
endpoints.insert(0, {
'region': 'override',
'publicURL': self.storage_url,
})
return [
{
'name': 'swift',
'type': 'object-store',
'endpoints': endpoints,
},
{
'name': 'auth',
'type': 'identity',
'endpoints': [{
'region': 'default',
'publicURL': self.auth_url,
}],
}
]
def url_for(self, **kwargs):
kwargs.setdefault('interface', 'public')
kwargs.setdefault('service_type', None)
if kwargs['service_type'] == 'object-store':
return self.storage_url
# Although our "catalog" includes an identity entry, nothing that uses
# url_for() (including `openstack endpoint list`) will know what to do
# with it. Better to just raise the exception, cribbing error messages
# from keystoneauth1/access/service_catalog.py
if 'service_name' in kwargs and 'region_name' in kwargs:
msg = ('%(interface)s endpoint for %(service_type)s service '
'named %(service_name)s in %(region_name)s region not '
'found' % kwargs)
elif 'service_name' in kwargs:
msg = ('%(interface)s endpoint for %(service_type)s service '
'named %(service_name)s not found' % kwargs)
elif 'region_name' in kwargs:
msg = ('%(interface)s endpoint for %(service_type)s service '
'in %(region_name)s region not found' % kwargs)
else:
msg = ('%(interface)s endpoint for %(service_type)s service '
'not found' % kwargs)
raise exceptions.EndpointNotFound(msg)
class AccessInfoV1(object):
"""An object for encapsulating a raw v1 auth token."""
def __init__(self, auth_url, storage_url, account, username, auth_token,
token_life):
self.auth_url = auth_url
self.storage_url = storage_url
self.account = account
self.service_catalog = ServiceCatalogV1(auth_url, storage_url, account)
self.username = username
self.auth_token = auth_token
self._issued = time.time()
try:
self._expires = self._issued + float(token_life)
except (TypeError, ValueError):
self._expires = None
# following is used by openstackclient
self.project_id = None
@property
def expires(self):
if self._expires is None:
return None
return datetime.datetime.fromtimestamp(self._expires, UTC)
@property
def issued(self):
return datetime.datetime.fromtimestamp(self._issued, UTC)
@property
def user_id(self):
# openstackclient wants this for the `token issue` command
return self.username
def will_expire_soon(self, stale_duration):
"""Determines if expiration is about to occur.
:returns: true if expiration is within the given duration
"""
if self._expires is None:
return False # assume no expiration
return time.time() + stale_duration > self._expires
def get_state(self):
"""Serialize the current state."""
return json.dumps({
'auth_url': self.auth_url,
'storage_url': self.storage_url,
'account': self.account,
'username': self.username,
'auth_token': self.auth_token,
'issued': self._issued,
'expires': self._expires}, sort_keys=True)
@classmethod
def from_state(cls, data):
"""Deserialize the given state.
:returns: a new AccessInfoV1 object with the given state
"""
data = json.loads(data)
access = cls(
data['auth_url'],
data['storage_url'],
data['account'],
data['username'],
data['auth_token'],
token_life=None)
access._issued = data['issued']
access._expires = data['expires']
return access
class PasswordPlugin(base.BaseIdentityPlugin):
"""A plugin for authenticating with a username and password.
Subclassing from BaseIdentityPlugin gets us a few niceties, like handling
token invalidation and locking during authentication.
:param string auth_url: Identity v1 endpoint for authorization.
:param string username: Username for authentication.
:param string password: Password for authentication.
:param string project_name: Swift account to use after authentication.
We use 'project_name' to be consistent with
other auth plugins.
:param string reauthenticate: Whether to allow re-authentication.
"""
access_class = AccessInfoV1
def __init__(self, auth_url, username, password, project_name=None,
reauthenticate=True):
super(PasswordPlugin, self).__init__(
auth_url=auth_url,
reauthenticate=reauthenticate)
self.user = username
self.key = password
self.account = project_name
def get_auth_ref(self, session, **kwargs):
"""Obtain a token from a v1 endpoint.
This function should not be called independently and is expected to be
invoked via the do_authenticate function.
This function will be invoked if the AcessInfo object cached by the
plugin is not valid. Thus plugins should always fetch a new AccessInfo
when invoked. If you are looking to just retrieve the current auth
data then you should use get_access.
:param session: A session object that can be used for communication.
:returns: Token access information.
"""
headers = {'X-Auth-User': self.user,
'X-Auth-Key': self.key}
resp = session.get(self.auth_url, headers=headers,
authenticated=False, log=False)
if resp.status_code // 100 != 2:
raise exceptions.InvalidResponse(response=resp)
if 'X-Storage-Url' not in resp.headers:
raise exceptions.InvalidResponse(response=resp)
if 'X-Auth-Token' not in resp.headers and \
'X-Storage-Token' not in resp.headers:
raise exceptions.InvalidResponse(response=resp)
token = resp.headers.get('X-Storage-Token',
resp.headers.get('X-Auth-Token'))
return AccessInfoV1(
auth_url=self.auth_url,
storage_url=resp.headers['X-Storage-Url'],
account=self.account,
username=self.user,
auth_token=token,
token_life=resp.headers.get('X-Auth-Token-Expires'))
def get_cache_id_elements(self):
"""Get the elements for this auth plugin that make it unique."""
return {'auth_url': self.auth_url,
'user': self.user,
'key': self.key,
'account': self.account}
def get_endpoint(self, session, interface='public', **kwargs):
"""Return an endpoint for the client."""
if interface is plugin.AUTH_INTERFACE:
return self.auth_url
else:
return self.get_access(session).service_catalog.url_for(
interface=interface, **kwargs)
def get_auth_state(self):
"""Retrieve the current authentication state for the plugin.
:returns: raw python data (which can be JSON serialized) that can be
moved into another plugin (of the same type) to have the
same authenticated state.
"""
if self.auth_ref:
return self.auth_ref.get_state()
def set_auth_state(self, data):
"""Install existing authentication state for a plugin.
Take the output of get_auth_state and install that authentication state
into the current authentication plugin.
"""
if data:
self.auth_ref = self.access_class.from_state(data)
else:
self.auth_ref = None
def get_sp_auth_url(self, *args, **kwargs):
raise NotImplementedError()
def get_sp_url(self, *args, **kwargs):
raise NotImplementedError()
def get_discovery(self, *args, **kwargs):
raise NotImplementedError()
class PasswordLoader(loading.BaseLoader):
"""Option handling for the ``v1password`` plugin."""
plugin_class = PasswordPlugin
def get_options(self):
"""Return the list of parameters associated with the auth plugin.
This list may be used to generate CLI or config arguments.
"""
return [
loading.Opt('auth-url', required=True,
help='Authentication URL'),
# overload project-name as a way to specify an alternate account,
# since:
# - in a world of just users & passwords, this seems the closest
# analog to a project, and
# - openstackclient will (or used to?) still require that you
# provide one anyway
loading.Opt('project-name', required=False,
help='Swift account to use'),
loading.Opt('username', required=True,
deprecated=[loading.Opt('user-name')],
help='Username to login with'),
loading.Opt('password', required=True, secret=True,
help='Password to use'),
]

File diff suppressed because it is too large Load Diff

View File

@ -1,194 +0,0 @@
# 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.
from swiftclient.utils import prt_bytes, split_request_headers
POLICY_HEADER_PREFIX = 'x-account-storage-policy-'
def stat_account(conn, options):
items = []
req_headers = split_request_headers(options.get('header', []))
headers = conn.head_account(headers=req_headers)
if options['verbose'] > 1:
items.extend([
('StorageURL', conn.url),
('Auth Token', conn.token),
])
container_count = int(headers.get('x-account-container-count', 0))
object_count = prt_bytes(headers.get('x-account-object-count', 0),
options['human']).lstrip()
bytes_used = prt_bytes(headers.get('x-account-bytes-used', 0),
options['human']).lstrip()
items.extend([
('Account', conn.url.rsplit('/', 1)[-1]),
('Containers', container_count),
('Objects', object_count),
('Bytes', bytes_used),
])
policies = set()
for header_key, header_value in headers.items():
if header_key.lower().startswith(POLICY_HEADER_PREFIX):
policy_name = header_key.rsplit('-', 2)[0].split('-', 4)[-1]
policies.add(policy_name)
for policy in policies:
container_count_header = (POLICY_HEADER_PREFIX + policy +
'-container-count')
if container_count_header in headers:
items.append(
('Containers in policy "' + policy + '"',
prt_bytes(headers[container_count_header],
options['human']).lstrip())
)
items.extend((
('Objects in policy "' + policy + '"',
prt_bytes(
headers.get(
POLICY_HEADER_PREFIX + policy + '-object-count', 0),
options['human']
).lstrip()),
('Bytes in policy "' + policy + '"',
prt_bytes(
headers.get(
POLICY_HEADER_PREFIX + policy + '-bytes-used', 0),
options['human']
).lstrip()),
))
return items, headers
def print_account_stats(items, headers, output_manager):
exclude_policy_headers = []
for header_key, header_value in headers.items():
if header_key.lower().startswith(POLICY_HEADER_PREFIX):
exclude_policy_headers.append(header_key)
items.extend(headers_to_items(
headers, meta_prefix='x-account-meta-',
exclude_headers=([
'content-length', 'date',
'x-account-container-count',
'x-account-object-count',
'x-account-bytes-used'] + exclude_policy_headers)))
# line up the items nicely
offset = max(len(item) for item, value in items)
output_manager.print_items(items, offset=offset)
def stat_container(conn, options, container):
req_headers = split_request_headers(options.get('header', []))
headers = conn.head_container(container, headers=req_headers)
items = []
if options['verbose'] > 1:
path = '%s/%s' % (conn.url, container)
items.extend([
('URL', path),
('Auth Token', conn.token)
])
object_count = prt_bytes(
headers.get('x-container-object-count', 0),
options['human']).lstrip()
bytes_used = prt_bytes(headers.get('x-container-bytes-used', 0),
options['human']).lstrip()
items.extend([
('Account', conn.url.rsplit('/', 1)[-1]),
('Container', container),
('Objects', object_count),
('Bytes', bytes_used),
('Read ACL', headers.get('x-container-read', '')),
('Write ACL', headers.get('x-container-write', '')),
('Sync To', headers.get('x-container-sync-to', '')),
('Sync Key', headers.get('x-container-sync-key', ''))
])
return items, headers
def print_container_stats(items, headers, output_manager):
items.extend(headers_to_items(
headers,
meta_prefix='x-container-meta-',
exclude_headers=(
'content-length', 'date',
'x-container-object-count',
'x-container-bytes-used',
'x-container-read',
'x-container-write',
'x-container-sync-to',
'x-container-sync-key'
)
))
# line up the items nicely
offset = max(len(item) for item, value in items)
output_manager.print_items(items, offset=offset)
def stat_object(conn, options, container, obj):
req_headers = split_request_headers(options.get('header', []))
headers = conn.head_object(container, obj, headers=req_headers)
items = []
if options['verbose'] > 1:
path = '%s/%s/%s' % (conn.url, container, obj)
items.extend([
('URL', path),
('Auth Token', conn.token)
])
content_length = prt_bytes(headers.get('content-length', 0),
options['human']).lstrip()
items.extend([
('Account', conn.url.rsplit('/', 1)[-1]),
('Container', container),
('Object', obj),
('Content Type', headers.get('content-type')),
('Content Length', content_length),
('Last Modified', headers.get('last-modified')),
('ETag', headers.get('etag')),
('Manifest', headers.get('x-object-manifest'))
])
return items, headers
def print_object_stats(items, headers, output_manager):
items.extend(headers_to_items(
headers,
meta_prefix='x-object-meta-',
exclude_headers=(
'content-type', 'content-length',
'last-modified', 'etag', 'date',
'x-object-manifest')
))
# line up the items nicely
offset = max(len(item) for item, value in items)
output_manager.print_items(items, offset=offset, skip_missing=True)
def headers_to_items(headers, meta_prefix='', exclude_headers=None):
exclude_headers = exclude_headers or []
other_items = []
meta_items = []
for key, value in headers.items():
if key not in exclude_headers:
if key.startswith(meta_prefix):
meta_key = 'Meta %s' % key[len(meta_prefix):].title()
meta_items.append((meta_key, value))
else:
other_items.append((key.title(), value))
return meta_items + other_items

View File

@ -1,81 +0,0 @@
# Copyright (c) 2010-2013 OpenStack, LLC.
#
# 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.
from six.moves import urllib
class ClientException(Exception):
def __init__(self, msg, http_scheme='', http_host='', http_port='',
http_path='', http_query='', http_status=None, http_reason='',
http_device='', http_response_content='',
http_response_headers=None):
super(ClientException, self).__init__(msg)
self.msg = msg
self.http_scheme = http_scheme
self.http_host = http_host
self.http_port = http_port
self.http_path = http_path
self.http_query = http_query
self.http_status = http_status
self.http_reason = http_reason
self.http_device = http_device
self.http_response_content = http_response_content
self.http_response_headers = http_response_headers
@classmethod
def from_response(cls, resp, msg=None, body=None):
msg = msg or '%s %s' % (resp.status_code, resp.reason)
body = body or resp.content
parsed_url = urllib.parse.urlparse(resp.request.url)
return cls(msg, parsed_url.scheme, parsed_url.hostname,
parsed_url.port, parsed_url.path, parsed_url.query,
resp.status_code, resp.reason, '', body, resp.headers)
def __str__(self):
a = self.msg
b = ''
if self.http_scheme:
b += '%s://' % self.http_scheme
if self.http_host:
b += self.http_host
if self.http_port:
b += ':%s' % self.http_port
if self.http_path:
b += self.http_path
if self.http_query:
b += '?%s' % self.http_query
if self.http_status:
if b:
b = '%s %s' % (b, self.http_status)
else:
b = str(self.http_status)
if self.http_reason:
if b:
b = '%s %s' % (b, self.http_reason)
else:
b = '- %s' % self.http_reason
if self.http_device:
if b:
b = '%s: device %s' % (b, self.http_device)
else:
b = 'device %s' % self.http_device
if self.http_response_content:
if len(self.http_response_content) <= 60:
b += ' %s' % self.http_response_content
else:
b += ' [first 60 chars of response] %s' \
% self.http_response_content[:60]
return b and '%s: %s' % (a, b) or a

View File

@ -1,192 +0,0 @@
# Copyright (c) 2010-2012 OpenStack, LLC.
#
# 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.
from __future__ import print_function
import six
import sys
from concurrent.futures import ThreadPoolExecutor
from six.moves.queue import PriorityQueue
class OutputManager(object):
"""
One object to manage and provide helper functions for output.
This object is a context manager and returns itself into the context. When
entering the context, two printing threads are created (see below) and they
are waited on and cleaned up when exiting the context.
Also, thread-safe printing to two streams is provided. The
:meth:`print_msg` method will print to the supplied ``print_stream``
(defaults to ``sys.stdout``) and the :meth:`error` method will print to the
supplied ``error_stream`` (defaults to ``sys.stderr``). Both of these
printing methods will format the given string with any supplied ``*args``
(a la printf). On Python 2, Unicode messages are encoded to utf8.
The attribute :attr:`self.error_count` is incremented once per error
message printed, so an application can tell if any worker threads
encountered exceptions or otherwise called :meth:`error` on this instance.
The swift command-line tool uses this to exit non-zero if any error strings
were printed.
"""
DEFAULT_OFFSET = 14
def __init__(self, print_stream=None, error_stream=None):
"""
:param print_stream: The stream to which :meth:`print_msg` sends
formatted messages.
:param error_stream: The stream to which :meth:`error` sends formatted
messages.
On Python 2, Unicode messages are encoded to utf8.
"""
self.print_stream = print_stream or sys.stdout
self.print_pool = ThreadPoolExecutor(max_workers=1)
self.error_stream = error_stream or sys.stderr
self.error_print_pool = ThreadPoolExecutor(max_workers=1)
self.error_count = 0
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.error_print_pool.__exit__(exc_type, exc_value, traceback)
self.print_pool.__exit__(exc_type, exc_value, traceback)
def print_raw(self, data):
self.print_pool.submit(self._write, data, self.print_stream)
def _write(self, data, stream):
if six.PY3:
stream.buffer.write(data)
stream.flush()
if six.PY2:
stream.write(data)
stream.flush()
def print_msg(self, msg, *fmt_args):
if fmt_args:
msg = msg % fmt_args
self.print_pool.submit(self._print, msg)
def print_items(self, items, offset=DEFAULT_OFFSET, skip_missing=False):
template = '%%%ds: %%s' % offset
for k, v in items:
if skip_missing and not v:
continue
self.print_msg((template % (k, v)).rstrip())
def error(self, msg, *fmt_args):
if fmt_args:
msg = msg % fmt_args
self.error_print_pool.submit(self._print_error, msg)
def get_error_count(self):
return self.error_count
def _print(self, item, stream=None):
if stream is None:
stream = self.print_stream
if six.PY2 and isinstance(item, six.text_type):
item = item.encode('utf8')
print(item, file=stream)
def _print_error(self, item, count=1):
self.error_count += count
return self._print(item, stream=self.error_stream)
def warning(self, msg, *fmt_args):
# print to error stream but do not increment error count
if fmt_args:
msg = msg % fmt_args
self.error_print_pool.submit(self._print_error, msg, count=0)
class MultiThreadingManager(object):
"""
One object to manage context for multi-threading. This should make
bin/swift less error-prone and allow us to test this code.
"""
def __init__(self, create_connection, segment_threads=10,
object_dd_threads=10, object_uu_threads=10,
container_threads=10):
"""
:param segment_threads: The number of threads allocated to segment
uploads
:param object_dd_threads: The number of threads allocated to object
download/delete jobs
:param object_uu_threads: The number of threads allocated to object
upload/update based jobs
:param container_threads: The number of threads allocated to
container/account level jobs
"""
self.segment_pool = ConnectionThreadPoolExecutor(
create_connection, max_workers=segment_threads)
self.object_dd_pool = ConnectionThreadPoolExecutor(
create_connection, max_workers=object_dd_threads)
self.object_uu_pool = ConnectionThreadPoolExecutor(
create_connection, max_workers=object_uu_threads)
self.container_pool = ConnectionThreadPoolExecutor(
create_connection, max_workers=container_threads)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.segment_pool.__exit__(exc_type, exc_value, traceback)
self.object_dd_pool.__exit__(exc_type, exc_value, traceback)
self.object_uu_pool.__exit__(exc_type, exc_value, traceback)
self.container_pool.__exit__(exc_type, exc_value, traceback)
class ConnectionThreadPoolExecutor(ThreadPoolExecutor):
"""
A wrapper class to maintain a pool of connections alongside the thread
pool. We start by creating a priority queue of connections, and each job
submitted takes one of those connections (initialising if necessary) and
passes it as the first arg to the executed function.
At the end of execution that connection is returned to the queue.
By using a PriorityQueue we avoid creating more connections than required.
We will only create as many connections as are required concurrently.
"""
def __init__(self, create_connection, max_workers):
self._connections = PriorityQueue()
self._create_connection = create_connection
for p in range(0, max_workers):
self._connections.put((p, None))
super(ConnectionThreadPoolExecutor, self).__init__(max_workers)
def submit(self, fn, *args, **kwargs):
def conn_fn():
priority = None
conn = None
try:
# If we get a connection we must put it back later
(priority, conn) = self._connections.get()
if conn is None:
conn = self._create_connection()
conn_args = (conn,) + args
return fn(*conn_args, **kwargs)
finally:
if priority is not None:
self._connections.put((priority, conn))
return super(ConnectionThreadPoolExecutor, self).submit(conn_fn)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,378 +0,0 @@
# Copyright (c) 2010-2012 OpenStack, LLC.
#
# 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.
"""Miscellaneous utility functions for use with Swift."""
from calendar import timegm
import collections
import gzip
import hashlib
import hmac
import json
import logging
import six
import time
import traceback
TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y'))
EMPTY_ETAG = 'd41d8cd98f00b204e9800998ecf8427e'
EXPIRES_ISO8601_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
SHORT_EXPIRES_ISO8601_FORMAT = '%Y-%m-%d'
TIME_ERRMSG = ('time must either be a whole number or in specific '
'ISO 8601 format.')
def config_true_value(value):
"""
Returns True if the value is either True or a string in TRUE_VALUES.
Returns False otherwise.
This function comes from swift.common.utils.config_true_value()
"""
return value is True or \
(isinstance(value, six.string_types) and value.lower() in TRUE_VALUES)
def prt_bytes(num_bytes, human_flag):
"""
convert a number > 1024 to printable format, either in 4 char -h format as
with ls -lh or return as 12 char right justified string
"""
if not human_flag:
return '%12s' % num_bytes
num = float(num_bytes)
suffixes = [None] + list('KMGTPEZY')
for suffix in suffixes[:-1]:
if num <= 1023:
break
num /= 1024.0
else:
suffix = suffixes[-1]
if not suffix: # num_bytes must be < 1024
return '%4s' % num_bytes
elif num >= 10:
return '%3d%s' % (num, suffix)
else:
return '%.1f%s' % (num, suffix)
def generate_temp_url(path, seconds, key, method, absolute=False,
prefix=False, iso8601=False):
"""Generates a temporary URL that gives unauthenticated access to the
Swift object.
:param path: The full path to the Swift object or prefix if
a prefix-based temporary URL should be generated. Example:
/v1/AUTH_account/c/o or /v1/AUTH_account/c/prefix.
:param seconds: time in seconds or ISO 8601 timestamp.
If absolute is False and this is the string representation of an
integer, then this specifies the amount of time in seconds for which
the temporary URL will be valid.
If absolute is True then this specifies an absolute time at which the
temporary URL will expire.
:param key: The secret temporary URL key set on the Swift
cluster. To set a key, run 'swift post -m
"Temp-URL-Key: <substitute tempurl key here>"'
:param method: A HTTP method, typically either GET or PUT, to allow
for this temporary URL.
:param absolute: if True then the seconds parameter is interpreted as a
Unix timestamp, if seconds represents an integer.
:param prefix: if True then a prefix-based temporary URL will be generated.
:param iso8601: if True, a URL containing an ISO 8601 UTC timestamp
instead of a UNIX timestamp will be created.
:raises ValueError: if timestamp or path is not in valid format.
:return: the path portion of a temporary URL
"""
try:
try:
timestamp = float(seconds)
except ValueError:
formats = (
EXPIRES_ISO8601_FORMAT,
EXPIRES_ISO8601_FORMAT[:-1],
SHORT_EXPIRES_ISO8601_FORMAT)
for f in formats:
try:
t = time.strptime(seconds, f)
except ValueError:
t = None
else:
if f == EXPIRES_ISO8601_FORMAT:
timestamp = timegm(t)
else:
# Use local time if UTC designator is missing.
timestamp = int(time.mktime(t))
absolute = True
break
if t is None:
raise ValueError()
else:
if not timestamp.is_integer():
raise ValueError()
timestamp = int(timestamp)
if timestamp < 0:
raise ValueError()
except ValueError:
raise ValueError(TIME_ERRMSG)
if isinstance(path, six.binary_type):
try:
path_for_body = path.decode('utf-8')
except UnicodeDecodeError:
raise ValueError('path must be representable as UTF-8')
else:
path_for_body = path
parts = path_for_body.split('/', 4)
if len(parts) != 5 or parts[0] or not all(parts[1:(4 if prefix else 5)]):
if prefix:
raise ValueError('path must at least contain /v1/a/c/')
else:
raise ValueError('path must be full path to an object'
' e.g. /v1/a/c/o')
standard_methods = ['GET', 'PUT', 'HEAD', 'POST', 'DELETE']
if method.upper() not in standard_methods:
logger = logging.getLogger("swiftclient")
logger.warning('Non default HTTP method %s for tempurl specified, '
'possibly an error', method.upper())
if not absolute:
expiration = int(time.time() + timestamp)
else:
expiration = timestamp
hmac_body = u'\n'.join([method.upper(), str(expiration),
('prefix:' if prefix else '') + path_for_body])
# Encode to UTF-8 for py3 compatibility
if not isinstance(key, six.binary_type):
key = key.encode('utf-8')
sig = hmac.new(key, hmac_body.encode('utf-8'), hashlib.sha1).hexdigest()
if iso8601:
expiration = time.strftime(
EXPIRES_ISO8601_FORMAT, time.gmtime(expiration))
temp_url = u'{path}?temp_url_sig={sig}&temp_url_expires={exp}'.format(
path=path_for_body, sig=sig, exp=expiration)
if prefix:
temp_url += u'&temp_url_prefix={}'.format(parts[4])
# Have return type match path from caller
if isinstance(path, six.binary_type):
return temp_url.encode('utf-8')
else:
return temp_url
def get_body(headers, body):
if headers.get('content-encoding') == 'gzip':
with gzip.GzipFile(fileobj=six.BytesIO(body), mode='r') as gz:
nbody = gz.read()
return nbody
return body
def parse_api_response(headers, body):
body = get_body(headers, body)
charset = 'utf-8'
# Swift *should* be speaking UTF-8, but check content-type just in case
content_type = headers.get('content-type', '')
if '; charset=' in content_type:
charset = content_type.split('; charset=', 1)[1].split(';', 1)[0]
return json.loads(body.decode(charset))
def split_request_headers(options, prefix=''):
headers = {}
if isinstance(options, collections.Mapping):
options = options.items()
for item in options:
if isinstance(item, six.string_types):
if ':' not in item:
raise ValueError(
"Metadata parameter %s must contain a ':'.\n"
"Example: 'Color:Blue' or 'Size:Large'"
% item
)
item = item.split(':', 1)
if len(item) != 2:
raise ValueError(
"Metadata parameter %r must have exactly two items.\n"
"Example: ('Color', 'Blue') or ['Size', 'Large']"
% (item, )
)
headers[(prefix + item[0]).title()] = item[1].strip()
return headers
def report_traceback():
"""
Reports a timestamp and full traceback for a given exception.
:return: Full traceback and timestamp.
"""
try:
formatted_lines = traceback.format_exc()
now = time.time()
return formatted_lines, now
except AttributeError:
return None, None
class NoopMD5(object):
def __init__(self, *a, **kw):
pass
def update(self, *a, **kw):
pass
def hexdigest(self, *a, **kw):
return ''
class ReadableToIterable(object):
"""
Wrap a filelike object and act as an iterator.
It is recommended to use this class only on files opened in binary mode.
Due to the Unicode changes in Python 3, files are now opened using an
encoding not suitable for use with the md5 class and because of this
hit the exception on every call to next. This could cause problems,
especially with large files and small chunk sizes.
"""
def __init__(self, content, chunk_size=65536, md5=False):
"""
:param content: The filelike object that is yielded from.
:param chunk_size: The max size of each yielded item.
:param md5: Flag to enable calculating the MD5 of the content
as it is yielded.
"""
self.md5sum = hashlib.md5() if md5 else NoopMD5()
self.content = content
self.chunk_size = chunk_size
def get_md5sum(self):
return self.md5sum.hexdigest()
def __next__(self):
"""
Both ``__next__`` and ``next`` are provided to allow compatibility
with python 2 and python 3 and their use of ``iterable.next()``
and ``next(iterable)`` respectively.
"""
chunk = self.content.read(self.chunk_size)
if not chunk:
raise StopIteration
try:
self.md5sum.update(chunk)
except TypeError:
self.md5sum.update(chunk.encode())
return chunk
def next(self):
return self.__next__()
def __iter__(self):
return self
class LengthWrapper(object):
"""
Wrap a filelike object with a maximum length.
Fix for https://github.com/kennethreitz/requests/issues/1648.
It is recommended to use this class only on files opened in binary mode.
"""
def __init__(self, readable, length, md5=False):
"""
:param readable: The filelike object to read from.
:param length: The maximum amount of content that can be read from
the filelike object before it is simulated to be
empty.
:param md5: Flag to enable calculating the MD5 of the content
as it is read.
"""
self._md5 = md5
self._reset_md5()
self._length = self._remaining = length
self._readable = readable
self._can_reset = all(hasattr(readable, attr)
for attr in ('seek', 'tell'))
if self._can_reset:
self._start = readable.tell()
def __len__(self):
return self._length
def _reset_md5(self):
self.md5sum = hashlib.md5() if self._md5 else NoopMD5()
def get_md5sum(self):
return self.md5sum.hexdigest()
def read(self, size=-1):
if self._remaining <= 0:
return ''
to_read = self._remaining if size < 0 else min(size, self._remaining)
chunk = self._readable.read(to_read)
self._remaining -= len(chunk)
try:
self.md5sum.update(chunk)
except TypeError:
self.md5sum.update(chunk.encode())
return chunk
@property
def reset(self):
if self._can_reset:
return self._reset
raise AttributeError("%r object has no attribute 'reset'" %
type(self).__name__)
def _reset(self, *args, **kwargs):
if not self._can_reset:
raise TypeError('%r object cannot be reset; needs both seek and '
'tell methods' % type(self._readable).__name__)
self._readable.seek(self._start)
self._reset_md5()
self._remaining = self._length
def iter_wrapper(iterable):
for chunk in iterable:
if len(chunk) == 0:
# If we emit an empty chunk, requests will go ahead and send it,
# causing the server to close the connection
continue
yield chunk
def n_at_a_time(seq, n):
for i in range(0, len(seq), n):
yield seq[i:i + n]
def n_groups(seq, n):
items_per_group = ((len(seq) - 1) // n) + 1
return n_at_a_time(seq, items_per_group)

View File

@ -1,28 +0,0 @@
# Copyright 2012 OpenStack LLC
#
# 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 pkg_resources
try:
# First, try to get our version out of PKG-INFO. If we're installed,
# this will let us find our version without pulling in pbr. After all, if
# we're installed on a system, we're not in a Git-managed source tree, so
# pbr doesn't really buy us anything.
version_string = pkg_resources.get_provider(
pkg_resources.Requirement.parse('python-swiftclient')).version
except pkg_resources.DistributionNotFound:
# No PKG-INFO? We're probably running from a checkout, then. Let pbr do
# its thing to figure out a version number.
import pbr.version
version_string = str(pbr.version.VersionInfo('python-swiftclient'))

View File

@ -1,7 +0,0 @@
hacking>=0.10.0,<0.11
coverage>=3.6
mock>=1.2
oslosphinx>=4.7.0 # Apache-2.0
sphinx>=1.1.2,<1.2
testrepository>=0.0.18

View File

View File

@ -1,519 +0,0 @@
# Copyright (c) 2014 Christian Schwede <christian.schwede@enovance.com>
#
# 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 os
import unittest
import time
from io import BytesIO
from six.moves import configparser
import swiftclient
class TestFunctional(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestFunctional, self).__init__(*args, **kwargs)
self.skip_tests = False
self._get_config()
self.test_data = b'42' * 10
self.etag = '2704306ec982238d85d4b235c925d58e'
self.containername = "functional-tests-container-%s" % int(time.time())
self.containername_2 = self.containername + '_second'
self.containername_3 = self.containername + '_third'
self.objectname = "functional-tests-object-%s" % int(time.time())
self.objectname_2 = self.objectname + '_second'
def _get_config(self):
config_file = os.environ.get('SWIFT_TEST_CONFIG_FILE',
'/etc/swift/test.conf')
config = configparser.ConfigParser({'auth_version': '1'})
config.read(config_file)
self.config = config
if config.has_section('func_test'):
auth_host = config.get('func_test', 'auth_host')
auth_port = config.getint('func_test', 'auth_port')
auth_ssl = config.getboolean('func_test', 'auth_ssl')
auth_prefix = config.get('func_test', 'auth_prefix')
self.auth_version = config.get('func_test', 'auth_version')
try:
self.account_username = config.get('func_test',
'account_username')
except configparser.NoOptionError:
account = config.get('func_test', 'account')
username = config.get('func_test', 'username')
self.account_username = "%s:%s" % (account, username)
self.password = config.get('func_test', 'password')
self.auth_url = ""
if auth_ssl:
self.auth_url += "https://"
else:
self.auth_url += "http://"
self.auth_url += "%s:%s%s" % (auth_host, auth_port, auth_prefix)
if self.auth_version == "1":
self.auth_url += 'v1.0'
else:
self.skip_tests = True
def _get_connection(self):
"""
Subclasses may override to use different connection setup
"""
return swiftclient.Connection(
self.auth_url, self.account_username, self.password,
auth_version=self.auth_version)
def setUp(self):
super(TestFunctional, self).setUp()
if self.skip_tests:
self.skipTest('SKIPPING FUNCTIONAL TESTS DUE TO NO CONFIG')
self.conn = self._get_connection()
self.conn.put_container(self.containername)
self.conn.put_container(self.containername_2)
self.conn.put_object(
self.containername, self.objectname, self.test_data)
self.conn.put_object(
self.containername, self.objectname_2, self.test_data)
def tearDown(self):
super(TestFunctional, self).tearDown()
for obj in [self.objectname, self.objectname_2]:
try:
self.conn.delete_object(self.containername, obj)
except swiftclient.ClientException:
pass
for container in [self.containername,
self.containername_2,
self.containername_3,
self.containername + '_segments']:
try:
self.conn.delete_container(container)
except swiftclient.ClientException:
pass
def _check_account_headers(self, headers):
headers_to_check = [
'content-length',
'x-account-object-count',
'x-timestamp',
'x-trans-id',
'date',
'x-account-bytes-used',
'x-account-container-count',
'content-type',
'accept-ranges',
]
for h in headers_to_check:
self.assertIn(h, headers)
self.assertTrue(headers[h])
def test_stat_account(self):
headers = self.conn.head_account()
self._check_account_headers(headers)
def test_list_account(self):
headers, containers = self.conn.get_account()
self._check_account_headers(headers)
self.assertTrue(len(containers))
test_container = [c
for c in containers
if c.get('name') == self.containername][0]
self.assertTrue(test_container.get('bytes') >= 0)
self.assertTrue(test_container.get('count') >= 0)
# Check if list limit is working
headers, containers = self.conn.get_account(limit=1)
self.assertEqual(1, len(containers))
# Check full listing
headers, containers = self.conn.get_account(limit=1, full_listing=True)
self.assertTrue(len(containers) >= 2) # there might be more containers
# Test marker
headers, containers = self.conn.get_account(marker=self.containername)
self.assertTrue(len(containers) >= 1)
self.assertEqual(self.containername_2, containers[0].get('name'))
def _check_container_headers(self, headers):
self.assertTrue(headers.get('content-length'))
self.assertTrue(headers.get('x-container-object-count'))
self.assertTrue(headers.get('x-timestamp'))
self.assertTrue(headers.get('x-trans-id'))
self.assertTrue(headers.get('date'))
self.assertTrue(headers.get('x-container-bytes-used'))
self.assertTrue(headers.get('x-container-object-count'))
self.assertTrue(headers.get('content-type'))
self.assertTrue(headers.get('accept-ranges'))
def test_stat_container(self):
headers = self.conn.head_container(self.containername)
self._check_container_headers(headers)
def test_list_container(self):
headers, objects = self.conn.get_container(self.containername)
self._check_container_headers(headers)
self.assertTrue(len(objects))
test_object = [o
for o in objects
if o.get('name') == self.objectname][0]
self.assertEqual(len(self.test_data), test_object.get('bytes'))
self.assertEqual(self.etag, test_object.get('hash'))
self.assertEqual('application/octet-stream',
test_object.get('content_type'))
# Check if list limit is working
headers, objects = self.conn.get_container(self.containername, limit=1)
self.assertEqual(1, len(objects))
# Check full listing
headers, objects = self.conn.get_container(
self.containername, limit=1, full_listing=True)
self.assertEqual(2, len(objects))
# Test marker
headers, objects = self.conn.get_container(
self.containername, marker=self.objectname)
self.assertEqual(1, len(objects))
self.assertEqual(self.objectname_2, objects[0].get('name'))
def test_create_container(self):
self.conn.put_container(self.containername_3)
self.assertTrue(self.conn.head_container(self.containername_3))
def test_delete(self):
self.conn.delete_object(self.containername, self.objectname)
self.conn.delete_object(self.containername, self.objectname_2)
self.conn.delete_container(self.containername)
# Container HEAD will raise an exception if container doesn't exist
# which is only possible if previous requests succeeded
self.assertRaises(
swiftclient.ClientException,
self.conn.head_container,
self.containername)
def test_upload_object(self):
# Object with content from string
self.conn.put_object(
self.containername, self.objectname, contents=self.test_data)
hdrs = self.conn.head_object(self.containername, self.objectname)
self.assertEqual(str(len(self.test_data)),
hdrs.get('content-length'))
self.assertEqual(self.etag, hdrs.get('etag'))
self.assertEqual('application/octet-stream',
hdrs.get('content-type'))
# Same but with content_type
self.conn.put_object(
self.containername, self.objectname,
content_type='text/plain', contents=self.test_data)
hdrs = self.conn.head_object(self.containername, self.objectname)
self.assertEqual(str(len(self.test_data)),
hdrs.get('content-length'))
self.assertEqual(self.etag, hdrs.get('etag'))
self.assertEqual('text/plain',
hdrs.get('content-type'))
# Same but with content-type in headers
self.conn.put_object(
self.containername, self.objectname,
headers={'Content-Type': 'text/plain'}, contents=self.test_data)
hdrs = self.conn.head_object(self.containername, self.objectname)
self.assertEqual(str(len(self.test_data)),
hdrs.get('content-length'))
self.assertEqual(self.etag, hdrs.get('etag'))
self.assertEqual('text/plain',
hdrs.get('content-type'))
# content_type rewrites content-type in headers
self.conn.put_object(
self.containername, self.objectname,
content_type='image/jpeg',
headers={'Content-Type': 'text/plain'}, contents=self.test_data)
hdrs = self.conn.head_object(self.containername, self.objectname)
self.assertEqual(str(len(self.test_data)),
hdrs.get('content-length'))
self.assertEqual(self.etag, hdrs.get('etag'))
self.assertEqual('image/jpeg',
hdrs.get('content-type'))
# Same but with content-length
self.conn.put_object(
self.containername, self.objectname,
contents=self.test_data, content_length=len(self.test_data))
hdrs = self.conn.head_object(self.containername, self.objectname)
self.assertEqual(str(len(self.test_data)),
hdrs.get('content-length'))
self.assertEqual(self.etag, hdrs.get('etag'))
self.assertEqual('application/octet-stream', hdrs.get('content-type'))
# Content from File-like object
fileobj = BytesIO(self.test_data)
self.conn.put_object(
self.containername, self.objectname, contents=fileobj)
hdrs = self.conn.head_object(self.containername, self.objectname)
self.assertEqual(str(len(self.test_data)),
hdrs.get('content-length'))
self.assertEqual(self.etag, hdrs.get('etag'))
self.assertEqual('application/octet-stream', hdrs.get('content-type'))
# Content from File-like object, but read in chunks
fileobj = BytesIO(self.test_data)
self.conn.put_object(
self.containername, self.objectname,
contents=fileobj, content_length=len(self.test_data),
chunk_size=10)
hdrs = self.conn.head_object(self.containername, self.objectname)
self.assertEqual(str(len(self.test_data)),
hdrs.get('content-length'))
self.assertEqual(self.etag, hdrs.get('etag'))
self.assertEqual('application/octet-stream', hdrs.get('content-type'))
# Wrong etag arg, should raise an exception
self.assertRaises(
swiftclient.ClientException,
self.conn.put_object,
self.containername, self.objectname,
contents=self.test_data, etag='invalid')
def test_download_object(self):
# Download whole object
hdrs, body = self.conn.get_object(self.containername, self.objectname)
self.assertEqual(self.test_data, body)
# Download in chunks, should return a generator
hdrs, body = self.conn.get_object(
self.containername, self.objectname,
resp_chunk_size=10)
downloaded_contents = b''
while True:
try:
chunk = next(body)
except StopIteration:
break
downloaded_contents += chunk
self.assertEqual(self.test_data, downloaded_contents)
# Download in chunks, should also work with read
hdrs, body = self.conn.get_object(
self.containername, self.objectname,
resp_chunk_size=10)
num_bytes = 5
downloaded_contents = body.read(num_bytes)
self.assertEqual(num_bytes, len(downloaded_contents))
downloaded_contents += body.read()
self.assertEqual(self.test_data, downloaded_contents)
def test_put_object_using_generator(self):
# verify that put using a generator yielding empty strings does not
# cause connection to be closed
def data():
yield b"should"
yield b""
yield b" tolerate"
yield b""
yield b" empty chunks"
self.conn.put_object(
self.containername, self.objectname, data())
hdrs, body = self.conn.get_object(self.containername, self.objectname)
self.assertEqual(b"should tolerate empty chunks", body)
def test_download_object_retry_chunked(self):
resp_chunk_size = 2
hdrs, body = self.conn.get_object(self.containername,
self.objectname,
resp_chunk_size=resp_chunk_size)
data = next(body)
self.assertEqual(self.test_data[:resp_chunk_size], data)
self.assertTrue(1, self.conn.attempts)
for chunk in body.resp:
# Flush remaining data from underlying response
# (simulate a dropped connection)
pass
# Trigger the retry
for chunk in body:
data += chunk
self.assertEqual(self.test_data, data)
self.assertEqual(2, self.conn.attempts)
def test_download_object_retry_chunked_auth_failure(self):
resp_chunk_size = 2
self.conn.token = 'invalid'
hdrs, body = self.conn.get_object(self.containername,
self.objectname,
resp_chunk_size=resp_chunk_size)
self.assertEqual(2, self.conn.attempts)
for chunk in body.resp:
# Flush remaining data from underlying response
# (simulate a dropped connection)
pass
self.conn.token = 'invalid'
data = next(body)
self.assertEqual(4, self.conn.attempts)
for chunk in body:
data += chunk
self.assertEqual(self.test_data, data)
self.assertEqual(4, self.conn.attempts)
def test_download_object_non_chunked(self):
hdrs, body = self.conn.get_object(self.containername, self.objectname)
data = body
self.assertEqual(self.test_data, data)
self.assertTrue(1, self.conn.attempts)
hdrs, body = self.conn.get_object(self.containername, self.objectname,
resp_chunk_size=0)
data = body
self.assertEqual(self.test_data, data)
self.assertTrue(1, self.conn.attempts)
def test_post_account(self):
self.conn.post_account({'x-account-meta-data': 'Something'})
headers = self.conn.head_account()
self.assertEqual('Something', headers.get('x-account-meta-data'))
def test_post_container(self):
self.conn.post_container(
self.containername, {'x-container-meta-color': 'Something'})
headers = self.conn.head_container(self.containername)
self.assertEqual('Something', headers.get('x-container-meta-color'))
def test_post_object(self):
self.conn.post_object(self.containername,
self.objectname,
{'x-object-meta-color': 'Something',
'x-object-meta-uni': b'\xd8\xaa'.decode('utf8'),
'x-object-meta-int': 123,
'x-object-meta-float': 45.67,
'x-object-meta-bool': False})
headers = self.conn.head_object(self.containername, self.objectname)
self.assertEqual('Something', headers.get('x-object-meta-color'))
self.assertEqual(b'\xd8\xaa'.decode('utf-8'),
headers.get('x-object-meta-uni'))
self.assertEqual('123', headers.get('x-object-meta-int'))
self.assertEqual('45.67', headers.get('x-object-meta-float'))
self.assertEqual('False', headers.get('x-object-meta-bool'))
def test_copy_object(self):
self.conn.put_object(
self.containername, self.objectname, self.test_data)
self.conn.copy_object(self.containername,
self.objectname,
headers={'x-object-meta-color': 'Something'})
headers = self.conn.head_object(self.containername, self.objectname)
self.assertEqual('Something', headers.get('x-object-meta-color'))
self.conn.copy_object(self.containername,
self.objectname,
headers={'x-object-meta-taste': 'Second'})
headers = self.conn.head_object(self.containername, self.objectname)
self.assertEqual('Something', headers.get('x-object-meta-color'))
self.assertEqual('Second', headers.get('x-object-meta-taste'))
destination = "/%s/%s" % (self.containername, self.objectname_2)
self.conn.copy_object(self.containername,
self.objectname,
destination,
headers={'x-object-meta-taste': 'Second'})
headers, data = self.conn.get_object(self.containername,
self.objectname_2)
self.assertEqual(self.test_data, data)
self.assertEqual('Something', headers.get('x-object-meta-color'))
self.assertEqual('Second', headers.get('x-object-meta-taste'))
destination = "/%s/%s" % (self.containername, self.objectname_2)
self.conn.copy_object(self.containername,
self.objectname,
destination,
headers={'x-object-meta-color': 'Else'},
fresh_metadata=True)
headers = self.conn.head_object(self.containername, self.objectname_2)
self.assertEqual('Else', headers.get('x-object-meta-color'))
self.assertIsNone(headers.get('x-object-meta-taste'))
def test_get_capabilities(self):
resp = self.conn.get_capabilities()
self.assertTrue(resp.get('swift'))
class TestUsingKeystone(TestFunctional):
"""
Repeat tests using os_options parameter to Connection.
"""
def _get_connection(self):
account = username = password = None
if self.auth_version not in ('2', '3'):
self.skipTest('SKIPPING KEYSTONE-SPECIFIC FUNCTIONAL TESTS')
try:
account = self.config.get('func_test', 'account')
username = self.config.get('func_test', 'username')
password = self.config.get('func_test', 'password')
except Exception:
self.skipTest('SKIPPING KEYSTONE-SPECIFIC FUNCTIONAL TESTS' +
' - NO CONFIG')
os_options = {'tenant_name': account}
return swiftclient.Connection(
self.auth_url, username, password, auth_version=self.auth_version,
os_options=os_options)
def setUp(self):
super(TestUsingKeystone, self).setUp()
class TestUsingKeystoneV3(TestFunctional):
"""
Repeat tests using a keystone user with domain specified.
"""
def _get_connection(self):
account = username = password = project_domain = user_domain = None
if self.auth_version != '3':
self.skipTest('SKIPPING KEYSTONE-V3-SPECIFIC FUNCTIONAL TESTS')
try:
account = self.config.get('func_test', 'account4')
username = self.config.get('func_test', 'username4')
user_domain = self.config.get('func_test', 'domain4')
project_domain = self.config.get('func_test', 'domain4')
password = self.config.get('func_test', 'password4')
except Exception:
self.skipTest('SKIPPING KEYSTONE-V3-SPECIFIC FUNCTIONAL TESTS' +
' - NO CONFIG')
os_options = {'project_name': account,
'project_domain_name': project_domain,
'user_domain_name': user_domain}
return swiftclient.Connection(self.auth_url, username, password,
auth_version=self.auth_version,
os_options=os_options)
def setUp(self):
super(TestUsingKeystoneV3, self).setUp()

View File

@ -1,32 +0,0 @@
[func_test]
# sample config
auth_host = 127.0.0.1
auth_port = 8080
auth_ssl = no
auth_prefix = /auth/
## sample config for Swift with Keystone v2 API
# For keystone v3 change auth_version to 3 and auth_prefix to /v3/
#auth_version = 2
#auth_host = localhost
#auth_port = 5000
#auth_ssl = no
#auth_prefix = /v2.0/
# Primary functional test account (needs admin access to the account).
# By default the tests use a swiftclient.client.Connection instance with user
# attribute set to 'account:username' based on the options 'account' and
# 'username' specified below. This can be overridden for auth systems that
# expect a different form of user attribute by setting the option
# 'account_username'.
# account_username = test_tester
account = test
username = tester
password = testing
# Another user is required for keystone v3 specific tests.
# Account must be in a non-default domain.
# (Suffix '4' is used to be consistent with swift functional test config).
#account4 = test4
#username4 = tester4
#password4 = testing4
#domain4 = test-domain

View File

View File

@ -1,246 +0,0 @@
# Copyright 2016 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 datetime
import json
import mock
import unittest
from keystoneauth1 import plugin
from keystoneauth1 import loading
from keystoneauth1 import exceptions
from swiftclient import authv1
class TestDataNoAccount(object):
options = dict(
auth_url='http://saio:8080/auth/v1.0',
username='test:tester',
password='testing')
storage_url = 'http://saio:8080/v1/AUTH_test'
expected_endpoint = storage_url
token = 'token'
class TestDataWithAccount(object):
options = dict(
auth_url='http://saio:8080/auth/v1.0',
username='test2:tester2',
project_name='SOME_other_account',
password='testing2')
storage_url = 'http://saio:8080/v1/AUTH_test2'
expected_endpoint = 'http://saio:8080/v1/SOME_other_account'
token = 'other_token'
class TestPluginLoading(TestDataNoAccount, unittest.TestCase):
def test_can_load(self):
loader = loading.get_plugin_loader('v1password')
self.assertIsInstance(loader, authv1.PasswordLoader)
auth_plugin = loader.load_from_options(**self.options)
self.assertIsInstance(auth_plugin, authv1.PasswordPlugin)
self.assertEqual(self.options['auth_url'], auth_plugin.auth_url)
self.assertEqual(self.options['username'], auth_plugin.user)
self.assertEqual(self.options.get('project_name'), auth_plugin.account)
self.assertEqual(self.options['password'], auth_plugin.key)
def test_get_state(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.assertIsNone(auth_plugin.get_auth_state())
with mock.patch('swiftclient.authv1.time.time', return_value=1234.56):
auth_plugin.auth_ref = authv1.AccessInfoV1(
self.options['auth_url'],
self.storage_url,
self.options.get('project_name'),
self.options['username'],
self.token,
60)
expected = json.dumps({
'auth_url': self.options['auth_url'],
'username': self.options['username'],
'account': self.options.get('project_name'),
'issued': 1234.56,
'storage_url': self.storage_url,
'auth_token': self.token,
'expires': 1234.56 + 60,
}, sort_keys=True)
self.assertEqual(expected, auth_plugin.auth_ref.get_state())
self.assertEqual(expected, auth_plugin.get_auth_state())
def test_set_state(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.assertIsNone(auth_plugin.auth_ref)
auth_plugin.auth_ref = object()
auth_plugin.set_auth_state(None)
self.assertIsNone(auth_plugin.get_auth_state())
state = json.dumps({
'auth_url': self.options['auth_url'],
'username': self.options['username'],
'account': self.options.get('project_name'),
'issued': 1234.56,
'storage_url': self.storage_url,
'auth_token': self.token,
'expires': None,
}, sort_keys=True)
auth_plugin.set_auth_state(state)
self.assertIsInstance(auth_plugin.auth_ref, authv1.AccessInfoV1)
self.assertEqual(self.options['username'],
auth_plugin.auth_ref.username)
self.assertEqual(self.options['auth_url'],
auth_plugin.auth_ref.auth_url)
self.assertEqual(self.storage_url, auth_plugin.auth_ref.storage_url)
self.assertEqual(self.options.get('project_name'), auth_plugin.account)
self.assertEqual(self.token, auth_plugin.auth_ref.auth_token)
self.assertEqual(1234.56, auth_plugin.auth_ref._issued)
self.assertIs(datetime.datetime, type(auth_plugin.auth_ref.issued))
self.assertIsNone(auth_plugin.auth_ref._expires)
self.assertIsNone(auth_plugin.auth_ref.expires)
class TestPluginLoadingWithAccount(TestDataWithAccount, TestPluginLoading):
pass
class TestPlugin(TestDataNoAccount, unittest.TestCase):
def setUp(self):
self.mock_session = mock.MagicMock()
self.mock_response = self.mock_session.get.return_value
self.mock_response.status_code = 200
self.mock_response.headers = {
'X-Auth-Token': self.token,
'X-Storage-Url': self.storage_url,
}
def test_get_access(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
with mock.patch('swiftclient.authv1.time.time', return_value=1234.56):
access = auth_plugin.get_access(self.mock_session)
self.assertEqual(self.mock_session.get.mock_calls, [mock.call(
self.options['auth_url'], authenticated=False, log=False, headers={
'X-Auth-User': self.options['username'],
'X-Auth-Key': self.options['password'],
})])
self.assertEqual(self.options['username'], access.username)
# `openstack token issue` requires a user_id property
self.assertEqual(self.options['username'], access.user_id)
self.assertEqual(self.storage_url, access.storage_url)
self.assertEqual(self.token, access.auth_token)
self.assertEqual(1234.56, access._issued)
self.assertIs(datetime.datetime, type(auth_plugin.auth_ref.issued))
self.assertIsNone(access.expires)
# `openstack catalog list/show` require a catalog property
catalog = access.service_catalog.catalog
self.assertEqual('swift', catalog[0].get('name'))
self.assertEqual('object-store', catalog[0].get('type'))
self.assertIn('endpoints', catalog[0])
self.assertIn(self.storage_url, [
e.get('publicURL') for e in catalog[0]['endpoints']])
def test_get_access_with_expiry(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.mock_response.headers['X-Auth-Token-Expires'] = '78.9'
with mock.patch('swiftclient.authv1.time.time',
return_value=1234.56) as mock_time:
access = auth_plugin.get_access(self.mock_session)
self.assertEqual(1234.56 + 78.9, access._expires)
self.assertIs(datetime.datetime,
type(auth_plugin.auth_ref.expires))
self.assertIs(True, access.will_expire_soon(90))
self.assertIs(False, access.will_expire_soon(60))
self.assertEqual(3, len(mock_time.mock_calls))
def test_get_access_bad_expiry(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.mock_response.headers['X-Auth-Token-Expires'] = 'foo'
access = auth_plugin.get_access(self.mock_session)
self.assertIsNone(access.expires)
self.assertIs(False, access.will_expire_soon(60))
self.assertIs(False, access.will_expire_soon(1e20))
def test_get_access_bad_status(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.mock_response.status_code = 401
self.assertRaises(exceptions.InvalidResponse,
auth_plugin.get_access, self.mock_session)
def test_get_access_missing_token(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.mock_response.headers.pop('X-Auth-Token')
self.assertRaises(exceptions.InvalidResponse,
auth_plugin.get_access, self.mock_session)
def test_get_access_accepts_storage_token(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.mock_response.headers.pop('X-Auth-Token')
self.mock_response.headers['X-Storage-Token'] = 'yet another token'
access = auth_plugin.get_access(self.mock_session)
self.assertEqual('yet another token', access.auth_token)
def test_get_access_missing_url(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
self.mock_response.headers.pop('X-Storage-Url')
self.assertRaises(exceptions.InvalidResponse,
auth_plugin.get_access, self.mock_session)
def test_get_endpoint(self):
auth_plugin = authv1.PasswordPlugin(**self.options)
object_store_endpoint = auth_plugin.get_endpoint(
self.mock_session, service_type='object-store')
self.assertEqual(object_store_endpoint, self.expected_endpoint)
auth_endpoint = auth_plugin.get_endpoint(
self.mock_session, interface=plugin.AUTH_INTERFACE)
self.assertEqual(auth_endpoint, self.options['auth_url'])
with self.assertRaises(exceptions.EndpointNotFound) as exc_mgr:
auth_plugin.get_endpoint(self.mock_session)
self.assertEqual('public endpoint for None service not found',
str(exc_mgr.exception))
with self.assertRaises(exceptions.EndpointNotFound) as exc_mgr:
auth_plugin.get_endpoint(
self.mock_session, service_type='identity', region_name='DFW')
self.assertEqual(
'public endpoint for identity service in DFW region not found',
str(exc_mgr.exception))
with self.assertRaises(exceptions.EndpointNotFound) as exc_mgr:
auth_plugin.get_endpoint(
self.mock_session, service_type='image', service_name='glance')
self.assertEqual(
'public endpoint for image service named glance not found',
str(exc_mgr.exception))
with self.assertRaises(exceptions.EndpointNotFound) as exc_mgr:
auth_plugin.get_endpoint(
self.mock_session, service_type='compute', service_name='nova',
region_name='IAD')
self.assertEqual('public endpoint for compute service named nova in '
'IAD region not found', str(exc_mgr.exception))
class TestPluginWithAccount(TestDataWithAccount, TestPlugin):
pass

View File

@ -1,249 +0,0 @@
# Copyright (c) 2010-2013 OpenStack, LLC.
#
# 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 mock
from six import StringIO
import unittest
from swiftclient import command_helpers as h
from swiftclient.multithreading import OutputManager
class TestStatHelpers(unittest.TestCase):
def setUp(self):
super(TestStatHelpers, self).setUp()
conn_attrs = {
'url': 'http://storage/v1/a',
'token': 'tk12345',
}
self.conn = mock.MagicMock(**conn_attrs)
self.options = {'human': False, 'verbose': 1}
self.stdout = StringIO()
self.stderr = StringIO()
self.output_manager = OutputManager(self.stdout, self.stderr)
def assertOut(self, expected):
real = self.stdout.getvalue()
# commonly if we strip of blank lines we have a match
try:
self.assertEqual(expected.strip('\n'),
real.strip('\n'))
except AssertionError:
# could be anything, try to find typos line by line
expected_lines = [line.lstrip() for line in
expected.splitlines() if line.strip()]
real_lines = [line.lstrip() for line in
real.splitlines() if line.strip()]
for expected, real in zip(expected_lines, real_lines):
self.assertEqual(expected, real)
# not a typo, might be an indent thing, hopefully you can spot it
raise
def test_stat_account_human(self):
self.options['human'] = True
# stub head_account
stub_headers = {
'x-account-container-count': 42,
'x-account-object-count': 1000000,
'x-account-bytes-used': 2 ** 30,
}
self.conn.head_account.return_value = stub_headers
with self.output_manager as output_manager:
items, headers = h.stat_account(self.conn, self.options)
h.print_account_stats(items, headers, output_manager)
expected = """
Account: a
Containers: 42
Objects: 976K
Bytes: 1.0G
"""
self.assertOut(expected)
def test_stat_account_verbose(self):
self.options['verbose'] += 1
# stub head_account
stub_headers = {
'x-account-container-count': 42,
'x-account-object-count': 1000000,
'x-account-bytes-used': 2 ** 30,
}
self.conn.head_account.return_value = stub_headers
with self.output_manager as output_manager:
items, headers = h.stat_account(self.conn, self.options)
h.print_account_stats(items, headers, output_manager)
expected = """
StorageURL: http://storage/v1/a
Auth Token: tk12345
Account: a
Containers: 42
Objects: 1000000
Bytes: 1073741824
"""
self.assertOut(expected)
def test_stat_account_policy_stat(self):
# stub head_account
stub_headers = {
'x-account-container-count': 42,
'x-account-object-count': 1000000,
'x-account-bytes-used': 2 ** 30,
'x-account-storage-policy-nada-object-count': 1000000,
'x-account-storage-policy-nada-bytes-used': 2 ** 30,
}
self.conn.head_account.return_value = stub_headers
with self.output_manager as output_manager:
items, headers = h.stat_account(self.conn, self.options)
h.print_account_stats(items, headers, output_manager)
expected = """
Account: a
Containers: 42
Objects: 1000000
Bytes: 1073741824
Objects in policy "nada": 1000000
Bytes in policy "nada": 1073741824
"""
self.assertOut(expected)
def test_stat_account_policy_stat_with_container_counts(self):
# stub head_account
stub_headers = {
'x-account-container-count': 42,
'x-account-object-count': 1000000,
'x-account-bytes-used': 2 ** 30,
'x-account-storage-policy-nada-container-count': 10,
'x-account-storage-policy-nada-object-count': 1000000,
'x-account-storage-policy-nada-bytes-used': 2 ** 30,
}
self.conn.head_account.return_value = stub_headers
with self.output_manager as output_manager:
items, headers = h.stat_account(self.conn, self.options)
h.print_account_stats(items, headers, output_manager)
expected = """
Account: a
Containers: 42
Objects: 1000000
Bytes: 1073741824
Containers in policy "nada": 10
Objects in policy "nada": 1000000
Bytes in policy "nada": 1073741824
"""
self.assertOut(expected)
def test_stat_container_human(self):
self.options['human'] = True
# stub head container request
stub_headers = {
'x-container-object-count': 10 ** 6,
'x-container-bytes-used': 2 ** 30,
}
self.conn.head_container.return_value = stub_headers
args = ('c',)
with self.output_manager as output_manager:
items, headers = h.stat_container(self.conn, self.options, *args)
h.print_container_stats(items, headers, output_manager)
expected = """
Account: a
Container: c
Objects: 976K
Bytes: 1.0G
Read ACL:
Write ACL:
Sync To:
Sync Key:
"""
self.assertOut(expected)
def test_stat_container_verbose(self):
self.options['verbose'] += 1
# stub head container request
stub_headers = {
'x-container-object-count': 10 ** 6,
'x-container-bytes-used': 2 ** 30,
}
self.conn.head_container.return_value = stub_headers
args = ('c',)
with self.output_manager as output_manager:
items, headers = h.stat_container(self.conn, self.options, *args)
h.print_container_stats(items, headers, output_manager)
expected = """
URL: http://storage/v1/a/c
Auth Token: tk12345
Account: a
Container: c
Objects: 1000000
Bytes: 1073741824
Read ACL:
Write ACL:
Sync To:
Sync Key:
"""
self.assertOut(expected)
def test_stat_object_human(self):
self.options['human'] = True
# stub head object request
stub_headers = {
'content-length': 2 ** 20,
'x-object-meta-color': 'blue',
'etag': '68b329da9893e34099c7d8ad5cb9c940',
'content-encoding': 'gzip',
}
self.conn.head_object.return_value = stub_headers
args = ('c', 'o')
with self.output_manager as output_manager:
items, headers = h.stat_object(self.conn, self.options, *args)
h.print_object_stats(items, headers, output_manager)
expected = """
Account: a
Container: c
Object: o
Content Length: 1.0M
ETag: 68b329da9893e34099c7d8ad5cb9c940
Meta Color: blue
Content-Encoding: gzip
"""
self.assertOut(expected)
def test_stat_object_verbose(self):
self.options['verbose'] += 1
# stub head object request
stub_headers = {
'content-length': 2 ** 20,
'x-object-meta-color': 'blue',
'etag': '68b329da9893e34099c7d8ad5cb9c940',
'content-encoding': 'gzip',
}
self.conn.head_object.return_value = stub_headers
args = ('c', 'o')
with self.output_manager as output_manager:
items, headers = h.stat_object(self.conn, self.options, *args)
h.print_object_stats(items, headers, output_manager)
expected = """
URL: http://storage/v1/a/c/o
Auth Token: tk12345
Account: a
Container: c
Object: o
Content Length: 1048576
ETag: 68b329da9893e34099c7d8ad5cb9c940
Meta Color: blue
Content-Encoding: gzip
"""
self.assertOut(expected)

View File

@ -1,240 +0,0 @@
# Copyright (c) 2010-2013 OpenStack, LLC.
#
# 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 sys
import unittest
import threading
import six
from concurrent.futures import as_completed
from six.moves.queue import Queue, Empty
from time import sleep
from swiftclient import multithreading as mt
from .utils import CaptureStream
class ThreadTestCase(unittest.TestCase):
def setUp(self):
super(ThreadTestCase, self).setUp()
self.got_items = Queue()
self.got_args_kwargs = Queue()
self.starting_thread_count = threading.active_count()
def _func(self, conn, item, *args, **kwargs):
self.got_items.put((conn, item))
self.got_args_kwargs.put((args, kwargs))
if item == 'sleep':
sleep(1)
if item == 'go boom':
raise Exception('I went boom!')
return 'success'
def _create_conn(self):
return "This is a connection"
def _create_conn_fail(self):
raise Exception("This is a failed connection")
def assertQueueContains(self, queue, expected_contents):
got_contents = []
try:
while True:
got_contents.append(queue.get(timeout=0.1))
except Empty:
pass
if isinstance(expected_contents, set):
got_contents = set(got_contents)
self.assertEqual(expected_contents, got_contents)
class TestConnectionThreadPoolExecutor(ThreadTestCase):
def setUp(self):
super(TestConnectionThreadPoolExecutor, self).setUp()
self.input_queue = Queue()
self.stored_results = []
def tearDown(self):
super(TestConnectionThreadPoolExecutor, self).tearDown()
def test_submit_good_connection(self):
ctpe = mt.ConnectionThreadPoolExecutor(self._create_conn, 1)
with ctpe as pool:
# Try submitting a job that should succeed
f = pool.submit(self._func, "succeed")
f.result()
self.assertQueueContains(
self.got_items,
[("This is a connection", "succeed")]
)
# Now a job that fails
try:
f = pool.submit(self._func, "go boom")
f.result()
except Exception as e:
self.assertEqual('I went boom!', str(e))
else:
self.fail('I never went boom!')
# Has the connection been returned to the pool?
f = pool.submit(self._func, "succeed")
f.result()
self.assertQueueContains(
self.got_items,
[
("This is a connection", "go boom"),
("This is a connection", "succeed")
]
)
def test_submit_bad_connection(self):
ctpe = mt.ConnectionThreadPoolExecutor(self._create_conn_fail, 1)
with ctpe as pool:
# Now a connection that fails
try:
f = pool.submit(self._func, "succeed")
f.result()
except Exception as e:
self.assertEqual('This is a failed connection', str(e))
else:
self.fail('The connection did not fail')
# Make sure we don't lock up on failed connections
try:
f = pool.submit(self._func, "go boom")
f.result()
except Exception as e:
self.assertEqual('This is a failed connection', str(e))
else:
self.fail('The connection did not fail')
def test_lazy_connections(self):
ctpe = mt.ConnectionThreadPoolExecutor(self._create_conn, 10)
with ctpe as pool:
# Submit multiple jobs sequentially - should only use 1 conn
f = pool.submit(self._func, "succeed")
f.result()
f = pool.submit(self._func, "succeed")
f.result()
f = pool.submit(self._func, "succeed")
f.result()
expected_connections = [(0, "This is a connection")]
expected_connections.extend([(x, None) for x in range(1, 10)])
self.assertQueueContains(
pool._connections, expected_connections
)
ctpe = mt.ConnectionThreadPoolExecutor(self._create_conn, 10)
with ctpe as pool:
fs = []
f1 = pool.submit(self._func, "sleep")
f2 = pool.submit(self._func, "sleep")
f3 = pool.submit(self._func, "sleep")
fs.extend([f1, f2, f3])
expected_connections = [
(0, "This is a connection"),
(1, "This is a connection"),
(2, "This is a connection")
]
expected_connections.extend([(x, None) for x in range(3, 10)])
for f in as_completed(fs):
f.result()
self.assertQueueContains(
pool._connections, expected_connections
)
class TestOutputManager(unittest.TestCase):
def test_instantiation(self):
output_manager = mt.OutputManager()
self.assertEqual(sys.stdout, output_manager.print_stream)
self.assertEqual(sys.stderr, output_manager.error_stream)
def test_printers(self):
out_stream = CaptureStream(sys.stdout)
err_stream = CaptureStream(sys.stderr)
starting_thread_count = threading.active_count()
with mt.OutputManager(
print_stream=out_stream,
error_stream=err_stream) as thread_manager:
# Sanity-checking these gives power to the previous test which
# looked at the default values of thread_manager.print/error_stream
self.assertEqual(out_stream, thread_manager.print_stream)
self.assertEqual(err_stream, thread_manager.error_stream)
# No printing has happened yet, so no new threads
self.assertEqual(starting_thread_count,
threading.active_count())
thread_manager.print_msg('one-argument')
thread_manager.print_msg('one %s, %d fish', 'fish', 88)
thread_manager.error('I have %d problems, but a %s is not one',
99, u'\u062A\u062A')
thread_manager.print_msg('some\n%s\nover the %r', 'where',
u'\u062A\u062A')
thread_manager.error('one-error-argument')
thread_manager.error('Sometimes\n%.1f%% just\ndoes not\nwork!',
3.14159)
thread_manager.print_raw(
u'some raw bytes: \u062A\u062A'.encode('utf-8'))
thread_manager.print_items([
('key', 'value'),
('object', u'O\u0308bject'),
])
thread_manager.print_raw(b'\xffugly\xffraw')
# Now we have a thread for error printing and a thread for
# normal print messages
self.assertEqual(starting_thread_count + 2,
threading.active_count())
# The threads should have been cleaned up
self.assertEqual(starting_thread_count, threading.active_count())
if six.PY3:
over_the = "over the '\u062a\u062a'\n"
else:
over_the = "over the u'\\u062a\\u062a'\n"
# We write to the CaptureStream so no decoding is performed
self.assertEqual(''.join([
'one-argument\n',
'one fish, 88 fish\n',
'some\n', 'where\n',
over_the,
u'some raw bytes: \u062a\u062a',
' key: value\n',
u' object: O\u0308bject\n'
]).encode('utf8') + b'\xffugly\xffraw', out_stream.getvalue())
self.assertEqual(''.join([
u'I have 99 problems, but a \u062A\u062A is not one\n',
'one-error-argument\n',
'Sometimes\n', '3.1% just\n', 'does not\n', 'work!\n'
]), err_stream.getvalue().decode('utf8'))
self.assertEqual(3, thread_manager.error_count)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,592 +0,0 @@
# Copyright (c) 2010-2013 OpenStack, LLC.
#
# 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 gzip
import unittest
import mock
import six
import tempfile
from time import gmtime, localtime, mktime, strftime, strptime
from hashlib import md5, sha1
from swiftclient import utils as u
class TestConfigTrueValue(unittest.TestCase):
def test_TRUE_VALUES(self):
for v in u.TRUE_VALUES:
self.assertEqual(v, v.lower())
@mock.patch.object(u, 'TRUE_VALUES', 'hello world'.split())
def test_config_true_value(self):
for val in 'hello world HELLO WORLD'.split():
self.assertIs(True, u.config_true_value(val))
self.assertIs(True, u.config_true_value(True))
self.assertIs(False, u.config_true_value('foo'))
self.assertIs(False, u.config_true_value(False))
class TestPrtBytes(unittest.TestCase):
def test_zero_bytes(self):
bytes_ = 0
raw = '0'
human = '0'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_one_byte(self):
bytes_ = 1
raw = '1'
human = '1'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_less_than_one_k(self):
bytes_ = (2 ** 10) - 1
raw = '1023'
human = '1023'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_one_k(self):
bytes_ = 2 ** 10
raw = '1024'
human = '1.0K'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_a_decimal_k(self):
bytes_ = (3 * 2 ** 10) + 512
raw = '3584'
human = '3.5K'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_a_bit_less_than_one_meg(self):
bytes_ = (2 ** 20) - (2 ** 10)
raw = '1047552'
human = '1023K'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_just_a_hair_less_than_one_meg(self):
bytes_ = (2 ** 20) - (2 ** 10) + 1
raw = '1047553'
human = '1.0M'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_one_meg(self):
bytes_ = 2 ** 20
raw = '1048576'
human = '1.0M'
self.assertEqual(raw, u.prt_bytes(bytes_, False).lstrip())
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_ten_meg(self):
bytes_ = 10 * 2 ** 20
human = '10M'
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_bit_less_than_ten_meg(self):
bytes_ = (10 * 2 ** 20) - (100 * 2 ** 10)
human = '9.9M'
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_just_a_hair_less_than_ten_meg(self):
bytes_ = (10 * 2 ** 20) - 1
human = '10.0M'
self.assertEqual(human, u.prt_bytes(bytes_, True).lstrip())
def test_a_yotta(self):
bytes_ = 42 * 2 ** 80
self.assertEqual('42Y', u.prt_bytes(bytes_, True).lstrip())
def test_overflow(self):
bytes_ = 2 ** 90
self.assertEqual('1024Y', u.prt_bytes(bytes_, True).lstrip())
class TestTempURL(unittest.TestCase):
url = '/v1/AUTH_account/c/o'
seconds = 3600
key = 'correcthorsebatterystaple'
method = 'GET'
expected_url = url + ('?temp_url_sig=temp_url_signature'
'&temp_url_expires=1400003600')
expected_body = '\n'.join([
method,
'1400003600',
url,
]).encode('utf-8')
@mock.patch('hmac.HMAC')
@mock.patch('time.time', return_value=1400000000)
def test_generate_temp_url(self, time_mock, hmac_mock):
hmac_mock().hexdigest.return_value = 'temp_url_signature'
url = u.generate_temp_url(self.url, self.seconds,
self.key, self.method)
key = self.key
if not isinstance(key, six.binary_type):
key = key.encode('utf-8')
self.assertEqual(url, self.expected_url)
self.assertEqual(hmac_mock.mock_calls, [
mock.call(),
mock.call(key, self.expected_body, sha1),
mock.call().hexdigest(),
])
self.assertIsInstance(url, type(self.url))
@mock.patch('hmac.HMAC')
def test_generate_temp_url_iso8601_argument(self, hmac_mock):
hmac_mock().hexdigest.return_value = 'temp_url_signature'
url = u.generate_temp_url(self.url, '2014-05-13T17:53:20Z',
self.key, self.method)
self.assertEqual(url, self.expected_url)
# Don't care about absolute arg.
url = u.generate_temp_url(self.url, '2014-05-13T17:53:20Z',
self.key, self.method, absolute=True)
self.assertEqual(url, self.expected_url)
lt = localtime()
expires = strftime(u.EXPIRES_ISO8601_FORMAT[:-1], lt)
if not isinstance(self.expected_url, six.string_types):
expected_url = self.expected_url.replace(
b'1400003600', bytes(str(int(mktime(lt))), encoding='ascii'))
else:
expected_url = self.expected_url.replace(
'1400003600', str(int(mktime(lt))))
url = u.generate_temp_url(self.url, expires,
self.key, self.method)
self.assertEqual(url, expected_url)
expires = strftime(u.SHORT_EXPIRES_ISO8601_FORMAT, lt)
lt = strptime(expires, u.SHORT_EXPIRES_ISO8601_FORMAT)
if not isinstance(self.expected_url, six.string_types):
expected_url = self.expected_url.replace(
b'1400003600', bytes(str(int(mktime(lt))), encoding='ascii'))
else:
expected_url = self.expected_url.replace(
'1400003600', str(int(mktime(lt))))
url = u.generate_temp_url(self.url, expires,
self.key, self.method)
self.assertEqual(url, expected_url)
@mock.patch('hmac.HMAC')
@mock.patch('time.time', return_value=1400000000)
def test_generate_temp_url_iso8601_output(self, time_mock, hmac_mock):
hmac_mock().hexdigest.return_value = 'temp_url_signature'
url = u.generate_temp_url(self.url, self.seconds,
self.key, self.method,
iso8601=True)
key = self.key
if not isinstance(key, six.binary_type):
key = key.encode('utf-8')
expires = strftime(u.EXPIRES_ISO8601_FORMAT, gmtime(1400003600))
if not isinstance(self.url, six.string_types):
self.assertTrue(url.endswith(bytes(expires, 'utf-8')))
else:
self.assertTrue(url.endswith(expires))
self.assertEqual(hmac_mock.mock_calls, [
mock.call(),
mock.call(key, self.expected_body, sha1),
mock.call().hexdigest(),
])
self.assertIsInstance(url, type(self.url))
@mock.patch('hmac.HMAC')
@mock.patch('time.time', return_value=1400000000)
def test_generate_temp_url_prefix(self, time_mock, hmac_mock):
hmac_mock().hexdigest.return_value = 'temp_url_signature'
prefixes = ['', 'o', 'p0/p1/']
for p in prefixes:
hmac_mock.reset_mock()
path = '/v1/AUTH_account/c/' + p
expected_url = path + ('?temp_url_sig=temp_url_signature'
'&temp_url_expires=1400003600'
'&temp_url_prefix=' + p)
expected_body = '\n'.join([
self.method,
'1400003600',
'prefix:' + path,
]).encode('utf-8')
url = u.generate_temp_url(path, self.seconds,
self.key, self.method, prefix=True)
key = self.key
if not isinstance(key, six.binary_type):
key = key.encode('utf-8')
self.assertEqual(url, expected_url)
self.assertEqual(hmac_mock.mock_calls, [
mock.call(key, expected_body, sha1),
mock.call().hexdigest(),
])
self.assertIsInstance(url, type(path))
def test_generate_temp_url_invalid_path(self):
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(b'/v1/a/c/\xff', self.seconds, self.key,
self.method)
self.assertEqual(exc_manager.exception.args[0],
'path must be representable as UTF-8')
@mock.patch('hmac.HMAC.hexdigest', return_value="temp_url_signature")
def test_generate_absolute_expiry_temp_url(self, hmac_mock):
if isinstance(self.expected_url, six.binary_type):
expected_url = self.expected_url.replace(
b'1400003600', b'2146636800')
else:
expected_url = self.expected_url.replace(
u'1400003600', u'2146636800')
url = u.generate_temp_url(self.url, 2146636800, self.key, self.method,
absolute=True)
self.assertEqual(url, expected_url)
def test_generate_temp_url_bad_time(self):
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(self.url, 'not_an_int', self.key, self.method)
self.assertEqual(exc_manager.exception.args[0], u.TIME_ERRMSG)
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(self.url, -1, self.key, self.method)
self.assertEqual(exc_manager.exception.args[0], u.TIME_ERRMSG)
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(self.url, 1.1, self.key, self.method)
self.assertEqual(exc_manager.exception.args[0], u.TIME_ERRMSG)
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(self.url, '-1', self.key, self.method)
self.assertEqual(exc_manager.exception.args[0], u.TIME_ERRMSG)
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(self.url, '1.1', self.key, self.method)
self.assertEqual(exc_manager.exception.args[0], u.TIME_ERRMSG)
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(self.url, '2015-05', self.key, self.method)
self.assertEqual(exc_manager.exception.args[0], u.TIME_ERRMSG)
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url(
self.url, '2015-05-01T01:00', self.key, self.method)
self.assertEqual(exc_manager.exception.args[0], u.TIME_ERRMSG)
def test_generate_temp_url_bad_path(self):
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url('/v1/a/c', 60, self.key, self.method)
self.assertEqual(exc_manager.exception.args[0],
'path must be full path to an object e.g. /v1/a/c/o')
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url('v1/a/c/o', 60, self.key, self.method)
self.assertEqual(exc_manager.exception.args[0],
'path must be full path to an object e.g. /v1/a/c/o')
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url('blah/v1/a/c/o', 60, self.key, self.method)
self.assertEqual(exc_manager.exception.args[0],
'path must be full path to an object e.g. /v1/a/c/o')
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url('/v1//c/o', 60, self.key, self.method)
self.assertEqual(exc_manager.exception.args[0],
'path must be full path to an object e.g. /v1/a/c/o')
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url('/v1/a/c/', 60, self.key, self.method)
self.assertEqual(exc_manager.exception.args[0],
'path must be full path to an object e.g. /v1/a/c/o')
with self.assertRaises(ValueError) as exc_manager:
u.generate_temp_url('/v1/a/c', 60, self.key, self.method,
prefix=True)
self.assertEqual(exc_manager.exception.args[0],
'path must at least contain /v1/a/c/')
class TestTempURLUnicodePathAndKey(TestTempURL):
url = u'/v1/\u00e4/c/\u00f3'
key = u'k\u00e9y'
expected_url = (u'%s?temp_url_sig=temp_url_signature'
u'&temp_url_expires=1400003600') % url
expected_body = u'\n'.join([
u'GET',
u'1400003600',
url,
]).encode('utf-8')
class TestTempURLUnicodePathBytesKey(TestTempURL):
url = u'/v1/\u00e4/c/\u00f3'
key = u'k\u00e9y'.encode('utf-8')
expected_url = (u'%s?temp_url_sig=temp_url_signature'
u'&temp_url_expires=1400003600') % url
expected_body = '\n'.join([
u'GET',
u'1400003600',
url,
]).encode('utf-8')
class TestTempURLBytesPathUnicodeKey(TestTempURL):
url = u'/v1/\u00e4/c/\u00f3'.encode('utf-8')
key = u'k\u00e9y'
expected_url = url + (b'?temp_url_sig=temp_url_signature'
b'&temp_url_expires=1400003600')
expected_body = b'\n'.join([
b'GET',
b'1400003600',
url,
])
class TestTempURLBytesPathAndKey(TestTempURL):
url = u'/v1/\u00e4/c/\u00f3'.encode('utf-8')
key = u'k\u00e9y'.encode('utf-8')
expected_url = url + (b'?temp_url_sig=temp_url_signature'
b'&temp_url_expires=1400003600')
expected_body = b'\n'.join([
b'GET',
b'1400003600',
url,
])
class TestTempURLBytesPathAndNonUtf8Key(TestTempURL):
url = u'/v1/\u00e4/c/\u00f3'.encode('utf-8')
key = b'k\xffy'
expected_url = url + (b'?temp_url_sig=temp_url_signature'
b'&temp_url_expires=1400003600')
expected_body = b'\n'.join([
b'GET',
b'1400003600',
url,
])
class TestReadableToIterable(unittest.TestCase):
def test_iter(self):
chunk_size = 4
write_data = tuple(x.encode() for x in ('a', 'b', 'c', 'd'))
actual_md5sum = md5()
with tempfile.TemporaryFile() as f:
for x in write_data:
f.write(x * chunk_size)
actual_md5sum.update(x * chunk_size)
f.seek(0)
data = u.ReadableToIterable(f, chunk_size, True)
for i, data_chunk in enumerate(data):
self.assertEqual(chunk_size, len(data_chunk))
self.assertEqual(data_chunk, write_data[i] * chunk_size)
self.assertEqual(actual_md5sum.hexdigest(), data.get_md5sum())
def test_md5_creation(self):
# Check creation with a real and noop md5 class
data = u.ReadableToIterable(None, None, md5=True)
self.assertEqual(md5().hexdigest(), data.get_md5sum())
self.assertIs(type(md5()), type(data.md5sum))
data = u.ReadableToIterable(None, None, md5=False)
self.assertEqual('', data.get_md5sum())
self.assertIs(u.NoopMD5, type(data.md5sum))
def test_unicode(self):
# Check no errors are raised if unicode data is feed in.
unicode_data = u'abc'
actual_md5sum = md5(unicode_data.encode()).hexdigest()
chunk_size = 2
with tempfile.TemporaryFile(mode='w+') as f:
f.write(unicode_data)
f.seek(0)
data = u.ReadableToIterable(f, chunk_size, True)
x = next(data)
self.assertEqual(2, len(x))
self.assertEqual(unicode_data[:2], x)
x = next(data)
self.assertEqual(1, len(x))
self.assertEqual(unicode_data[2:], x)
self.assertEqual(actual_md5sum, data.get_md5sum())
class TestLengthWrapper(unittest.TestCase):
def test_stringio(self):
contents = six.StringIO(u'a' * 50 + u'b' * 50)
contents.seek(22)
data = u.LengthWrapper(contents, 42, True)
s = u'a' * 28 + u'b' * 14
read_data = u''.join(iter(data.read, ''))
self.assertEqual(42, len(data))
self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data)
self.assertEqual(md5(s.encode()).hexdigest(), data.get_md5sum())
data.reset()
self.assertEqual(md5().hexdigest(), data.get_md5sum())
read_data = u''.join(iter(data.read, ''))
self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data)
self.assertEqual(md5(s.encode()).hexdigest(), data.get_md5sum())
def test_bytesio(self):
contents = six.BytesIO(b'a' * 50 + b'b' * 50)
contents.seek(22)
data = u.LengthWrapper(contents, 42, True)
s = b'a' * 28 + b'b' * 14
read_data = b''.join(iter(data.read, ''))
self.assertEqual(42, len(data))
self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum())
def test_tempfile(self):
with tempfile.NamedTemporaryFile(mode='wb') as f:
f.write(b'a' * 100)
f.flush()
contents = open(f.name, 'rb')
data = u.LengthWrapper(contents, 42, True)
s = b'a' * 42
read_data = b''.join(iter(data.read, ''))
self.assertEqual(42, len(data))
self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum())
def test_segmented_file(self):
with tempfile.NamedTemporaryFile(mode='wb') as f:
segment_length = 1024
segments = ('a', 'b', 'c', 'd')
for c in segments:
f.write((c * segment_length).encode())
f.flush()
for i, c in enumerate(segments):
contents = open(f.name, 'rb')
contents.seek(i * segment_length)
data = u.LengthWrapper(contents, segment_length, True)
read_data = b''.join(iter(data.read, ''))
s = (c * segment_length).encode()
self.assertEqual(segment_length, len(data))
self.assertEqual(segment_length, len(read_data))
self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum())
data.reset()
self.assertEqual(md5().hexdigest(), data.get_md5sum())
read_data = b''.join(iter(data.read, ''))
self.assertEqual(segment_length, len(data))
self.assertEqual(segment_length, len(read_data))
self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum())
class TestGroupers(unittest.TestCase):
def test_n_at_a_time(self):
result = list(u.n_at_a_time(range(100), 9))
self.assertEqual([9] * 11 + [1], list(map(len, result)))
result = list(u.n_at_a_time(range(100), 10))
self.assertEqual([10] * 10, list(map(len, result)))
result = list(u.n_at_a_time(range(100), 11))
self.assertEqual([11] * 9 + [1], list(map(len, result)))
result = list(u.n_at_a_time(range(100), 12))
self.assertEqual([12] * 8 + [4], list(map(len, result)))
def test_n_groups(self):
result = list(u.n_groups(range(100), 9))
self.assertEqual([12] * 8 + [4], list(map(len, result)))
result = list(u.n_groups(range(100), 10))
self.assertEqual([10] * 10, list(map(len, result)))
result = list(u.n_groups(range(100), 11))
self.assertEqual([10] * 10, list(map(len, result)))
result = list(u.n_groups(range(100), 12))
self.assertEqual([9] * 11 + [1], list(map(len, result)))
class TestApiResponeParser(unittest.TestCase):
def test_utf8_default(self):
result = u.parse_api_response(
{}, u'{"test": "\u2603"}'.encode('utf8'))
self.assertEqual({'test': u'\u2603'}, result)
result = u.parse_api_response(
{}, u'{"test": "\\u2603"}'.encode('utf8'))
self.assertEqual({'test': u'\u2603'}, result)
def test_bad_json(self):
self.assertRaises(ValueError, u.parse_api_response,
{}, b'{"foo": "bar}')
def test_bad_utf8(self):
self.assertRaises(UnicodeDecodeError, u.parse_api_response,
{}, b'{"foo": "b\xffr"}')
def test_latin_1(self):
result = u.parse_api_response(
{'content-type': 'application/json; charset=iso8859-1'},
b'{"t\xe9st": "\xff"}')
self.assertEqual({u't\xe9st': u'\xff'}, result)
def test_gzipped_utf8(self):
buf = six.BytesIO()
gz = gzip.GzipFile(fileobj=buf, mode='w')
gz.write(u'{"test": "\u2603"}'.encode('utf8'))
gz.close()
result = u.parse_api_response(
{'content-encoding': 'gzip'},
buf.getvalue())
self.assertEqual({'test': u'\u2603'}, result)
class TestGetBody(unittest.TestCase):
def test_not_gzipped(self):
result = u.parse_api_response(
{}, u'{"test": "\\u2603"}'.encode('utf8'))
self.assertEqual({'test': u'\u2603'}, result)
def test_gzipped_body(self):
buf = six.BytesIO()
gz = gzip.GzipFile(fileobj=buf, mode='w')
gz.write(u'{"test": "\u2603"}'.encode('utf8'))
gz.close()
result = u.parse_api_response(
{'content-encoding': 'gzip'},
buf.getvalue())
self.assertEqual({'test': u'\u2603'}, result)

View File

@ -1,550 +0,0 @@
# Copyright (c) 2010-2012 OpenStack, LLC.
#
# 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 functools
import sys
from requests import RequestException
from requests.structures import CaseInsensitiveDict
from time import sleep
import unittest
import mock
import six
from six.moves import reload_module
from six.moves.urllib.parse import urlparse, ParseResult
from swiftclient import client as c
from swiftclient import shell as s
from swiftclient.utils import EMPTY_ETAG
def fake_get_auth_keystone(expected_os_options=None, exc=None,
storage_url='http://url/', token='token',
**kwargs):
def fake_get_auth_keystone(auth_url,
user,
key,
actual_os_options, **actual_kwargs):
if exc:
raise exc('test')
# TODO: some way to require auth_url, user and key?
if expected_os_options:
for key, value in actual_os_options.items():
if value and value != expected_os_options.get(key):
return "", None
if 'required_kwargs' in kwargs:
for k, v in kwargs['required_kwargs'].items():
if v != actual_kwargs.get(k):
return "", None
if auth_url.startswith("https") and \
auth_url.endswith("invalid-certificate") and \
not actual_kwargs['insecure']:
from swiftclient import client as c
raise c.ClientException("invalid-certificate")
if auth_url.startswith("https") and \
auth_url.endswith("self-signed-certificate") and \
not actual_kwargs['insecure'] and \
actual_kwargs['cacert'] is None:
from swiftclient import client as c
raise c.ClientException("unverified-certificate")
if auth_url.startswith("https") and \
auth_url.endswith("client-certificate") and \
not (actual_kwargs['cert'] and actual_kwargs['cert_key']):
from swiftclient import client as c
raise c.ClientException("noclient-certificate")
return storage_url, token
return fake_get_auth_keystone
class StubResponse(object):
"""
Placeholder structure for use with fake_http_connect's code_iter to modify
response attributes (status, body, headers) on a per-request basis.
"""
def __init__(self, status=200, body='', headers=None):
self.status = status
self.body = body
self.headers = headers or {}
def fake_http_connect(*code_iter, **kwargs):
"""
Generate a callable which yields a series of stubbed responses. Because
swiftclient will reuse an HTTP connection across pipelined requests it is
not always the case that this fake is used strictly for mocking an HTTP
connection, but rather each HTTP response (i.e. each call to requests
get_response).
"""
class FakeConn(object):
def __init__(self, status, etag=None, body='', timestamp='1',
headers=None):
self.status_code = self.status = status
self.reason = 'Fake'
self.scheme = 'http'
self.host = '1.2.3.4'
self.port = '1234'
self.sent = 0
self.received = 0
self.etag = etag
self.content = self.body = body
self.timestamp = timestamp
self._is_closed = True
self.headers = headers or {}
self.request = None
def getresponse(self):
if kwargs.get('raise_exc'):
raise Exception('test')
return self
def getheaders(self):
if self.headers:
return self.headers.items()
headers = {'content-length': str(len(self.body)),
'content-type': 'x-application/test',
'x-timestamp': self.timestamp,
'last-modified': self.timestamp,
'x-object-meta-test': 'testing',
'etag':
self.etag or '"%s"' % EMPTY_ETAG,
'x-works': 'yes',
'x-account-container-count': '12345'}
if not self.timestamp:
del headers['x-timestamp']
try:
if next(container_ts_iter) is False:
headers['x-container-timestamp'] = '1'
except StopIteration:
pass
if 'slow' in kwargs:
headers['content-length'] = '4'
if 'headers' in kwargs:
headers.update(kwargs['headers'])
if 'auth_v1' in kwargs:
headers.update(
{'x-storage-url': 'storageURL',
'x-auth-token': 'someauthtoken'})
return headers.items()
def read(self, amt=None):
if 'slow' in kwargs:
if self.sent < 4:
self.sent += 1
sleep(0.1)
return ' '
rv = self.body[:amt]
if amt is not None:
self.body = self.body[amt:]
else:
self.body = ''
return rv
def send(self, amt=None):
if 'slow' in kwargs:
if self.received < 4:
self.received += 1
sleep(0.1)
def getheader(self, name, default=None):
return dict(self.getheaders()).get(name.lower(), default)
timestamps_iter = iter(kwargs.get('timestamps') or ['1'] * len(code_iter))
etag_iter = iter(kwargs.get('etags') or [None] * len(code_iter))
x = kwargs.get('missing_container', [False] * len(code_iter))
if not isinstance(x, (tuple, list)):
x = [x] * len(code_iter)
container_ts_iter = iter(x)
code_iter = iter(code_iter)
def connect(*args, **ckwargs):
if 'give_content_type' in kwargs:
if len(args) >= 7 and 'Content-Type' in args[6]:
kwargs['give_content_type'](args[6]['Content-Type'])
else:
kwargs['give_content_type']('')
if 'give_connect' in kwargs:
kwargs['give_connect'](*args, **ckwargs)
status = next(code_iter)
if isinstance(status, StubResponse):
fake_conn = FakeConn(status.status, body=status.body,
headers=status.headers)
else:
etag = next(etag_iter)
timestamp = next(timestamps_iter)
fake_conn = FakeConn(status, etag, body=kwargs.get('body', ''),
timestamp=timestamp)
if fake_conn.status <= 0:
raise RequestException()
return fake_conn
connect.code_iter = code_iter
return connect
class MockHttpTest(unittest.TestCase):
def setUp(self):
super(MockHttpTest, self).setUp()
self.fake_connect = None
self.request_log = []
# Capture output, since the test-runner stdout/stderr monkey-patching
# won't cover the references to sys.stdout/sys.stderr in
# swiftclient.multithreading
self.capture_output = CaptureOutput()
self.capture_output.__enter__()
def fake_http_connection(*args, **kwargs):
self.validateMockedRequestsConsumed()
self.request_log = []
self.fake_connect = fake_http_connect(*args, **kwargs)
_orig_http_connection = c.http_connection
query_string = kwargs.get('query_string')
storage_url = kwargs.get('storage_url')
auth_token = kwargs.get('auth_token')
exc = kwargs.get('exc')
on_request = kwargs.get('on_request')
def wrapper(url, proxy=None, cacert=None, insecure=False,
cert=None, cert_key=None,
ssl_compression=True, timeout=None):
if storage_url:
self.assertEqual(storage_url, url)
parsed, _conn = _orig_http_connection(url, proxy=proxy)
class RequestsWrapper(object):
pass
conn = RequestsWrapper()
def request(method, path, *args, **kwargs):
try:
conn.resp = self.fake_connect()
except StopIteration:
self.fail('Unexpected %s request for %s' % (
method, path))
self.request_log.append((parsed, method, path, args,
kwargs, conn.resp))
conn.host = conn.resp.host
conn.resp.request = RequestsWrapper()
conn.resp.request.url = '%s://%s%s' % (
conn.resp.scheme, conn.resp.host, path)
conn.resp.has_been_read = False
_orig_read = conn.resp.read
def read(*args, **kwargs):
conn.resp.has_been_read = True
return _orig_read(*args, **kwargs)
conn.resp.read = read
if on_request:
status = on_request(method, path, *args, **kwargs)
conn.resp.status = status
if auth_token:
headers = args[1]
self.assertEqual(auth_token,
headers.get('X-Auth-Token'))
if query_string:
self.assertTrue(path.endswith('?' + query_string))
if path.endswith('invalid_cert') and not insecure:
from swiftclient import client as c
raise c.ClientException("invalid_certificate")
if exc:
raise exc
return conn.resp
def putrequest(path, data=None, headers=None, **kwargs):
request('PUT', path, data, headers, **kwargs)
conn.request = request
conn.putrequest = putrequest
def getresponse():
return conn.resp
conn.getresponse = getresponse
return parsed, conn
return wrapper
self.fake_http_connection = fake_http_connection
def iter_request_log(self):
for parsed, method, path, args, kwargs, resp in self.request_log:
parts = parsed._asdict()
parts['path'] = path
full_path = ParseResult(**parts).geturl()
args = list(args)
log = dict(zip(('body', 'headers'), args))
log.update({
'method': method,
'full_path': full_path,
'parsed_path': urlparse(full_path),
'path': path,
'headers': CaseInsensitiveDict(log.get('headers')),
'resp': resp,
'status': resp.status,
})
yield log
orig_assertEqual = unittest.TestCase.assertEqual
def assert_request_equal(self, expected, real_request):
method, path = expected[:2]
if urlparse(path).scheme:
match_path = real_request['full_path']
else:
match_path = real_request['path']
self.assertEqual((method, path), (real_request['method'],
match_path))
if len(expected) > 2:
body = expected[2]
real_request['expected'] = body
err_msg = 'Body mismatch for %(method)s %(path)s, ' \
'expected %(expected)r, and got %(body)r' % real_request
self.orig_assertEqual(body, real_request['body'], err_msg)
if len(expected) > 3:
headers = CaseInsensitiveDict(expected[3])
for key, value in headers.items():
real_request['key'] = key
real_request['expected_value'] = value
real_request['value'] = real_request['headers'].get(key)
err_msg = (
'Header mismatch on %(key)r, '
'expected %(expected_value)r and got %(value)r '
'for %(method)s %(path)s %(headers)r' % real_request)
self.orig_assertEqual(value, real_request['value'],
err_msg)
real_request['extra_headers'] = dict(
(key, value) for key, value in real_request['headers'].items()
if key not in headers)
if real_request['extra_headers']:
self.fail('Received unexpected headers for %(method)s '
'%(path)s, got %(extra_headers)r' % real_request)
def assertRequests(self, expected_requests):
"""
Make sure some requests were made like you expected, provide a list of
expected requests, typically in the form of [(method, path), ...]
or [(method, path, body, headers), ...]
"""
real_requests = self.iter_request_log()
for expected in expected_requests:
real_request = next(real_requests)
self.assert_request_equal(expected, real_request)
try:
real_request = next(real_requests)
except StopIteration:
pass
else:
self.fail('At least one extra request received: %r' %
real_request)
def assert_request(self, expected_request):
"""
Make sure a request was made as expected. Provide the
expected request in the form of [(method, path), ...]
"""
real_requests = self.iter_request_log()
for real_request in real_requests:
try:
self.assert_request_equal(expected_request, real_request)
break
except AssertionError:
pass
else:
raise AssertionError(
"Expected request %s not found in actual requests %s"
% (expected_request, self.request_log)
)
def validateMockedRequestsConsumed(self):
if not self.fake_connect:
return
unused_responses = list(self.fake_connect.code_iter)
if unused_responses:
self.fail('Unused responses %r' % (unused_responses,))
def tearDown(self):
self.validateMockedRequestsConsumed()
super(MockHttpTest, self).tearDown()
# TODO: this nuke from orbit clean up seems to be encouraging
# un-hygienic mocking on the swiftclient.client module; which may lead
# to some unfortunate test order dependency bugs by way of the broken
# window theory if any other modules are similarly patched
self.capture_output.__exit__()
reload_module(c)
class CaptureStreamPrinter(object):
"""
CaptureStreamPrinter is used for testing unicode writing for PY3. Anything
written here is encoded as utf-8 and written to the parent CaptureStream
"""
def __init__(self, captured_stream):
self._captured_stream = captured_stream
def write(self, data):
# No encoding, just convert the raw bytes into a str for testing
# The below call also validates that we have a byte string.
self._captured_stream.write(
data if isinstance(data, six.binary_type) else data.encode('utf8'))
class CaptureStream(object):
def __init__(self, stream):
self.stream = stream
self._buffer = six.BytesIO()
self._capture = CaptureStreamPrinter(self._buffer)
self.streams = [self._capture]
@property
def buffer(self):
if six.PY3:
return self._buffer
else:
raise AttributeError(
'Output stream has no attribute "buffer" in Python2')
def flush(self):
pass
def write(self, *args, **kwargs):
for stream in self.streams:
stream.write(*args, **kwargs)
def writelines(self, *args, **kwargs):
for stream in self.streams:
stream.writelines(*args, **kwargs)
def getvalue(self):
return self._buffer.getvalue()
def clear(self):
self._buffer.truncate(0)
self._buffer.seek(0)
class CaptureOutput(object):
def __init__(self, suppress_systemexit=False):
self._out = CaptureStream(sys.stdout)
self._err = CaptureStream(sys.stderr)
self.patchers = []
WrappedOutputManager = functools.partial(s.OutputManager,
print_stream=self._out,
error_stream=self._err)
if suppress_systemexit:
self.patchers += [
mock.patch('swiftclient.shell.OutputManager.get_error_count',
return_value=0)
]
self.patchers += [
mock.patch('swiftclient.shell.OutputManager',
WrappedOutputManager),
mock.patch('sys.stdout', self._out),
mock.patch('sys.stderr', self._err),
]
def __enter__(self):
for patcher in self.patchers:
patcher.start()
return self
def __exit__(self, *args, **kwargs):
for patcher in self.patchers:
patcher.stop()
@property
def out(self):
return self._out.getvalue().decode('utf8')
@property
def err(self):
return self._err.getvalue().decode('utf8')
def clear(self):
self._out.clear()
self._err.clear()
# act like the string captured by stdout
def __str__(self):
return self.out
def __len__(self):
return len(self.out)
def __eq__(self, other):
return self.out == other
def __ne__(self, other):
return not self.__eq__(other)
def __getattr__(self, name):
return getattr(self.out, name)
class FakeKeystone(object):
'''
Fake keystone client module. Returns given endpoint url and auth token.
'''
def __init__(self, endpoint, token):
self.calls = []
self.auth_version = None
self.endpoint = endpoint
self.token = token
class _Client(object):
def __init__(self, endpoint, auth_token, **kwargs):
self.auth_token = auth_token
self.endpoint = endpoint
self.service_catalog = self.ServiceCatalog(endpoint)
class ServiceCatalog(object):
def __init__(self, endpoint):
self.calls = []
self.endpoint_url = endpoint
def url_for(self, **kwargs):
self.calls.append(kwargs)
return self.endpoint_url
def Client(self, **kwargs):
self.calls.append(kwargs)
self.client = self._Client(
endpoint=self.endpoint, auth_token=self.token, **kwargs)
return self.client
class Unauthorized(Exception):
pass
class AuthorizationFailure(Exception):
pass
class EndpointNotFound(Exception):
pass
def _make_fake_import_keystone_client(fake_import):
def _fake_import_keystone_client(auth_version):
fake_import.auth_version = auth_version
return fake_import, fake_import
return _fake_import_keystone_client

View File

@ -1,31 +0,0 @@
#!/usr/bin/env bash
# Client constraint file contains this client version pin that is in conflict
# with installing the client from source. We should remove the version pin in
# the constraints file before applying it for from-source installation.
set -e
if [[ -z "$CONSTRAINTS_FILE" ]]; then
echo 'WARNING: expected $CONSTRAINTS_FILE to be set' >&2
PIP_FLAGS=(-U)
else
# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
# published to logs.openstack.org for easy debugging.
localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
if [[ "$CONSTRAINTS_FILE" != http* ]]; then
CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
fi
curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
pip install -c"$localfile" openstack-requirements
# This is the main purpose of the script: Allow local installation of
# the current repo. It is listed in constraints file and thus any
# install will be constrained and we need to unconstrain it.
edit-constraints "$localfile" -- "$CLIENT_NAME"
PIP_FLAGS=(-c"$localfile" -U)
fi
pip install "${PIP_FLAGS[@]}" "$@"

73
tox.ini
View File

@ -1,73 +0,0 @@
[tox]
envlist = py27,py34,py35,pypy,pep8
minversion = 2.0
skipsdist = True
[testenv]
usedevelop = True
install_command = {toxinidir}/tools/tox_install.sh {opts} {packages}
setenv =
LANG=en_US.utf8
VIRTUAL_ENV={envdir}
BRANCH_NAME=master
CLIENT_NAME=python-swiftclient
CONSTRAINTS_FILE={env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
.[keystone]
commands = sh -c '(find . -not \( -type d -name .?\* -prune \) \
\( -type d -name "__pycache__" -or -type f -name "*.py[co]" \) \
-print0; find . -name "*.dbm*" -print0) | xargs -0 rm -rf'
python setup.py testr --testr-args="{posargs}"
whitelist_externals = sh
passenv = SWIFT_* *_proxy
[testenv:pep8]
commands =
flake8 swiftclient tests
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage
coverage report
[testenv:func]
setenv =
{[testenv]setenv}
OS_TEST_PATH=tests.functional
whitelist_externals =
coverage
rm
commands =
python setup.py testr --coverage --testr-args="--concurrency=1"
coverage report -m
rm -f .coverage
[testenv:docs]
commands=
python setup.py build_sphinx
[flake8]
# it's not a bug that we aren't using all of hacking, ignore:
# H101: Use TODO(NAME)
# H301: one import per line
# H306: imports not in alphabetical order (time, os)
# H401: docstring should not start with a space
# H403: multi line docstrings should end on a new line
# H404: multi line docstring should start without a leading new line
# H405: multi line docstring summary not separated with an empty line
ignore = H101,H301,H306,H401,H403,H404,H405
show-source = True
exclude = .venv,.tox,dist,doc,*egg
[testenv:bindep]
# Do not install any requirements. We want this to be fast and work even if
# system dependencies are missing, since it's used to tell you what system
# dependencies are missing! This also means that bindep must be installed
# separately, outside of the requirements files.
usedevelop = False
deps = bindep
commands = bindep test