winazurestorageのフォーク
Revision | 1c45a2a66d33081ae5f4d1ca1a8456e24db51c0f (tree) |
---|---|
Time | 2012-01-10 08:08:54 |
Author | Steve Marx <Steve.Marx@micr...> |
Commiter | Steve Marx |
support latest x-ms-version, add put_block, and add a few more tests
@@ -0,0 +1 @@ | ||
1 | +*.pyc | |
\ No newline at end of file |
@@ -1,6 +1,8 @@ | ||
1 | 1 | from winazurestorage import * |
2 | +import base64 | |
3 | +import sys | |
2 | 4 | |
3 | -def do_blob_tests(): | |
5 | +def do_blob_tests(account, key): | |
4 | 6 | '''Expected output: |
5 | 7 | Starting blob tests |
6 | 8 | create_container: 201 |
@@ -10,15 +12,41 @@ def do_blob_tests(): | ||
10 | 12 | Done. |
11 | 13 | ''' |
12 | 14 | print "Starting blob tests" |
13 | - blobs = BlobStorage() | |
15 | + if account is None or key is None: blobs = BlobStorage() | |
16 | + else: blobs = BlobStorage(CLOUD_BLOB_HOST, account, key) | |
14 | 17 | print "\tcreate_container: %d" % blobs.create_container("testcontainer", True) |
15 | - print "\tput_blob: %d" % blobs.put_blob("testcontainer", "testblob.txt", "Hello, World!", "text/plain") | |
18 | + print "\tput_blob: %d" % blobs.put_blob("testcontainer", "testblob.txt", "Hello, World!") | |
16 | 19 | print "\tget_blob: %s" % blobs.get_blob("testcontainer", "testblob.txt") |
20 | + print "\tput_block: %d" % blobs.put_block("testcontainer", "testblob.txt", base64.encodestring('foobar'), 'something') | |
17 | 21 | print "\tdelete_container: %d" % blobs.delete_container("testcontainer") |
18 | 22 | print "Done." |
19 | 23 | |
20 | -def run_tests(): | |
21 | - do_blob_tests() | |
24 | +def do_table_tests(account, key): | |
25 | + if account is None or key is None: | |
26 | + print "Skipping table tests, since no account and key were passed on the command line." | |
27 | + return | |
28 | + print "Starting table tests" | |
29 | + tables = TableStorage(CLOUD_TABLE_HOST, account, key) | |
30 | + print "\tcreate_table: %d" % tables.create_table("testtable") | |
31 | + print "\tget_all: %d" % len(tables.get_all("testtable")) | |
32 | + print "\tdelete_table: %d" % tables.delete_table("testtable") | |
33 | + print "Done" | |
34 | + | |
35 | +def do_queue_tests(account, key): | |
36 | + print "Starting queue tests" | |
37 | + if account is None or key is None: queues = QueueStorage() | |
38 | + else: queues = QueueStorage(CLOUD_QUEUE_HOST, account, key) | |
39 | + print "\tcreate_queue: %d" % queues.create_queue("testqueue") | |
40 | + print "\tdelete_queue: %d" % queues.delete_queue("testqueue") | |
41 | + print "Done" | |
42 | + | |
43 | +def run_tests(account, key): | |
44 | + do_blob_tests(account, key) | |
45 | + do_table_tests(account, key) | |
46 | + do_queue_tests(account, key) | |
22 | 47 | |
23 | 48 | if __name__ == '__main__': |
24 | - run_tests() | |
\ No newline at end of file | ||
49 | + if len(sys.argv) > 2: | |
50 | + run_tests(sys.argv[1], sys.argv[2]) | |
51 | + else: | |
52 | + run_tests(None, None) | |
\ No newline at end of file |
@@ -16,17 +16,19 @@ from xml.dom import minidom #TODO: Use a faster way of processing XML | ||
16 | 16 | import re |
17 | 17 | from urllib2 import Request, urlopen, URLError |
18 | 18 | from urllib import urlencode |
19 | -from urlparse import urlsplit | |
19 | +from urlparse import urlsplit, parse_qs | |
20 | 20 | from datetime import datetime, timedelta |
21 | 21 | |
22 | 22 | DEVSTORE_ACCOUNT = "devstoreaccount1" |
23 | 23 | DEVSTORE_SECRET_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" |
24 | 24 | |
25 | 25 | DEVSTORE_BLOB_HOST = "127.0.0.1:10000" |
26 | +DEVSTORE_QUEUE_HOST = "127.0.0.1:10001" | |
26 | 27 | DEVSTORE_TABLE_HOST = "127.0.0.1:10002" |
27 | 28 | |
28 | 29 | CLOUD_BLOB_HOST = "blob.core.windows.net" |
29 | 30 | CLOUD_TABLE_HOST = "table.core.windows.net" |
31 | +CLOUD_QUEUE_HOST = "queue.core.windows.net" | |
30 | 32 | |
31 | 33 | PREFIX_PROPERTIES = "x-ms-prop-" |
32 | 34 | PREFIX_METADATA = "x-ms-meta-" |
@@ -64,27 +66,39 @@ class SharedKeyCredentials(object): | ||
64 | 66 | path = path[path.index('/'):] |
65 | 67 | |
66 | 68 | canonicalized_resource = "/" + self._account + path |
67 | - match = re.search(r'comp=[^&]*', query) | |
68 | - if match is not None: | |
69 | - canonicalized_resource += "?" + match.group(0) | |
70 | - | |
69 | + q = parse_qs(query) | |
70 | + if len(q.keys()) > 0: | |
71 | + canonicalized_resource +=''.join(["\n%s:%s" % (k, ','.join(sorted(q[k]))) for k in sorted(q.keys())]) | |
72 | + | |
71 | 73 | if use_path_style_uris is None: |
72 | 74 | use_path_style_uris = re.match('^[\d.:]+$', host) is not None |
73 | 75 | |
76 | + request.add_header(PREFIX_STORAGE_HEADER + 'version', '2011-08-18') | |
74 | 77 | request.add_header(PREFIX_STORAGE_HEADER + 'date', time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())) #RFC 1123 |
78 | + if for_tables: | |
79 | + request.add_header('Date', request.get_header((PREFIX_STORAGE_HEADER + 'date').capitalize())) | |
80 | + request.add_header('DataServiceVersion', '1.0;NetFx') | |
81 | + request.add_header('MaxDataServiceVersion', '1.0;NetFx') | |
75 | 82 | canonicalized_headers = NEW_LINE.join(('%s:%s' % (k.lower(), request.get_header(k).strip()) for k in sorted(request.headers.keys(), lambda x,y: cmp(x.lower(), y.lower())) if k.lower().startswith(PREFIX_STORAGE_HEADER))) |
76 | 83 | |
77 | 84 | string_to_sign = request.get_method().upper() + NEW_LINE # verb |
78 | - string_to_sign += NEW_LINE # MD5 not required | |
79 | - if request.get_header('Content-type') is not None: # Content-Type | |
80 | - string_to_sign += request.get_header('Content-type') | |
81 | - string_to_sign += NEW_LINE | |
82 | - if for_tables: string_to_sign += request.get_header(PREFIX_STORAGE_HEADER.capitalize() + 'date') + NEW_LINE | |
83 | - else: string_to_sign += NEW_LINE # Date | |
84 | 85 | if not for_tables: |
85 | - string_to_sign += canonicalized_headers + NEW_LINE # Canonicalized headers | |
86 | - string_to_sign += canonicalized_resource # Canonicalized resource | |
87 | - | |
86 | + string_to_sign += (request.get_header('Content-encoding') or '') + NEW_LINE | |
87 | + string_to_sign += (request.get_header('Content-language') or '') + NEW_LINE | |
88 | + string_to_sign += (request.get_header('Content-length') or '') + NEW_LINE | |
89 | + string_to_sign += (request.get_header('Content-md5') or '') + NEW_LINE | |
90 | + string_to_sign += (request.get_header('Content-type') or '') + NEW_LINE | |
91 | + string_to_sign += (request.get_header('Date') or '') + NEW_LINE | |
92 | + if not for_tables: | |
93 | + string_to_sign += (request.get_header('If-modified-since') or '') + NEW_LINE | |
94 | + string_to_sign += (request.get_header('If-match') or '') + NEW_LINE | |
95 | + string_to_sign += (request.get_header('If-none-match') or '') + NEW_LINE | |
96 | + string_to_sign += (request.get_header('If-unmodified-since') or '') + NEW_LINE | |
97 | + string_to_sign += (request.get_header('Range') or '') + NEW_LINE | |
98 | + if not for_tables: | |
99 | + string_to_sign += canonicalized_headers + NEW_LINE | |
100 | + string_to_sign += canonicalized_resource | |
101 | + | |
88 | 102 | request.add_header('Authorization', 'SharedKey ' + self._account + ':' + base64.encodestring(hmac.new(self._key, unicode(string_to_sign).encode("utf-8"), hashlib.sha256).digest()).strip()) |
89 | 103 | return request |
90 | 104 |
@@ -130,7 +144,7 @@ class TableEntity(object): pass | ||
130 | 144 | class QueueMessage(): pass |
131 | 145 | |
132 | 146 | class QueueStorage(Storage): |
133 | - def __init__(self, host, account_name, secret_key, use_path_style_uris = None): | |
147 | + def __init__(self, host = DEVSTORE_QUEUE_HOST, account_name = DEVSTORE_ACCOUNT, secret_key = DEVSTORE_SECRET_KEY, use_path_style_uris = None): | |
134 | 148 | super(QueueStorage, self).__init__(host, account_name, secret_key, use_path_style_uris) |
135 | 149 | |
136 | 150 | def create_queue(self, name): |
@@ -279,7 +293,7 @@ class BlobStorage(Storage): | ||
279 | 293 | super(BlobStorage, self).__init__(host, account_name, secret_key, use_path_style_uris) |
280 | 294 | |
281 | 295 | def create_container(self, container_name, is_public = False): |
282 | - req = RequestWithMethod("PUT", "%s/%s" % (self.get_base_url(), container_name)) | |
296 | + req = RequestWithMethod("PUT", "%s/%s?restype=container" % (self.get_base_url(), container_name)) | |
283 | 297 | req.add_header("Content-Length", "0") |
284 | 298 | if is_public: req.add_header(PREFIX_PROPERTIES + "publicaccess", "true") |
285 | 299 | self._credentials.sign_request(req) |
@@ -290,7 +304,7 @@ class BlobStorage(Storage): | ||
290 | 304 | return e.code |
291 | 305 | |
292 | 306 | def delete_container(self, container_name): |
293 | - req = RequestWithMethod("DELETE", "%s/%s" % (self.get_base_url(), container_name)) | |
307 | + req = RequestWithMethod("DELETE", "%s/%s?restype=container" % (self.get_base_url(), container_name)) | |
294 | 308 | self._credentials.sign_request(req) |
295 | 309 | try: |
296 | 310 | response = urlopen(req) |
@@ -314,6 +328,7 @@ class BlobStorage(Storage): | ||
314 | 328 | def put_blob(self, container_name, blob_name, data, content_type = "", metadata = {}): |
315 | 329 | req = RequestWithMethod("PUT", "%s/%s/%s" % (self.get_base_url(), container_name, blob_name), data=data) |
316 | 330 | req.add_header("Content-Length", "%d" % len(data)) |
331 | + req.add_header('x-ms-blob-type', 'BlockBlob') | |
317 | 332 | for key, value in metadata.items(): |
318 | 333 | req.add_header("x-ms-meta-%s" % key, value) |
319 | 334 | req.add_header("Content-Type", content_type) |
@@ -372,6 +387,18 @@ class BlobStorage(Storage): | ||
372 | 387 | except: marker = None |
373 | 388 | if marker is None: break |
374 | 389 | |
390 | + def put_block(self, container_name, blob_name, block_id, data): | |
391 | + encoded_block_id = urlencode({"comp": "block", "blockid": block_id}) | |
392 | + req = RequestWithMethod("PUT", "%s/%s/%s?%s" % (self.get_base_url(), container_name, blob_name, encoded_block_id), data=data) | |
393 | + req.add_header("Content-Type", "") | |
394 | + req.add_header("Content-Length", "%d" % len(data)) | |
395 | + self._credentials.sign_request(req) | |
396 | + try: | |
397 | + response = urlopen(req) | |
398 | + return response.code | |
399 | + except URLError, e: | |
400 | + return e.code | |
401 | + | |
375 | 402 | def main(): |
376 | 403 | pass |
377 | 404 |