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:
parent
3db6ddd6e4
commit
61c3872483
@ -1,6 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = swiftclient
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
10
.functests
10
.functests
@ -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
18
.gitignore
vendored
@ -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/
|
@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/python-swiftclient.git
|
96
.mailmap
96
.mailmap
@ -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>
|
18
.manpages
18
.manpages
@ -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"
|
@ -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
|
@ -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
143
AUTHORS
@ -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)
|
@ -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
562
ChangeLog
@ -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
175
LICENSE
@ -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.
|
@ -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
14
README
Normal 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.
|
56
README.rst
56
README.rst
@ -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:
|
24
bin/swift
24
bin/swift
@ -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())
|
@ -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]
|
90
doc/Makefile
90
doc/Makefile
@ -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."
|
@ -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/
|
@ -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
|
@ -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)
|
@ -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
|
@ -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.
|
||||
|
@ -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.
|
@ -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
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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)
|
||||
)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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.
|
@ -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.
|
@ -1,3 +0,0 @@
|
||||
futures>=3.0;python_version=='2.7' or python_version=='2.6' # BSD
|
||||
requests>=1.1
|
||||
six>=1.5.2
|
49
run_tests.sh
49
run_tests.sh
@ -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
|
59
setup.cfg
59
setup.cfg
@ -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
|
26
setup.py
26
setup.py
@ -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)
|
@ -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
|
@ -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
@ -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
|
@ -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
|
@ -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
1759
swiftclient/shell.py
1759
swiftclient/shell.py
File diff suppressed because it is too large
Load Diff
@ -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)
|
@ -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'))
|
@ -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
|
@ -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()
|
@ -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
|
@ -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
|
@ -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)
|
@ -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
@ -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)
|
@ -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
|
@ -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
73
tox.ini
@ -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
|
Loading…
Reference in New Issue
Block a user