Merge lp:~ttx/swift/1.4.1-proposed into lp:~hudson-openstack/swift/milestone-proposed

Proposed by Thierry Carrez
Status: Merged
Approved by: John Dickinson
Approved revision: 304
Merged at revision: 304
Proposed branch: lp:~ttx/swift/1.4.1-proposed
Merge into: lp:~hudson-openstack/swift/milestone-proposed
Diff against target: 11368 lines (+2932/-7158)
46 files modified
CHANGELOG (+16/-0)
bin/st (+0/-1812)
bin/swauth-add-account (+0/-68)
bin/swauth-add-user (+0/-93)
bin/swauth-cleanup-tokens (+0/-118)
bin/swauth-delete-account (+0/-60)
bin/swauth-delete-user (+0/-60)
bin/swauth-list (+0/-86)
bin/swauth-prep (+0/-59)
bin/swauth-set-account-service (+0/-73)
bin/swift (+1812/-0)
doc/source/admin_guide.rst (+0/-16)
doc/source/deployment_guide.rst (+37/-28)
doc/source/development_auth.rst (+7/-7)
doc/source/development_saio.rst (+12/-20)
doc/source/howto_installmultinode.rst (+39/-80)
doc/source/misc.rst (+6/-6)
doc/source/overview_auth.rst (+8/-151)
doc/source/overview_large_objects.rst (+12/-12)
doc/source/overview_stats.rst (+6/-5)
etc/log-processor.conf-sample (+1/-0)
etc/proxy-server.conf-sample (+27/-17)
setup.py (+2/-6)
swift/__init__.py (+1/-1)
swift/account/server.py (+3/-3)
swift/common/bench.py (+4/-3)
swift/common/daemon.py (+2/-1)
swift/common/db.py (+38/-15)
swift/common/db_replicator.py (+32/-3)
swift/common/middleware/staticweb.py (+8/-8)
swift/common/middleware/swauth.py (+0/-1374)
swift/common/middleware/tempauth.py (+482/-0)
swift/common/utils.py (+1/-1)
swift/container/server.py (+2/-2)
swift/proxy/server.py (+64/-15)
swift/stats/db_stats_collector.py (+32/-7)
swift/stats/log_uploader.py (+1/-1)
test/functional/tests.py (+4/-4)
test/probe/common.py (+19/-21)
test/unit/account/test_server.py (+3/-0)
test/unit/common/middleware/test_staticweb.py (+6/-6)
test/unit/common/middleware/test_tempauth.py (+72/-2905)
test/unit/common/test_utils.py (+4/-0)
test/unit/container/test_server.py (+3/-0)
test/unit/proxy/test_server.py (+75/-3)
test/unit/stats/test_db_stats_collector.py (+91/-8)
To merge this branch: bzr merge lp:~ttx/swift/1.4.1-proposed
Reviewer Review Type Date Requested Status
Swift Core security contacts Pending
Review via email: mp+64648@code.launchpad.net

Commit message

Merge 1.4.1 development from trunk (rev312)

Description of the change

Merge 1.4.1 development from trunk into milestone-proposed branch.

This was taken from rev312 in trunk (the last revision before 1.4.2 versioning)

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CHANGELOG'
2--- CHANGELOG 2011-05-27 13:48:41 +0000
3+++ CHANGELOG 2011-06-15 09:24:34 +0000
4@@ -1,3 +1,19 @@
5+swift (1.4.1)
6+
7+ * st renamed to swift
8+
9+ * swauth was separated froms swift. It is now its own project and can be
10+ found at https://github.com/gholt/swauth.
11+
12+ * tempauth middleware added as an extremely limited auth system for dev
13+ work.
14+
15+ * Account and container listings now properly labeled UTF-8 (previously the
16+ label was "utf8").
17+
18+ * Accounts are auto-created if an auth token is valid when the
19+ account_autocreate proxy config parameter is set to true.
20+
21 swift (1.4.0)
22
23 * swift-bench now cleans up containers it creates.
24
25=== removed file 'bin/st'
26--- bin/st 2011-05-19 14:48:15 +0000
27+++ bin/st 1970-01-01 00:00:00 +0000
28@@ -1,1812 +0,0 @@
29-#!/usr/bin/python -u
30-# Copyright (c) 2010-2011 OpenStack, LLC.
31-#
32-# Licensed under the Apache License, Version 2.0 (the "License");
33-# you may not use this file except in compliance with the License.
34-# You may obtain a copy of the License at
35-#
36-# http://www.apache.org/licenses/LICENSE-2.0
37-#
38-# Unless required by applicable law or agreed to in writing, software
39-# distributed under the License is distributed on an "AS IS" BASIS,
40-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
41-# implied.
42-# See the License for the specific language governing permissions and
43-# limitations under the License.
44-
45-from errno import EEXIST, ENOENT
46-from hashlib import md5
47-from optparse import OptionParser
48-from os import environ, listdir, makedirs, utime
49-from os.path import basename, dirname, getmtime, getsize, isdir, join
50-from Queue import Empty, Queue
51-from sys import argv, exc_info, exit, stderr, stdout
52-from threading import enumerate as threading_enumerate, Thread
53-from time import sleep
54-from traceback import format_exception
55-
56-
57-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
58-# Inclusion of swift.common.client for convenience of single file distribution
59-
60-import socket
61-from cStringIO import StringIO
62-from re import compile, DOTALL
63-from tokenize import generate_tokens, STRING, NAME, OP
64-from urllib import quote as _quote, unquote
65-from urlparse import urlparse, urlunparse
66-
67-try:
68- from eventlet.green.httplib import HTTPException, HTTPSConnection
69-except ImportError:
70- from httplib import HTTPException, HTTPSConnection
71-
72-try:
73- from eventlet import sleep
74-except ImportError:
75- from time import sleep
76-
77-try:
78- from swift.common.bufferedhttp \
79- import BufferedHTTPConnection as HTTPConnection
80-except ImportError:
81- try:
82- from eventlet.green.httplib import HTTPConnection
83- except ImportError:
84- from httplib import HTTPConnection
85-
86-
87-def quote(value, safe='/'):
88- """
89- Patched version of urllib.quote that encodes utf8 strings before quoting
90- """
91- if isinstance(value, unicode):
92- value = value.encode('utf8')
93- return _quote(value, safe)
94-
95-
96-# look for a real json parser first
97-try:
98- # simplejson is popular and pretty good
99- from simplejson import loads as json_loads
100-except ImportError:
101- try:
102- # 2.6 will have a json module in the stdlib
103- from json import loads as json_loads
104- except ImportError:
105- # fall back on local parser otherwise
106- comments = compile(r'/\*.*\*/|//[^\r\n]*', DOTALL)
107-
108- def json_loads(string):
109- '''
110- Fairly competent json parser exploiting the python tokenizer and
111- eval(). -- From python-cloudfiles
112-
113- _loads(serialized_json) -> object
114- '''
115- try:
116- res = []
117- consts = {'true': True, 'false': False, 'null': None}
118- string = '(' + comments.sub('', string) + ')'
119- for type, val, _junk, _junk, _junk in \
120- generate_tokens(StringIO(string).readline):
121- if (type == OP and val not in '[]{}:,()-') or \
122- (type == NAME and val not in consts):
123- raise AttributeError()
124- elif type == STRING:
125- res.append('u')
126- res.append(val.replace('\\/', '/'))
127- else:
128- res.append(val)
129- return eval(''.join(res), {}, consts)
130- except Exception:
131- raise AttributeError()
132-
133-
134-class ClientException(Exception):
135-
136- def __init__(self, msg, http_scheme='', http_host='', http_port='',
137- http_path='', http_query='', http_status=0, http_reason='',
138- http_device=''):
139- Exception.__init__(self, msg)
140- self.msg = msg
141- self.http_scheme = http_scheme
142- self.http_host = http_host
143- self.http_port = http_port
144- self.http_path = http_path
145- self.http_query = http_query
146- self.http_status = http_status
147- self.http_reason = http_reason
148- self.http_device = http_device
149-
150- def __str__(self):
151- a = self.msg
152- b = ''
153- if self.http_scheme:
154- b += '%s://' % self.http_scheme
155- if self.http_host:
156- b += self.http_host
157- if self.http_port:
158- b += ':%s' % self.http_port
159- if self.http_path:
160- b += self.http_path
161- if self.http_query:
162- b += '?%s' % self.http_query
163- if self.http_status:
164- if b:
165- b = '%s %s' % (b, self.http_status)
166- else:
167- b = str(self.http_status)
168- if self.http_reason:
169- if b:
170- b = '%s %s' % (b, self.http_reason)
171- else:
172- b = '- %s' % self.http_reason
173- if self.http_device:
174- if b:
175- b = '%s: device %s' % (b, self.http_device)
176- else:
177- b = 'device %s' % self.http_device
178- return b and '%s: %s' % (a, b) or a
179-
180-
181-def http_connection(url):
182- """
183- Make an HTTPConnection or HTTPSConnection
184-
185- :param url: url to connect to
186- :returns: tuple of (parsed url, connection object)
187- :raises ClientException: Unable to handle protocol scheme
188- """
189- parsed = urlparse(url)
190- if parsed.scheme == 'http':
191- conn = HTTPConnection(parsed.netloc)
192- elif parsed.scheme == 'https':
193- conn = HTTPSConnection(parsed.netloc)
194- else:
195- raise ClientException('Cannot handle protocol scheme %s for url %s' %
196- (parsed.scheme, repr(url)))
197- return parsed, conn
198-
199-
200-def get_auth(url, user, key, snet=False):
201- """
202- Get authentication/authorization credentials.
203-
204- The snet parameter is used for Rackspace's ServiceNet internal network
205- implementation. In this function, it simply adds *snet-* to the beginning
206- of the host name for the returned storage URL. With Rackspace Cloud Files,
207- use of this network path causes no bandwidth charges but requires the
208- client to be running on Rackspace's ServiceNet network.
209-
210- :param url: authentication/authorization URL
211- :param user: user to authenticate as
212- :param key: key or password for authorization
213- :param snet: use SERVICENET internal network (see above), default is False
214- :returns: tuple of (storage URL, auth token)
215- :raises ClientException: HTTP GET request to auth URL failed
216- """
217- parsed, conn = http_connection(url)
218- conn.request('GET', parsed.path, '',
219- {'X-Auth-User': user, 'X-Auth-Key': key})
220- resp = conn.getresponse()
221- resp.read()
222- if resp.status < 200 or resp.status >= 300:
223- raise ClientException('Auth GET failed', http_scheme=parsed.scheme,
224- http_host=conn.host, http_port=conn.port,
225- http_path=parsed.path, http_status=resp.status,
226- http_reason=resp.reason)
227- url = resp.getheader('x-storage-url')
228- if snet:
229- parsed = list(urlparse(url))
230- # Second item in the list is the netloc
231- parsed[1] = 'snet-' + parsed[1]
232- url = urlunparse(parsed)
233- return url, resp.getheader('x-storage-token',
234- resp.getheader('x-auth-token'))
235-
236-
237-def get_account(url, token, marker=None, limit=None, prefix=None,
238- http_conn=None, full_listing=False):
239- """
240- Get a listing of containers for the account.
241-
242- :param url: storage URL
243- :param token: auth token
244- :param marker: marker query
245- :param limit: limit query
246- :param prefix: prefix query
247- :param http_conn: HTTP connection object (If None, it will create the
248- conn object)
249- :param full_listing: if True, return a full listing, else returns a max
250- of 10000 listings
251- :returns: a tuple of (response headers, a list of containers) The response
252- headers will be a dict and all header names will be lowercase.
253- :raises ClientException: HTTP GET request failed
254- """
255- if not http_conn:
256- http_conn = http_connection(url)
257- if full_listing:
258- rv = get_account(url, token, marker, limit, prefix, http_conn)
259- listing = rv[1]
260- while listing:
261- marker = listing[-1]['name']
262- listing = \
263- get_account(url, token, marker, limit, prefix, http_conn)[1]
264- if listing:
265- rv[1].extend(listing)
266- return rv
267- parsed, conn = http_conn
268- qs = 'format=json'
269- if marker:
270- qs += '&marker=%s' % quote(marker)
271- if limit:
272- qs += '&limit=%d' % limit
273- if prefix:
274- qs += '&prefix=%s' % quote(prefix)
275- conn.request('GET', '%s?%s' % (parsed.path, qs), '',
276- {'X-Auth-Token': token})
277- resp = conn.getresponse()
278- resp_headers = {}
279- for header, value in resp.getheaders():
280- resp_headers[header.lower()] = value
281- if resp.status < 200 or resp.status >= 300:
282- resp.read()
283- raise ClientException('Account GET failed', http_scheme=parsed.scheme,
284- http_host=conn.host, http_port=conn.port,
285- http_path=parsed.path, http_query=qs, http_status=resp.status,
286- http_reason=resp.reason)
287- if resp.status == 204:
288- resp.read()
289- return resp_headers, []
290- return resp_headers, json_loads(resp.read())
291-
292-
293-def head_account(url, token, http_conn=None):
294- """
295- Get account stats.
296-
297- :param url: storage URL
298- :param token: auth token
299- :param http_conn: HTTP connection object (If None, it will create the
300- conn object)
301- :returns: a dict containing the response's headers (all header names will
302- be lowercase)
303- :raises ClientException: HTTP HEAD request failed
304- """
305- if http_conn:
306- parsed, conn = http_conn
307- else:
308- parsed, conn = http_connection(url)
309- conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token})
310- resp = conn.getresponse()
311- resp.read()
312- if resp.status < 200 or resp.status >= 300:
313- raise ClientException('Account HEAD failed', http_scheme=parsed.scheme,
314- http_host=conn.host, http_port=conn.port,
315- http_path=parsed.path, http_status=resp.status,
316- http_reason=resp.reason)
317- resp_headers = {}
318- for header, value in resp.getheaders():
319- resp_headers[header.lower()] = value
320- return resp_headers
321-
322-
323-def post_account(url, token, headers, http_conn=None):
324- """
325- Update an account's metadata.
326-
327- :param url: storage URL
328- :param token: auth token
329- :param headers: additional headers to include in the request
330- :param http_conn: HTTP connection object (If None, it will create the
331- conn object)
332- :raises ClientException: HTTP POST request failed
333- """
334- if http_conn:
335- parsed, conn = http_conn
336- else:
337- parsed, conn = http_connection(url)
338- headers['X-Auth-Token'] = token
339- conn.request('POST', parsed.path, '', headers)
340- resp = conn.getresponse()
341- resp.read()
342- if resp.status < 200 or resp.status >= 300:
343- raise ClientException('Account POST failed',
344- http_scheme=parsed.scheme, http_host=conn.host,
345- http_port=conn.port, http_path=path, http_status=resp.status,
346- http_reason=resp.reason)
347-
348-
349-def get_container(url, token, container, marker=None, limit=None,
350- prefix=None, delimiter=None, http_conn=None,
351- full_listing=False):
352- """
353- Get a listing of objects for the container.
354-
355- :param url: storage URL
356- :param token: auth token
357- :param container: container name to get a listing for
358- :param marker: marker query
359- :param limit: limit query
360- :param prefix: prefix query
361- :param delimeter: string to delimit the queries on
362- :param http_conn: HTTP connection object (If None, it will create the
363- conn object)
364- :param full_listing: if True, return a full listing, else returns a max
365- of 10000 listings
366- :returns: a tuple of (response headers, a list of objects) The response
367- headers will be a dict and all header names will be lowercase.
368- :raises ClientException: HTTP GET request failed
369- """
370- if not http_conn:
371- http_conn = http_connection(url)
372- if full_listing:
373- rv = get_container(url, token, container, marker, limit, prefix,
374- delimiter, http_conn)
375- listing = rv[1]
376- while listing:
377- if not delimiter:
378- marker = listing[-1]['name']
379- else:
380- marker = listing[-1].get('name', listing[-1].get('subdir'))
381- listing = get_container(url, token, container, marker, limit,
382- prefix, delimiter, http_conn)[1]
383- if listing:
384- rv[1].extend(listing)
385- return rv
386- parsed, conn = http_conn
387- path = '%s/%s' % (parsed.path, quote(container))
388- qs = 'format=json'
389- if marker:
390- qs += '&marker=%s' % quote(marker)
391- if limit:
392- qs += '&limit=%d' % limit
393- if prefix:
394- qs += '&prefix=%s' % quote(prefix)
395- if delimiter:
396- qs += '&delimiter=%s' % quote(delimiter)
397- conn.request('GET', '%s?%s' % (path, qs), '', {'X-Auth-Token': token})
398- resp = conn.getresponse()
399- if resp.status < 200 or resp.status >= 300:
400- resp.read()
401- raise ClientException('Container GET failed',
402- http_scheme=parsed.scheme, http_host=conn.host,
403- http_port=conn.port, http_path=path, http_query=qs,
404- http_status=resp.status, http_reason=resp.reason)
405- resp_headers = {}
406- for header, value in resp.getheaders():
407- resp_headers[header.lower()] = value
408- if resp.status == 204:
409- resp.read()
410- return resp_headers, []
411- return resp_headers, json_loads(resp.read())
412-
413-
414-def head_container(url, token, container, http_conn=None):
415- """
416- Get container stats.
417-
418- :param url: storage URL
419- :param token: auth token
420- :param container: container name to get stats for
421- :param http_conn: HTTP connection object (If None, it will create the
422- conn object)
423- :returns: a dict containing the response's headers (all header names will
424- be lowercase)
425- :raises ClientException: HTTP HEAD request failed
426- """
427- if http_conn:
428- parsed, conn = http_conn
429- else:
430- parsed, conn = http_connection(url)
431- path = '%s/%s' % (parsed.path, quote(container))
432- conn.request('HEAD', path, '', {'X-Auth-Token': token})
433- resp = conn.getresponse()
434- resp.read()
435- if resp.status < 200 or resp.status >= 300:
436- raise ClientException('Container HEAD failed',
437- http_scheme=parsed.scheme, http_host=conn.host,
438- http_port=conn.port, http_path=path, http_status=resp.status,
439- http_reason=resp.reason)
440- resp_headers = {}
441- for header, value in resp.getheaders():
442- resp_headers[header.lower()] = value
443- return resp_headers
444-
445-
446-def put_container(url, token, container, headers=None, http_conn=None):
447- """
448- Create a container
449-
450- :param url: storage URL
451- :param token: auth token
452- :param container: container name to create
453- :param headers: additional headers to include in the request
454- :param http_conn: HTTP connection object (If None, it will create the
455- conn object)
456- :raises ClientException: HTTP PUT request failed
457- """
458- if http_conn:
459- parsed, conn = http_conn
460- else:
461- parsed, conn = http_connection(url)
462- path = '%s/%s' % (parsed.path, quote(container))
463- if not headers:
464- headers = {}
465- headers['X-Auth-Token'] = token
466- conn.request('PUT', path, '', headers)
467- resp = conn.getresponse()
468- resp.read()
469- if resp.status < 200 or resp.status >= 300:
470- raise ClientException('Container PUT failed',
471- http_scheme=parsed.scheme, http_host=conn.host,
472- http_port=conn.port, http_path=path, http_status=resp.status,
473- http_reason=resp.reason)
474-
475-
476-def post_container(url, token, container, headers, http_conn=None):
477- """
478- Update a container's metadata.
479-
480- :param url: storage URL
481- :param token: auth token
482- :param container: container name to update
483- :param headers: additional headers to include in the request
484- :param http_conn: HTTP connection object (If None, it will create the
485- conn object)
486- :raises ClientException: HTTP POST request failed
487- """
488- if http_conn:
489- parsed, conn = http_conn
490- else:
491- parsed, conn = http_connection(url)
492- path = '%s/%s' % (parsed.path, quote(container))
493- headers['X-Auth-Token'] = token
494- conn.request('POST', path, '', headers)
495- resp = conn.getresponse()
496- resp.read()
497- if resp.status < 200 or resp.status >= 300:
498- raise ClientException('Container POST failed',
499- http_scheme=parsed.scheme, http_host=conn.host,
500- http_port=conn.port, http_path=path, http_status=resp.status,
501- http_reason=resp.reason)
502-
503-
504-def delete_container(url, token, container, http_conn=None):
505- """
506- Delete a container
507-
508- :param url: storage URL
509- :param token: auth token
510- :param container: container name to delete
511- :param http_conn: HTTP connection object (If None, it will create the
512- conn object)
513- :raises ClientException: HTTP DELETE request failed
514- """
515- if http_conn:
516- parsed, conn = http_conn
517- else:
518- parsed, conn = http_connection(url)
519- path = '%s/%s' % (parsed.path, quote(container))
520- conn.request('DELETE', path, '', {'X-Auth-Token': token})
521- resp = conn.getresponse()
522- resp.read()
523- if resp.status < 200 or resp.status >= 300:
524- raise ClientException('Container DELETE failed',
525- http_scheme=parsed.scheme, http_host=conn.host,
526- http_port=conn.port, http_path=path, http_status=resp.status,
527- http_reason=resp.reason)
528-
529-
530-def get_object(url, token, container, name, http_conn=None,
531- resp_chunk_size=None):
532- """
533- Get an object
534-
535- :param url: storage URL
536- :param token: auth token
537- :param container: container name that the object is in
538- :param name: object name to get
539- :param http_conn: HTTP connection object (If None, it will create the
540- conn object)
541- :param resp_chunk_size: if defined, chunk size of data to read. NOTE: If
542- you specify a resp_chunk_size you must fully read
543- the object's contents before making another
544- request.
545- :returns: a tuple of (response headers, the object's contents) The response
546- headers will be a dict and all header names will be lowercase.
547- :raises ClientException: HTTP GET request failed
548- """
549- if http_conn:
550- parsed, conn = http_conn
551- else:
552- parsed, conn = http_connection(url)
553- path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
554- conn.request('GET', path, '', {'X-Auth-Token': token})
555- resp = conn.getresponse()
556- if resp.status < 200 or resp.status >= 300:
557- resp.read()
558- raise ClientException('Object GET failed', http_scheme=parsed.scheme,
559- http_host=conn.host, http_port=conn.port, http_path=path,
560- http_status=resp.status, http_reason=resp.reason)
561- if resp_chunk_size:
562-
563- def _object_body():
564- buf = resp.read(resp_chunk_size)
565- while buf:
566- yield buf
567- buf = resp.read(resp_chunk_size)
568- object_body = _object_body()
569- else:
570- object_body = resp.read()
571- resp_headers = {}
572- for header, value in resp.getheaders():
573- resp_headers[header.lower()] = value
574- return resp_headers, object_body
575-
576-
577-def head_object(url, token, container, name, http_conn=None):
578- """
579- Get object info
580-
581- :param url: storage URL
582- :param token: auth token
583- :param container: container name that the object is in
584- :param name: object name to get info for
585- :param http_conn: HTTP connection object (If None, it will create the
586- conn object)
587- :returns: a dict containing the response's headers (all header names will
588- be lowercase)
589- :raises ClientException: HTTP HEAD request failed
590- """
591- if http_conn:
592- parsed, conn = http_conn
593- else:
594- parsed, conn = http_connection(url)
595- path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
596- conn.request('HEAD', path, '', {'X-Auth-Token': token})
597- resp = conn.getresponse()
598- resp.read()
599- if resp.status < 200 or resp.status >= 300:
600- raise ClientException('Object HEAD failed', http_scheme=parsed.scheme,
601- http_host=conn.host, http_port=conn.port, http_path=path,
602- http_status=resp.status, http_reason=resp.reason)
603- resp_headers = {}
604- for header, value in resp.getheaders():
605- resp_headers[header.lower()] = value
606- return resp_headers
607-
608-
609-def put_object(url, token, container, name, contents, content_length=None,
610- etag=None, chunk_size=65536, content_type=None, headers=None,
611- http_conn=None):
612- """
613- Put an object
614-
615- :param url: storage URL
616- :param token: auth token
617- :param container: container name that the object is in
618- :param name: object name to put
619- :param contents: a string or a file like object to read object data from
620- :param content_length: value to send as content-length header; also limits
621- the amount read from contents
622- :param etag: etag of contents
623- :param chunk_size: chunk size of data to write
624- :param content_type: value to send as content-type header
625- :param headers: additional headers to include in the request
626- :param http_conn: HTTP connection object (If None, it will create the
627- conn object)
628- :returns: etag from server response
629- :raises ClientException: HTTP PUT request failed
630- """
631- if http_conn:
632- parsed, conn = http_conn
633- else:
634- parsed, conn = http_connection(url)
635- path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
636- if not headers:
637- headers = {}
638- headers['X-Auth-Token'] = token
639- if etag:
640- headers['ETag'] = etag.strip('"')
641- if content_length is not None:
642- headers['Content-Length'] = str(content_length)
643- if content_type is not None:
644- headers['Content-Type'] = content_type
645- if not contents:
646- headers['Content-Length'] = '0'
647- if hasattr(contents, 'read'):
648- conn.putrequest('PUT', path)
649- for header, value in headers.iteritems():
650- conn.putheader(header, value)
651- if content_length is None:
652- conn.putheader('Transfer-Encoding', 'chunked')
653- conn.endheaders()
654- chunk = contents.read(chunk_size)
655- while chunk:
656- conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
657- chunk = contents.read(chunk_size)
658- conn.send('0\r\n\r\n')
659- else:
660- conn.endheaders()
661- left = content_length
662- while left > 0:
663- size = chunk_size
664- if size > left:
665- size = left
666- chunk = contents.read(size)
667- conn.send(chunk)
668- left -= len(chunk)
669- else:
670- conn.request('PUT', path, contents, headers)
671- resp = conn.getresponse()
672- resp.read()
673- if resp.status < 200 or resp.status >= 300:
674- raise ClientException('Object PUT failed', http_scheme=parsed.scheme,
675- http_host=conn.host, http_port=conn.port, http_path=path,
676- http_status=resp.status, http_reason=resp.reason)
677- return resp.getheader('etag').strip('"')
678-
679-
680-def post_object(url, token, container, name, headers, http_conn=None):
681- """
682- Update object metadata
683-
684- :param url: storage URL
685- :param token: auth token
686- :param container: container name that the object is in
687- :param name: name of the object to update
688- :param headers: additional headers to include in the request
689- :param http_conn: HTTP connection object (If None, it will create the
690- conn object)
691- :raises ClientException: HTTP POST request failed
692- """
693- if http_conn:
694- parsed, conn = http_conn
695- else:
696- parsed, conn = http_connection(url)
697- path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
698- headers['X-Auth-Token'] = token
699- conn.request('POST', path, '', headers)
700- resp = conn.getresponse()
701- resp.read()
702- if resp.status < 200 or resp.status >= 300:
703- raise ClientException('Object POST failed', http_scheme=parsed.scheme,
704- http_host=conn.host, http_port=conn.port, http_path=path,
705- http_status=resp.status, http_reason=resp.reason)
706-
707-
708-def delete_object(url, token, container, name, http_conn=None):
709- """
710- Delete object
711-
712- :param url: storage URL
713- :param token: auth token
714- :param container: container name that the object is in
715- :param name: object name to delete
716- :param http_conn: HTTP connection object (If None, it will create the
717- conn object)
718- :raises ClientException: HTTP DELETE request failed
719- """
720- if http_conn:
721- parsed, conn = http_conn
722- else:
723- parsed, conn = http_connection(url)
724- path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
725- conn.request('DELETE', path, '', {'X-Auth-Token': token})
726- resp = conn.getresponse()
727- resp.read()
728- if resp.status < 200 or resp.status >= 300:
729- raise ClientException('Object DELETE failed',
730- http_scheme=parsed.scheme, http_host=conn.host,
731- http_port=conn.port, http_path=path, http_status=resp.status,
732- http_reason=resp.reason)
733-
734-
735-class Connection(object):
736- """Convenience class to make requests that will also retry the request"""
737-
738- def __init__(self, authurl, user, key, retries=5, preauthurl=None,
739- preauthtoken=None, snet=False, starting_backoff=1):
740- """
741- :param authurl: authenitcation URL
742- :param user: user name to authenticate as
743- :param key: key/password to authenticate with
744- :param retries: Number of times to retry the request before failing
745- :param preauthurl: storage URL (if you have already authenticated)
746- :param preauthtoken: authentication token (if you have already
747- authenticated)
748- :param snet: use SERVICENET internal network default is False
749- """
750- self.authurl = authurl
751- self.user = user
752- self.key = key
753- self.retries = retries
754- self.http_conn = None
755- self.url = preauthurl
756- self.token = preauthtoken
757- self.attempts = 0
758- self.snet = snet
759- self.starting_backoff = starting_backoff
760-
761- def get_auth(self):
762- return get_auth(self.authurl, self.user, self.key, snet=self.snet)
763-
764- def http_connection(self):
765- return http_connection(self.url)
766-
767- def _retry(self, reset_func, func, *args, **kwargs):
768- self.attempts = 0
769- backoff = self.starting_backoff
770- while self.attempts <= self.retries:
771- self.attempts += 1
772- try:
773- if not self.url or not self.token:
774- self.url, self.token = self.get_auth()
775- self.http_conn = None
776- if not self.http_conn:
777- self.http_conn = self.http_connection()
778- kwargs['http_conn'] = self.http_conn
779- rv = func(self.url, self.token, *args, **kwargs)
780- return rv
781- except (socket.error, HTTPException):
782- if self.attempts > self.retries:
783- raise
784- self.http_conn = None
785- except ClientException, err:
786- if self.attempts > self.retries:
787- raise
788- if err.http_status == 401:
789- self.url = self.token = None
790- if self.attempts > 1:
791- raise
792- elif err.http_status == 408:
793- self.http_conn = None
794- elif 500 <= err.http_status <= 599:
795- pass
796- else:
797- raise
798- sleep(backoff)
799- backoff *= 2
800- if reset_func:
801- reset_func(func, *args, **kwargs)
802-
803- def head_account(self):
804- """Wrapper for :func:`head_account`"""
805- return self._retry(None, head_account)
806-
807- def get_account(self, marker=None, limit=None, prefix=None,
808- full_listing=False):
809- """Wrapper for :func:`get_account`"""
810- # TODO(unknown): With full_listing=True this will restart the entire
811- # listing with each retry. Need to make a better version that just
812- # retries where it left off.
813- return self._retry(None, get_account, marker=marker, limit=limit,
814- prefix=prefix, full_listing=full_listing)
815-
816- def post_account(self, headers):
817- """Wrapper for :func:`post_account`"""
818- return self._retry(None, post_account, headers)
819-
820- def head_container(self, container):
821- """Wrapper for :func:`head_container`"""
822- return self._retry(None, head_container, container)
823-
824- def get_container(self, container, marker=None, limit=None, prefix=None,
825- delimiter=None, full_listing=False):
826- """Wrapper for :func:`get_container`"""
827- # TODO(unknown): With full_listing=True this will restart the entire
828- # listing with each retry. Need to make a better version that just
829- # retries where it left off.
830- return self._retry(None, get_container, container, marker=marker,
831- limit=limit, prefix=prefix, delimiter=delimiter,
832- full_listing=full_listing)
833-
834- def put_container(self, container, headers=None):
835- """Wrapper for :func:`put_container`"""
836- return self._retry(None, put_container, container, headers=headers)
837-
838- def post_container(self, container, headers):
839- """Wrapper for :func:`post_container`"""
840- return self._retry(None, post_container, container, headers)
841-
842- def delete_container(self, container):
843- """Wrapper for :func:`delete_container`"""
844- return self._retry(None, delete_container, container)
845-
846- def head_object(self, container, obj):
847- """Wrapper for :func:`head_object`"""
848- return self._retry(None, head_object, container, obj)
849-
850- def get_object(self, container, obj, resp_chunk_size=None):
851- """Wrapper for :func:`get_object`"""
852- return self._retry(None, get_object, container, obj,
853- resp_chunk_size=resp_chunk_size)
854-
855- def put_object(self, container, obj, contents, content_length=None,
856- etag=None, chunk_size=65536, content_type=None,
857- headers=None):
858- """Wrapper for :func:`put_object`"""
859-
860- def _default_reset(*args, **kwargs):
861- raise ClientException('put_object(%r, %r, ...) failure and no '
862- 'ability to reset contents for reupload.' % (container, obj))
863-
864- reset_func = _default_reset
865- tell = getattr(contents, 'tell', None)
866- seek = getattr(contents, 'seek', None)
867- if tell and seek:
868- orig_pos = tell()
869- reset_func = lambda *a, **k: seek(orig_pos)
870- elif not contents:
871- reset_func = lambda *a, **k: None
872-
873- return self._retry(reset_func, put_object, container, obj, contents,
874- content_length=content_length, etag=etag, chunk_size=chunk_size,
875- content_type=content_type, headers=headers)
876-
877- def post_object(self, container, obj, headers):
878- """Wrapper for :func:`post_object`"""
879- return self._retry(None, post_object, container, obj, headers)
880-
881- def delete_object(self, container, obj):
882- """Wrapper for :func:`delete_object`"""
883- return self._retry(None, delete_object, container, obj)
884-
885-# End inclusion of swift.common.client
886-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
887-
888-
889-def mkdirs(path):
890- try:
891- makedirs(path)
892- except OSError, err:
893- if err.errno != EEXIST:
894- raise
895-
896-
897-def put_errors_from_threads(threads, error_queue):
898- """
899- Places any errors from the threads into error_queue.
900- :param threads: A list of QueueFunctionThread instances.
901- :param error_queue: A queue to put error strings into.
902- :returns: True if any errors were found.
903- """
904- was_error = False
905- for thread in threads:
906- for info in thread.exc_infos:
907- was_error = True
908- if isinstance(info[1], ClientException):
909- error_queue.put(str(info[1]))
910- else:
911- error_queue.put(''.join(format_exception(*info)))
912- return was_error
913-
914-
915-class QueueFunctionThread(Thread):
916-
917- def __init__(self, queue, func, *args, **kwargs):
918- """ Calls func for each item in queue; func is called with a queued
919- item as the first arg followed by *args and **kwargs. Use the abort
920- attribute to have the thread empty the queue (without processing)
921- and exit. """
922- Thread.__init__(self)
923- self.abort = False
924- self.queue = queue
925- self.func = func
926- self.args = args
927- self.kwargs = kwargs
928- self.exc_infos = []
929-
930- def run(self):
931- try:
932- while True:
933- try:
934- item = self.queue.get_nowait()
935- if not self.abort:
936- self.func(item, *self.args, **self.kwargs)
937- self.queue.task_done()
938- except Empty:
939- if self.abort:
940- break
941- sleep(0.01)
942- except Exception:
943- self.exc_infos.append(exc_info())
944-
945-
946-st_delete_help = '''
947-delete --all OR delete container [--leave-segments] [object] [object] ...
948- Deletes everything in the account (with --all), or everything in a
949- container, or a list of objects depending on the args given. Segments of
950- manifest objects will be deleted as well, unless you specify the
951- --leave-segments option.'''.strip('\n')
952-
953-
954-def st_delete(parser, args, print_queue, error_queue):
955- parser.add_option('-a', '--all', action='store_true', dest='yes_all',
956- default=False, help='Indicates that you really want to delete '
957- 'everything in the account')
958- parser.add_option('', '--leave-segments', action='store_true',
959- dest='leave_segments', default=False, help='Indicates that you want '
960- 'the segments of manifest objects left alone')
961- (options, args) = parse_args(parser, args)
962- args = args[1:]
963- if (not args and not options.yes_all) or (args and options.yes_all):
964- error_queue.put('Usage: %s [options] %s' %
965- (basename(argv[0]), st_delete_help))
966- return
967-
968- def _delete_segment((container, obj), conn):
969- conn.delete_object(container, obj)
970- if options.verbose:
971- if conn.attempts > 2:
972- print_queue.put('%s/%s [after %d attempts]' %
973- (container, obj, conn.attempts))
974- else:
975- print_queue.put('%s/%s' % (container, obj))
976-
977- object_queue = Queue(10000)
978-
979- def _delete_object((container, obj), conn):
980- try:
981- old_manifest = None
982- if not options.leave_segments:
983- try:
984- old_manifest = conn.head_object(container, obj).get(
985- 'x-object-manifest')
986- except ClientException, err:
987- if err.http_status != 404:
988- raise
989- conn.delete_object(container, obj)
990- if old_manifest:
991- segment_queue = Queue(10000)
992- scontainer, sprefix = old_manifest.split('/', 1)
993- for delobj in conn.get_container(scontainer,
994- prefix=sprefix)[1]:
995- segment_queue.put((scontainer, delobj['name']))
996- if not segment_queue.empty():
997- segment_threads = [QueueFunctionThread(segment_queue,
998- _delete_segment, create_connection()) for _junk in
999- xrange(10)]
1000- for thread in segment_threads:
1001- thread.start()
1002- while not segment_queue.empty():
1003- sleep(0.01)
1004- for thread in segment_threads:
1005- thread.abort = True
1006- while thread.isAlive():
1007- thread.join(0.01)
1008- put_errors_from_threads(segment_threads, error_queue)
1009- if options.verbose:
1010- path = options.yes_all and join(container, obj) or obj
1011- if path[:1] in ('/', '\\'):
1012- path = path[1:]
1013- if conn.attempts > 1:
1014- print_queue.put('%s [after %d attempts]' %
1015- (path, conn.attempts))
1016- else:
1017- print_queue.put(path)
1018- except ClientException, err:
1019- if err.http_status != 404:
1020- raise
1021- error_queue.put('Object %s not found' %
1022- repr('%s/%s' % (container, obj)))
1023-
1024- container_queue = Queue(10000)
1025-
1026- def _delete_container(container, conn):
1027- try:
1028- marker = ''
1029- while True:
1030- objects = [o['name'] for o in
1031- conn.get_container(container, marker=marker)[1]]
1032- if not objects:
1033- break
1034- for obj in objects:
1035- object_queue.put((container, obj))
1036- marker = objects[-1]
1037- while not object_queue.empty():
1038- sleep(0.01)
1039- attempts = 1
1040- while True:
1041- try:
1042- conn.delete_container(container)
1043- break
1044- except ClientException, err:
1045- if err.http_status != 409:
1046- raise
1047- if attempts > 10:
1048- raise
1049- attempts += 1
1050- sleep(1)
1051- except ClientException, err:
1052- if err.http_status != 404:
1053- raise
1054- error_queue.put('Container %s not found' % repr(container))
1055-
1056- url, token = get_auth(options.auth, options.user, options.key,
1057- snet=options.snet)
1058- create_connection = lambda: Connection(options.auth, options.user,
1059- options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
1060- object_threads = [QueueFunctionThread(object_queue, _delete_object,
1061- create_connection()) for _junk in xrange(10)]
1062- for thread in object_threads:
1063- thread.start()
1064- container_threads = [QueueFunctionThread(container_queue,
1065- _delete_container, create_connection()) for _junk in xrange(10)]
1066- for thread in container_threads:
1067- thread.start()
1068- if not args:
1069- conn = create_connection()
1070- try:
1071- marker = ''
1072- while True:
1073- containers = \
1074- [c['name'] for c in conn.get_account(marker=marker)[1]]
1075- if not containers:
1076- break
1077- for container in containers:
1078- container_queue.put(container)
1079- marker = containers[-1]
1080- while not container_queue.empty():
1081- sleep(0.01)
1082- while not object_queue.empty():
1083- sleep(0.01)
1084- except ClientException, err:
1085- if err.http_status != 404:
1086- raise
1087- error_queue.put('Account not found')
1088- elif len(args) == 1:
1089- if '/' in args[0]:
1090- print >> stderr, 'WARNING: / in container name; you might have ' \
1091- 'meant %r instead of %r.' % \
1092- (args[0].replace('/', ' ', 1), args[0])
1093- conn = create_connection()
1094- _delete_container(args[0], conn)
1095- else:
1096- for obj in args[1:]:
1097- object_queue.put((args[0], obj))
1098- while not container_queue.empty():
1099- sleep(0.01)
1100- for thread in container_threads:
1101- thread.abort = True
1102- while thread.isAlive():
1103- thread.join(0.01)
1104- put_errors_from_threads(container_threads, error_queue)
1105- while not object_queue.empty():
1106- sleep(0.01)
1107- for thread in object_threads:
1108- thread.abort = True
1109- while thread.isAlive():
1110- thread.join(0.01)
1111- put_errors_from_threads(object_threads, error_queue)
1112-
1113-
1114-st_download_help = '''
1115-download --all OR download container [options] [object] [object] ...
1116- Downloads everything in the account (with --all), or everything in a
1117- container, or a list of objects depending on the args given. For a single
1118- object download, you may use the -o [--output] <filename> option to
1119- redirect the output to a specific file or if "-" then just redirect to
1120- stdout.'''.strip('\n')
1121-
1122-
1123-def st_download(options, args, print_queue, error_queue):
1124- parser.add_option('-a', '--all', action='store_true', dest='yes_all',
1125- default=False, help='Indicates that you really want to download '
1126- 'everything in the account')
1127- parser.add_option('-o', '--output', dest='out_file', help='For a single '
1128- 'file download, stream the output to an alternate location ')
1129- (options, args) = parse_args(parser, args)
1130- args = args[1:]
1131- if options.out_file == '-':
1132- options.verbose = 0
1133- if options.out_file and len(args) != 2:
1134- exit('-o option only allowed for single file downloads')
1135- if (not args and not options.yes_all) or (args and options.yes_all):
1136- error_queue.put('Usage: %s [options] %s' %
1137- (basename(argv[0]), st_download_help))
1138- return
1139-
1140- object_queue = Queue(10000)
1141-
1142- def _download_object(queue_arg, conn):
1143- if len(queue_arg) == 2:
1144- container, obj = queue_arg
1145- out_file = None
1146- elif len(queue_arg) == 3:
1147- container, obj, out_file = queue_arg
1148- else:
1149- raise Exception("Invalid queue_arg length of %s" % len(queue_arg))
1150- try:
1151- headers, body = \
1152- conn.get_object(container, obj, resp_chunk_size=65536)
1153- content_type = headers.get('content-type')
1154- if 'content-length' in headers:
1155- content_length = int(headers.get('content-length'))
1156- else:
1157- content_length = None
1158- etag = headers.get('etag')
1159- path = options.yes_all and join(container, obj) or obj
1160- if path[:1] in ('/', '\\'):
1161- path = path[1:]
1162- md5sum = None
1163- make_dir = out_file != "-"
1164- if content_type.split(';', 1)[0] == 'text/directory':
1165- if make_dir and not isdir(path):
1166- mkdirs(path)
1167- read_length = 0
1168- if 'x-object-manifest' not in headers:
1169- md5sum = md5()
1170- for chunk in body:
1171- read_length += len(chunk)
1172- if md5sum:
1173- md5sum.update(chunk)
1174- else:
1175- dirpath = dirname(path)
1176- if make_dir and dirpath and not isdir(dirpath):
1177- mkdirs(dirpath)
1178- if out_file == "-":
1179- fp = stdout
1180- elif out_file:
1181- fp = open(out_file, 'wb')
1182- else:
1183- fp = open(path, 'wb')
1184- read_length = 0
1185- if 'x-object-manifest' not in headers:
1186- md5sum = md5()
1187- for chunk in body:
1188- fp.write(chunk)
1189- read_length += len(chunk)
1190- if md5sum:
1191- md5sum.update(chunk)
1192- fp.close()
1193- if md5sum and md5sum.hexdigest() != etag:
1194- error_queue.put('%s: md5sum != etag, %s != %s' %
1195- (path, md5sum.hexdigest(), etag))
1196- if content_length is not None and read_length != content_length:
1197- error_queue.put('%s: read_length != content_length, %d != %d' %
1198- (path, read_length, content_length))
1199- if 'x-object-meta-mtime' in headers and not options.out_file:
1200- mtime = float(headers['x-object-meta-mtime'])
1201- utime(path, (mtime, mtime))
1202- if options.verbose:
1203- if conn.attempts > 1:
1204- print_queue.put('%s [after %d attempts' %
1205- (path, conn.attempts))
1206- else:
1207- print_queue.put(path)
1208- except ClientException, err:
1209- if err.http_status != 404:
1210- raise
1211- error_queue.put('Object %s not found' %
1212- repr('%s/%s' % (container, obj)))
1213-
1214- container_queue = Queue(10000)
1215-
1216- def _download_container(container, conn):
1217- try:
1218- marker = ''
1219- while True:
1220- objects = [o['name'] for o in
1221- conn.get_container(container, marker=marker)[1]]
1222- if not objects:
1223- break
1224- for obj in objects:
1225- object_queue.put((container, obj))
1226- marker = objects[-1]
1227- except ClientException, err:
1228- if err.http_status != 404:
1229- raise
1230- error_queue.put('Container %s not found' % repr(container))
1231-
1232- url, token = get_auth(options.auth, options.user, options.key,
1233- snet=options.snet)
1234- create_connection = lambda: Connection(options.auth, options.user,
1235- options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
1236- object_threads = [QueueFunctionThread(object_queue, _download_object,
1237- create_connection()) for _junk in xrange(10)]
1238- for thread in object_threads:
1239- thread.start()
1240- container_threads = [QueueFunctionThread(container_queue,
1241- _download_container, create_connection()) for _junk in xrange(10)]
1242- for thread in container_threads:
1243- thread.start()
1244- if not args:
1245- conn = create_connection()
1246- try:
1247- marker = ''
1248- while True:
1249- containers = [c['name']
1250- for c in conn.get_account(marker=marker)[1]]
1251- if not containers:
1252- break
1253- for container in containers:
1254- container_queue.put(container)
1255- marker = containers[-1]
1256- except ClientException, err:
1257- if err.http_status != 404:
1258- raise
1259- error_queue.put('Account not found')
1260- elif len(args) == 1:
1261- if '/' in args[0]:
1262- print >> stderr, 'WARNING: / in container name; you might have ' \
1263- 'meant %r instead of %r.' % \
1264- (args[0].replace('/', ' ', 1), args[0])
1265- _download_container(args[0], create_connection())
1266- else:
1267- if len(args) == 2:
1268- obj = args[1]
1269- object_queue.put((args[0], obj, options.out_file))
1270- else:
1271- for obj in args[1:]:
1272- object_queue.put((args[0], obj))
1273- while not container_queue.empty():
1274- sleep(0.01)
1275- for thread in container_threads:
1276- thread.abort = True
1277- while thread.isAlive():
1278- thread.join(0.01)
1279- put_errors_from_threads(container_threads, error_queue)
1280- while not object_queue.empty():
1281- sleep(0.01)
1282- for thread in object_threads:
1283- thread.abort = True
1284- while thread.isAlive():
1285- thread.join(0.01)
1286- put_errors_from_threads(object_threads, error_queue)
1287-
1288-
1289-st_list_help = '''
1290-list [options] [container]
1291- Lists the containers for the account or the objects for a container. -p or
1292- --prefix is an option that will only list items beginning with that prefix.
1293- -d or --delimiter is option (for container listings only) that will roll up
1294- items with the given delimiter (see Cloud Files general documentation for
1295- what this means).
1296-'''.strip('\n')
1297-
1298-
1299-def st_list(options, args, print_queue, error_queue):
1300- parser.add_option('-p', '--prefix', dest='prefix', help='Will only list '
1301- 'items beginning with the prefix')
1302- parser.add_option('-d', '--delimiter', dest='delimiter', help='Will roll '
1303- 'up items with the given delimiter (see Cloud Files general '
1304- 'documentation for what this means)')
1305- (options, args) = parse_args(parser, args)
1306- args = args[1:]
1307- if options.delimiter and not args:
1308- exit('-d option only allowed for container listings')
1309- if len(args) > 1:
1310- error_queue.put('Usage: %s [options] %s' %
1311- (basename(argv[0]), st_list_help))
1312- return
1313- conn = Connection(options.auth, options.user, options.key,
1314- snet=options.snet)
1315- try:
1316- marker = ''
1317- while True:
1318- if not args:
1319- items = \
1320- conn.get_account(marker=marker, prefix=options.prefix)[1]
1321- else:
1322- items = conn.get_container(args[0], marker=marker,
1323- prefix=options.prefix, delimiter=options.delimiter)[1]
1324- if not items:
1325- break
1326- for item in items:
1327- print_queue.put(item.get('name', item.get('subdir')))
1328- marker = items[-1].get('name', items[-1].get('subdir'))
1329- except ClientException, err:
1330- if err.http_status != 404:
1331- raise
1332- if not args:
1333- error_queue.put('Account not found')
1334- else:
1335- error_queue.put('Container %s not found' % repr(args[0]))
1336-
1337-
1338-st_stat_help = '''
1339-stat [container] [object]
1340- Displays information for the account, container, or object depending on the
1341- args given (if any).'''.strip('\n')
1342-
1343-
1344-def st_stat(options, args, print_queue, error_queue):
1345- (options, args) = parse_args(parser, args)
1346- args = args[1:]
1347- conn = Connection(options.auth, options.user, options.key)
1348- if not args:
1349- try:
1350- headers = conn.head_account()
1351- if options.verbose > 1:
1352- print_queue.put('''
1353-StorageURL: %s
1354-Auth Token: %s
1355-'''.strip('\n') % (conn.url, conn.token))
1356- container_count = int(headers.get('x-account-container-count', 0))
1357- object_count = int(headers.get('x-account-object-count', 0))
1358- bytes_used = int(headers.get('x-account-bytes-used', 0))
1359- print_queue.put('''
1360- Account: %s
1361-Containers: %d
1362- Objects: %d
1363- Bytes: %d'''.strip('\n') % (conn.url.rsplit('/', 1)[-1], container_count,
1364- object_count, bytes_used))
1365- for key, value in headers.items():
1366- if key.startswith('x-account-meta-'):
1367- print_queue.put('%10s: %s' % ('Meta %s' %
1368- key[len('x-account-meta-'):].title(), value))
1369- for key, value in headers.items():
1370- if not key.startswith('x-account-meta-') and key not in (
1371- 'content-length', 'date', 'x-account-container-count',
1372- 'x-account-object-count', 'x-account-bytes-used'):
1373- print_queue.put(
1374- '%10s: %s' % (key.title(), value))
1375- except ClientException, err:
1376- if err.http_status != 404:
1377- raise
1378- error_queue.put('Account not found')
1379- elif len(args) == 1:
1380- if '/' in args[0]:
1381- print >> stderr, 'WARNING: / in container name; you might have ' \
1382- 'meant %r instead of %r.' % \
1383- (args[0].replace('/', ' ', 1), args[0])
1384- try:
1385- headers = conn.head_container(args[0])
1386- object_count = int(headers.get('x-container-object-count', 0))
1387- bytes_used = int(headers.get('x-container-bytes-used', 0))
1388- print_queue.put('''
1389- Account: %s
1390-Container: %s
1391- Objects: %d
1392- Bytes: %d
1393- Read ACL: %s
1394-Write ACL: %s'''.strip('\n') % (conn.url.rsplit('/', 1)[-1], args[0],
1395- object_count, bytes_used,
1396- headers.get('x-container-read', ''),
1397- headers.get('x-container-write', '')))
1398- for key, value in headers.items():
1399- if key.startswith('x-container-meta-'):
1400- print_queue.put('%9s: %s' % ('Meta %s' %
1401- key[len('x-container-meta-'):].title(), value))
1402- for key, value in headers.items():
1403- if not key.startswith('x-container-meta-') and key not in (
1404- 'content-length', 'date', 'x-container-object-count',
1405- 'x-container-bytes-used', 'x-container-read',
1406- 'x-container-write'):
1407- print_queue.put(
1408- '%9s: %s' % (key.title(), value))
1409- except ClientException, err:
1410- if err.http_status != 404:
1411- raise
1412- error_queue.put('Container %s not found' % repr(args[0]))
1413- elif len(args) == 2:
1414- try:
1415- headers = conn.head_object(args[0], args[1])
1416- print_queue.put('''
1417- Account: %s
1418- Container: %s
1419- Object: %s
1420- Content Type: %s'''.strip('\n') % (conn.url.rsplit('/', 1)[-1], args[0],
1421- args[1], headers.get('content-type')))
1422- if 'content-length' in headers:
1423- print_queue.put('Content Length: %s' %
1424- headers['content-length'])
1425- if 'last-modified' in headers:
1426- print_queue.put(' Last Modified: %s' %
1427- headers['last-modified'])
1428- if 'etag' in headers:
1429- print_queue.put(' ETag: %s' % headers['etag'])
1430- if 'x-object-manifest' in headers:
1431- print_queue.put(' Manifest: %s' %
1432- headers['x-object-manifest'])
1433- for key, value in headers.items():
1434- if key.startswith('x-object-meta-'):
1435- print_queue.put('%14s: %s' % ('Meta %s' %
1436- key[len('x-object-meta-'):].title(), value))
1437- for key, value in headers.items():
1438- if not key.startswith('x-object-meta-') and key not in (
1439- 'content-type', 'content-length', 'last-modified',
1440- 'etag', 'date', 'x-object-manifest'):
1441- print_queue.put(
1442- '%14s: %s' % (key.title(), value))
1443- except ClientException, err:
1444- if err.http_status != 404:
1445- raise
1446- error_queue.put('Object %s not found' %
1447- repr('%s/%s' % (args[0], args[1])))
1448- else:
1449- error_queue.put('Usage: %s [options] %s' %
1450- (basename(argv[0]), st_stat_help))
1451-
1452-
1453-st_post_help = '''
1454-post [options] [container] [object]
1455- Updates meta information for the account, container, or object depending on
1456- the args given. If the container is not found, it will be created
1457- automatically; but this is not true for accounts and objects. Containers
1458- also allow the -r (or --read-acl) and -w (or --write-acl) options. The -m
1459- or --meta option is allowed on all and used to define the user meta data
1460- items to set in the form Name:Value. This option can be repeated. Example:
1461- post -m Color:Blue -m Size:Large'''.strip('\n')
1462-
1463-
1464-def st_post(options, args, print_queue, error_queue):
1465- parser.add_option('-r', '--read-acl', dest='read_acl', help='Sets the '
1466- 'Read ACL for containers. Quick summary of ACL syntax: .r:*, '
1467- '.r:-.example.com, .r:www.example.com, account1, account2:user2')
1468- parser.add_option('-w', '--write-acl', dest='write_acl', help='Sets the '
1469- 'Write ACL for containers. Quick summary of ACL syntax: account1, '
1470- 'account2:user2')
1471- parser.add_option('-m', '--meta', action='append', dest='meta', default=[],
1472- help='Sets a meta data item with the syntax name:value. This option '
1473- 'may be repeated. Example: -m Color:Blue -m Size:Large')
1474- (options, args) = parse_args(parser, args)
1475- args = args[1:]
1476- if (options.read_acl or options.write_acl) and not args:
1477- exit('-r and -w options only allowed for containers')
1478- conn = Connection(options.auth, options.user, options.key)
1479- if not args:
1480- headers = {}
1481- for item in options.meta:
1482- split_item = item.split(':')
1483- headers['X-Account-Meta-' + split_item[0]] = \
1484- len(split_item) > 1 and split_item[1]
1485- try:
1486- conn.post_account(headers=headers)
1487- except ClientException, err:
1488- if err.http_status != 404:
1489- raise
1490- error_queue.put('Account not found')
1491- elif len(args) == 1:
1492- if '/' in args[0]:
1493- print >> stderr, 'WARNING: / in container name; you might have ' \
1494- 'meant %r instead of %r.' % \
1495- (args[0].replace('/', ' ', 1), args[0])
1496- headers = {}
1497- for item in options.meta:
1498- split_item = item.split(':')
1499- headers['X-Container-Meta-' + split_item[0]] = \
1500- len(split_item) > 1 and split_item[1]
1501- if options.read_acl is not None:
1502- headers['X-Container-Read'] = options.read_acl
1503- if options.write_acl is not None:
1504- headers['X-Container-Write'] = options.write_acl
1505- try:
1506- conn.post_container(args[0], headers=headers)
1507- except ClientException, err:
1508- if err.http_status != 404:
1509- raise
1510- conn.put_container(args[0], headers=headers)
1511- elif len(args) == 2:
1512- headers = {}
1513- for item in options.meta:
1514- split_item = item.split(':')
1515- headers['X-Object-Meta-' + split_item[0]] = \
1516- len(split_item) > 1 and split_item[1]
1517- try:
1518- conn.post_object(args[0], args[1], headers=headers)
1519- except ClientException, err:
1520- if err.http_status != 404:
1521- raise
1522- error_queue.put('Object %s not found' %
1523- repr('%s/%s' % (args[0], args[1])))
1524- else:
1525- error_queue.put('Usage: %s [options] %s' %
1526- (basename(argv[0]), st_post_help))
1527-
1528-
1529-st_upload_help = '''
1530-upload [options] container file_or_directory [file_or_directory] [...]
1531- Uploads to the given container the files and directories specified by the
1532- remaining args. -c or --changed is an option that will only upload files
1533- that have changed since the last upload. -S <size> or --segment-size <size>
1534- and --leave-segments are options as well (see --help for more).
1535-'''.strip('\n')
1536-
1537-
1538-def st_upload(options, args, print_queue, error_queue):
1539- parser.add_option('-c', '--changed', action='store_true', dest='changed',
1540- default=False, help='Will only upload files that have changed since '
1541- 'the last upload')
1542- parser.add_option('-S', '--segment-size', dest='segment_size', help='Will '
1543- 'upload files in segments no larger than <size> and then create a '
1544- '"manifest" file that will download all the segments as if it were '
1545- 'the original file. The segments will be uploaded to a '
1546- '<container>_segments container so as to not pollute the main '
1547- '<container> listings.')
1548- parser.add_option('', '--leave-segments', action='store_true',
1549- dest='leave_segments', default=False, help='Indicates that you want '
1550- 'the older segments of manifest objects left alone (in the case of '
1551- 'overwrites)')
1552- (options, args) = parse_args(parser, args)
1553- args = args[1:]
1554- if len(args) < 2:
1555- error_queue.put('Usage: %s [options] %s' %
1556- (basename(argv[0]), st_upload_help))
1557- return
1558- object_queue = Queue(10000)
1559-
1560- def _segment_job(job, conn):
1561- if job.get('delete', False):
1562- conn.delete_object(job['container'], job['obj'])
1563- else:
1564- fp = open(job['path'], 'rb')
1565- fp.seek(job['segment_start'])
1566- conn.put_object(job.get('container', args[0] + '_segments'),
1567- job['obj'], fp, content_length=job['segment_size'])
1568- if options.verbose and 'log_line' in job:
1569- if conn.attempts > 1:
1570- print_queue.put('%s [after %d attempts]' %
1571- (job['log_line'], conn.attempts))
1572- else:
1573- print_queue.put(job['log_line'])
1574-
1575- def _object_job(job, conn):
1576- path = job['path']
1577- container = job.get('container', args[0])
1578- dir_marker = job.get('dir_marker', False)
1579- try:
1580- obj = path
1581- if obj.startswith('./') or obj.startswith('.\\'):
1582- obj = obj[2:]
1583- put_headers = {'x-object-meta-mtime': str(getmtime(path))}
1584- if dir_marker:
1585- if options.changed:
1586- try:
1587- headers = conn.head_object(container, obj)
1588- ct = headers.get('content-type')
1589- cl = int(headers.get('content-length'))
1590- et = headers.get('etag')
1591- mt = headers.get('x-object-meta-mtime')
1592- if ct.split(';', 1)[0] == 'text/directory' and \
1593- cl == 0 and \
1594- et == 'd41d8cd98f00b204e9800998ecf8427e' and \
1595- mt == put_headers['x-object-meta-mtime']:
1596- return
1597- except ClientException, err:
1598- if err.http_status != 404:
1599- raise
1600- conn.put_object(container, obj, '', content_length=0,
1601- content_type='text/directory',
1602- headers=put_headers)
1603- else:
1604- # We need to HEAD all objects now in case we're overwriting a
1605- # manifest object and need to delete the old segments
1606- # ourselves.
1607- old_manifest = None
1608- if options.changed or not options.leave_segments:
1609- try:
1610- headers = conn.head_object(container, obj)
1611- cl = int(headers.get('content-length'))
1612- mt = headers.get('x-object-meta-mtime')
1613- if options.changed and cl == getsize(path) and \
1614- mt == put_headers['x-object-meta-mtime']:
1615- return
1616- if not options.leave_segments:
1617- old_manifest = headers.get('x-object-manifest')
1618- except ClientException, err:
1619- if err.http_status != 404:
1620- raise
1621- if options.segment_size and \
1622- getsize(path) < options.segment_size:
1623- full_size = getsize(path)
1624- segment_queue = Queue(10000)
1625- segment_threads = [QueueFunctionThread(segment_queue,
1626- _segment_job, create_connection()) for _junk in
1627- xrange(10)]
1628- for thread in segment_threads:
1629- thread.start()
1630- segment = 0
1631- segment_start = 0
1632- while segment_start < full_size:
1633- segment_size = int(options.segment_size)
1634- if segment_start + segment_size > full_size:
1635- segment_size = full_size - segment_start
1636- segment_queue.put({'path': path,
1637- 'obj': '%s/%s/%s/%08d' % (obj,
1638- put_headers['x-object-meta-mtime'], full_size,
1639- segment),
1640- 'segment_start': segment_start,
1641- 'segment_size': segment_size,
1642- 'log_line': '%s segment %s' % (obj, segment)})
1643- segment += 1
1644- segment_start += segment_size
1645- while not segment_queue.empty():
1646- sleep(0.01)
1647- for thread in segment_threads:
1648- thread.abort = True
1649- while thread.isAlive():
1650- thread.join(0.01)
1651- if put_errors_from_threads(segment_threads, error_queue):
1652- raise ClientException('Aborting manifest creation '
1653- 'because not all segments could be uploaded. %s/%s'
1654- % (container, obj))
1655- new_object_manifest = '%s_segments/%s/%s/%s/' % (
1656- container, obj, put_headers['x-object-meta-mtime'],
1657- full_size)
1658- if old_manifest == new_object_manifest:
1659- old_manifest = None
1660- put_headers['x-object-manifest'] = new_object_manifest
1661- conn.put_object(container, obj, '', content_length=0,
1662- headers=put_headers)
1663- else:
1664- conn.put_object(container, obj, open(path, 'rb'),
1665- content_length=getsize(path), headers=put_headers)
1666- if old_manifest:
1667- segment_queue = Queue(10000)
1668- scontainer, sprefix = old_manifest.split('/', 1)
1669- for delobj in conn.get_container(scontainer,
1670- prefix=sprefix)[1]:
1671- segment_queue.put({'delete': True,
1672- 'container': scontainer, 'obj': delobj['name']})
1673- if not segment_queue.empty():
1674- segment_threads = [QueueFunctionThread(segment_queue,
1675- _segment_job, create_connection()) for _junk in
1676- xrange(10)]
1677- for thread in segment_threads:
1678- thread.start()
1679- while not segment_queue.empty():
1680- sleep(0.01)
1681- for thread in segment_threads:
1682- thread.abort = True
1683- while thread.isAlive():
1684- thread.join(0.01)
1685- put_errors_from_threads(segment_threads, error_queue)
1686- if options.verbose:
1687- if conn.attempts > 1:
1688- print_queue.put(
1689- '%s [after %d attempts]' % (obj, conn.attempts))
1690- else:
1691- print_queue.put(obj)
1692- except OSError, err:
1693- if err.errno != ENOENT:
1694- raise
1695- error_queue.put('Local file %s not found' % repr(path))
1696-
1697- def _upload_dir(path):
1698- names = listdir(path)
1699- if not names:
1700- object_queue.put({'path': path, 'dir_marker': True})
1701- else:
1702- for name in listdir(path):
1703- subpath = join(path, name)
1704- if isdir(subpath):
1705- _upload_dir(subpath)
1706- else:
1707- object_queue.put({'path': subpath})
1708-
1709- url, token = get_auth(options.auth, options.user, options.key,
1710- snet=options.snet)
1711- create_connection = lambda: Connection(options.auth, options.user,
1712- options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
1713- object_threads = [QueueFunctionThread(object_queue, _object_job,
1714- create_connection()) for _junk in xrange(10)]
1715- for thread in object_threads:
1716- thread.start()
1717- conn = create_connection()
1718- # Try to create the container, just in case it doesn't exist. If this
1719- # fails, it might just be because the user doesn't have container PUT
1720- # permissions, so we'll ignore any error. If there's really a problem,
1721- # it'll surface on the first object PUT.
1722- try:
1723- conn.put_container(args[0])
1724- if options.segment_size is not None:
1725- conn.put_container(args[0] + '_segments')
1726- except Exception:
1727- pass
1728- try:
1729- for arg in args[1:]:
1730- if isdir(arg):
1731- _upload_dir(arg)
1732- else:
1733- object_queue.put({'path': arg})
1734- while not object_queue.empty():
1735- sleep(0.01)
1736- for thread in object_threads:
1737- thread.abort = True
1738- while thread.isAlive():
1739- thread.join(0.01)
1740- put_errors_from_threads(object_threads, error_queue)
1741- except ClientException, err:
1742- if err.http_status != 404:
1743- raise
1744- error_queue.put('Account not found')
1745-
1746-
1747-def parse_args(parser, args, enforce_requires=True):
1748- if not args:
1749- args = ['-h']
1750- (options, args) = parser.parse_args(args)
1751- if enforce_requires and \
1752- not (options.auth and options.user and options.key):
1753- exit('''
1754-Requires ST_AUTH, ST_USER, and ST_KEY environment variables be set or
1755-overridden with -A, -U, or -K.'''.strip('\n'))
1756- return options, args
1757-
1758-
1759-if __name__ == '__main__':
1760- parser = OptionParser(version='%prog 1.0', usage='''
1761-Usage: %%prog <command> [options] [args]
1762-
1763-Commands:
1764- %(st_stat_help)s
1765- %(st_list_help)s
1766- %(st_upload_help)s
1767- %(st_post_help)s
1768- %(st_download_help)s
1769- %(st_delete_help)s
1770-
1771-Example:
1772- %%prog -A https://auth.api.rackspacecloud.com/v1.0 -U user -K key stat
1773-'''.strip('\n') % globals())
1774- parser.add_option('-s', '--snet', action='store_true', dest='snet',
1775- default=False, help='Use SERVICENET internal network')
1776- parser.add_option('-v', '--verbose', action='count', dest='verbose',
1777- default=1, help='Print more info')
1778- parser.add_option('-q', '--quiet', action='store_const', dest='verbose',
1779- const=0, default=1, help='Suppress status output')
1780- parser.add_option('-A', '--auth', dest='auth',
1781- default=environ.get('ST_AUTH'),
1782- help='URL for obtaining an auth token')
1783- parser.add_option('-U', '--user', dest='user',
1784- default=environ.get('ST_USER'),
1785- help='User name for obtaining an auth token')
1786- parser.add_option('-K', '--key', dest='key',
1787- default=environ.get('ST_KEY'),
1788- help='Key for obtaining an auth token')
1789- parser.disable_interspersed_args()
1790- (options, args) = parse_args(parser, argv[1:], enforce_requires=False)
1791- parser.enable_interspersed_args()
1792-
1793- commands = ('delete', 'download', 'list', 'post', 'stat', 'upload')
1794- if not args or args[0] not in commands:
1795- parser.print_usage()
1796- if args:
1797- exit('no such command: %s' % args[0])
1798- exit()
1799-
1800- print_queue = Queue(10000)
1801-
1802- def _print(item):
1803- if isinstance(item, unicode):
1804- item = item.encode('utf8')
1805- print item
1806-
1807- print_thread = QueueFunctionThread(print_queue, _print)
1808- print_thread.start()
1809-
1810- error_queue = Queue(10000)
1811-
1812- def _error(item):
1813- if isinstance(item, unicode):
1814- item = item.encode('utf8')
1815- print >> stderr, item
1816-
1817- error_thread = QueueFunctionThread(error_queue, _error)
1818- error_thread.start()
1819-
1820- try:
1821- parser.usage = globals()['st_%s_help' % args[0]]
1822- try:
1823- globals()['st_%s' % args[0]](parser, argv[1:], print_queue,
1824- error_queue)
1825- except (ClientException, HTTPException, socket.error), err:
1826- error_queue.put(str(err))
1827- while not print_queue.empty():
1828- sleep(0.01)
1829- print_thread.abort = True
1830- while print_thread.isAlive():
1831- print_thread.join(0.01)
1832- while not error_queue.empty():
1833- sleep(0.01)
1834- error_thread.abort = True
1835- while error_thread.isAlive():
1836- error_thread.join(0.01)
1837- except (SystemExit, Exception):
1838- for thread in threading_enumerate():
1839- thread.abort = True
1840- raise
1841
1842=== removed file 'bin/swauth-add-account'
1843--- bin/swauth-add-account 2011-04-18 16:08:48 +0000
1844+++ bin/swauth-add-account 1970-01-01 00:00:00 +0000
1845@@ -1,68 +0,0 @@
1846-#!/usr/bin/env python
1847-# Copyright (c) 2010 OpenStack, LLC.
1848-#
1849-# Licensed under the Apache License, Version 2.0 (the "License");
1850-# you may not use this file except in compliance with the License.
1851-# You may obtain a copy of the License at
1852-#
1853-# http://www.apache.org/licenses/LICENSE-2.0
1854-#
1855-# Unless required by applicable law or agreed to in writing, software
1856-# distributed under the License is distributed on an "AS IS" BASIS,
1857-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
1858-# implied.
1859-# See the License for the specific language governing permissions and
1860-# limitations under the License.
1861-
1862-import gettext
1863-from optparse import OptionParser
1864-from os.path import basename
1865-from sys import argv, exit
1866-
1867-from swift.common.bufferedhttp import http_connect_raw as http_connect
1868-from swift.common.utils import urlparse
1869-
1870-
1871-if __name__ == '__main__':
1872- gettext.install('swift', unicode=1)
1873- parser = OptionParser(usage='Usage: %prog [options] <account>')
1874- parser.add_option('-s', '--suffix', dest='suffix',
1875- default='', help='The suffix to use with the reseller prefix as the '
1876- 'storage account name (default: <randomly-generated-uuid4>) Note: If '
1877- 'the account already exists, this will have no effect on existing '
1878- 'service URLs. Those will need to be updated with '
1879- 'swauth-set-account-service')
1880- parser.add_option('-A', '--admin-url', dest='admin_url',
1881- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
1882- 'subsystem (default: http://127.0.0.1:8080/auth/)')
1883- parser.add_option('-U', '--admin-user', dest='admin_user',
1884- default='.super_admin', help='The user with admin rights to add users '
1885- '(default: .super_admin).')
1886- parser.add_option('-K', '--admin-key', dest='admin_key',
1887- help='The key for the user with admin rights to add users.')
1888- args = argv[1:]
1889- if not args:
1890- args.append('-h')
1891- (options, args) = parser.parse_args(args)
1892- if len(args) != 1:
1893- parser.parse_args(['-h'])
1894- account = args[0]
1895- parsed = urlparse(options.admin_url)
1896- if parsed.scheme not in ('http', 'https'):
1897- raise Exception('Cannot handle protocol scheme %s for url %s' %
1898- (parsed.scheme, repr(options.admin_url)))
1899- parsed_path = parsed.path
1900- if not parsed_path:
1901- parsed_path = '/'
1902- elif parsed_path[-1] != '/':
1903- parsed_path += '/'
1904- path = '%sv2/%s' % (parsed_path, account)
1905- headers = {'X-Auth-Admin-User': options.admin_user,
1906- 'X-Auth-Admin-Key': options.admin_key}
1907- if options.suffix:
1908- headers['X-Account-Suffix'] = options.suffix
1909- conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
1910- ssl=(parsed.scheme == 'https'))
1911- resp = conn.getresponse()
1912- if resp.status // 100 != 2:
1913- exit('Account creation failed: %s %s' % (resp.status, resp.reason))
1914
1915=== removed file 'bin/swauth-add-user'
1916--- bin/swauth-add-user 2011-04-18 16:08:48 +0000
1917+++ bin/swauth-add-user 1970-01-01 00:00:00 +0000
1918@@ -1,93 +0,0 @@
1919-#!/usr/bin/env python
1920-# Copyright (c) 2010 OpenStack, LLC.
1921-#
1922-# Licensed under the Apache License, Version 2.0 (the "License");
1923-# you may not use this file except in compliance with the License.
1924-# You may obtain a copy of the License at
1925-#
1926-# http://www.apache.org/licenses/LICENSE-2.0
1927-#
1928-# Unless required by applicable law or agreed to in writing, software
1929-# distributed under the License is distributed on an "AS IS" BASIS,
1930-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
1931-# implied.
1932-# See the License for the specific language governing permissions and
1933-# limitations under the License.
1934-
1935-import gettext
1936-from optparse import OptionParser
1937-from os.path import basename
1938-from sys import argv, exit
1939-
1940-from swift.common.bufferedhttp import http_connect_raw as http_connect
1941-from swift.common.utils import urlparse
1942-
1943-
1944-if __name__ == '__main__':
1945- gettext.install('swift', unicode=1)
1946- parser = OptionParser(
1947- usage='Usage: %prog [options] <account> <user> <password>')
1948- parser.add_option('-a', '--admin', dest='admin', action='store_true',
1949- default=False, help='Give the user administrator access; otherwise '
1950- 'the user will only have access to containers specifically allowed '
1951- 'with ACLs.')
1952- parser.add_option('-r', '--reseller-admin', dest='reseller_admin',
1953- action='store_true', default=False, help='Give the user full reseller '
1954- 'administrator access, giving them full access to all accounts within '
1955- 'the reseller, including the ability to create new accounts. Creating '
1956- 'a new reseller admin requires super_admin rights.')
1957- parser.add_option('-s', '--suffix', dest='suffix',
1958- default='', help='The suffix to use with the reseller prefix as the '
1959- 'storage account name (default: <randomly-generated-uuid4>) Note: If '
1960- 'the account already exists, this will have no effect on existing '
1961- 'service URLs. Those will need to be updated with '
1962- 'swauth-set-account-service')
1963- parser.add_option('-A', '--admin-url', dest='admin_url',
1964- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
1965- 'subsystem (default: http://127.0.0.1:8080/auth/')
1966- parser.add_option('-U', '--admin-user', dest='admin_user',
1967- default='.super_admin', help='The user with admin rights to add users '
1968- '(default: .super_admin).')
1969- parser.add_option('-K', '--admin-key', dest='admin_key',
1970- help='The key for the user with admin rights to add users.')
1971- args = argv[1:]
1972- if not args:
1973- args.append('-h')
1974- (options, args) = parser.parse_args(args)
1975- if len(args) != 3:
1976- parser.parse_args(['-h'])
1977- account, user, password = args
1978- parsed = urlparse(options.admin_url)
1979- if parsed.scheme not in ('http', 'https'):
1980- raise Exception('Cannot handle protocol scheme %s for url %s' %
1981- (parsed.scheme, repr(options.admin_url)))
1982- parsed_path = parsed.path
1983- if not parsed_path:
1984- parsed_path = '/'
1985- elif parsed_path[-1] != '/':
1986- parsed_path += '/'
1987- # Ensure the account exists
1988- path = '%sv2/%s' % (parsed_path, account)
1989- headers = {'X-Auth-Admin-User': options.admin_user,
1990- 'X-Auth-Admin-Key': options.admin_key}
1991- if options.suffix:
1992- headers['X-Account-Suffix'] = options.suffix
1993- conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
1994- ssl=(parsed.scheme == 'https'))
1995- resp = conn.getresponse()
1996- if resp.status // 100 != 2:
1997- print 'Account creation failed: %s %s' % (resp.status, resp.reason)
1998- # Add the user
1999- path = '%sv2/%s/%s' % (parsed_path, account, user)
2000- headers = {'X-Auth-Admin-User': options.admin_user,
2001- 'X-Auth-Admin-Key': options.admin_key,
2002- 'X-Auth-User-Key': password}
2003- if options.admin:
2004- headers['X-Auth-User-Admin'] = 'true'
2005- if options.reseller_admin:
2006- headers['X-Auth-User-Reseller-Admin'] = 'true'
2007- conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
2008- ssl=(parsed.scheme == 'https'))
2009- resp = conn.getresponse()
2010- if resp.status // 100 != 2:
2011- exit('User creation failed: %s %s' % (resp.status, resp.reason))
2012
2013=== removed file 'bin/swauth-cleanup-tokens'
2014--- bin/swauth-cleanup-tokens 2011-04-18 16:08:48 +0000
2015+++ bin/swauth-cleanup-tokens 1970-01-01 00:00:00 +0000
2016@@ -1,118 +0,0 @@
2017-#!/usr/bin/env python
2018-# Copyright (c) 2010 OpenStack, LLC.
2019-#
2020-# Licensed under the Apache License, Version 2.0 (the "License");
2021-# you may not use this file except in compliance with the License.
2022-# You may obtain a copy of the License at
2023-#
2024-# http://www.apache.org/licenses/LICENSE-2.0
2025-#
2026-# Unless required by applicable law or agreed to in writing, software
2027-# distributed under the License is distributed on an "AS IS" BASIS,
2028-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2029-# implied.
2030-# See the License for the specific language governing permissions and
2031-# limitations under the License.
2032-
2033-try:
2034- import simplejson as json
2035-except ImportError:
2036- import json
2037-import gettext
2038-import re
2039-from datetime import datetime, timedelta
2040-from optparse import OptionParser
2041-from sys import argv, exit
2042-from time import sleep, time
2043-
2044-from swift.common.client import Connection, ClientException
2045-
2046-
2047-if __name__ == '__main__':
2048- gettext.install('swift', unicode=1)
2049- parser = OptionParser(usage='Usage: %prog [options]')
2050- parser.add_option('-t', '--token-life', dest='token_life',
2051- default='86400', help='The expected life of tokens; token objects '
2052- 'modified more than this number of seconds ago will be checked for '
2053- 'expiration (default: 86400).')
2054- parser.add_option('-s', '--sleep', dest='sleep',
2055- default='0.1', help='The number of seconds to sleep between token '
2056- 'checks (default: 0.1)')
2057- parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
2058- default=False, help='Outputs everything done instead of just the '
2059- 'deletions.')
2060- parser.add_option('-A', '--admin-url', dest='admin_url',
2061- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
2062- 'subsystem (default: http://127.0.0.1:8080/auth/)')
2063- parser.add_option('-K', '--admin-key', dest='admin_key',
2064- help='The key for .super_admin.')
2065- args = argv[1:]
2066- if not args:
2067- args.append('-h')
2068- (options, args) = parser.parse_args(args)
2069- if len(args) != 0:
2070- parser.parse_args(['-h'])
2071- options.admin_url = options.admin_url.rstrip('/')
2072- if not options.admin_url.endswith('/v1.0'):
2073- options.admin_url += '/v1.0'
2074- options.admin_user = '.super_admin:.super_admin'
2075- options.token_life = timedelta(0, float(options.token_life))
2076- options.sleep = float(options.sleep)
2077- conn = Connection(options.admin_url, options.admin_user, options.admin_key)
2078- for x in xrange(16):
2079- container = '.token_%x' % x
2080- marker = None
2081- while True:
2082- if options.verbose:
2083- print 'GET %s?marker=%s' % (container, marker)
2084- try:
2085- objs = conn.get_container(container, marker=marker)[1]
2086- except ClientException, e:
2087- if e.http_status == 404:
2088- exit('Container %s not found. swauth-prep needs to be '
2089- 'rerun' % (container))
2090- else:
2091- exit('Object listing on container %s failed with status '
2092- 'code %d' % (container, e.http_status))
2093- if objs:
2094- marker = objs[-1]['name']
2095- else:
2096- if options.verbose:
2097- print 'No more objects in %s' % container
2098- break
2099- for obj in objs:
2100- last_modified = datetime(*map(int, re.split('[^\d]',
2101- obj['last_modified'])[:-1]))
2102- ago = datetime.utcnow() - last_modified
2103- if ago > options.token_life:
2104- if options.verbose:
2105- print '%s/%s last modified %ss ago; investigating' % \
2106- (container, obj['name'],
2107- ago.days * 86400 + ago.seconds)
2108- print 'GET %s/%s' % (container, obj['name'])
2109- detail = conn.get_object(container, obj['name'])[1]
2110- detail = json.loads(detail)
2111- if detail['expires'] < time():
2112- if options.verbose:
2113- print '%s/%s expired %ds ago; deleting' % \
2114- (container, obj['name'],
2115- time() - detail['expires'])
2116- print 'DELETE %s/%s' % (container, obj['name'])
2117- try:
2118- conn.delete_object(container, obj['name'])
2119- except ClientException, e:
2120- if e.http_status != 404:
2121- print 'DELETE of %s/%s failed with status ' \
2122- 'code %d' % (container, obj['name'],
2123- e.http_status)
2124- elif options.verbose:
2125- print "%s/%s won't expire for %ds; skipping" % \
2126- (container, obj['name'],
2127- detail['expires'] - time())
2128- elif options.verbose:
2129- print '%s/%s last modified %ss ago; skipping' % \
2130- (container, obj['name'],
2131- ago.days * 86400 + ago.seconds)
2132- sleep(options.sleep)
2133- if options.verbose:
2134- print 'Done.'
2135
2136=== removed file 'bin/swauth-delete-account'
2137--- bin/swauth-delete-account 2011-04-18 16:08:48 +0000
2138+++ bin/swauth-delete-account 1970-01-01 00:00:00 +0000
2139@@ -1,60 +0,0 @@
2140-#!/usr/bin/env python
2141-# Copyright (c) 2010 OpenStack, LLC.
2142-#
2143-# Licensed under the Apache License, Version 2.0 (the "License");
2144-# you may not use this file except in compliance with the License.
2145-# You may obtain a copy of the License at
2146-#
2147-# http://www.apache.org/licenses/LICENSE-2.0
2148-#
2149-# Unless required by applicable law or agreed to in writing, software
2150-# distributed under the License is distributed on an "AS IS" BASIS,
2151-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2152-# implied.
2153-# See the License for the specific language governing permissions and
2154-# limitations under the License.
2155-
2156-import gettext
2157-from optparse import OptionParser
2158-from os.path import basename
2159-from sys import argv, exit
2160-
2161-from swift.common.bufferedhttp import http_connect_raw as http_connect
2162-from swift.common.utils import urlparse
2163-
2164-
2165-if __name__ == '__main__':
2166- gettext.install('swift', unicode=1)
2167- parser = OptionParser(usage='Usage: %prog [options] <account>')
2168- parser.add_option('-A', '--admin-url', dest='admin_url',
2169- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
2170- 'subsystem (default: http://127.0.0.1:8080/auth/')
2171- parser.add_option('-U', '--admin-user', dest='admin_user',
2172- default='.super_admin', help='The user with admin rights to add users '
2173- '(default: .super_admin).')
2174- parser.add_option('-K', '--admin-key', dest='admin_key',
2175- help='The key for the user with admin rights to add users.')
2176- args = argv[1:]
2177- if not args:
2178- args.append('-h')
2179- (options, args) = parser.parse_args(args)
2180- if len(args) != 1:
2181- parser.parse_args(['-h'])
2182- account = args[0]
2183- parsed = urlparse(options.admin_url)
2184- if parsed.scheme not in ('http', 'https'):
2185- raise Exception('Cannot handle protocol scheme %s for url %s' %
2186- (parsed.scheme, repr(options.admin_url)))
2187- parsed_path = parsed.path
2188- if not parsed_path:
2189- parsed_path = '/'
2190- elif parsed_path[-1] != '/':
2191- parsed_path += '/'
2192- path = '%sv2/%s' % (parsed_path, account)
2193- headers = {'X-Auth-Admin-User': options.admin_user,
2194- 'X-Auth-Admin-Key': options.admin_key}
2195- conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
2196- ssl=(parsed.scheme == 'https'))
2197- resp = conn.getresponse()
2198- if resp.status // 100 != 2:
2199- exit('Account deletion failed: %s %s' % (resp.status, resp.reason))
2200
2201=== removed file 'bin/swauth-delete-user'
2202--- bin/swauth-delete-user 2011-04-18 16:08:48 +0000
2203+++ bin/swauth-delete-user 1970-01-01 00:00:00 +0000
2204@@ -1,60 +0,0 @@
2205-#!/usr/bin/env python
2206-# Copyright (c) 2010 OpenStack, LLC.
2207-#
2208-# Licensed under the Apache License, Version 2.0 (the "License");
2209-# you may not use this file except in compliance with the License.
2210-# You may obtain a copy of the License at
2211-#
2212-# http://www.apache.org/licenses/LICENSE-2.0
2213-#
2214-# Unless required by applicable law or agreed to in writing, software
2215-# distributed under the License is distributed on an "AS IS" BASIS,
2216-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2217-# implied.
2218-# See the License for the specific language governing permissions and
2219-# limitations under the License.
2220-
2221-import gettext
2222-from optparse import OptionParser
2223-from os.path import basename
2224-from sys import argv, exit
2225-
2226-from swift.common.bufferedhttp import http_connect_raw as http_connect
2227-from swift.common.utils import urlparse
2228-
2229-
2230-if __name__ == '__main__':
2231- gettext.install('swift', unicode=1)
2232- parser = OptionParser(usage='Usage: %prog [options] <account> <user>')
2233- parser.add_option('-A', '--admin-url', dest='admin_url',
2234- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
2235- 'subsystem (default: http://127.0.0.1:8080/auth/')
2236- parser.add_option('-U', '--admin-user', dest='admin_user',
2237- default='.super_admin', help='The user with admin rights to add users '
2238- '(default: .super_admin).')
2239- parser.add_option('-K', '--admin-key', dest='admin_key',
2240- help='The key for the user with admin rights to add users.')
2241- args = argv[1:]
2242- if not args:
2243- args.append('-h')
2244- (options, args) = parser.parse_args(args)
2245- if len(args) != 2:
2246- parser.parse_args(['-h'])
2247- account, user = args
2248- parsed = urlparse(options.admin_url)
2249- if parsed.scheme not in ('http', 'https'):
2250- raise Exception('Cannot handle protocol scheme %s for url %s' %
2251- (parsed.scheme, repr(options.admin_url)))
2252- parsed_path = parsed.path
2253- if not parsed_path:
2254- parsed_path = '/'
2255- elif parsed_path[-1] != '/':
2256- parsed_path += '/'
2257- path = '%sv2/%s/%s' % (parsed_path, account, user)
2258- headers = {'X-Auth-Admin-User': options.admin_user,
2259- 'X-Auth-Admin-Key': options.admin_key}
2260- conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
2261- ssl=(parsed.scheme == 'https'))
2262- resp = conn.getresponse()
2263- if resp.status // 100 != 2:
2264- exit('User deletion failed: %s %s' % (resp.status, resp.reason))
2265
2266=== removed file 'bin/swauth-list'
2267--- bin/swauth-list 2011-04-18 16:08:48 +0000
2268+++ bin/swauth-list 1970-01-01 00:00:00 +0000
2269@@ -1,86 +0,0 @@
2270-#!/usr/bin/env python
2271-# Copyright (c) 2010 OpenStack, LLC.
2272-#
2273-# Licensed under the Apache License, Version 2.0 (the "License");
2274-# you may not use this file except in compliance with the License.
2275-# You may obtain a copy of the License at
2276-#
2277-# http://www.apache.org/licenses/LICENSE-2.0
2278-#
2279-# Unless required by applicable law or agreed to in writing, software
2280-# distributed under the License is distributed on an "AS IS" BASIS,
2281-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2282-# implied.
2283-# See the License for the specific language governing permissions and
2284-# limitations under the License.
2285-
2286-try:
2287- import simplejson as json
2288-except ImportError:
2289- import json
2290-import gettext
2291-from optparse import OptionParser
2292-from os.path import basename
2293-from sys import argv, exit
2294-
2295-from swift.common.bufferedhttp import http_connect_raw as http_connect
2296-from swift.common.utils import urlparse
2297-
2298-
2299-if __name__ == '__main__':
2300- gettext.install('swift', unicode=1)
2301- parser = OptionParser(usage='''
2302-Usage: %prog [options] [account] [user]
2303-
2304-If [account] and [user] are omitted, a list of accounts will be output.
2305-
2306-If [account] is included but not [user], an account's information will be
2307-output, including a list of users within the account.
2308-
2309-If [account] and [user] are included, the user's information will be output,
2310-including a list of groups the user belongs to.
2311-
2312-If the [user] is '.groups', the active groups for the account will be listed.
2313-'''.strip())
2314- parser.add_option('-p', '--plain-text', dest='plain_text',
2315- action='store_true', default=False, help='Changes the output from '
2316- 'JSON to plain text. This will cause an account to list only the '
2317- 'users and a user to list only the groups.')
2318- parser.add_option('-A', '--admin-url', dest='admin_url',
2319- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
2320- 'subsystem (default: http://127.0.0.1:8080/auth/')
2321- parser.add_option('-U', '--admin-user', dest='admin_user',
2322- default='.super_admin', help='The user with admin rights to add users '
2323- '(default: .super_admin).')
2324- parser.add_option('-K', '--admin-key', dest='admin_key',
2325- help='The key for the user with admin rights to add users.')
2326- args = argv[1:]
2327- if not args:
2328- args.append('-h')
2329- (options, args) = parser.parse_args(args)
2330- if len(args) > 2:
2331- parser.parse_args(['-h'])
2332- parsed = urlparse(options.admin_url)
2333- if parsed.scheme not in ('http', 'https'):
2334- raise Exception('Cannot handle protocol scheme %s for url %s' %
2335- (parsed.scheme, repr(options.admin_url)))
2336- parsed_path = parsed.path
2337- if not parsed_path:
2338- parsed_path = '/'
2339- elif parsed_path[-1] != '/':
2340- parsed_path += '/'
2341- path = '%sv2/%s' % (parsed_path, '/'.join(args))
2342- headers = {'X-Auth-Admin-User': options.admin_user,
2343- 'X-Auth-Admin-Key': options.admin_key}
2344- conn = http_connect(parsed.hostname, parsed.port, 'GET', path, headers,
2345- ssl=(parsed.scheme == 'https'))
2346- resp = conn.getresponse()
2347- body = resp.read()
2348- if resp.status // 100 != 2:
2349- exit('List failed: %s %s' % (resp.status, resp.reason))
2350- if options.plain_text:
2351- info = json.loads(body)
2352- for group in info[['accounts', 'users', 'groups'][len(args)]]:
2353- print group['name']
2354- else:
2355- print body
2356
2357=== removed file 'bin/swauth-prep'
2358--- bin/swauth-prep 2011-04-18 16:08:48 +0000
2359+++ bin/swauth-prep 1970-01-01 00:00:00 +0000
2360@@ -1,59 +0,0 @@
2361-#!/usr/bin/env python
2362-# Copyright (c) 2010 OpenStack, LLC.
2363-#
2364-# Licensed under the Apache License, Version 2.0 (the "License");
2365-# you may not use this file except in compliance with the License.
2366-# You may obtain a copy of the License at
2367-#
2368-# http://www.apache.org/licenses/LICENSE-2.0
2369-#
2370-# Unless required by applicable law or agreed to in writing, software
2371-# distributed under the License is distributed on an "AS IS" BASIS,
2372-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2373-# implied.
2374-# See the License for the specific language governing permissions and
2375-# limitations under the License.
2376-
2377-import gettext
2378-from optparse import OptionParser
2379-from os.path import basename
2380-from sys import argv, exit
2381-
2382-from swift.common.bufferedhttp import http_connect_raw as http_connect
2383-from swift.common.utils import urlparse
2384-
2385-
2386-if __name__ == '__main__':
2387- gettext.install('swift', unicode=1)
2388- parser = OptionParser(usage='Usage: %prog [options]')
2389- parser.add_option('-A', '--admin-url', dest='admin_url',
2390- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
2391- 'subsystem (default: http://127.0.0.1:8080/auth/')
2392- parser.add_option('-U', '--admin-user', dest='admin_user',
2393- default='.super_admin', help='The user with admin rights to add users '
2394- '(default: .super_admin).')
2395- parser.add_option('-K', '--admin-key', dest='admin_key',
2396- help='The key for the user with admin rights to add users.')
2397- args = argv[1:]
2398- if not args:
2399- args.append('-h')
2400- (options, args) = parser.parse_args(args)
2401- if args:
2402- parser.parse_args(['-h'])
2403- parsed = urlparse(options.admin_url)
2404- if parsed.scheme not in ('http', 'https'):
2405- raise Exception('Cannot handle protocol scheme %s for url %s' %
2406- (parsed.scheme, repr(options.admin_url)))
2407- parsed_path = parsed.path
2408- if not parsed_path:
2409- parsed_path = '/'
2410- elif parsed_path[-1] != '/':
2411- parsed_path += '/'
2412- path = '%sv2/.prep' % parsed_path
2413- headers = {'X-Auth-Admin-User': options.admin_user,
2414- 'X-Auth-Admin-Key': options.admin_key}
2415- conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
2416- ssl=(parsed.scheme == 'https'))
2417- resp = conn.getresponse()
2418- if resp.status // 100 != 2:
2419- exit('Auth subsystem prep failed: %s %s' % (resp.status, resp.reason))
2420
2421=== removed file 'bin/swauth-set-account-service'
2422--- bin/swauth-set-account-service 2011-04-18 16:08:48 +0000
2423+++ bin/swauth-set-account-service 1970-01-01 00:00:00 +0000
2424@@ -1,73 +0,0 @@
2425-#!/usr/bin/env python
2426-# Copyright (c) 2010 OpenStack, LLC.
2427-#
2428-# Licensed under the Apache License, Version 2.0 (the "License");
2429-# you may not use this file except in compliance with the License.
2430-# You may obtain a copy of the License at
2431-#
2432-# http://www.apache.org/licenses/LICENSE-2.0
2433-#
2434-# Unless required by applicable law or agreed to in writing, software
2435-# distributed under the License is distributed on an "AS IS" BASIS,
2436-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2437-# implied.
2438-# See the License for the specific language governing permissions and
2439-# limitations under the License.
2440-
2441-try:
2442- import simplejson as json
2443-except ImportError:
2444- import json
2445-import gettext
2446-from optparse import OptionParser
2447-from os.path import basename
2448-from sys import argv, exit
2449-
2450-from swift.common.bufferedhttp import http_connect_raw as http_connect
2451-from swift.common.utils import urlparse
2452-
2453-
2454-if __name__ == '__main__':
2455- gettext.install('swift', unicode=1)
2456- parser = OptionParser(usage='''
2457-Usage: %prog [options] <account> <service> <name> <value>
2458-
2459-Sets a service URL for an account. Can only be set by a reseller admin.
2460-
2461-Example: %prog -K swauthkey test storage local http://127.0.0.1:8080/v1/AUTH_018c3946-23f8-4efb-a8fb-b67aae8e4162
2462-'''.strip())
2463- parser.add_option('-A', '--admin-url', dest='admin_url',
2464- default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
2465- 'subsystem (default: http://127.0.0.1:8080/auth/)')
2466- parser.add_option('-U', '--admin-user', dest='admin_user',
2467- default='.super_admin', help='The user with admin rights to add users '
2468- '(default: .super_admin).')
2469- parser.add_option('-K', '--admin-key', dest='admin_key',
2470- help='The key for the user with admin rights to add users.')
2471- args = argv[1:]
2472- if not args:
2473- args.append('-h')
2474- (options, args) = parser.parse_args(args)
2475- if len(args) != 4:
2476- parser.parse_args(['-h'])
2477- account, service, name, url = args
2478- parsed = urlparse(options.admin_url)
2479- if parsed.scheme not in ('http', 'https'):
2480- raise Exception('Cannot handle protocol scheme %s for url %s' %
2481- (parsed.scheme, repr(options.admin_url)))
2482- parsed_path = parsed.path
2483- if not parsed_path:
2484- parsed_path = '/'
2485- elif parsed_path[-1] != '/':
2486- parsed_path += '/'
2487- path = '%sv2/%s/.services' % (parsed_path, account)
2488- body = json.dumps({service: {name: url}})
2489- headers = {'Content-Length': str(len(body)),
2490- 'X-Auth-Admin-User': options.admin_user,
2491- 'X-Auth-Admin-Key': options.admin_key}
2492- conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
2493- ssl=(parsed.scheme == 'https'))
2494- conn.send(body)
2495- resp = conn.getresponse()
2496- if resp.status // 100 != 2:
2497- exit('Service set failed: %s %s' % (resp.status, resp.reason))
2498
2499=== added file 'bin/swift'
2500--- bin/swift 1970-01-01 00:00:00 +0000
2501+++ bin/swift 2011-06-15 09:24:34 +0000
2502@@ -0,0 +1,1812 @@
2503+#!/usr/bin/python -u
2504+# Copyright (c) 2010-2011 OpenStack, LLC.
2505+#
2506+# Licensed under the Apache License, Version 2.0 (the "License");
2507+# you may not use this file except in compliance with the License.
2508+# You may obtain a copy of the License at
2509+#
2510+# http://www.apache.org/licenses/LICENSE-2.0
2511+#
2512+# Unless required by applicable law or agreed to in writing, software
2513+# distributed under the License is distributed on an "AS IS" BASIS,
2514+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
2515+# implied.
2516+# See the License for the specific language governing permissions and
2517+# limitations under the License.
2518+
2519+from errno import EEXIST, ENOENT
2520+from hashlib import md5
2521+from optparse import OptionParser
2522+from os import environ, listdir, makedirs, utime
2523+from os.path import basename, dirname, getmtime, getsize, isdir, join
2524+from Queue import Empty, Queue
2525+from sys import argv, exc_info, exit, stderr, stdout
2526+from threading import enumerate as threading_enumerate, Thread
2527+from time import sleep
2528+from traceback import format_exception
2529+
2530+
2531+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
2532+# Inclusion of swift.common.client for convenience of single file distribution
2533+
2534+import socket
2535+from cStringIO import StringIO
2536+from re import compile, DOTALL
2537+from tokenize import generate_tokens, STRING, NAME, OP
2538+from urllib import quote as _quote, unquote
2539+from urlparse import urlparse, urlunparse
2540+
2541+try:
2542+ from eventlet.green.httplib import HTTPException, HTTPSConnection
2543+except ImportError:
2544+ from httplib import HTTPException, HTTPSConnection
2545+
2546+try:
2547+ from eventlet import sleep
2548+except ImportError:
2549+ from time import sleep
2550+
2551+try:
2552+ from swift.common.bufferedhttp \
2553+ import BufferedHTTPConnection as HTTPConnection
2554+except ImportError:
2555+ try:
2556+ from eventlet.green.httplib import HTTPConnection
2557+ except ImportError:
2558+ from httplib import HTTPConnection
2559+
2560+
2561+def quote(value, safe='/'):
2562+ """
2563+ Patched version of urllib.quote that encodes utf8 strings before quoting
2564+ """
2565+ if isinstance(value, unicode):
2566+ value = value.encode('utf8')
2567+ return _quote(value, safe)
2568+
2569+
2570+# look for a real json parser first
2571+try:
2572+ # simplejson is popular and pretty good
2573+ from simplejson import loads as json_loads
2574+except ImportError:
2575+ try:
2576+ # 2.6 will have a json module in the stdlib
2577+ from json import loads as json_loads
2578+ except ImportError:
2579+ # fall back on local parser otherwise
2580+ comments = compile(r'/\*.*\*/|//[^\r\n]*', DOTALL)
2581+
2582+ def json_loads(string):
2583+ '''
2584+ Fairly competent json parser exploiting the python tokenizer and
2585+ eval(). -- From python-cloudfiles
2586+
2587+ _loads(serialized_json) -> object
2588+ '''
2589+ try:
2590+ res = []
2591+ consts = {'true': True, 'false': False, 'null': None}
2592+ string = '(' + comments.sub('', string) + ')'
2593+ for type, val, _junk, _junk, _junk in \
2594+ generate_tokens(StringIO(string).readline):
2595+ if (type == OP and val not in '[]{}:,()-') or \
2596+ (type == NAME and val not in consts):
2597+ raise AttributeError()
2598+ elif type == STRING:
2599+ res.append('u')
2600+ res.append(val.replace('\\/', '/'))
2601+ else:
2602+ res.append(val)
2603+ return eval(''.join(res), {}, consts)
2604+ except Exception:
2605+ raise AttributeError()
2606+
2607+
2608+class ClientException(Exception):
2609+
2610+ def __init__(self, msg, http_scheme='', http_host='', http_port='',
2611+ http_path='', http_query='', http_status=0, http_reason='',
2612+ http_device=''):
2613+ Exception.__init__(self, msg)
2614+ self.msg = msg
2615+ self.http_scheme = http_scheme
2616+ self.http_host = http_host
2617+ self.http_port = http_port
2618+ self.http_path = http_path
2619+ self.http_query = http_query
2620+ self.http_status = http_status
2621+ self.http_reason = http_reason
2622+ self.http_device = http_device
2623+
2624+ def __str__(self):
2625+ a = self.msg
2626+ b = ''
2627+ if self.http_scheme:
2628+ b += '%s://' % self.http_scheme
2629+ if self.http_host:
2630+ b += self.http_host
2631+ if self.http_port:
2632+ b += ':%s' % self.http_port
2633+ if self.http_path:
2634+ b += self.http_path
2635+ if self.http_query:
2636+ b += '?%s' % self.http_query
2637+ if self.http_status:
2638+ if b:
2639+ b = '%s %s' % (b, self.http_status)
2640+ else:
2641+ b = str(self.http_status)
2642+ if self.http_reason:
2643+ if b:
2644+ b = '%s %s' % (b, self.http_reason)
2645+ else:
2646+ b = '- %s' % self.http_reason
2647+ if self.http_device:
2648+ if b:
2649+ b = '%s: device %s' % (b, self.http_device)
2650+ else:
2651+ b = 'device %s' % self.http_device
2652+ return b and '%s: %s' % (a, b) or a
2653+
2654+
2655+def http_connection(url):
2656+ """
2657+ Make an HTTPConnection or HTTPSConnection
2658+
2659+ :param url: url to connect to
2660+ :returns: tuple of (parsed url, connection object)
2661+ :raises ClientException: Unable to handle protocol scheme
2662+ """
2663+ parsed = urlparse(url)
2664+ if parsed.scheme == 'http':
2665+ conn = HTTPConnection(parsed.netloc)
2666+ elif parsed.scheme == 'https':
2667+ conn = HTTPSConnection(parsed.netloc)
2668+ else:
2669+ raise ClientException('Cannot handle protocol scheme %s for url %s' %
2670+ (parsed.scheme, repr(url)))
2671+ return parsed, conn
2672+
2673+
2674+def get_auth(url, user, key, snet=False):
2675+ """
2676+ Get authentication/authorization credentials.
2677+
2678+ The snet parameter is used for Rackspace's ServiceNet internal network
2679+ implementation. In this function, it simply adds *snet-* to the beginning
2680+ of the host name for the returned storage URL. With Rackspace Cloud Files,
2681+ use of this network path causes no bandwidth charges but requires the
2682+ client to be running on Rackspace's ServiceNet network.
2683+
2684+ :param url: authentication/authorization URL
2685+ :param user: user to authenticate as
2686+ :param key: key or password for authorization
2687+ :param snet: use SERVICENET internal network (see above), default is False
2688+ :returns: tuple of (storage URL, auth token)
2689+ :raises ClientException: HTTP GET request to auth URL failed
2690+ """
2691+ parsed, conn = http_connection(url)
2692+ conn.request('GET', parsed.path, '',
2693+ {'X-Auth-User': user, 'X-Auth-Key': key})
2694+ resp = conn.getresponse()
2695+ resp.read()
2696+ if resp.status < 200 or resp.status >= 300:
2697+ raise ClientException('Auth GET failed', http_scheme=parsed.scheme,
2698+ http_host=conn.host, http_port=conn.port,
2699+ http_path=parsed.path, http_status=resp.status,
2700+ http_reason=resp.reason)
2701+ url = resp.getheader('x-storage-url')
2702+ if snet:
2703+ parsed = list(urlparse(url))
2704+ # Second item in the list is the netloc
2705+ parsed[1] = 'snet-' + parsed[1]
2706+ url = urlunparse(parsed)
2707+ return url, resp.getheader('x-storage-token',
2708+ resp.getheader('x-auth-token'))
2709+
2710+
2711+def get_account(url, token, marker=None, limit=None, prefix=None,
2712+ http_conn=None, full_listing=False):
2713+ """
2714+ Get a listing of containers for the account.
2715+
2716+ :param url: storage URL
2717+ :param token: auth token
2718+ :param marker: marker query
2719+ :param limit: limit query
2720+ :param prefix: prefix query
2721+ :param http_conn: HTTP connection object (If None, it will create the
2722+ conn object)
2723+ :param full_listing: if True, return a full listing, else returns a max
2724+ of 10000 listings
2725+ :returns: a tuple of (response headers, a list of containers) The response
2726+ headers will be a dict and all header names will be lowercase.
2727+ :raises ClientException: HTTP GET request failed
2728+ """
2729+ if not http_conn:
2730+ http_conn = http_connection(url)
2731+ if full_listing:
2732+ rv = get_account(url, token, marker, limit, prefix, http_conn)
2733+ listing = rv[1]
2734+ while listing:
2735+ marker = listing[-1]['name']
2736+ listing = \
2737+ get_account(url, token, marker, limit, prefix, http_conn)[1]
2738+ if listing:
2739+ rv[1].extend(listing)
2740+ return rv
2741+ parsed, conn = http_conn
2742+ qs = 'format=json'
2743+ if marker:
2744+ qs += '&marker=%s' % quote(marker)
2745+ if limit:
2746+ qs += '&limit=%d' % limit
2747+ if prefix:
2748+ qs += '&prefix=%s' % quote(prefix)
2749+ conn.request('GET', '%s?%s' % (parsed.path, qs), '',
2750+ {'X-Auth-Token': token})
2751+ resp = conn.getresponse()
2752+ resp_headers = {}
2753+ for header, value in resp.getheaders():
2754+ resp_headers[header.lower()] = value
2755+ if resp.status < 200 or resp.status >= 300:
2756+ resp.read()
2757+ raise ClientException('Account GET failed', http_scheme=parsed.scheme,
2758+ http_host=conn.host, http_port=conn.port,
2759+ http_path=parsed.path, http_query=qs, http_status=resp.status,
2760+ http_reason=resp.reason)
2761+ if resp.status == 204:
2762+ resp.read()
2763+ return resp_headers, []
2764+ return resp_headers, json_loads(resp.read())
2765+
2766+
2767+def head_account(url, token, http_conn=None):
2768+ """
2769+ Get account stats.
2770+
2771+ :param url: storage URL
2772+ :param token: auth token
2773+ :param http_conn: HTTP connection object (If None, it will create the
2774+ conn object)
2775+ :returns: a dict containing the response's headers (all header names will
2776+ be lowercase)
2777+ :raises ClientException: HTTP HEAD request failed
2778+ """
2779+ if http_conn:
2780+ parsed, conn = http_conn
2781+ else:
2782+ parsed, conn = http_connection(url)
2783+ conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token})
2784+ resp = conn.getresponse()
2785+ resp.read()
2786+ if resp.status < 200 or resp.status >= 300:
2787+ raise ClientException('Account HEAD failed', http_scheme=parsed.scheme,
2788+ http_host=conn.host, http_port=conn.port,
2789+ http_path=parsed.path, http_status=resp.status,
2790+ http_reason=resp.reason)
2791+ resp_headers = {}
2792+ for header, value in resp.getheaders():
2793+ resp_headers[header.lower()] = value
2794+ return resp_headers
2795+
2796+
2797+def post_account(url, token, headers, http_conn=None):
2798+ """
2799+ Update an account's metadata.
2800+
2801+ :param url: storage URL
2802+ :param token: auth token
2803+ :param headers: additional headers to include in the request
2804+ :param http_conn: HTTP connection object (If None, it will create the
2805+ conn object)
2806+ :raises ClientException: HTTP POST request failed
2807+ """
2808+ if http_conn:
2809+ parsed, conn = http_conn
2810+ else:
2811+ parsed, conn = http_connection(url)
2812+ headers['X-Auth-Token'] = token
2813+ conn.request('POST', parsed.path, '', headers)
2814+ resp = conn.getresponse()
2815+ resp.read()
2816+ if resp.status < 200 or resp.status >= 300:
2817+ raise ClientException('Account POST failed',
2818+ http_scheme=parsed.scheme, http_host=conn.host,
2819+ http_port=conn.port, http_path=path, http_status=resp.status,
2820+ http_reason=resp.reason)
2821+
2822+
2823+def get_container(url, token, container, marker=None, limit=None,
2824+ prefix=None, delimiter=None, http_conn=None,
2825+ full_listing=False):
2826+ """
2827+ Get a listing of objects for the container.
2828+
2829+ :param url: storage URL
2830+ :param token: auth token
2831+ :param container: container name to get a listing for
2832+ :param marker: marker query
2833+ :param limit: limit query
2834+ :param prefix: prefix query
2835+ :param delimeter: string to delimit the queries on
2836+ :param http_conn: HTTP connection object (If None, it will create the
2837+ conn object)
2838+ :param full_listing: if True, return a full listing, else returns a max
2839+ of 10000 listings
2840+ :returns: a tuple of (response headers, a list of objects) The response
2841+ headers will be a dict and all header names will be lowercase.
2842+ :raises ClientException: HTTP GET request failed
2843+ """
2844+ if not http_conn:
2845+ http_conn = http_connection(url)
2846+ if full_listing:
2847+ rv = get_container(url, token, container, marker, limit, prefix,
2848+ delimiter, http_conn)
2849+ listing = rv[1]
2850+ while listing:
2851+ if not delimiter:
2852+ marker = listing[-1]['name']
2853+ else:
2854+ marker = listing[-1].get('name', listing[-1].get('subdir'))
2855+ listing = get_container(url, token, container, marker, limit,
2856+ prefix, delimiter, http_conn)[1]
2857+ if listing:
2858+ rv[1].extend(listing)
2859+ return rv
2860+ parsed, conn = http_conn
2861+ path = '%s/%s' % (parsed.path, quote(container))
2862+ qs = 'format=json'
2863+ if marker:
2864+ qs += '&marker=%s' % quote(marker)
2865+ if limit:
2866+ qs += '&limit=%d' % limit
2867+ if prefix:
2868+ qs += '&prefix=%s' % quote(prefix)
2869+ if delimiter:
2870+ qs += '&delimiter=%s' % quote(delimiter)
2871+ conn.request('GET', '%s?%s' % (path, qs), '', {'X-Auth-Token': token})
2872+ resp = conn.getresponse()
2873+ if resp.status < 200 or resp.status >= 300:
2874+ resp.read()
2875+ raise ClientException('Container GET failed',
2876+ http_scheme=parsed.scheme, http_host=conn.host,
2877+ http_port=conn.port, http_path=path, http_query=qs,
2878+ http_status=resp.status, http_reason=resp.reason)
2879+ resp_headers = {}
2880+ for header, value in resp.getheaders():
2881+ resp_headers[header.lower()] = value
2882+ if resp.status == 204:
2883+ resp.read()
2884+ return resp_headers, []
2885+ return resp_headers, json_loads(resp.read())
2886+
2887+
2888+def head_container(url, token, container, http_conn=None):
2889+ """
2890+ Get container stats.
2891+
2892+ :param url: storage URL
2893+ :param token: auth token
2894+ :param container: container name to get stats for
2895+ :param http_conn: HTTP connection object (If None, it will create the
2896+ conn object)
2897+ :returns: a dict containing the response's headers (all header names will
2898+ be lowercase)
2899+ :raises ClientException: HTTP HEAD request failed
2900+ """
2901+ if http_conn:
2902+ parsed, conn = http_conn
2903+ else:
2904+ parsed, conn = http_connection(url)
2905+ path = '%s/%s' % (parsed.path, quote(container))
2906+ conn.request('HEAD', path, '', {'X-Auth-Token': token})
2907+ resp = conn.getresponse()
2908+ resp.read()
2909+ if resp.status < 200 or resp.status >= 300:
2910+ raise ClientException('Container HEAD failed',
2911+ http_scheme=parsed.scheme, http_host=conn.host,
2912+ http_port=conn.port, http_path=path, http_status=resp.status,
2913+ http_reason=resp.reason)
2914+ resp_headers = {}
2915+ for header, value in resp.getheaders():
2916+ resp_headers[header.lower()] = value
2917+ return resp_headers
2918+
2919+
2920+def put_container(url, token, container, headers=None, http_conn=None):
2921+ """
2922+ Create a container
2923+
2924+ :param url: storage URL
2925+ :param token: auth token
2926+ :param container: container name to create
2927+ :param headers: additional headers to include in the request
2928+ :param http_conn: HTTP connection object (If None, it will create the
2929+ conn object)
2930+ :raises ClientException: HTTP PUT request failed
2931+ """
2932+ if http_conn:
2933+ parsed, conn = http_conn
2934+ else:
2935+ parsed, conn = http_connection(url)
2936+ path = '%s/%s' % (parsed.path, quote(container))
2937+ if not headers:
2938+ headers = {}
2939+ headers['X-Auth-Token'] = token
2940+ conn.request('PUT', path, '', headers)
2941+ resp = conn.getresponse()
2942+ resp.read()
2943+ if resp.status < 200 or resp.status >= 300:
2944+ raise ClientException('Container PUT failed',
2945+ http_scheme=parsed.scheme, http_host=conn.host,
2946+ http_port=conn.port, http_path=path, http_status=resp.status,
2947+ http_reason=resp.reason)
2948+
2949+
2950+def post_container(url, token, container, headers, http_conn=None):
2951+ """
2952+ Update a container's metadata.
2953+
2954+ :param url: storage URL
2955+ :param token: auth token
2956+ :param container: container name to update
2957+ :param headers: additional headers to include in the request
2958+ :param http_conn: HTTP connection object (If None, it will create the
2959+ conn object)
2960+ :raises ClientException: HTTP POST request failed
2961+ """
2962+ if http_conn:
2963+ parsed, conn = http_conn
2964+ else:
2965+ parsed, conn = http_connection(url)
2966+ path = '%s/%s' % (parsed.path, quote(container))
2967+ headers['X-Auth-Token'] = token
2968+ conn.request('POST', path, '', headers)
2969+ resp = conn.getresponse()
2970+ resp.read()
2971+ if resp.status < 200 or resp.status >= 300:
2972+ raise ClientException('Container POST failed',
2973+ http_scheme=parsed.scheme, http_host=conn.host,
2974+ http_port=conn.port, http_path=path, http_status=resp.status,
2975+ http_reason=resp.reason)
2976+
2977+
2978+def delete_container(url, token, container, http_conn=None):
2979+ """
2980+ Delete a container
2981+
2982+ :param url: storage URL
2983+ :param token: auth token
2984+ :param container: container name to delete
2985+ :param http_conn: HTTP connection object (If None, it will create the
2986+ conn object)
2987+ :raises ClientException: HTTP DELETE request failed
2988+ """
2989+ if http_conn:
2990+ parsed, conn = http_conn
2991+ else:
2992+ parsed, conn = http_connection(url)
2993+ path = '%s/%s' % (parsed.path, quote(container))
2994+ conn.request('DELETE', path, '', {'X-Auth-Token': token})
2995+ resp = conn.getresponse()
2996+ resp.read()
2997+ if resp.status < 200 or resp.status >= 300:
2998+ raise ClientException('Container DELETE failed',
2999+ http_scheme=parsed.scheme, http_host=conn.host,
3000+ http_port=conn.port, http_path=path, http_status=resp.status,
3001+ http_reason=resp.reason)
3002+
3003+
3004+def get_object(url, token, container, name, http_conn=None,
3005+ resp_chunk_size=None):
3006+ """
3007+ Get an object
3008+
3009+ :param url: storage URL
3010+ :param token: auth token
3011+ :param container: container name that the object is in
3012+ :param name: object name to get
3013+ :param http_conn: HTTP connection object (If None, it will create the
3014+ conn object)
3015+ :param resp_chunk_size: if defined, chunk size of data to read. NOTE: If
3016+ you specify a resp_chunk_size you must fully read
3017+ the object's contents before making another
3018+ request.
3019+ :returns: a tuple of (response headers, the object's contents) The response
3020+ headers will be a dict and all header names will be lowercase.
3021+ :raises ClientException: HTTP GET request failed
3022+ """
3023+ if http_conn:
3024+ parsed, conn = http_conn
3025+ else:
3026+ parsed, conn = http_connection(url)
3027+ path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
3028+ conn.request('GET', path, '', {'X-Auth-Token': token})
3029+ resp = conn.getresponse()
3030+ if resp.status < 200 or resp.status >= 300:
3031+ resp.read()
3032+ raise ClientException('Object GET failed', http_scheme=parsed.scheme,
3033+ http_host=conn.host, http_port=conn.port, http_path=path,
3034+ http_status=resp.status, http_reason=resp.reason)
3035+ if resp_chunk_size:
3036+
3037+ def _object_body():
3038+ buf = resp.read(resp_chunk_size)
3039+ while buf:
3040+ yield buf
3041+ buf = resp.read(resp_chunk_size)
3042+ object_body = _object_body()
3043+ else:
3044+ object_body = resp.read()
3045+ resp_headers = {}
3046+ for header, value in resp.getheaders():
3047+ resp_headers[header.lower()] = value
3048+ return resp_headers, object_body
3049+
3050+
3051+def head_object(url, token, container, name, http_conn=None):
3052+ """
3053+ Get object info
3054+
3055+ :param url: storage URL
3056+ :param token: auth token
3057+ :param container: container name that the object is in
3058+ :param name: object name to get info for
3059+ :param http_conn: HTTP connection object (If None, it will create the
3060+ conn object)
3061+ :returns: a dict containing the response's headers (all header names will
3062+ be lowercase)
3063+ :raises ClientException: HTTP HEAD request failed
3064+ """
3065+ if http_conn:
3066+ parsed, conn = http_conn
3067+ else:
3068+ parsed, conn = http_connection(url)
3069+ path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
3070+ conn.request('HEAD', path, '', {'X-Auth-Token': token})
3071+ resp = conn.getresponse()
3072+ resp.read()
3073+ if resp.status < 200 or resp.status >= 300:
3074+ raise ClientException('Object HEAD failed', http_scheme=parsed.scheme,
3075+ http_host=conn.host, http_port=conn.port, http_path=path,
3076+ http_status=resp.status, http_reason=resp.reason)
3077+ resp_headers = {}
3078+ for header, value in resp.getheaders():
3079+ resp_headers[header.lower()] = value
3080+ return resp_headers
3081+
3082+
3083+def put_object(url, token, container, name, contents, content_length=None,
3084+ etag=None, chunk_size=65536, content_type=None, headers=None,
3085+ http_conn=None):
3086+ """
3087+ Put an object
3088+
3089+ :param url: storage URL
3090+ :param token: auth token
3091+ :param container: container name that the object is in
3092+ :param name: object name to put
3093+ :param contents: a string or a file like object to read object data from
3094+ :param content_length: value to send as content-length header; also limits
3095+ the amount read from contents
3096+ :param etag: etag of contents
3097+ :param chunk_size: chunk size of data to write
3098+ :param content_type: value to send as content-type header
3099+ :param headers: additional headers to include in the request
3100+ :param http_conn: HTTP connection object (If None, it will create the
3101+ conn object)
3102+ :returns: etag from server response
3103+ :raises ClientException: HTTP PUT request failed
3104+ """
3105+ if http_conn:
3106+ parsed, conn = http_conn
3107+ else:
3108+ parsed, conn = http_connection(url)
3109+ path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
3110+ if not headers:
3111+ headers = {}
3112+ headers['X-Auth-Token'] = token
3113+ if etag:
3114+ headers['ETag'] = etag.strip('"')
3115+ if content_length is not None:
3116+ headers['Content-Length'] = str(content_length)
3117+ if content_type is not None:
3118+ headers['Content-Type'] = content_type
3119+ if not contents:
3120+ headers['Content-Length'] = '0'
3121+ if hasattr(contents, 'read'):
3122+ conn.putrequest('PUT', path)
3123+ for header, value in headers.iteritems():
3124+ conn.putheader(header, value)
3125+ if content_length is None:
3126+ conn.putheader('Transfer-Encoding', 'chunked')
3127+ conn.endheaders()
3128+ chunk = contents.read(chunk_size)
3129+ while chunk:
3130+ conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
3131+ chunk = contents.read(chunk_size)
3132+ conn.send('0\r\n\r\n')
3133+ else:
3134+ conn.endheaders()
3135+ left = content_length
3136+ while left > 0:
3137+ size = chunk_size
3138+ if size > left:
3139+ size = left
3140+ chunk = contents.read(size)
3141+ conn.send(chunk)
3142+ left -= len(chunk)
3143+ else:
3144+ conn.request('PUT', path, contents, headers)
3145+ resp = conn.getresponse()
3146+ resp.read()
3147+ if resp.status < 200 or resp.status >= 300:
3148+ raise ClientException('Object PUT failed', http_scheme=parsed.scheme,
3149+ http_host=conn.host, http_port=conn.port, http_path=path,
3150+ http_status=resp.status, http_reason=resp.reason)
3151+ return resp.getheader('etag').strip('"')
3152+
3153+
3154+def post_object(url, token, container, name, headers, http_conn=None):
3155+ """
3156+ Update object metadata
3157+
3158+ :param url: storage URL
3159+ :param token: auth token
3160+ :param container: container name that the object is in
3161+ :param name: name of the object to update
3162+ :param headers: additional headers to include in the request
3163+ :param http_conn: HTTP connection object (If None, it will create the
3164+ conn object)
3165+ :raises ClientException: HTTP POST request failed
3166+ """
3167+ if http_conn:
3168+ parsed, conn = http_conn
3169+ else:
3170+ parsed, conn = http_connection(url)
3171+ path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
3172+ headers['X-Auth-Token'] = token
3173+ conn.request('POST', path, '', headers)
3174+ resp = conn.getresponse()
3175+ resp.read()
3176+ if resp.status < 200 or resp.status >= 300:
3177+ raise ClientException('Object POST failed', http_scheme=parsed.scheme,
3178+ http_host=conn.host, http_port=conn.port, http_path=path,
3179+ http_status=resp.status, http_reason=resp.reason)
3180+
3181+
3182+def delete_object(url, token, container, name, http_conn=None):
3183+ """
3184+ Delete object
3185+
3186+ :param url: storage URL
3187+ :param token: auth token
3188+ :param container: container name that the object is in
3189+ :param name: object name to delete
3190+ :param http_conn: HTTP connection object (If None, it will create the
3191+ conn object)
3192+ :raises ClientException: HTTP DELETE request failed
3193+ """
3194+ if http_conn:
3195+ parsed, conn = http_conn
3196+ else:
3197+ parsed, conn = http_connection(url)
3198+ path = '%s/%s/%s' % (parsed.path, quote(container), quote(name))
3199+ conn.request('DELETE', path, '', {'X-Auth-Token': token})
3200+ resp = conn.getresponse()
3201+ resp.read()
3202+ if resp.status < 200 or resp.status >= 300:
3203+ raise ClientException('Object DELETE failed',
3204+ http_scheme=parsed.scheme, http_host=conn.host,
3205+ http_port=conn.port, http_path=path, http_status=resp.status,
3206+ http_reason=resp.reason)
3207+
3208+
3209+class Connection(object):
3210+ """Convenience class to make requests that will also retry the request"""
3211+
3212+ def __init__(self, authurl, user, key, retries=5, preauthurl=None,
3213+ preauthtoken=None, snet=False, starting_backoff=1):
3214+ """
3215+ :param authurl: authenitcation URL
3216+ :param user: user name to authenticate as
3217+ :param key: key/password to authenticate with
3218+ :param retries: Number of times to retry the request before failing
3219+ :param preauthurl: storage URL (if you have already authenticated)
3220+ :param preauthtoken: authentication token (if you have already
3221+ authenticated)
3222+ :param snet: use SERVICENET internal network default is False
3223+ """
3224+ self.authurl = authurl
3225+ self.user = user
3226+ self.key = key
3227+ self.retries = retries
3228+ self.http_conn = None
3229+ self.url = preauthurl
3230+ self.token = preauthtoken
3231+ self.attempts = 0
3232+ self.snet = snet
3233+ self.starting_backoff = starting_backoff
3234+
3235+ def get_auth(self):
3236+ return get_auth(self.authurl, self.user, self.key, snet=self.snet)
3237+
3238+ def http_connection(self):
3239+ return http_connection(self.url)
3240+
3241+ def _retry(self, reset_func, func, *args, **kwargs):
3242+ self.attempts = 0
3243+ backoff = self.starting_backoff
3244+ while self.attempts <= self.retries:
3245+ self.attempts += 1
3246+ try:
3247+ if not self.url or not self.token:
3248+ self.url, self.token = self.get_auth()
3249+ self.http_conn = None
3250+ if not self.http_conn:
3251+ self.http_conn = self.http_connection()
3252+ kwargs['http_conn'] = self.http_conn
3253+ rv = func(self.url, self.token, *args, **kwargs)
3254+ return rv
3255+ except (socket.error, HTTPException):
3256+ if self.attempts > self.retries:
3257+ raise
3258+ self.http_conn = None
3259+ except ClientException, err:
3260+ if self.attempts > self.retries:
3261+ raise
3262+ if err.http_status == 401:
3263+ self.url = self.token = None
3264+ if self.attempts > 1:
3265+ raise
3266+ elif err.http_status == 408:
3267+ self.http_conn = None
3268+ elif 500 <= err.http_status <= 599:
3269+ pass
3270+ else:
3271+ raise
3272+ sleep(backoff)
3273+ backoff *= 2
3274+ if reset_func:
3275+ reset_func(func, *args, **kwargs)
3276+
3277+ def head_account(self):
3278+ """Wrapper for :func:`head_account`"""
3279+ return self._retry(None, head_account)
3280+
3281+ def get_account(self, marker=None, limit=None, prefix=None,
3282+ full_listing=False):
3283+ """Wrapper for :func:`get_account`"""
3284+ # TODO(unknown): With full_listing=True this will restart the entire
3285+ # listing with each retry. Need to make a better version that just
3286+ # retries where it left off.
3287+ return self._retry(None, get_account, marker=marker, limit=limit,
3288+ prefix=prefix, full_listing=full_listing)
3289+
3290+ def post_account(self, headers):
3291+ """Wrapper for :func:`post_account`"""
3292+ return self._retry(None, post_account, headers)
3293+
3294+ def head_container(self, container):
3295+ """Wrapper for :func:`head_container`"""
3296+ return self._retry(None, head_container, container)
3297+
3298+ def get_container(self, container, marker=None, limit=None, prefix=None,
3299+ delimiter=None, full_listing=False):
3300+ """Wrapper for :func:`get_container`"""
3301+ # TODO(unknown): With full_listing=True this will restart the entire
3302+ # listing with each retry. Need to make a better version that just
3303+ # retries where it left off.
3304+ return self._retry(None, get_container, container, marker=marker,
3305+ limit=limit, prefix=prefix, delimiter=delimiter,
3306+ full_listing=full_listing)
3307+
3308+ def put_container(self, container, headers=None):
3309+ """Wrapper for :func:`put_container`"""
3310+ return self._retry(None, put_container, container, headers=headers)
3311+
3312+ def post_container(self, container, headers):
3313+ """Wrapper for :func:`post_container`"""
3314+ return self._retry(None, post_container, container, headers)
3315+
3316+ def delete_container(self, container):
3317+ """Wrapper for :func:`delete_container`"""
3318+ return self._retry(None, delete_container, container)
3319+
3320+ def head_object(self, container, obj):
3321+ """Wrapper for :func:`head_object`"""
3322+ return self._retry(None, head_object, container, obj)
3323+
3324+ def get_object(self, container, obj, resp_chunk_size=None):
3325+ """Wrapper for :func:`get_object`"""
3326+ return self._retry(None, get_object, container, obj,
3327+ resp_chunk_size=resp_chunk_size)
3328+
3329+ def put_object(self, container, obj, contents, content_length=None,
3330+ etag=None, chunk_size=65536, content_type=None,
3331+ headers=None):
3332+ """Wrapper for :func:`put_object`"""
3333+
3334+ def _default_reset(*args, **kwargs):
3335+ raise ClientException('put_object(%r, %r, ...) failure and no '
3336+ 'ability to reset contents for reupload.' % (container, obj))
3337+
3338+ reset_func = _default_reset
3339+ tell = getattr(contents, 'tell', None)
3340+ seek = getattr(contents, 'seek', None)
3341+ if tell and seek:
3342+ orig_pos = tell()
3343+ reset_func = lambda *a, **k: seek(orig_pos)
3344+ elif not contents:
3345+ reset_func = lambda *a, **k: None
3346+
3347+ return self._retry(reset_func, put_object, container, obj, contents,
3348+ content_length=content_length, etag=etag, chunk_size=chunk_size,
3349+ content_type=content_type, headers=headers)
3350+
3351+ def post_object(self, container, obj, headers):
3352+ """Wrapper for :func:`post_object`"""
3353+ return self._retry(None, post_object, container, obj, headers)
3354+
3355+ def delete_object(self, container, obj):
3356+ """Wrapper for :func:`delete_object`"""
3357+ return self._retry(None, delete_object, container, obj)
3358+
3359+# End inclusion of swift.common.client
3360+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
3361+
3362+
3363+def mkdirs(path):
3364+ try:
3365+ makedirs(path)
3366+ except OSError, err:
3367+ if err.errno != EEXIST:
3368+ raise
3369+
3370+
3371+def put_errors_from_threads(threads, error_queue):
3372+ """
3373+ Places any errors from the threads into error_queue.
3374+ :param threads: A list of QueueFunctionThread instances.
3375+ :param error_queue: A queue to put error strings into.
3376+ :returns: True if any errors were found.
3377+ """
3378+ was_error = False
3379+ for thread in threads:
3380+ for info in thread.exc_infos:
3381+ was_error = True
3382+ if isinstance(info[1], ClientException):
3383+ error_queue.put(str(info[1]))
3384+ else:
3385+ error_queue.put(''.join(format_exception(*info)))
3386+ return was_error
3387+
3388+
3389+class QueueFunctionThread(Thread):
3390+
3391+ def __init__(self, queue, func, *args, **kwargs):
3392+ """ Calls func for each item in queue; func is called with a queued
3393+ item as the first arg followed by *args and **kwargs. Use the abort
3394+ attribute to have the thread empty the queue (without processing)
3395+ and exit. """
3396+ Thread.__init__(self)
3397+ self.abort = False
3398+ self.queue = queue
3399+ self.func = func
3400+ self.args = args
3401+ self.kwargs = kwargs
3402+ self.exc_infos = []
3403+
3404+ def run(self):
3405+ try:
3406+ while True:
3407+ try:
3408+ item = self.queue.get_nowait()
3409+ if not self.abort:
3410+ self.func(item, *self.args, **self.kwargs)
3411+ self.queue.task_done()
3412+ except Empty:
3413+ if self.abort:
3414+ break
3415+ sleep(0.01)
3416+ except Exception:
3417+ self.exc_infos.append(exc_info())
3418+
3419+
3420+st_delete_help = '''
3421+delete --all OR delete container [--leave-segments] [object] [object] ...
3422+ Deletes everything in the account (with --all), or everything in a
3423+ container, or a list of objects depending on the args given. Segments of
3424+ manifest objects will be deleted as well, unless you specify the
3425+ --leave-segments option.'''.strip('\n')
3426+
3427+
3428+def st_delete(parser, args, print_queue, error_queue):
3429+ parser.add_option('-a', '--all', action='store_true', dest='yes_all',
3430+ default=False, help='Indicates that you really want to delete '
3431+ 'everything in the account')
3432+ parser.add_option('', '--leave-segments', action='store_true',
3433+ dest='leave_segments', default=False, help='Indicates that you want '
3434+ 'the segments of manifest objects left alone')
3435+ (options, args) = parse_args(parser, args)
3436+ args = args[1:]
3437+ if (not args and not options.yes_all) or (args and options.yes_all):
3438+ error_queue.put('Usage: %s [options] %s' %
3439+ (basename(argv[0]), st_delete_help))
3440+ return
3441+
3442+ def _delete_segment((container, obj), conn):
3443+ conn.delete_object(container, obj)
3444+ if options.verbose:
3445+ if conn.attempts > 2:
3446+ print_queue.put('%s/%s [after %d attempts]' %
3447+ (container, obj, conn.attempts))
3448+ else:
3449+ print_queue.put('%s/%s' % (container, obj))
3450+
3451+ object_queue = Queue(10000)
3452+
3453+ def _delete_object((container, obj), conn):
3454+ try:
3455+ old_manifest = None
3456+ if not options.leave_segments:
3457+ try:
3458+ old_manifest = conn.head_object(container, obj).get(
3459+ 'x-object-manifest')
3460+ except ClientException, err:
3461+ if err.http_status != 404:
3462+ raise
3463+ conn.delete_object(container, obj)
3464+ if old_manifest:
3465+ segment_queue = Queue(10000)
3466+ scontainer, sprefix = old_manifest.split('/', 1)
3467+ for delobj in conn.get_container(scontainer,
3468+ prefix=sprefix)[1]:
3469+ segment_queue.put((scontainer, delobj['name']))
3470+ if not segment_queue.empty():
3471+ segment_threads = [QueueFunctionThread(segment_queue,
3472+ _delete_segment, create_connection()) for _junk in
3473+ xrange(10)]
3474+ for thread in segment_threads:
3475+ thread.start()
3476+ while not segment_queue.empty():
3477+ sleep(0.01)
3478+ for thread in segment_threads:
3479+ thread.abort = True
3480+ while thread.isAlive():
3481+ thread.join(0.01)
3482+ put_errors_from_threads(segment_threads, error_queue)
3483+ if options.verbose:
3484+ path = options.yes_all and join(container, obj) or obj
3485+ if path[:1] in ('/', '\\'):
3486+ path = path[1:]
3487+ if conn.attempts > 1:
3488+ print_queue.put('%s [after %d attempts]' %
3489+ (path, conn.attempts))
3490+ else:
3491+ print_queue.put(path)
3492+ except ClientException, err:
3493+ if err.http_status != 404:
3494+ raise
3495+ error_queue.put('Object %s not found' %
3496+ repr('%s/%s' % (container, obj)))
3497+
3498+ container_queue = Queue(10000)
3499+
3500+ def _delete_container(container, conn):
3501+ try:
3502+ marker = ''
3503+ while True:
3504+ objects = [o['name'] for o in
3505+ conn.get_container(container, marker=marker)[1]]
3506+ if not objects:
3507+ break
3508+ for obj in objects:
3509+ object_queue.put((container, obj))
3510+ marker = objects[-1]
3511+ while not object_queue.empty():
3512+ sleep(0.01)
3513+ attempts = 1
3514+ while True:
3515+ try:
3516+ conn.delete_container(container)
3517+ break
3518+ except ClientException, err:
3519+ if err.http_status != 409:
3520+ raise
3521+ if attempts > 10:
3522+ raise
3523+ attempts += 1
3524+ sleep(1)
3525+ except ClientException, err:
3526+ if err.http_status != 404:
3527+ raise
3528+ error_queue.put('Container %s not found' % repr(container))
3529+
3530+ url, token = get_auth(options.auth, options.user, options.key,
3531+ snet=options.snet)
3532+ create_connection = lambda: Connection(options.auth, options.user,
3533+ options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
3534+ object_threads = [QueueFunctionThread(object_queue, _delete_object,
3535+ create_connection()) for _junk in xrange(10)]
3536+ for thread in object_threads:
3537+ thread.start()
3538+ container_threads = [QueueFunctionThread(container_queue,
3539+ _delete_container, create_connection()) for _junk in xrange(10)]
3540+ for thread in container_threads:
3541+ thread.start()
3542+ if not args:
3543+ conn = create_connection()
3544+ try:
3545+ marker = ''
3546+ while True:
3547+ containers = \
3548+ [c['name'] for c in conn.get_account(marker=marker)[1]]
3549+ if not containers:
3550+ break
3551+ for container in containers:
3552+ container_queue.put(container)
3553+ marker = containers[-1]
3554+ while not container_queue.empty():
3555+ sleep(0.01)
3556+ while not object_queue.empty():
3557+ sleep(0.01)
3558+ except ClientException, err:
3559+ if err.http_status != 404:
3560+ raise
3561+ error_queue.put('Account not found')
3562+ elif len(args) == 1:
3563+ if '/' in args[0]:
3564+ print >> stderr, 'WARNING: / in container name; you might have ' \
3565+ 'meant %r instead of %r.' % \
3566+ (args[0].replace('/', ' ', 1), args[0])
3567+ conn = create_connection()
3568+ _delete_container(args[0], conn)
3569+ else:
3570+ for obj in args[1:]:
3571+ object_queue.put((args[0], obj))
3572+ while not container_queue.empty():
3573+ sleep(0.01)
3574+ for thread in container_threads:
3575+ thread.abort = True
3576+ while thread.isAlive():
3577+ thread.join(0.01)
3578+ put_errors_from_threads(container_threads, error_queue)
3579+ while not object_queue.empty():
3580+ sleep(0.01)
3581+ for thread in object_threads:
3582+ thread.abort = True
3583+ while thread.isAlive():
3584+ thread.join(0.01)
3585+ put_errors_from_threads(object_threads, error_queue)
3586+
3587+
3588+st_download_help = '''
3589+download --all OR download container [options] [object] [object] ...
3590+ Downloads everything in the account (with --all), or everything in a
3591+ container, or a list of objects depending on the args given. For a single
3592+ object download, you may use the -o [--output] <filename> option to
3593+ redirect the output to a specific file or if "-" then just redirect to
3594+ stdout.'''.strip('\n')
3595+
3596+
3597+def st_download(options, args, print_queue, error_queue):
3598+ parser.add_option('-a', '--all', action='store_true', dest='yes_all',
3599+ default=False, help='Indicates that you really want to download '
3600+ 'everything in the account')
3601+ parser.add_option('-o', '--output', dest='out_file', help='For a single '
3602+ 'file download, stream the output to an alternate location ')
3603+ (options, args) = parse_args(parser, args)
3604+ args = args[1:]
3605+ if options.out_file == '-':
3606+ options.verbose = 0
3607+ if options.out_file and len(args) != 2:
3608+ exit('-o option only allowed for single file downloads')
3609+ if (not args and not options.yes_all) or (args and options.yes_all):
3610+ error_queue.put('Usage: %s [options] %s' %
3611+ (basename(argv[0]), st_download_help))
3612+ return
3613+
3614+ object_queue = Queue(10000)
3615+
3616+ def _download_object(queue_arg, conn):
3617+ if len(queue_arg) == 2:
3618+ container, obj = queue_arg
3619+ out_file = None
3620+ elif len(queue_arg) == 3:
3621+ container, obj, out_file = queue_arg
3622+ else:
3623+ raise Exception("Invalid queue_arg length of %s" % len(queue_arg))
3624+ try:
3625+ headers, body = \
3626+ conn.get_object(container, obj, resp_chunk_size=65536)
3627+ content_type = headers.get('content-type')
3628+ if 'content-length' in headers:
3629+ content_length = int(headers.get('content-length'))
3630+ else:
3631+ content_length = None
3632+ etag = headers.get('etag')
3633+ path = options.yes_all and join(container, obj) or obj
3634+ if path[:1] in ('/', '\\'):
3635+ path = path[1:]
3636+ md5sum = None
3637+ make_dir = out_file != "-"
3638+ if content_type.split(';', 1)[0] == 'text/directory':
3639+ if make_dir and not isdir(path):
3640+ mkdirs(path)
3641+ read_length = 0
3642+ if 'x-object-manifest' not in headers:
3643+ md5sum = md5()
3644+ for chunk in body:
3645+ read_length += len(chunk)
3646+ if md5sum:
3647+ md5sum.update(chunk)
3648+ else:
3649+ dirpath = dirname(path)
3650+ if make_dir and dirpath and not isdir(dirpath):
3651+ mkdirs(dirpath)
3652+ if out_file == "-":
3653+ fp = stdout
3654+ elif out_file:
3655+ fp = open(out_file, 'wb')
3656+ else:
3657+ fp = open(path, 'wb')
3658+ read_length = 0
3659+ if 'x-object-manifest' not in headers:
3660+ md5sum = md5()
3661+ for chunk in body:
3662+ fp.write(chunk)
3663+ read_length += len(chunk)
3664+ if md5sum:
3665+ md5sum.update(chunk)
3666+ fp.close()
3667+ if md5sum and md5sum.hexdigest() != etag:
3668+ error_queue.put('%s: md5sum != etag, %s != %s' %
3669+ (path, md5sum.hexdigest(), etag))
3670+ if content_length is not None and read_length != content_length:
3671+ error_queue.put('%s: read_length != content_length, %d != %d' %
3672+ (path, read_length, content_length))
3673+ if 'x-object-meta-mtime' in headers and not options.out_file:
3674+ mtime = float(headers['x-object-meta-mtime'])
3675+ utime(path, (mtime, mtime))
3676+ if options.verbose:
3677+ if conn.attempts > 1:
3678+ print_queue.put('%s [after %d attempts' %
3679+ (path, conn.attempts))
3680+ else:
3681+ print_queue.put(path)
3682+ except ClientException, err:
3683+ if err.http_status != 404:
3684+ raise
3685+ error_queue.put('Object %s not found' %
3686+ repr('%s/%s' % (container, obj)))
3687+
3688+ container_queue = Queue(10000)
3689+
3690+ def _download_container(container, conn):
3691+ try:
3692+ marker = ''
3693+ while True:
3694+ objects = [o['name'] for o in
3695+ conn.get_container(container, marker=marker)[1]]
3696+ if not objects:
3697+ break
3698+ for obj in objects:
3699+ object_queue.put((container, obj))
3700+ marker = objects[-1]
3701+ except ClientException, err:
3702+ if err.http_status != 404:
3703+ raise
3704+ error_queue.put('Container %s not found' % repr(container))
3705+
3706+ url, token = get_auth(options.auth, options.user, options.key,
3707+ snet=options.snet)
3708+ create_connection = lambda: Connection(options.auth, options.user,
3709+ options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
3710+ object_threads = [QueueFunctionThread(object_queue, _download_object,
3711+ create_connection()) for _junk in xrange(10)]
3712+ for thread in object_threads:
3713+ thread.start()
3714+ container_threads = [QueueFunctionThread(container_queue,
3715+ _download_container, create_connection()) for _junk in xrange(10)]
3716+ for thread in container_threads:
3717+ thread.start()
3718+ if not args:
3719+ conn = create_connection()
3720+ try:
3721+ marker = ''
3722+ while True:
3723+ containers = [c['name']
3724+ for c in conn.get_account(marker=marker)[1]]
3725+ if not containers:
3726+ break
3727+ for container in containers:
3728+ container_queue.put(container)
3729+ marker = containers[-1]
3730+ except ClientException, err:
3731+ if err.http_status != 404:
3732+ raise
3733+ error_queue.put('Account not found')
3734+ elif len(args) == 1:
3735+ if '/' in args[0]:
3736+ print >> stderr, 'WARNING: / in container name; you might have ' \
3737+ 'meant %r instead of %r.' % \
3738+ (args[0].replace('/', ' ', 1), args[0])
3739+ _download_container(args[0], create_connection())
3740+ else:
3741+ if len(args) == 2:
3742+ obj = args[1]
3743+ object_queue.put((args[0], obj, options.out_file))
3744+ else:
3745+ for obj in args[1:]:
3746+ object_queue.put((args[0], obj))
3747+ while not container_queue.empty():
3748+ sleep(0.01)
3749+ for thread in container_threads:
3750+ thread.abort = True
3751+ while thread.isAlive():
3752+ thread.join(0.01)
3753+ put_errors_from_threads(container_threads, error_queue)
3754+ while not object_queue.empty():
3755+ sleep(0.01)
3756+ for thread in object_threads:
3757+ thread.abort = True
3758+ while thread.isAlive():
3759+ thread.join(0.01)
3760+ put_errors_from_threads(object_threads, error_queue)
3761+
3762+
3763+st_list_help = '''
3764+list [options] [container]
3765+ Lists the containers for the account or the objects for a container. -p or
3766+ --prefix is an option that will only list items beginning with that prefix.
3767+ -d or --delimiter is option (for container listings only) that will roll up
3768+ items with the given delimiter (see Cloud Files general documentation for
3769+ what this means).
3770+'''.strip('\n')
3771+
3772+
3773+def st_list(options, args, print_queue, error_queue):
3774+ parser.add_option('-p', '--prefix', dest='prefix', help='Will only list '
3775+ 'items beginning with the prefix')
3776+ parser.add_option('-d', '--delimiter', dest='delimiter', help='Will roll '
3777+ 'up items with the given delimiter (see Cloud Files general '
3778+ 'documentation for what this means)')
3779+ (options, args) = parse_args(parser, args)
3780+ args = args[1:]
3781+ if options.delimiter and not args:
3782+ exit('-d option only allowed for container listings')
3783+ if len(args) > 1:
3784+ error_queue.put('Usage: %s [options] %s' %
3785+ (basename(argv[0]), st_list_help))
3786+ return
3787+ conn = Connection(options.auth, options.user, options.key,
3788+ snet=options.snet)
3789+ try:
3790+ marker = ''
3791+ while True:
3792+ if not args:
3793+ items = \
3794+ conn.get_account(marker=marker, prefix=options.prefix)[1]
3795+ else:
3796+ items = conn.get_container(args[0], marker=marker,
3797+ prefix=options.prefix, delimiter=options.delimiter)[1]
3798+ if not items:
3799+ break
3800+ for item in items:
3801+ print_queue.put(item.get('name', item.get('subdir')))
3802+ marker = items[-1].get('name', items[-1].get('subdir'))
3803+ except ClientException, err:
3804+ if err.http_status != 404:
3805+ raise
3806+ if not args:
3807+ error_queue.put('Account not found')
3808+ else:
3809+ error_queue.put('Container %s not found' % repr(args[0]))
3810+
3811+
3812+st_stat_help = '''
3813+stat [container] [object]
3814+ Displays information for the account, container, or object depending on the
3815+ args given (if any).'''.strip('\n')
3816+
3817+
3818+def st_stat(options, args, print_queue, error_queue):
3819+ (options, args) = parse_args(parser, args)
3820+ args = args[1:]
3821+ conn = Connection(options.auth, options.user, options.key)
3822+ if not args:
3823+ try:
3824+ headers = conn.head_account()
3825+ if options.verbose > 1:
3826+ print_queue.put('''
3827+StorageURL: %s
3828+Auth Token: %s
3829+'''.strip('\n') % (conn.url, conn.token))
3830+ container_count = int(headers.get('x-account-container-count', 0))
3831+ object_count = int(headers.get('x-account-object-count', 0))
3832+ bytes_used = int(headers.get('x-account-bytes-used', 0))
3833+ print_queue.put('''
3834+ Account: %s
3835+Containers: %d
3836+ Objects: %d
3837+ Bytes: %d'''.strip('\n') % (conn.url.rsplit('/', 1)[-1], container_count,
3838+ object_count, bytes_used))
3839+ for key, value in headers.items():
3840+ if key.startswith('x-account-meta-'):
3841+ print_queue.put('%10s: %s' % ('Meta %s' %
3842+ key[len('x-account-meta-'):].title(), value))
3843+ for key, value in headers.items():
3844+ if not key.startswith('x-account-meta-') and key not in (
3845+ 'content-length', 'date', 'x-account-container-count',
3846+ 'x-account-object-count', 'x-account-bytes-used'):
3847+ print_queue.put(
3848+ '%10s: %s' % (key.title(), value))
3849+ except ClientException, err:
3850+ if err.http_status != 404:
3851+ raise
3852+ error_queue.put('Account not found')
3853+ elif len(args) == 1:
3854+ if '/' in args[0]:
3855+ print >> stderr, 'WARNING: / in container name; you might have ' \
3856+ 'meant %r instead of %r.' % \
3857+ (args[0].replace('/', ' ', 1), args[0])
3858+ try:
3859+ headers = conn.head_container(args[0])
3860+ object_count = int(headers.get('x-container-object-count', 0))
3861+ bytes_used = int(headers.get('x-container-bytes-used', 0))
3862+ print_queue.put('''
3863+ Account: %s
3864+Container: %s
3865+ Objects: %d
3866+ Bytes: %d
3867+ Read ACL: %s
3868+Write ACL: %s'''.strip('\n') % (conn.url.rsplit('/', 1)[-1], args[0],
3869+ object_count, bytes_used,
3870+ headers.get('x-container-read', ''),
3871+ headers.get('x-container-write', '')))
3872+ for key, value in headers.items():
3873+ if key.startswith('x-container-meta-'):
3874+ print_queue.put('%9s: %s' % ('Meta %s' %
3875+ key[len('x-container-meta-'):].title(), value))
3876+ for key, value in headers.items():
3877+ if not key.startswith('x-container-meta-') and key not in (
3878+ 'content-length', 'date', 'x-container-object-count',
3879+ 'x-container-bytes-used', 'x-container-read',
3880+ 'x-container-write'):
3881+ print_queue.put(
3882+ '%9s: %s' % (key.title(), value))
3883+ except ClientException, err:
3884+ if err.http_status != 404:
3885+ raise
3886+ error_queue.put('Container %s not found' % repr(args[0]))
3887+ elif len(args) == 2:
3888+ try:
3889+ headers = conn.head_object(args[0], args[1])
3890+ print_queue.put('''
3891+ Account: %s
3892+ Container: %s
3893+ Object: %s
3894+ Content Type: %s'''.strip('\n') % (conn.url.rsplit('/', 1)[-1], args[0],
3895+ args[1], headers.get('content-type')))
3896+ if 'content-length' in headers:
3897+ print_queue.put('Content Length: %s' %
3898+ headers['content-length'])
3899+ if 'last-modified' in headers:
3900+ print_queue.put(' Last Modified: %s' %
3901+ headers['last-modified'])
3902+ if 'etag' in headers:
3903+ print_queue.put(' ETag: %s' % headers['etag'])
3904+ if 'x-object-manifest' in headers:
3905+ print_queue.put(' Manifest: %s' %
3906+ headers['x-object-manifest'])
3907+ for key, value in headers.items():
3908+ if key.startswith('x-object-meta-'):
3909+ print_queue.put('%14s: %s' % ('Meta %s' %
3910+ key[len('x-object-meta-'):].title(), value))
3911+ for key, value in headers.items():
3912+ if not key.startswith('x-object-meta-') and key not in (
3913+ 'content-type', 'content-length', 'last-modified',
3914+ 'etag', 'date', 'x-object-manifest'):
3915+ print_queue.put(
3916+ '%14s: %s' % (key.title(), value))
3917+ except ClientException, err:
3918+ if err.http_status != 404:
3919+ raise
3920+ error_queue.put('Object %s not found' %
3921+ repr('%s/%s' % (args[0], args[1])))
3922+ else:
3923+ error_queue.put('Usage: %s [options] %s' %
3924+ (basename(argv[0]), st_stat_help))
3925+
3926+
3927+st_post_help = '''
3928+post [options] [container] [object]
3929+ Updates meta information for the account, container, or object depending on
3930+ the args given. If the container is not found, it will be created
3931+ automatically; but this is not true for accounts and objects. Containers
3932+ also allow the -r (or --read-acl) and -w (or --write-acl) options. The -m
3933+ or --meta option is allowed on all and used to define the user meta data
3934+ items to set in the form Name:Value. This option can be repeated. Example:
3935+ post -m Color:Blue -m Size:Large'''.strip('\n')
3936+
3937+
3938+def st_post(options, args, print_queue, error_queue):
3939+ parser.add_option('-r', '--read-acl', dest='read_acl', help='Sets the '
3940+ 'Read ACL for containers. Quick summary of ACL syntax: .r:*, '
3941+ '.r:-.example.com, .r:www.example.com, account1, account2:user2')
3942+ parser.add_option('-w', '--write-acl', dest='write_acl', help='Sets the '
3943+ 'Write ACL for containers. Quick summary of ACL syntax: account1, '
3944+ 'account2:user2')
3945+ parser.add_option('-m', '--meta', action='append', dest='meta', default=[],
3946+ help='Sets a meta data item with the syntax name:value. This option '
3947+ 'may be repeated. Example: -m Color:Blue -m Size:Large')
3948+ (options, args) = parse_args(parser, args)
3949+ args = args[1:]
3950+ if (options.read_acl or options.write_acl) and not args:
3951+ exit('-r and -w options only allowed for containers')
3952+ conn = Connection(options.auth, options.user, options.key)
3953+ if not args:
3954+ headers = {}
3955+ for item in options.meta:
3956+ split_item = item.split(':')
3957+ headers['X-Account-Meta-' + split_item[0]] = \
3958+ len(split_item) > 1 and split_item[1]
3959+ try:
3960+ conn.post_account(headers=headers)
3961+ except ClientException, err:
3962+ if err.http_status != 404:
3963+ raise
3964+ error_queue.put('Account not found')
3965+ elif len(args) == 1:
3966+ if '/' in args[0]:
3967+ print >> stderr, 'WARNING: / in container name; you might have ' \
3968+ 'meant %r instead of %r.' % \
3969+ (args[0].replace('/', ' ', 1), args[0])
3970+ headers = {}
3971+ for item in options.meta:
3972+ split_item = item.split(':')
3973+ headers['X-Container-Meta-' + split_item[0]] = \
3974+ len(split_item) > 1 and split_item[1]
3975+ if options.read_acl is not None:
3976+ headers['X-Container-Read'] = options.read_acl
3977+ if options.write_acl is not None:
3978+ headers['X-Container-Write'] = options.write_acl
3979+ try:
3980+ conn.post_container(args[0], headers=headers)
3981+ except ClientException, err:
3982+ if err.http_status != 404:
3983+ raise
3984+ conn.put_container(args[0], headers=headers)
3985+ elif len(args) == 2:
3986+ headers = {}
3987+ for item in options.meta:
3988+ split_item = item.split(':')
3989+ headers['X-Object-Meta-' + split_item[0]] = \
3990+ len(split_item) > 1 and split_item[1]
3991+ try:
3992+ conn.post_object(args[0], args[1], headers=headers)
3993+ except ClientException, err:
3994+ if err.http_status != 404:
3995+ raise
3996+ error_queue.put('Object %s not found' %
3997+ repr('%s/%s' % (args[0], args[1])))
3998+ else:
3999+ error_queue.put('Usage: %s [options] %s' %
4000+ (basename(argv[0]), st_post_help))
4001+
4002+
4003+st_upload_help = '''
4004+upload [options] container file_or_directory [file_or_directory] [...]
4005+ Uploads to the given container the files and directories specified by the
4006+ remaining args. -c or --changed is an option that will only upload files
4007+ that have changed since the last upload. -S <size> or --segment-size <size>
4008+ and --leave-segments are options as well (see --help for more).
4009+'''.strip('\n')
4010+
4011+
4012+def st_upload(options, args, print_queue, error_queue):
4013+ parser.add_option('-c', '--changed', action='store_true', dest='changed',
4014+ default=False, help='Will only upload files that have changed since '
4015+ 'the last upload')
4016+ parser.add_option('-S', '--segment-size', dest='segment_size', help='Will '
4017+ 'upload files in segments no larger than <size> and then create a '
4018+ '"manifest" file that will download all the segments as if it were '
4019+ 'the original file. The segments will be uploaded to a '
4020+ '<container>_segments container so as to not pollute the main '
4021+ '<container> listings.')
4022+ parser.add_option('', '--leave-segments', action='store_true',
4023+ dest='leave_segments', default=False, help='Indicates that you want '
4024+ 'the older segments of manifest objects left alone (in the case of '
4025+ 'overwrites)')
4026+ (options, args) = parse_args(parser, args)
4027+ args = args[1:]
4028+ if len(args) < 2:
4029+ error_queue.put('Usage: %s [options] %s' %
4030+ (basename(argv[0]), st_upload_help))
4031+ return
4032+ object_queue = Queue(10000)
4033+
4034+ def _segment_job(job, conn):
4035+ if job.get('delete', False):
4036+ conn.delete_object(job['container'], job['obj'])
4037+ else:
4038+ fp = open(job['path'], 'rb')
4039+ fp.seek(job['segment_start'])
4040+ conn.put_object(job.get('container', args[0] + '_segments'),
4041+ job['obj'], fp, content_length=job['segment_size'])
4042+ if options.verbose and 'log_line' in job:
4043+ if conn.attempts > 1:
4044+ print_queue.put('%s [after %d attempts]' %
4045+ (job['log_line'], conn.attempts))
4046+ else:
4047+ print_queue.put(job['log_line'])
4048+
4049+ def _object_job(job, conn):
4050+ path = job['path']
4051+ container = job.get('container', args[0])
4052+ dir_marker = job.get('dir_marker', False)
4053+ try:
4054+ obj = path
4055+ if obj.startswith('./') or obj.startswith('.\\'):
4056+ obj = obj[2:]
4057+ put_headers = {'x-object-meta-mtime': str(getmtime(path))}
4058+ if dir_marker:
4059+ if options.changed:
4060+ try:
4061+ headers = conn.head_object(container, obj)
4062+ ct = headers.get('content-type')
4063+ cl = int(headers.get('content-length'))
4064+ et = headers.get('etag')
4065+ mt = headers.get('x-object-meta-mtime')
4066+ if ct.split(';', 1)[0] == 'text/directory' and \
4067+ cl == 0 and \
4068+ et == 'd41d8cd98f00b204e9800998ecf8427e' and \
4069+ mt == put_headers['x-object-meta-mtime']:
4070+ return
4071+ except ClientException, err:
4072+ if err.http_status != 404:
4073+ raise
4074+ conn.put_object(container, obj, '', content_length=0,
4075+ content_type='text/directory',
4076+ headers=put_headers)
4077+ else:
4078+ # We need to HEAD all objects now in case we're overwriting a
4079+ # manifest object and need to delete the old segments
4080+ # ourselves.
4081+ old_manifest = None
4082+ if options.changed or not options.leave_segments:
4083+ try:
4084+ headers = conn.head_object(container, obj)
4085+ cl = int(headers.get('content-length'))
4086+ mt = headers.get('x-object-meta-mtime')
4087+ if options.changed and cl == getsize(path) and \
4088+ mt == put_headers['x-object-meta-mtime']:
4089+ return
4090+ if not options.leave_segments:
4091+ old_manifest = headers.get('x-object-manifest')
4092+ except ClientException, err:
4093+ if err.http_status != 404:
4094+ raise
4095+ if options.segment_size and \
4096+ getsize(path) < options.segment_size:
4097+ full_size = getsize(path)
4098+ segment_queue = Queue(10000)
4099+ segment_threads = [QueueFunctionThread(segment_queue,
4100+ _segment_job, create_connection()) for _junk in
4101+ xrange(10)]
4102+ for thread in segment_threads:
4103+ thread.start()
4104+ segment = 0
4105+ segment_start = 0
4106+ while segment_start < full_size:
4107+ segment_size = int(options.segment_size)
4108+ if segment_start + segment_size > full_size:
4109+ segment_size = full_size - segment_start
4110+ segment_queue.put({'path': path,
4111+ 'obj': '%s/%s/%s/%08d' % (obj,
4112+ put_headers['x-object-meta-mtime'], full_size,
4113+ segment),
4114+ 'segment_start': segment_start,
4115+ 'segment_size': segment_size,
4116+ 'log_line': '%s segment %s' % (obj, segment)})
4117+ segment += 1
4118+ segment_start += segment_size
4119+ while not segment_queue.empty():
4120+ sleep(0.01)
4121+ for thread in segment_threads:
4122+ thread.abort = True
4123+ while thread.isAlive():
4124+ thread.join(0.01)
4125+ if put_errors_from_threads(segment_threads, error_queue):
4126+ raise ClientException('Aborting manifest creation '
4127+ 'because not all segments could be uploaded. %s/%s'
4128+ % (container, obj))
4129+ new_object_manifest = '%s_segments/%s/%s/%s/' % (
4130+ container, obj, put_headers['x-object-meta-mtime'],
4131+ full_size)
4132+ if old_manifest == new_object_manifest:
4133+ old_manifest = None
4134+ put_headers['x-object-manifest'] = new_object_manifest
4135+ conn.put_object(container, obj, '', content_length=0,
4136+ headers=put_headers)
4137+ else:
4138+ conn.put_object(container, obj, open(path, 'rb'),
4139+ content_length=getsize(path), headers=put_headers)
4140+ if old_manifest:
4141+ segment_queue = Queue(10000)
4142+ scontainer, sprefix = old_manifest.split('/', 1)
4143+ for delobj in conn.get_container(scontainer,
4144+ prefix=sprefix)[1]:
4145+ segment_queue.put({'delete': True,
4146+ 'container': scontainer, 'obj': delobj['name']})
4147+ if not segment_queue.empty():
4148+ segment_threads = [QueueFunctionThread(segment_queue,
4149+ _segment_job, create_connection()) for _junk in
4150+ xrange(10)]
4151+ for thread in segment_threads:
4152+ thread.start()
4153+ while not segment_queue.empty():
4154+ sleep(0.01)
4155+ for thread in segment_threads:
4156+ thread.abort = True
4157+ while thread.isAlive():
4158+ thread.join(0.01)
4159+ put_errors_from_threads(segment_threads, error_queue)
4160+ if options.verbose:
4161+ if conn.attempts > 1:
4162+ print_queue.put(
4163+ '%s [after %d attempts]' % (obj, conn.attempts))
4164+ else:
4165+ print_queue.put(obj)
4166+ except OSError, err:
4167+ if err.errno != ENOENT:
4168+ raise
4169+ error_queue.put('Local file %s not found' % repr(path))
4170+
4171+ def _upload_dir(path):
4172+ names = listdir(path)
4173+ if not names:
4174+ object_queue.put({'path': path, 'dir_marker': True})
4175+ else:
4176+ for name in listdir(path):
4177+ subpath = join(path, name)
4178+ if isdir(subpath):
4179+ _upload_dir(subpath)
4180+ else:
4181+ object_queue.put({'path': subpath})
4182+
4183+ url, token = get_auth(options.auth, options.user, options.key,
4184+ snet=options.snet)
4185+ create_connection = lambda: Connection(options.auth, options.user,
4186+ options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
4187+ object_threads = [QueueFunctionThread(object_queue, _object_job,
4188+ create_connection()) for _junk in xrange(10)]
4189+ for thread in object_threads:
4190+ thread.start()
4191+ conn = create_connection()
4192+ # Try to create the container, just in case it doesn't exist. If this
4193+ # fails, it might just be because the user doesn't have container PUT
4194+ # permissions, so we'll ignore any error. If there's really a problem,
4195+ # it'll surface on the first object PUT.
4196+ try:
4197+ conn.put_container(args[0])
4198+ if options.segment_size is not None:
4199+ conn.put_container(args[0] + '_segments')
4200+ except Exception:
4201+ pass
4202+ try:
4203+ for arg in args[1:]:
4204+ if isdir(arg):
4205+ _upload_dir(arg)
4206+ else:
4207+ object_queue.put({'path': arg})
4208+ while not object_queue.empty():
4209+ sleep(0.01)
4210+ for thread in object_threads:
4211+ thread.abort = True
4212+ while thread.isAlive():
4213+ thread.join(0.01)
4214+ put_errors_from_threads(object_threads, error_queue)
4215+ except ClientException, err:
4216+ if err.http_status != 404:
4217+ raise
4218+ error_queue.put('Account not found')
4219+
4220+
4221+def parse_args(parser, args, enforce_requires=True):
4222+ if not args:
4223+ args = ['-h']
4224+ (options, args) = parser.parse_args(args)
4225+ if enforce_requires and \
4226+ not (options.auth and options.user and options.key):
4227+ exit('''
4228+Requires ST_AUTH, ST_USER, and ST_KEY environment variables be set or
4229+overridden with -A, -U, or -K.'''.strip('\n'))
4230+ return options, args
4231+
4232+
4233+if __name__ == '__main__':
4234+ parser = OptionParser(version='%prog 1.0', usage='''
4235+Usage: %%prog <command> [options] [args]
4236+
4237+Commands:
4238+ %(st_stat_help)s
4239+ %(st_list_help)s
4240+ %(st_upload_help)s
4241+ %(st_post_help)s
4242+ %(st_download_help)s
4243+ %(st_delete_help)s
4244+
4245+Example:
4246+ %%prog -A https://auth.api.rackspacecloud.com/v1.0 -U user -K key stat
4247+'''.strip('\n') % globals())
4248+ parser.add_option('-s', '--snet', action='store_true', dest='snet',
4249+ default=False, help='Use SERVICENET internal network')
4250+ parser.add_option('-v', '--verbose', action='count', dest='verbose',
4251+ default=1, help='Print more info')
4252+ parser.add_option('-q', '--quiet', action='store_const', dest='verbose',
4253+ const=0, default=1, help='Suppress status output')
4254+ parser.add_option('-A', '--auth', dest='auth',
4255+ default=environ.get('ST_AUTH'),
4256+ help='URL for obtaining an auth token')
4257+ parser.add_option('-U', '--user', dest='user',
4258+ default=environ.get('ST_USER'),
4259+ help='User name for obtaining an auth token')
4260+ parser.add_option('-K', '--key', dest='key',
4261+ default=environ.get('ST_KEY'),
4262+ help='Key for obtaining an auth token')
4263+ parser.disable_interspersed_args()
4264+ (options, args) = parse_args(parser, argv[1:], enforce_requires=False)
4265+ parser.enable_interspersed_args()
4266+
4267+ commands = ('delete', 'download', 'list', 'post', 'stat', 'upload')
4268+ if not args or args[0] not in commands:
4269+ parser.print_usage()
4270+ if args:
4271+ exit('no such command: %s' % args[0])
4272+ exit()
4273+
4274+ print_queue = Queue(10000)
4275+
4276+ def _print(item):
4277+ if isinstance(item, unicode):
4278+ item = item.encode('utf8')
4279+ print item
4280+
4281+ print_thread = QueueFunctionThread(print_queue, _print)
4282+ print_thread.start()
4283+
4284+ error_queue = Queue(10000)
4285+
4286+ def _error(item):
4287+ if isinstance(item, unicode):
4288+ item = item.encode('utf8')
4289+ print >> stderr, item
4290+
4291+ error_thread = QueueFunctionThread(error_queue, _error)
4292+ error_thread.start()
4293+
4294+ try:
4295+ parser.usage = globals()['st_%s_help' % args[0]]
4296+ try:
4297+ globals()['st_%s' % args[0]](parser, argv[1:], print_queue,
4298+ error_queue)
4299+ except (ClientException, HTTPException, socket.error), err:
4300+ error_queue.put(str(err))
4301+ while not print_queue.empty():
4302+ sleep(0.01)
4303+ print_thread.abort = True
4304+ while print_thread.isAlive():
4305+ print_thread.join(0.01)
4306+ while not error_queue.empty():
4307+ sleep(0.01)
4308+ error_thread.abort = True
4309+ while error_thread.isAlive():
4310+ error_thread.join(0.01)
4311+ except (SystemExit, Exception):
4312+ for thread in threading_enumerate():
4313+ thread.abort = True
4314+ raise
4315
4316=== modified file 'doc/source/admin_guide.rst'
4317--- doc/source/admin_guide.rst 2011-03-31 22:32:41 +0000
4318+++ doc/source/admin_guide.rst 2011-06-15 09:24:34 +0000
4319@@ -222,22 +222,6 @@
4320 Sample represents 1.00% of the object partition space
4321
4322
4323-------------------------------------
4324-Additional Cleanup Script for Swauth
4325-------------------------------------
4326-
4327-With Swauth, you'll want to install a cronjob to clean up any
4328-orphaned expired tokens. These orphaned tokens can occur when a "stampede"
4329-occurs where a single user authenticates several times concurrently. Generally,
4330-these orphaned tokens don't pose much of an issue, but it's good to clean them
4331-up once a "token life" period (default: 1 day or 86400 seconds).
4332-
4333-This should be as simple as adding `swauth-cleanup-tokens -A
4334-https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
4335-entry on one of the proxies that is running Swauth; but run
4336-`swauth-cleanup-tokens` with no arguments for detailed help on the options
4337-available.
4338-
4339 ------------------------
4340 Debugging Tips and Tools
4341 ------------------------
4342
4343=== modified file 'doc/source/deployment_guide.rst'
4344--- doc/source/deployment_guide.rst 2011-01-25 00:28:22 +0000
4345+++ doc/source/deployment_guide.rst 2011-06-15 09:24:34 +0000
4346@@ -547,37 +547,23 @@
4347 node error limited
4348 allow_account_management false Whether account PUTs and DELETEs
4349 are even callable
4350+account_autocreate false If set to 'true' authorized
4351+ accounts that do not yet exist
4352+ within the Swift cluster will
4353+ be automatically created.
4354 ============================ =============== =============================
4355
4356-[auth]
4357-
4358-============ =================================== ========================
4359-Option Default Description
4360------------- ----------------------------------- ------------------------
4361-use Entry point for paste.deploy
4362- to use for auth. To
4363- use the swift dev auth,
4364- set to:
4365- `egg:swift#auth`
4366-ip 127.0.0.1 IP address of auth
4367- server
4368-port 11000 Port of auth server
4369-ssl False If True, use SSL to
4370- connect to auth
4371-node_timeout 10 Request timeout
4372-============ =================================== ========================
4373-
4374-[swauth]
4375+[tempauth]
4376
4377 ===================== =============================== =======================
4378 Option Default Description
4379 --------------------- ------------------------------- -----------------------
4380 use Entry point for
4381 paste.deploy to use for
4382- auth. To use the swauth
4383+ auth. To use tempauth
4384 set to:
4385- `egg:swift#swauth`
4386-set log_name auth-server Label used when logging
4387+ `egg:swift#tempauth`
4388+set log_name tempauth Label used when logging
4389 set log_facility LOG_LOCAL0 Syslog log facility
4390 set log_level INFO Log level
4391 set log_headers True If True, log headers in
4392@@ -593,16 +579,39 @@
4393 reserves anything
4394 beginning with the
4395 letter `v`.
4396-default_swift_cluster local#http://127.0.0.1:8080/v1 The default Swift
4397- cluster to place newly
4398- created accounts on.
4399 token_life 86400 The number of seconds a
4400 token is valid.
4401-node_timeout 10 Request timeout
4402-super_admin_key None The key for the
4403- .super_admin account.
4404 ===================== =============================== =======================
4405
4406+Additionally, you need to list all the accounts/users you want here. The format
4407+is::
4408+
4409+ user_<account>_<user> = <key> [group] [group] [...] [storage_url]
4410+
4411+There are special groups of::
4412+
4413+ .reseller_admin = can do anything to any account for this auth
4414+ .admin = can do anything within the account
4415+
4416+If neither of these groups are specified, the user can only access containers
4417+that have been explicitly allowed for them by a .admin or .reseller_admin.
4418+
4419+The trailing optional storage_url allows you to specify an alternate url to
4420+hand back to the user upon authentication. If not specified, this defaults to::
4421+
4422+ http[s]://<ip>:<port>/v1/<reseller_prefix>_<account>
4423+
4424+Where http or https depends on whether cert_file is specified in the [DEFAULT]
4425+section, <ip> and <port> are based on the [DEFAULT] section's bind_ip and
4426+bind_port (falling back to 127.0.0.1 and 8080), <reseller_prefix> is from this
4427+section, and <account> is from the user_<account>_<user> name.
4428+
4429+Here are example entries, required for running the tests::
4430+
4431+ user_admin_admin = admin .admin .reseller_admin
4432+ user_test_tester = testing .admin
4433+ user_test2_tester2 = testing2 .admin
4434+ user_test_tester3 = testing3
4435
4436 ------------------------
4437 Memcached Considerations
4438
4439=== modified file 'doc/source/development_auth.rst'
4440--- doc/source/development_auth.rst 2011-03-14 02:56:37 +0000
4441+++ doc/source/development_auth.rst 2011-06-15 09:24:34 +0000
4442@@ -6,7 +6,7 @@
4443 Creating Your Own Auth Server and Middleware
4444 --------------------------------------------
4445
4446-The included swift/common/middleware/swauth.py is a good example of how to
4447+The included swift/common/middleware/tempauth.py is a good example of how to
4448 create an auth subsystem with proxy server auth middleware. The main points are
4449 that the auth middleware can reject requests up front, before they ever get to
4450 the Swift Proxy application, and afterwards when the proxy issues callbacks to
4451@@ -27,7 +27,7 @@
4452 environ['REMOTE_USER'] set to the authenticated user string but often more
4453 information is needed than just that.
4454
4455-The included Swauth will set the REMOTE_USER to a comma separated list of
4456+The included TempAuth will set the REMOTE_USER to a comma separated list of
4457 groups the user belongs to. The first group will be the "user's group", a group
4458 that only the user belongs to. The second group will be the "account's group",
4459 a group that includes all users for that auth account (different than the
4460@@ -37,7 +37,7 @@
4461
4462 It is highly recommended that authentication server implementers prefix their
4463 tokens and Swift storage accounts they create with a configurable reseller
4464-prefix (`AUTH_` by default with the included Swauth). This prefix will avoid
4465+prefix (`AUTH_` by default with the included TempAuth). This prefix will avoid
4466 conflicts with other authentication servers that might be using the same
4467 Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
4468 until one validates a token or all fail.
4469@@ -46,14 +46,14 @@
4470 '.' as that is reserved for internal Swift use (such as the .r for referrer
4471 designations as you'll see later).
4472
4473-Example Authentication with Swauth:
4474+Example Authentication with TempAuth:
4475
4476- * Token AUTH_tkabcd is given to the Swauth middleware in a request's
4477+ * Token AUTH_tkabcd is given to the TempAuth middleware in a request's
4478 X-Auth-Token header.
4479- * The Swauth middleware validates the token AUTH_tkabcd and discovers
4480+ * The TempAuth middleware validates the token AUTH_tkabcd and discovers
4481 it matches the "tester" user within the "test" account for the storage
4482 account "AUTH_storage_xyz".
4483- * The Swauth server sets the REMOTE_USER to
4484+ * The TempAuth middleware sets the REMOTE_USER to
4485 "test:tester,test,AUTH_storage_xyz"
4486 * Now this user will have full access (via authorization procedures later)
4487 to the AUTH_storage_xyz Swift storage account and access to containers in
4488
4489=== modified file 'doc/source/development_saio.rst'
4490--- doc/source/development_saio.rst 2011-05-18 15:26:52 +0000
4491+++ doc/source/development_saio.rst 2011-06-15 09:24:34 +0000
4492@@ -265,16 +265,18 @@
4493 log_facility = LOG_LOCAL1
4494
4495 [pipeline:main]
4496- pipeline = healthcheck cache swauth proxy-server
4497+ pipeline = healthcheck cache tempauth proxy-server
4498
4499 [app:proxy-server]
4500 use = egg:swift#proxy
4501 allow_account_management = true
4502
4503- [filter:swauth]
4504- use = egg:swift#swauth
4505- # Highly recommended to change this.
4506- super_admin_key = swauthkey
4507+ [filter:tempauth]
4508+ use = egg:swift#tempauth
4509+ user_admin_admin = admin .admin .reseller_admin
4510+ user_test_tester = testing .admin
4511+ user_test2_tester2 = testing2 .admin
4512+ user_test_tester3 = testing3
4513
4514 [filter:healthcheck]
4515 use = egg:swift#healthcheck
4516@@ -558,8 +560,10 @@
4517 ------------------------------------
4518
4519 #. Create `~/bin/resetswift.`
4520- If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
4521- If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
4522+
4523+ If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
4524+
4525+ If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
4526
4527 #!/bin/bash
4528
4529@@ -608,18 +612,6 @@
4530
4531 swift-init main start
4532
4533- #. Create `~/bin/recreateaccounts`::
4534-
4535- #!/bin/bash
4536-
4537- # Replace swauthkey with whatever your super_admin key is (recorded in
4538- # /etc/swift/proxy-server.conf).
4539- swauth-prep -K swauthkey
4540- swauth-add-user -K swauthkey -a test tester testing
4541- swauth-add-user -K swauthkey -a test2 tester2 testing2
4542- swauth-add-user -K swauthkey test tester3 testing3
4543- swauth-add-user -K swauthkey -a -r reseller reseller reseller
4544-
4545 #. Create `~/bin/startrest`::
4546
4547 #!/bin/bash
4548@@ -633,7 +625,7 @@
4549 #. `recreateaccounts`
4550 #. Get an `X-Storage-Url` and `X-Auth-Token`: ``curl -v -H 'X-Storage-User: test:tester' -H 'X-Storage-Pass: testing' http://127.0.0.1:8080/auth/v1.0``
4551 #. Check that you can GET account: ``curl -v -H 'X-Auth-Token: <token-from-x-auth-token-above>' <url-from-x-storage-url-above>``
4552- #. Check that `st` works: `st -A http://127.0.0.1:8080/auth/v1.0 -U test:tester -K testing stat`
4553+ #. Check that `swift` works: `swift -A http://127.0.0.1:8080/auth/v1.0 -U test:tester -K testing stat`
4554 #. `cp ~/swift/trunk/test/functional/sample.conf /etc/swift/func_test.conf`
4555 #. `cd ~/swift/trunk; ./.functests` (Note: functional tests will first delete
4556 everything in the configured accounts.)
4557
4558=== modified file 'doc/source/howto_installmultinode.rst'
4559--- doc/source/howto_installmultinode.rst 2011-05-17 03:59:57 +0000
4560+++ doc/source/howto_installmultinode.rst 2011-06-15 09:24:34 +0000
4561@@ -13,7 +13,7 @@
4562 Basic architecture and terms
4563 ----------------------------
4564 - *node* - a host machine running one or more Swift services
4565-- *Proxy node* - node that runs Proxy services; also runs Swauth
4566+- *Proxy node* - node that runs Proxy services; also runs TempAuth
4567 - *Storage node* - node that runs Account, Container, and Object services
4568 - *ring* - a set of mappings of Swift data to physical devices
4569
4570@@ -23,7 +23,7 @@
4571
4572 - Runs the swift-proxy-server processes which proxy requests to the
4573 appropriate Storage nodes. The proxy server will also contain
4574- the Swauth service as WSGI middleware.
4575+ the TempAuth service as WSGI middleware.
4576
4577 - five Storage nodes
4578
4579@@ -130,17 +130,15 @@
4580 user = swift
4581
4582 [pipeline:main]
4583- pipeline = healthcheck cache swauth proxy-server
4584+ pipeline = healthcheck cache tempauth proxy-server
4585
4586 [app:proxy-server]
4587 use = egg:swift#proxy
4588 allow_account_management = true
4589
4590- [filter:swauth]
4591- use = egg:swift#swauth
4592- default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
4593- # Highly recommended to change this key to something else!
4594- super_admin_key = swauthkey
4595+ [filter:tempauth]
4596+ use = egg:swift#tempauth
4597+ user_system_root = testpass .admin https://$PROXY_LOCAL_NET_IP:8080/v1/AUTH_system
4598
4599 [filter:healthcheck]
4600 use = egg:swift#healthcheck
4601@@ -366,16 +364,6 @@
4602
4603 You run these commands from the Proxy node.
4604
4605-#. Create a user with administrative privileges (account = system,
4606- username = root, password = testpass). Make sure to replace
4607- ``swauthkey`` with whatever super_admin key you assigned in
4608- the proxy-server.conf file
4609- above. *Note: None of the values of
4610- account, username, or password are special - they can be anything.*::
4611-
4612- swauth-prep -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey
4613- swauth-add-user -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey -a system root testpass
4614-
4615 #. Get an X-Storage-Url and X-Auth-Token::
4616
4617 curl -k -v -H 'X-Storage-User: system:root' -H 'X-Storage-Pass: testpass' https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0
4618@@ -384,34 +372,34 @@
4619
4620 curl -k -v -H 'X-Auth-Token: <token-from-x-auth-token-above>' <url-from-x-storage-url-above>
4621
4622-#. Check that ``st`` works (at this point, expect zero containers, zero objects, and zero bytes)::
4623-
4624- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass stat
4625-
4626-#. Use ``st`` to upload a few files named 'bigfile[1-2].tgz' to a container named 'myfiles'::
4627-
4628- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile1.tgz
4629- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile2.tgz
4630-
4631-#. Use ``st`` to download all files from the 'myfiles' container::
4632-
4633- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass download myfiles
4634-
4635-#. Use ``st`` to save a backup of your builder files to a container named 'builders'. Very important not to lose your builders!::
4636-
4637- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass upload builders /etc/swift/*.builder
4638-
4639-#. Use ``st`` to list your containers::
4640-
4641- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass list
4642-
4643-#. Use ``st`` to list the contents of your 'builders' container::
4644-
4645- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass list builders
4646-
4647-#. Use ``st`` to download all files from the 'builders' container::
4648-
4649- st -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass download builders
4650+#. Check that ``swift`` works (at this point, expect zero containers, zero objects, and zero bytes)::
4651+
4652+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass stat
4653+
4654+#. Use ``swift`` to upload a few files named 'bigfile[1-2].tgz' to a container named 'myfiles'::
4655+
4656+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile1.tgz
4657+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile2.tgz
4658+
4659+#. Use ``swift`` to download all files from the 'myfiles' container::
4660+
4661+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass download myfiles
4662+
4663+#. Use ``swift`` to save a backup of your builder files to a container named 'builders'. Very important not to lose your builders!::
4664+
4665+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass upload builders /etc/swift/*.builder
4666+
4667+#. Use ``swift`` to list your containers::
4668+
4669+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass list
4670+
4671+#. Use ``swift`` to list the contents of your 'builders' container::
4672+
4673+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass list builders
4674+
4675+#. Use ``swift`` to download all files from the 'builders' container::
4676+
4677+ swift -A https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0 -U system:root -K testpass download builders
4678
4679 .. _add-proxy-server:
4680
4681@@ -430,45 +418,16 @@
4682 use = egg:swift#memcache
4683 memcache_servers = $PROXY_LOCAL_NET_IP:11211
4684
4685-#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
4686-
4687- [filter:swauth]
4688- use = egg:swift#swauth
4689- default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
4690- # Highly recommended to change this key to something else!
4691- super_admin_key = swauthkey
4692-
4693-#. The above will make new accounts with the new default_swift_cluster URL, however it won't change any existing accounts. You can change a service URL for existing accounts with::
4694-
4695- First retreve what the URL was::
4696-
4697- swauth-list -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account>
4698-
4699- And then update it with::
4700-
4701- swauth-set-account-service -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account> storage local <new_url_for_the_account>
4702-
4703- Make the <new_url_for_the_account> look just like it's original URL but with the host:port update you want.
4704+#. Change the storage url for any users to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
4705+
4706+ [filter:tempauth]
4707+ use = egg:swift#tempauth
4708+ user_system_root = testpass .admin http[s]://<LOAD_BALANCER_HOSTNAME>:<PORT>/v1/AUTH_system
4709
4710 #. Next, copy all the ring information to all the nodes, including your new proxy nodes, and ensure the ring info gets to all the storage nodes as well.
4711
4712 #. After you sync all the nodes, make sure the admin has the keys in /etc/swift and the ownership for the ring file is correct.
4713
4714-Additional Cleanup Script for Swauth
4715-------------------------------------
4716-
4717-With Swauth, you'll want to install a cronjob to clean up any
4718-orphaned expired tokens. These orphaned tokens can occur when a "stampede"
4719-occurs where a single user authenticates several times concurrently. Generally,
4720-these orphaned tokens don't pose much of an issue, but it's good to clean them
4721-up once a "token life" period (default: 1 day or 86400 seconds).
4722-
4723-This should be as simple as adding `swauth-cleanup-tokens -A
4724-https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
4725-entry on one of the proxies that is running Swauth; but run
4726-`swauth-cleanup-tokens` with no arguments for detailed help on the options
4727-available.
4728-
4729 Troubleshooting Notes
4730 ---------------------
4731 If you see problems, look in var/log/syslog (or messages on some distros).
4732
4733=== modified file 'doc/source/misc.rst'
4734--- doc/source/misc.rst 2011-03-24 03:37:07 +0000
4735+++ doc/source/misc.rst 2011-06-15 09:24:34 +0000
4736@@ -33,12 +33,12 @@
4737 :members:
4738 :show-inheritance:
4739
4740-.. _common_swauth:
4741-
4742-Swauth
4743-======
4744-
4745-.. automodule:: swift.common.middleware.swauth
4746+.. _common_tempauth:
4747+
4748+TempAuth
4749+========
4750+
4751+.. automodule:: swift.common.middleware.tempauth
4752 :members:
4753 :show-inheritance:
4754
4755
4756=== modified file 'doc/source/overview_auth.rst'
4757--- doc/source/overview_auth.rst 2011-03-14 02:56:37 +0000
4758+++ doc/source/overview_auth.rst 2011-06-15 09:24:34 +0000
4759@@ -2,9 +2,9 @@
4760 The Auth System
4761 ===============
4762
4763-------
4764-Swauth
4765-------
4766+--------
4767+TempAuth
4768+--------
4769
4770 The auth system for Swift is loosely based on the auth system from the existing
4771 Rackspace architecture -- actually from a few existing auth systems -- and is
4772@@ -27,7 +27,7 @@
4773 Swift will make calls to the auth system, giving the auth token to be
4774 validated. For a valid token, the auth system responds with an overall
4775 expiration in seconds from now. Swift will cache the token up to the expiration
4776-time. The included Swauth also has the concept of admin and non-admin users
4777+time. The included TempAuth also has the concept of admin and non-admin users
4778 within an account. Admin users can do anything within the account. Non-admin
4779 users can only perform operations per container based on the container's
4780 X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
4781@@ -40,152 +40,9 @@
4782 Extending Auth
4783 --------------
4784
4785-Swauth is written as wsgi middleware, so implementing your own auth is as easy
4786-as writing new wsgi middleware, and plugging it in to the proxy server.
4787+TempAuth is written as wsgi middleware, so implementing your own auth is as
4788+easy as writing new wsgi middleware, and plugging it in to the proxy server.
4789+The KeyStone project and the Swauth project are examples of additional auth
4790+services.
4791
4792 Also, see :doc:`development_auth`.
4793-
4794-
4795---------------
4796-Swauth Details
4797---------------
4798-
4799-The Swauth system is included at swift/common/middleware/swauth.py; a scalable
4800-authentication and authorization system that uses Swift itself as its backing
4801-store. This section will describe how it stores its data.
4802-
4803-At the topmost level, the auth system has its own Swift account it stores its
4804-own account information within. This Swift account is known as
4805-self.auth_account in the code and its name is in the format
4806-self.reseller_prefix + ".auth". In this text, we'll refer to this account as
4807-<auth_account>.
4808-
4809-The containers whose names do not begin with a period represent the accounts
4810-within the auth service. For example, the <auth_account>/test container would
4811-represent the "test" account.
4812-
4813-The objects within each container represent the users for that auth service
4814-account. For example, the <auth_account>/test/bob object would represent the
4815-user "bob" within the auth service account of "test". Each of these user
4816-objects contain a JSON dictionary of the format::
4817-
4818- {"auth": "<auth_type>:<auth_value>", "groups": <groups_array>}
4819-
4820-The `<auth_type>` can only be `plaintext` at this time, and the `<auth_value>`
4821-is the plain text password itself.
4822-
4823-The `<groups_array>` contains at least two groups. The first is a unique group
4824-identifying that user and it's name is of the format `<user>:<account>`. The
4825-second group is the `<account>` itself. Additional groups of `.admin` for
4826-account administrators and `.reseller_admin` for reseller administrators may
4827-exist. Here's an example user JSON dictionary::
4828-
4829- {"auth": "plaintext:testing",
4830- "groups": ["name": "test:tester", "name": "test", "name": ".admin"]}
4831-
4832-To map an auth service account to a Swift storage account, the Service Account
4833-Id string is stored in the `X-Container-Meta-Account-Id` header for the
4834-<auth_account>/<account> container. To map back the other way, an
4835-<auth_account>/.account_id/<account_id> object is created with the contents of
4836-the corresponding auth service's account name.
4837-
4838-Also, to support a future where the auth service will support multiple Swift
4839-clusters or even multiple services for the same auth service account, an
4840-<auth_account>/<account>/.services object is created with its contents having a
4841-JSON dictionary of the format::
4842-
4843- {"storage": {"default": "local", "local": <url>}}
4844-
4845-The "default" is always "local" right now, and "local" is always the single
4846-Swift cluster URL; but in the future there can be more than one cluster with
4847-various names instead of just "local", and the "default" key's value will
4848-contain the primary cluster to use for that account. Also, there may be more
4849-services in addition to the current "storage" service right now.
4850-
4851-Here's an example .services dictionary at the moment::
4852-
4853- {"storage":
4854- {"default": "local",
4855- "local": "http://127.0.0.1:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
4856-
4857-But, here's an example of what the dictionary may look like in the future::
4858-
4859- {"storage":
4860- {"default": "dfw",
4861- "dfw": "http://dfw.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
4862- "ord": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
4863- "sat": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"},
4864- "servers":
4865- {"default": "dfw",
4866- "dfw": "http://dfw.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
4867- "ord": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
4868- "sat": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
4869-
4870-Lastly, the tokens themselves are stored as objects in the
4871-`<auth_account>/.token_[0-f]` containers. The names of the objects are the
4872-token strings themselves, such as `AUTH_tked86bbd01864458aa2bd746879438d5a`.
4873-The exact `.token_[0-f]` container chosen is based on the final digit of the
4874-token name, such as `.token_a` for the token
4875-`AUTH_tked86bbd01864458aa2bd746879438d5a`. The contents of the token objects
4876-are JSON dictionaries of the format::
4877-
4878- {"account": <account>,
4879- "user": <user>,
4880- "account_id": <account_id>,
4881- "groups": <groups_array>,
4882- "expires": <time.time() value>}
4883-
4884-The `<account>` is the auth service account's name for that token. The `<user>`
4885-is the user within the account for that token. The `<account_id>` is the
4886-same as the `X-Container-Meta-Account-Id` for the auth service's account,
4887-as described above. The `<groups_array>` is the user's groups, as described
4888-above with the user object. The "expires" value indicates when the token is no
4889-longer valid, as compared to Python's time.time() value.
4890-
4891-Here's an example token object's JSON dictionary::
4892-
4893- {"account": "test",
4894- "user": "tester",
4895- "account_id": "AUTH_8980f74b1cda41e483cbe0a925f448a9",
4896- "groups": ["name": "test:tester", "name": "test", "name": ".admin"],
4897- "expires": 1291273147.1624689}
4898-
4899-To easily map a user to an already issued token, the token name is stored in
4900-the user object's `X-Object-Meta-Auth-Token` header.
4901-
4902-Here is an example full listing of an <auth_account>::
4903-
4904- .account_id
4905- AUTH_2282f516-559f-4966-b239-b5c88829e927
4906- AUTH_f6f57a3c-33b5-4e85-95a5-a801e67505c8
4907- AUTH_fea96a36-c177-4ca4-8c7e-b8c715d9d37b
4908- .token_0
4909- .token_1
4910- .token_2
4911- .token_3
4912- .token_4
4913- .token_5
4914- .token_6
4915- AUTH_tk9d2941b13d524b268367116ef956dee6
4916- .token_7
4917- .token_8
4918- AUTH_tk93627c6324c64f78be746f1e6a4e3f98
4919- .token_9
4920- .token_a
4921- .token_b
4922- .token_c
4923- .token_d
4924- .token_e
4925- AUTH_tk0d37d286af2c43ffad06e99112b3ec4e
4926- .token_f
4927- AUTH_tk766bbde93771489982d8dc76979d11cf
4928- reseller
4929- .services
4930- reseller
4931- test
4932- .services
4933- tester
4934- tester3
4935- test2
4936- .services
4937- tester2
4938
4939=== modified file 'doc/source/overview_large_objects.rst'
4940--- doc/source/overview_large_objects.rst 2010-12-06 20:01:19 +0000
4941+++ doc/source/overview_large_objects.rst 2011-06-15 09:24:34 +0000
4942@@ -14,24 +14,24 @@
4943 with the possibility of parallel uploads of the segments.
4944
4945 ----------------------------------
4946-Using ``st`` for Segmented Objects
4947+Using ``swift`` for Segmented Objects
4948 ----------------------------------
4949
4950-The quickest way to try out this feature is use the included ``st`` Swift Tool.
4951+The quickest way to try out this feature is use the included ``swift`` Swift Tool.
4952 You can use the ``-S`` option to specify the segment size to use when splitting
4953 a large file. For example::
4954
4955- st upload test_container -S 1073741824 large_file
4956+ swift upload test_container -S 1073741824 large_file
4957
4958 This would split the large_file into 1G segments and begin uploading those
4959-segments in parallel. Once all the segments have been uploaded, ``st`` will
4960+segments in parallel. Once all the segments have been uploaded, ``swift`` will
4961 then create the manifest file so the segments can be downloaded as one.
4962
4963-So now, the following ``st`` command would download the entire large object::
4964-
4965- st download test_container large_file
4966-
4967-``st`` uses a strict convention for its segmented object support. In the above
4968+So now, the following ``swift`` command would download the entire large object::
4969+
4970+ swift download test_container large_file
4971+
4972+``swift`` uses a strict convention for its segmented object support. In the above
4973 example it will upload all the segments into a second container named
4974 test_container_segments. These segments will have names like
4975 large_file/1290206778.25/21474836480/00000000,
4976@@ -43,7 +43,7 @@
4977 upload of a new file with the same name won't overwrite the contents of the
4978 first until the last moment when the manifest file is updated.
4979
4980-``st`` will manage these segment files for you, deleting old segments on
4981+``swift`` will manage these segment files for you, deleting old segments on
4982 deletes and overwrites, etc. You can override this behavior with the
4983 ``--leave-segments`` option if desired; this is useful if you want to have
4984 multiple versions of the same large object available.
4985@@ -53,14 +53,14 @@
4986 ----------
4987
4988 You can also work with the segments and manifests directly with HTTP requests
4989-instead of having ``st`` do that for you. You can just upload the segments like
4990+instead of having ``swift`` do that for you. You can just upload the segments like
4991 you would any other object and the manifest is just a zero-byte file with an
4992 extra ``X-Object-Manifest`` header.
4993
4994 All the object segments need to be in the same container, have a common object
4995 name prefix, and their names sort in the order they should be concatenated.
4996 They don't have to be in the same container as the manifest file will be, which
4997-is useful to keep container listings clean as explained above with ``st``.
4998+is useful to keep container listings clean as explained above with ``swift``.
4999
5000 The manifest file is simply a zero-byte file with the extra
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches