Merge lp:~oubiwann/txaws/474353-storage-ec2-symmetry into lp:txaws

Proposed by Duncan McGreggor
Status: Merged
Merged at revision: not available
Proposed branch: lp:~oubiwann/txaws/474353-storage-ec2-symmetry
Merge into: lp:txaws
Diff against target: 3433 lines (+1320/-696)
17 files modified
txaws/client/base.py (+75/-0)
txaws/client/gui/gtk.py (+1/-4)
txaws/client/tests/test_client.py (+110/-0)
txaws/credentials.py (+6/-3)
txaws/ec2/client.py (+125/-139)
txaws/ec2/exception.py (+1/-0)
txaws/ec2/tests/test_client.py (+241/-115)
txaws/ec2/tests/test_exception.py (+1/-1)
txaws/s3/client.py (+211/-134)
txaws/s3/model.py (+16/-0)
txaws/s3/tests/test_client.py (+380/-237)
txaws/service.py (+31/-8)
txaws/testing/payload.py (+67/-31)
txaws/tests/test_service.py (+51/-21)
txaws/tests/test_util.py (+1/-1)
txaws/util.py (+1/-1)
txaws/version.py (+2/-1)
To merge this branch: bzr merge lp:~oubiwann/txaws/474353-storage-ec2-symmetry
Reviewer Review Type Date Requested Status
txAWS Committers Pending
Original txAWS Team Pending
Review via email: mp+15341@code.launchpad.net

This proposal supersedes a proposal from 2009-11-22.

To post a comment you must log in.
Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

This branch refactors the txaws.storage code (and renames it to txaws.s3) to match the work that was done in txaws.ec2. This includes code implementation as well as unit test idioms.

Once this branch is merged, work will begin immediately on the following:
 * bug #475571: Create s3_error_wrapper
 * bug #484858: Add storage scripts

The the remaining list of storage tickets in the queue, be sure to see this blueprint:
  https://blueprints.edge.launchpad.net/txaws/+spec/improve-s3-support

Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

Landscape trunk has been tested against this branch.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'txaws/client/base.py'
2--- txaws/client/base.py 1970-01-01 00:00:00 +0000
3+++ txaws/client/base.py 2009-11-28 01:10:27 +0000
4@@ -0,0 +1,75 @@
5+from twisted.internet import reactor, ssl
6+from twisted.web.client import HTTPClientFactory
7+
8+from txaws.util import parse
9+from txaws.credentials import AWSCredentials
10+from txaws.service import AWSServiceEndpoint
11+
12+
13+class BaseClient(object):
14+ """Create an AWS client.
15+
16+ @param creds: User authentication credentials to use.
17+ @param endpoint: The service endpoint URI.
18+ @param query_factory: The class or function that produces a query
19+ object for making requests to the EC2 service.
20+ """
21+ def __init__(self, creds=None, endpoint=None, query_factory=None):
22+ if creds is None:
23+ creds = AWSCredentials()
24+ if endpoint is None:
25+ endpoint = AWSServiceEndpoint()
26+ self.creds = creds
27+ self.endpoint = endpoint
28+ self.query_factory = query_factory
29+
30+
31+class BaseQuery(object):
32+
33+ def __init__(self, action=None, creds=None, endpoint=None):
34+ if not action:
35+ raise TypeError("The query requires an action parameter.")
36+ self.factory = HTTPClientFactory
37+ self.action = action
38+ self.creds = creds
39+ self.endpoint = endpoint
40+ self.client = None
41+
42+ def get_page(self, url, *args, **kwds):
43+ """
44+ Define our own get_page method so that we can easily override the
45+ factory when we need to. This was copied from the following:
46+ * twisted.web.client.getPage
47+ * twisted.web.client._makeGetterFactory
48+ """
49+ contextFactory = None
50+ scheme, host, port, path = parse(url)
51+ self.client = self.factory(url, *args, **kwds)
52+ if scheme == 'https':
53+ contextFactory = ssl.ClientContextFactory()
54+ reactor.connectSSL(host, port, self.client, contextFactory)
55+ else:
56+ reactor.connectTCP(host, port, self.client)
57+ return self.client.deferred
58+
59+ def get_request_headers(self, *args, **kwds):
60+ """
61+ A convenience method for obtaining the headers that were sent to the
62+ S3 server.
63+
64+ The AWS S3 API depends upon setting headers. This method is provided as
65+ a convenience for debugging issues with the S3 communications.
66+ """
67+ if self.client:
68+ return self.client.headers
69+
70+ def get_response_headers(self, *args, **kwargs):
71+ """
72+ A convenience method for obtaining the headers that were sent from the
73+ S3 server.
74+
75+ The AWS S3 API depends upon setting headers. This method is used by the
76+ head_object API call for getting a S3 object's metadata.
77+ """
78+ if self.client:
79+ return self.client.response_headers
80
81=== modified file 'txaws/client/gui/gtk.py'
82--- txaws/client/gui/gtk.py 2009-11-06 22:16:13 +0000
83+++ txaws/client/gui/gtk.py 2009-11-28 01:10:27 +0000
84@@ -70,7 +70,6 @@
85
86 def from_gnomekeyring(self):
87 # Try for gtk gui specific credentials.
88- creds = None
89 try:
90 items = gnomekeyring.find_items_sync(
91 gnomekeyring.ITEM_GENERIC_SECRET,
92@@ -129,8 +128,6 @@
93 deferred.addCallbacks(self.shutdown_instances, self.show_error)
94
95 def save_key(self, response_id, data):
96- # handle the dialog
97- dialog = self.password_dialog
98 try:
99 if data != gtk.RESPONSE_ACCEPT:
100 # User cancelled. They can ask for the password again somehow.
101@@ -204,6 +201,6 @@
102 from twisted.internet import gtk2reactor
103 gtk2reactor.install()
104 from twisted.internet import reactor
105- status = AWSStatusIcon(reactor)
106+ AWSStatusIcon(reactor)
107 gobject.set_application_name("aws-status")
108 reactor.run()
109
110=== added directory 'txaws/client/tests'
111=== added file 'txaws/client/tests/__init__.py'
112=== added file 'txaws/client/tests/test_client.py'
113--- txaws/client/tests/test_client.py 1970-01-01 00:00:00 +0000
114+++ txaws/client/tests/test_client.py 2009-11-28 01:10:27 +0000
115@@ -0,0 +1,110 @@
116+import os
117+
118+from twisted.internet import reactor
119+from twisted.protocols.policies import WrappingFactory
120+from twisted.python import log
121+from twisted.python.filepath import FilePath
122+from twisted.web.client import HTTPClientFactory
123+from twisted.web import server, static
124+
125+from txaws.client.base import BaseClient, BaseQuery
126+from txaws.testing.base import TXAWSTestCase
127+
128+
129+class BaseClientTestCase(TXAWSTestCase):
130+
131+ def test_creation(self):
132+ client = BaseClient("creds", "endpoint", "query factory")
133+ self.assertEquals(client.creds, "creds")
134+ self.assertEquals(client.endpoint, "endpoint")
135+ self.assertEquals(client.query_factory, "query factory")
136+
137+
138+class BaseQueryTestCase(TXAWSTestCase):
139+
140+ def setUp(self):
141+ self.cleanupServerConnections = 0
142+ name = self.mktemp()
143+ os.mkdir(name)
144+ FilePath(name).child("file").setContent("0123456789")
145+ r = static.File(name)
146+ self.site = server.Site(r, timeout=None)
147+ self.wrapper = WrappingFactory(self.site)
148+ self.port = self._listen(self.wrapper)
149+ self.portno = self.port.getHost().port
150+
151+ def tearDown(self):
152+ # If the test indicated it might leave some server-side connections
153+ # around, clean them up.
154+ connections = self.wrapper.protocols.keys()
155+ # If there are fewer server-side connections than requested,
156+ # that's okay. Some might have noticed that the client closed
157+ # the connection and cleaned up after themselves.
158+ for n in range(min(len(connections), self.cleanupServerConnections)):
159+ proto = connections.pop()
160+ log.msg("Closing %r" % (proto,))
161+ proto.transport.loseConnection()
162+ if connections:
163+ log.msg("Some left-over connections; this test is probably buggy.")
164+ return self.port.stopListening()
165+
166+ def _listen(self, site):
167+ return reactor.listenTCP(0, site, interface="127.0.0.1")
168+
169+ def _get_url(self, path):
170+ return "http://127.0.0.1:%d/%s" % (self.portno, path)
171+
172+ def test_creation(self):
173+ query = BaseQuery("an action", "creds", "http://endpoint")
174+ self.assertEquals(query.factory, HTTPClientFactory)
175+ self.assertEquals(query.action, "an action")
176+ self.assertEquals(query.creds, "creds")
177+ self.assertEquals(query.endpoint, "http://endpoint")
178+
179+ def test_init_requires_action(self):
180+ self.assertRaises(TypeError, BaseQuery)
181+
182+ def test_init_requires_creds(self):
183+ self.assertRaises(TypeError, BaseQuery, None)
184+
185+ def test_get_page(self):
186+ query = BaseQuery("an action", "creds", "http://endpoint")
187+ d = query.get_page(self._get_url("file"))
188+ d.addCallback(self.assertEquals, "0123456789")
189+ return d
190+
191+ def test_get_request_headers_no_client(self):
192+
193+ query = BaseQuery("an action", "creds", "http://endpoint")
194+ results = query.get_request_headers()
195+ self.assertEquals(results, None)
196+
197+ def test_get_request_headers_with_client(self):
198+
199+ def check_results(results):
200+ self.assertEquals(results.keys(), [])
201+ self.assertEquals(results.values(), [])
202+
203+ query = BaseQuery("an action", "creds", "http://endpoint")
204+ d = query.get_page(self._get_url("file"))
205+ d.addCallback(query.get_request_headers)
206+ return d.addCallback(check_results)
207+
208+ def test_get_response_headers_no_client(self):
209+
210+ query = BaseQuery("an action", "creds", "http://endpoint")
211+ results = query.get_response_headers()
212+ self.assertEquals(results, None)
213+
214+ def test_get_response_headers_with_client(self):
215+
216+ def check_results(results):
217+ self.assertEquals(sorted(results.keys()), [
218+ "accept-ranges", "content-length", "content-type", "date",
219+ "last-modified", "server"])
220+ self.assertEquals(len(results.values()), 6)
221+
222+ query = BaseQuery("an action", "creds", "http://endpoint")
223+ d = query.get_page(self._get_url("file"))
224+ d.addCallback(query.get_response_headers)
225+ return d.addCallback(check_results)
226
227=== modified file 'txaws/credentials.py'
228--- txaws/credentials.py 2009-10-21 19:40:18 +0000
229+++ txaws/credentials.py 2009-11-28 01:10:27 +0000
230@@ -5,7 +5,7 @@
231
232 import os
233
234-from txaws.util import hmac_sha256
235+from txaws.util import hmac_sha256, hmac_sha1
236
237
238 __all__ = ["AWSCredentials"]
239@@ -38,6 +38,9 @@
240 if not self.secret_key:
241 raise ValueError("Could not find %s" % ENV_SECRET_KEY)
242
243- def sign(self, bytes):
244+ def sign(self, bytes, hash_type="sha256"):
245 """Sign some bytes."""
246- return hmac_sha256(self.secret_key, bytes)
247+ if hash_type == "sha256":
248+ return hmac_sha256(self.secret_key, bytes)
249+ elif hash_type == "sha1":
250+ return hmac_sha1(self.secret_key, bytes)
251
252=== modified file 'txaws/ec2/client.py'
253--- txaws/ec2/client.py 2009-10-28 19:59:33 +0000
254+++ txaws/ec2/client.py 2009-11-28 01:10:27 +0000
255@@ -10,18 +10,15 @@
256 from base64 import b64encode
257 from xml.parsers.expat import ExpatError
258
259-from twisted.internet import reactor, ssl
260 from twisted.web import http
261-from twisted.web.client import HTTPClientFactory
262 from twisted.web.error import Error as TwistedWebError
263
264 from txaws import version
265-from txaws.credentials import AWSCredentials
266+from txaws.client.base import BaseClient, BaseQuery
267 from txaws.ec2 import model
268 from txaws.ec2.exception import EC2Error
269 from txaws.exception import AWSResponseParseError
270-from txaws.service import AWSServiceEndpoint
271-from txaws.util import iso8601time, parse, XML
272+from txaws.util import iso8601time, XML
273
274
275 __all__ = ["EC2Client"]
276@@ -49,7 +46,7 @@
277 try:
278 fallback_error = EC2Error(xml_payload, error.value.status,
279 error.value.message, error.value.response)
280- except (ExpatError, AWSResponseParseError), parse_error:
281+ except (ExpatError, AWSResponseParseError):
282 error_message = http.RESPONSES.get(http_status)
283 fallback_error = TwistedWebError(http_status, error_message,
284 error.value.response)
285@@ -58,32 +55,23 @@
286 error.raiseException()
287
288
289-class EC2Client(object):
290+class EC2Client(BaseClient):
291 """A client for EC2."""
292
293 def __init__(self, creds=None, endpoint=None, query_factory=None):
294- """Create an EC2Client.
295-
296- @param creds: User authentication credentials to use.
297- @param endpoint: The service endpoint URI.
298- @param query_factory: The class or function that produces a query
299- object for making requests to the EC2 service.
300- """
301- self.creds = creds or AWSCredentials()
302- self.endpoint = endpoint or AWSServiceEndpoint()
303 if query_factory is None:
304- self.query_factory = Query
305- else:
306- self.query_factory = query_factory
307+ query_factory = Query
308+ super(EC2Client, self).__init__(creds, endpoint, query_factory)
309
310 def describe_instances(self, *instance_ids):
311 """Describe current instances."""
312- instanceset = {}
313+ instances= {}
314 for pos, instance_id in enumerate(instance_ids):
315- instanceset["InstanceId.%d" % (pos + 1)] = instance_id
316- q = self.query_factory("DescribeInstances", self.creds, self.endpoint,
317- instanceset)
318- d = q.submit()
319+ instances["InstanceId.%d" % (pos + 1)] = instance_id
320+ query = self.query_factory(
321+ action="DescribeInstances", creds=self.creds,
322+ endpoint=self.endpoint, other_params=instances)
323+ d = query.submit()
324 return d.addCallback(self._parse_describe_instances)
325
326 def _parse_instances_set(self, root, reservation):
327@@ -176,9 +164,10 @@
328 params["KernelId"] = kernel_id
329 if ramdisk_id is not None:
330 params["RamdiskId"] = ramdisk_id
331- q = self.query_factory(
332- "RunInstances", self.creds, self.endpoint, params)
333- d = q.submit()
334+ query = self.query_factory(
335+ action="RunInstances", creds=self.creds, endpoint=self.endpoint,
336+ other_params=params)
337+ d = query.submit()
338 return d.addCallback(self._parse_run_instances)
339
340 def _parse_run_instances(self, xml_bytes):
341@@ -208,12 +197,13 @@
342 @return: A deferred which on success gives an iterable of
343 (id, old-state, new-state) tuples.
344 """
345- instanceset = {}
346+ instances = {}
347 for pos, instance_id in enumerate(instance_ids):
348- instanceset["InstanceId.%d" % (pos+1)] = instance_id
349- q = self.query_factory("TerminateInstances", self.creds, self.endpoint,
350- instanceset)
351- d = q.submit()
352+ instances["InstanceId.%d" % (pos+1)] = instance_id
353+ query = self.query_factory(
354+ action="TerminateInstances", creds=self.creds,
355+ endpoint=self.endpoint, other_params=instances)
356+ d = query.submit()
357 return d.addCallback(self._parse_terminate_instances)
358
359 def _parse_terminate_instances(self, xml_bytes):
360@@ -237,12 +227,13 @@
361 @return: A C{Deferred} that will fire with a list of L{SecurityGroup}s
362 retrieved from the cloud.
363 """
364- group_names = None
365+ group_names = {}
366 if names:
367 group_names = dict([("GroupName.%d" % (i+1), name)
368 for i, name in enumerate(names)])
369- query = self.query_factory("DescribeSecurityGroups", self.creds,
370- self.endpoint, group_names)
371+ query = self.query_factory(
372+ action="DescribeSecurityGroups", creds=self.creds,
373+ endpoint=self.endpoint, other_params=group_names)
374 d = query.submit()
375 return d.addCallback(self._parse_describe_security_groups)
376
377@@ -295,8 +286,9 @@
378 success of the operation.
379 """
380 parameters = {"GroupName": name, "GroupDescription": description}
381- query = self.query_factory("CreateSecurityGroup", self.creds,
382- self.endpoint, parameters)
383+ query = self.query_factory(
384+ action="CreateSecurityGroup", creds=self.creds,
385+ endpoint=self.endpoint, other_params=parameters)
386 d = query.submit()
387 return d.addCallback(self._parse_truth_return)
388
389@@ -311,8 +303,9 @@
390 success of the operation.
391 """
392 parameter = {"GroupName": name}
393- query = self.query_factory("DeleteSecurityGroup", self.creds,
394- self.endpoint, parameter)
395+ query = self.query_factory(
396+ action="DeleteSecurityGroup", creds=self.creds,
397+ endpoint=self.endpoint, other_params=parameter)
398 d = query.submit()
399 return d.addCallback(self._parse_truth_return)
400
401@@ -367,8 +360,9 @@
402 "all the ip parameters.")
403 raise ValueError(msg)
404 parameters["GroupName"] = group_name
405- query = self.query_factory("AuthorizeSecurityGroupIngress", self.creds,
406- self.endpoint, parameters)
407+ query = self.query_factory(
408+ action="AuthorizeSecurityGroupIngress", creds=self.creds,
409+ endpoint=self.endpoint, other_params=parameters)
410 d = query.submit()
411 return d.addCallback(self._parse_truth_return)
412
413@@ -451,8 +445,9 @@
414 "all the ip parameters.")
415 raise ValueError(msg)
416 parameters["GroupName"] = group_name
417- query = self.query_factory("RevokeSecurityGroupIngress", self.creds,
418- self.endpoint, parameters)
419+ query = self.query_factory(
420+ action="RevokeSecurityGroupIngress", creds=self.creds,
421+ endpoint=self.endpoint, other_params=parameters)
422 d = query.submit()
423 return d.addCallback(self._parse_truth_return)
424
425@@ -489,9 +484,10 @@
426 volumeset = {}
427 for pos, volume_id in enumerate(volume_ids):
428 volumeset["VolumeId.%d" % (pos + 1)] = volume_id
429- q = self.query_factory(
430- "DescribeVolumes", self.creds, self.endpoint, volumeset)
431- d = q.submit()
432+ query = self.query_factory(
433+ action="DescribeVolumes", creds=self.creds, endpoint=self.endpoint,
434+ other_params=volumeset)
435+ d = query.submit()
436 return d.addCallback(self._parse_describe_volumes)
437
438 def _parse_describe_volumes(self, xml_bytes):
439@@ -532,9 +528,10 @@
440 params["Size"] = str(size)
441 if snapshot_id is not None:
442 params["SnapshotId"] = snapshot_id
443- q = self.query_factory(
444- "CreateVolume", self.creds, self.endpoint, params)
445- d = q.submit()
446+ query = self.query_factory(
447+ action="CreateVolume", creds=self.creds, endpoint=self.endpoint,
448+ other_params=params)
449+ d = query.submit()
450 return d.addCallback(self._parse_create_volume)
451
452 def _parse_create_volume(self, xml_bytes):
453@@ -553,9 +550,10 @@
454 return volume
455
456 def delete_volume(self, volume_id):
457- q = self.query_factory(
458- "DeleteVolume", self.creds, self.endpoint, {"VolumeId": volume_id})
459- d = q.submit()
460+ query = self.query_factory(
461+ action="DeleteVolume", creds=self.creds, endpoint=self.endpoint,
462+ other_params={"VolumeId": volume_id})
463+ d = query.submit()
464 return d.addCallback(self._parse_truth_return)
465
466 def describe_snapshots(self, *snapshot_ids):
467@@ -563,9 +561,10 @@
468 snapshot_set = {}
469 for pos, snapshot_id in enumerate(snapshot_ids):
470 snapshot_set["SnapshotId.%d" % (pos + 1)] = snapshot_id
471- q = self.query_factory(
472- "DescribeSnapshots", self.creds, self.endpoint, snapshot_set)
473- d = q.submit()
474+ query = self.query_factory(
475+ action="DescribeSnapshots", creds=self.creds,
476+ endpoint=self.endpoint, other_params=snapshot_set)
477+ d = query.submit()
478 return d.addCallback(self._parse_snapshots)
479
480 def _parse_snapshots(self, xml_bytes):
481@@ -587,10 +586,10 @@
482
483 def create_snapshot(self, volume_id):
484 """Create a new snapshot of an existing volume."""
485- q = self.query_factory(
486- "CreateSnapshot", self.creds, self.endpoint,
487- {"VolumeId": volume_id})
488- d = q.submit()
489+ query = self.query_factory(
490+ action="CreateSnapshot", creds=self.creds, endpoint=self.endpoint,
491+ other_params={"VolumeId": volume_id})
492+ d = query.submit()
493 return d.addCallback(self._parse_create_snapshot)
494
495 def _parse_create_snapshot(self, xml_bytes):
496@@ -608,19 +607,19 @@
497
498 def delete_snapshot(self, snapshot_id):
499 """Remove a previously created snapshot."""
500- q = self.query_factory(
501- "DeleteSnapshot", self.creds, self.endpoint,
502- {"SnapshotId": snapshot_id})
503- d = q.submit()
504+ query = self.query_factory(
505+ action="DeleteSnapshot", creds=self.creds, endpoint=self.endpoint,
506+ other_params={"SnapshotId": snapshot_id})
507+ d = query.submit()
508 return d.addCallback(self._parse_truth_return)
509
510 def attach_volume(self, volume_id, instance_id, device):
511 """Attach the given volume to the specified instance at C{device}."""
512- q = self.query_factory(
513- "AttachVolume", self.creds, self.endpoint,
514- {"VolumeId": volume_id, "InstanceId": instance_id,
515- "Device": device})
516- d = q.submit()
517+ query = self.query_factory(
518+ action="AttachVolume", creds=self.creds, endpoint=self.endpoint,
519+ other_params={"VolumeId": volume_id, "InstanceId": instance_id,
520+ "Device": device})
521+ d = query.submit()
522 return d.addCallback(self._parse_attach_volume)
523
524 def _parse_attach_volume(self, xml_bytes):
525@@ -633,12 +632,13 @@
526
527 def describe_keypairs(self, *keypair_names):
528 """Returns information about key pairs available."""
529- keypair_set = {}
530- for pos, keypair_name in enumerate(keypair_names):
531- keypair_set["KeyPair.%d" % (pos + 1)] = keypair_name
532- q = self.query_factory("DescribeKeyPairs", self.creds, self.endpoint,
533- keypair_set)
534- d = q.submit()
535+ keypairs = {}
536+ for index, keypair_name in enumerate(keypair_names):
537+ keypairs["KeyPair.%d" % (index + 1)] = keypair_name
538+ query = self.query_factory(
539+ action="DescribeKeyPairs", creds=self.creds,
540+ endpoint=self.endpoint, other_params=keypairs)
541+ d = query.submit()
542 return d.addCallback(self._parse_describe_keypairs)
543
544 def _parse_describe_keypairs(self, xml_bytes):
545@@ -658,10 +658,10 @@
546 Create a new 2048 bit RSA key pair and return a unique ID that can be
547 used to reference the created key pair when launching new instances.
548 """
549- q = self.query_factory(
550- "CreateKeyPair", self.creds, self.endpoint,
551- {"KeyName": keypair_name})
552- d = q.submit()
553+ query = self.query_factory(
554+ action="CreateKeyPair", creds=self.creds, endpoint=self.endpoint,
555+ other_params={"KeyName": keypair_name})
556+ d = query.submit()
557 return d.addCallback(self._parse_create_keypair)
558
559 def _parse_create_keypair(self, xml_bytes):
560@@ -673,10 +673,10 @@
561
562 def delete_keypair(self, keypair_name):
563 """Delete a given keypair."""
564- q = self.query_factory(
565- "DeleteKeyPair", self.creds, self.endpoint,
566- {"KeyName": keypair_name})
567- d = q.submit()
568+ query = self.query_factory(
569+ action="DeleteKeyPair", creds=self.creds, endpoint=self.endpoint,
570+ other_params={"KeyName": keypair_name})
571+ d = query.submit()
572 return d.addCallback(self._parse_truth_return)
573
574 def allocate_address(self):
575@@ -686,9 +686,11 @@
576
577 @return: the IP address allocated.
578 """
579- q = self.query_factory(
580- "AllocateAddress", self.creds, self.endpoint, {})
581- d = q.submit()
582+ # XXX remove empty other_params
583+ query = self.query_factory(
584+ action="AllocateAddress", creds=self.creds, endpoint=self.endpoint,
585+ other_params={})
586+ d = query.submit()
587 return d.addCallback(self._parse_allocate_address)
588
589 def _parse_allocate_address(self, xml_bytes):
590@@ -701,10 +703,10 @@
591
592 @return: C{True} if the operation succeeded.
593 """
594- q = self.query_factory(
595- "ReleaseAddress", self.creds, self.endpoint,
596- {"PublicIp": address})
597- d = q.submit()
598+ query = self.query_factory(
599+ action="ReleaseAddress", creds=self.creds, endpoint=self.endpoint,
600+ other_params={"PublicIp": address})
601+ d = query.submit()
602 return d.addCallback(self._parse_truth_return)
603
604 def associate_address(self, instance_id, address):
605@@ -714,10 +716,11 @@
606
607 @return: C{True} if the operation succeeded.
608 """
609- q = self.query_factory(
610- "AssociateAddress", self.creds, self.endpoint,
611- {"InstanceId": instance_id, "PublicIp": address})
612- d = q.submit()
613+ query = self.query_factory(
614+ action="AssociateAddress", creds=self.creds,
615+ endpoint=self.endpoint,
616+ other_params={"InstanceId": instance_id, "PublicIp": address})
617+ d = query.submit()
618 return d.addCallback(self._parse_truth_return)
619
620 def disassociate_address(self, address):
621@@ -726,10 +729,10 @@
622 C{associate_address}. This is an idempotent operation, so it can be
623 called several times without error.
624 """
625- q = self.query_factory(
626- "DisassociateAddress", self.creds, self.endpoint,
627- {"PublicIp": address})
628- d = q.submit()
629+ query = self.query_factory(
630+ action="DisassociateAddress", creds=self.creds,
631+ endpoint=self.endpoint, other_params={"PublicIp": address})
632+ d = query.submit()
633 return d.addCallback(self._parse_truth_return)
634
635 def describe_addresses(self, *addresses):
636@@ -744,9 +747,10 @@
637 address_set = {}
638 for pos, address in enumerate(addresses):
639 address_set["PublicIp.%d" % (pos + 1)] = address
640- q = self.query_factory(
641- "DescribeAddresses", self.creds, self.endpoint, address_set)
642- d = q.submit()
643+ query = self.query_factory(
644+ action="DescribeAddresses", creds=self.creds,
645+ endpoint=self.endpoint, other_params=address_set)
646+ d = query.submit()
647 return d.addCallback(self._parse_describe_addresses)
648
649 def _parse_describe_addresses(self, xml_bytes):
650@@ -763,8 +767,9 @@
651 if names:
652 zone_names = dict([("ZoneName.%d" % (i+1), name)
653 for i, name in enumerate(names)])
654- query = self.query_factory("DescribeAvailabilityZones", self.creds,
655- self.endpoint, zone_names)
656+ query = self.query_factory(
657+ action="DescribeAvailabilityZones", creds=self.creds,
658+ endpoint=self.endpoint, other_params=zone_names)
659 d = query.submit()
660 return d.addCallback(self._parse_describe_availability_zones)
661
662@@ -778,51 +783,53 @@
663 return results
664
665
666-class Query(object):
667+class Query(BaseQuery):
668 """A query that may be submitted to EC2."""
669
670- def __init__(self, action, creds, endpoint, other_params=None,
671- time_tuple=None, api_version=None):
672+ def __init__(self, other_params=None, time_tuple=None, api_version=None,
673+ *args, **kwargs):
674 """Create a Query to submit to EC2."""
675- self.factory = HTTPClientFactory
676- self.creds = creds
677- self.endpoint = endpoint
678+ super(Query, self).__init__(*args, **kwargs)
679 # Currently, txAWS only supports version 2008-12-01
680 if api_version is None:
681- api_version = version.aws_api
682+ api_version = version.ec2_api
683 self.params = {
684 "Version": api_version,
685 "SignatureVersion": "2",
686 "SignatureMethod": "HmacSHA256",
687- "Action": action,
688+ "Action": self.action,
689 "AWSAccessKeyId": self.creds.access_key,
690 "Timestamp": iso8601time(time_tuple),
691 }
692 if other_params:
693 self.params.update(other_params)
694
695- def canonical_query_params(self):
696+ def get_canonical_query_params(self):
697 """Return the canonical query params (used in signing)."""
698 result = []
699 for key, value in self.sorted_params():
700 result.append("%s=%s" % (self.encode(key), self.encode(value)))
701 return "&".join(result)
702
703- def encode(self, a_string):
704+ def encode(self, string):
705 """Encode a_string as per the canonicalisation encoding rules.
706
707 See the AWS dev reference page 90 (2008-12-01 version).
708 @return: a_string encoded.
709 """
710- return quote(a_string, safe="~")
711+ return quote(string, safe="~")
712
713 def signing_text(self):
714 """Return the text to be signed when signing the query."""
715 result = "%s\n%s\n%s\n%s" % (self.endpoint.method, self.endpoint.host,
716 self.endpoint.path,
717- self.canonical_query_params())
718+ self.get_canonical_query_params())
719 return result
720
721+ def sorted_params(self):
722+ """Return the query parameters sorted appropriately for signing."""
723+ return sorted(self.params.items())
724+
725 def sign(self):
726 """Sign this query using its built in credentials.
727
728@@ -832,27 +839,6 @@
729 """
730 self.params["Signature"] = self.creds.sign(self.signing_text())
731
732- def sorted_params(self):
733- """Return the query parameters sorted appropriately for signing."""
734- return sorted(self.params.items())
735-
736- def get_page(self, url, *args, **kwds):
737- """
738- Define our own get_page method so that we can easily override the
739- factory when we need to. This was copied from the following:
740- * twisted.web.client.getPage
741- * twisted.web.client._makeGetterFactory
742- """
743- contextFactory = None
744- scheme, host, port, path = parse(url)
745- factory = self.factory(url, *args, **kwds)
746- if scheme == 'https':
747- contextFactory = ssl.ClientContextFactory()
748- reactor.connectSSL(host, port, factory, contextFactory)
749- else:
750- reactor.connectTCP(host, port, factory)
751- return factory.deferred
752-
753 def submit(self):
754 """Submit this query.
755
756@@ -860,7 +846,7 @@
757 """
758 self.sign()
759 url = "%s?%s" % (self.endpoint.get_uri(),
760- self.canonical_query_params())
761- deferred = self.get_page(url, method=self.endpoint.method)
762- deferred.addErrback(ec2_error_wrapper)
763- return deferred
764+ self.get_canonical_query_params())
765+ d = self.get_page(url, method=self.endpoint.method)
766+ d.addErrback(ec2_error_wrapper)
767+ return d
768
769=== modified file 'txaws/ec2/exception.py'
770--- txaws/ec2/exception.py 2009-11-13 23:56:18 +0000
771+++ txaws/ec2/exception.py 2009-11-28 01:10:27 +0000
772@@ -86,6 +86,7 @@
773 def parse(self, xml_bytes=""):
774 if not xml_bytes:
775 xml_bytes = self.original
776+ self.original = xml_bytes
777 tree = XML(xml_bytes.strip())
778 self._check_for_html(tree)
779 self._set_request_id(tree)
780
781=== modified file 'txaws/ec2/tests/test_client.py'
782--- txaws/ec2/tests/test_client.py 2009-11-13 21:13:23 +0000
783+++ txaws/ec2/tests/test_client.py 2009-11-28 01:10:27 +0000
784@@ -73,14 +73,18 @@
785 self.assertEqual(creds, ec2.creds)
786
787 def test_describe_availability_zones_single(self):
788+
789 class StubQuery(object):
790- def __init__(stub, action, creds, endpoint, other_params):
791+
792+ def __init__(stub, action="", creds=None, endpoint=None,
793+ other_params={}):
794 self.assertEqual(action, "DescribeAvailabilityZones")
795 self.assertEqual(creds.access_key, "foo")
796 self.assertEqual(creds.secret_key, "bar")
797 self.assertEqual(
798- {"ZoneName.1": "us-east-1a"},
799- other_params)
800+ other_params,
801+ {"ZoneName.1": "us-east-1a"})
802+
803 def submit(self):
804 return succeed(
805 payload.sample_describe_availability_zones_single_result)
806@@ -99,11 +103,15 @@
807 return d
808
809 def test_describe_availability_zones_multiple(self):
810+
811 class StubQuery(object):
812- def __init__(stub, action, creds, endpoint, other_params):
813+
814+ def __init__(stub, action="", creds=None, endpoint=None,
815+ other_params={}):
816 self.assertEqual(action, "DescribeAvailabilityZones")
817 self.assertEqual(creds.access_key, "foo")
818 self.assertEqual(creds.secret_key, "bar")
819+
820 def submit(self):
821 return succeed(
822 payload.sample_describe_availability_zones_multiple_results)
823@@ -190,14 +198,19 @@
824 self.check_parsed_instances(results)
825
826 def test_describe_instances(self):
827+
828 class StubQuery(object):
829- def __init__(stub, action, creds, endpoint, params):
830+
831+ def __init__(stub, action="", creds=None, endpoint=None,
832+ other_params={}):
833 self.assertEqual(action, "DescribeInstances")
834 self.assertEqual(creds.access_key, "foo")
835 self.assertEqual(creds.secret_key, "bar")
836- self.assertEquals(params, {})
837+ self.assertEquals(other_params, {})
838+
839 def submit(self):
840 return succeed(payload.sample_describe_instances_result)
841+
842 creds = AWSCredentials("foo", "bar")
843 ec2 = client.EC2Client(creds, query_factory=StubQuery)
844 d = ec2.describe_instances()
845@@ -205,15 +218,20 @@
846 return d
847
848 def test_describe_instances_required(self):
849+
850 class StubQuery(object):
851- def __init__(stub, action, creds, endpoint, params):
852+
853+ def __init__(stub, action="", creds=None, endpoint=None,
854+ other_params={}):
855 self.assertEqual(action, "DescribeInstances")
856 self.assertEqual(creds.access_key, "foo")
857 self.assertEqual(creds.secret_key, "bar")
858- self.assertEquals(params, {})
859+ self.assertEquals(other_params, {})
860+
861 def submit(self):
862 return succeed(
863 payload.sample_required_describe_instances_result)
864+
865 creds = AWSCredentials("foo", "bar")
866 ec2 = client.EC2Client(creds, query_factory=StubQuery)
867 d = ec2.describe_instances()
868@@ -221,18 +239,23 @@
869 return d
870
871 def test_describe_instances_specific_instances(self):
872+
873 class StubQuery(object):
874- def __init__(stub, action, creds, endpoint, params):
875+
876+ def __init__(stub, action="", creds=None, endpoint=None,
877+ other_params={}):
878 self.assertEqual(action, "DescribeInstances")
879 self.assertEqual(creds.access_key, "foo")
880 self.assertEqual(creds.secret_key, "bar")
881 self.assertEquals(
882- params,
883+ other_params,
884 {"InstanceId.1": "i-16546401",
885 "InstanceId.2": "i-49873415"})
886+
887 def submit(self):
888 return succeed(
889 payload.sample_required_describe_instances_result)
890+
891 creds = AWSCredentials("foo", "bar")
892 ec2 = client.EC2Client(creds, query_factory=StubQuery)
893 d = ec2.describe_instances("i-16546401", "i-49873415")
894@@ -240,24 +263,30 @@
895 return d
896
897 def test_terminate_instances(self):
898+
899 class StubQuery(object):
900- def __init__(stub, action, creds, endpoint, other_params):
901+
902+ def __init__(stub, action="", creds=None, endpoint=None,
903+ other_params={}):
904 self.assertEqual(action, "TerminateInstances")
905 self.assertEqual(creds.access_key, "foo")
906 self.assertEqual(creds.secret_key, "bar")
907 self.assertEqual(
908- {"InstanceId.1": "i-1234", "InstanceId.2": "i-5678"},
909- other_params)
910+ other_params,
911+ {"InstanceId.1": "i-1234", "InstanceId.2": "i-5678"})
912+
913 def submit(self):
914 return succeed(payload.sample_terminate_instances_result)
915+
916+ def check_transition(changes):
917+ self.assertEqual([("i-1234", "running", "shutting-down"),
918+ ("i-5678", "shutting-down", "shutting-down")], sorted(changes))
919+
920 creds = AWSCredentials("foo", "bar")
921 endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US)
922 ec2 = client.EC2Client(creds=creds, endpoint=endpoint,
923 query_factory=StubQuery)
924 d = ec2.terminate_instances("i-1234", "i-5678")
925- def check_transition(changes):
926- self.assertEqual([("i-1234", "running", "shutting-down"),
927- ("i-5678", "shutting-down", "shutting-down")], sorted(changes))
928 d.addCallback(check_transition)
929 return d
930
931@@ -289,17 +318,20 @@
932 def test_run_instances(self):
933
934 class StubQuery(object):
935- def __init__(stub, action, creds, endpoint, params):
936+
937+ def __init__(stub, action="", creds=None, endpoint=None,
938+ other_params={}):
939 self.assertEqual(action, "RunInstances")
940 self.assertEqual(creds.access_key, "foo")
941 self.assertEqual(creds.secret_key, "bar")
942 self.assertEquals(
943- params,
944+ other_params,
945 {"ImageId": "ami-1234", "MaxCount": "2", "MinCount": "1",
946 "SecurityGroup.1": u"group1", "KeyName": u"default",
947 "UserData": "Zm9v", "InstanceType": u"m1.small",
948 "Placement.AvailabilityZone": u"us-east-1b",
949 "KernelId": u"k-1234", "RamdiskId": u"r-1234"})
950+
951 def submit(self):
952 return succeed(
953 payload.sample_run_instances_result)
954@@ -322,11 +354,14 @@
955 using XML data received from the cloud.
956 """
957 class StubQuery(object):
958- def __init__(stub, action, creds, endpoint, other_params=None):
959+
960+ def __init__(stub, action="", creds=None, endpoint=None,
961+ other_params={}):
962 self.assertEqual(action, "DescribeSecurityGroups")
963 self.assertEqual(creds.access_key, "foo")
964 self.assertEqual(creds.secret_key, "bar")
965- self.assertEqual(other_params, None)
966+ self.assertEqual(other_params, {})
967+
968 def submit(self):
969 return succeed(payload.sample_describe_security_groups_result)
970
971@@ -354,11 +389,14 @@
972 information about more than one L{SecurityGroup}.
973 """
974 class StubQuery(object):
975- def __init__(stub, action, creds, endpoint, other_params=None):
976+
977+ def __init__(stub, action="", creds=None, endpoint=None,
978+ other_params={}):
979 self.assertEqual(action, "DescribeSecurityGroups")
980 self.assertEqual(creds.access_key, "foo")
981 self.assertEqual(creds.secret_key, "bar")
982- self.assertEqual(other_params, None)
983+ self.assertEqual(other_params, {})
984+
985 def submit(self):
986 return succeed(
987 payload.sample_describe_security_groups_multiple_result)
988@@ -401,11 +439,14 @@
989 security group names to limit results to.
990 """
991 class StubQuery(object):
992- def __init__(stub, action, creds, endpoint, other_params=None):
993+
994+ def __init__(stub, action="", creds=None, endpoint=None,
995+ other_params={}):
996 self.assertEqual(action, "DescribeSecurityGroups")
997 self.assertEqual(creds.access_key, "foo")
998 self.assertEqual(creds.secret_key, "bar")
999 self.assertEqual(other_params, {"GroupName.1": "WebServers"})
1000+
1001 def submit(self):
1002 return succeed(payload.sample_describe_security_groups_result)
1003
1004@@ -425,7 +466,9 @@
1005 operation.
1006 """
1007 class StubQuery(object):
1008- def __init__(stub, action, creds, endpoint, other_params=None):
1009+
1010+ def __init__(stub, action="", creds=None, endpoint=None,
1011+ other_params={}):
1012 self.assertEqual(action, "CreateSecurityGroup")
1013 self.assertEqual(creds.access_key, "foo")
1014 self.assertEqual(creds.secret_key, "bar")
1015@@ -433,6 +476,7 @@
1016 "GroupName": "WebServers",
1017 "GroupDescription": "The group for the web server farm.",
1018 })
1019+
1020 def submit(self):
1021 return succeed(payload.sample_create_security_group)
1022
1023@@ -450,13 +494,16 @@
1024 operation.
1025 """
1026 class StubQuery(object):
1027- def __init__(stub, action, creds, endpoint, other_params=None):
1028+
1029+ def __init__(stub, action="", creds=None, endpoint=None,
1030+ other_params={}):
1031 self.assertEqual(action, "DeleteSecurityGroup")
1032 self.assertEqual(creds.access_key, "foo")
1033 self.assertEqual(creds.secret_key, "bar")
1034 self.assertEqual(other_params, {
1035 "GroupName": "WebServers",
1036 })
1037+
1038 def submit(self):
1039 return succeed(payload.sample_delete_security_group)
1040
1041@@ -472,13 +519,16 @@
1042 that another group uses in that other group's policy.
1043 """
1044 class StubQuery(object):
1045- def __init__(stub, action, creds, endpoint, other_params=None):
1046+
1047+ def __init__(stub, action="", creds=None, endpoint=None,
1048+ other_params={}):
1049 self.assertEqual(action, "DeleteSecurityGroup")
1050 self.assertEqual(creds.access_key, "foo")
1051 self.assertEqual(creds.secret_key, "bar")
1052 self.assertEqual(other_params, {
1053 "GroupName": "GroupReferredTo",
1054 })
1055+
1056 def submit(self):
1057 error = EC2Error(payload.sample_delete_security_group_failure)
1058 return fail(error)
1059@@ -504,7 +554,9 @@
1060 way.
1061 """
1062 class StubQuery(object):
1063- def __init__(stub, action, creds, endpoint, other_params=None):
1064+
1065+ def __init__(stub, action="", creds=None, endpoint=None,
1066+ other_params={}):
1067 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1068 self.assertEqual(creds.access_key, "foo")
1069 self.assertEqual(creds.secret_key, "bar")
1070@@ -513,6 +565,7 @@
1071 "SourceSecurityGroupName": "AppServers",
1072 "SourceSecurityGroupOwnerId": "123456789123",
1073 })
1074+
1075 def submit(self):
1076 return succeed(payload.sample_authorize_security_group)
1077
1078@@ -532,7 +585,9 @@
1079 way.
1080 """
1081 class StubQuery(object):
1082- def __init__(stub, action, creds, endpoint, other_params=None):
1083+
1084+ def __init__(stub, action="", creds=None, endpoint=None,
1085+ other_params={}):
1086 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1087 self.assertEqual(creds.access_key, "foo")
1088 self.assertEqual(creds.secret_key, "bar")
1089@@ -541,6 +596,7 @@
1090 "FromPort": "22", "ToPort": "80",
1091 "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0",
1092 })
1093+
1094 def submit(self):
1095 return succeed(payload.sample_authorize_security_group)
1096
1097@@ -580,7 +636,9 @@
1098 operation.
1099 """
1100 class StubQuery(object):
1101- def __init__(stub, action, creds, endpoint, other_params=None):
1102+
1103+ def __init__(stub, action="", creds=None, endpoint=None,
1104+ other_params={}):
1105 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1106 self.assertEqual(creds.access_key, "foo")
1107 self.assertEqual(creds.secret_key, "bar")
1108@@ -589,6 +647,7 @@
1109 "SourceSecurityGroupName": "AppServers",
1110 "SourceSecurityGroupOwnerId": "123456789123",
1111 })
1112+
1113 def submit(self):
1114 return succeed(payload.sample_authorize_security_group)
1115
1116@@ -606,7 +665,9 @@
1117 operation.
1118 """
1119 class StubQuery(object):
1120- def __init__(stub, action, creds, endpoint, other_params=None):
1121+
1122+ def __init__(stub, action="", creds=None, endpoint=None,
1123+ other_params={}):
1124 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1125 self.assertEqual(creds.access_key, "foo")
1126 self.assertEqual(creds.secret_key, "bar")
1127@@ -615,6 +676,7 @@
1128 "FromPort": "22", "ToPort": "80",
1129 "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0",
1130 })
1131+
1132 def submit(self):
1133 return succeed(payload.sample_authorize_security_group)
1134
1135@@ -634,7 +696,9 @@
1136 way.
1137 """
1138 class StubQuery(object):
1139- def __init__(stub, action, creds, endpoint, other_params=None):
1140+
1141+ def __init__(stub, action="", creds=None, endpoint=None,
1142+ other_params={}):
1143 self.assertEqual(action, "RevokeSecurityGroupIngress")
1144 self.assertEqual(creds.access_key, "foo")
1145 self.assertEqual(creds.secret_key, "bar")
1146@@ -643,6 +707,7 @@
1147 "SourceSecurityGroupName": "AppServers",
1148 "SourceSecurityGroupOwnerId": "123456789123",
1149 })
1150+
1151 def submit(self):
1152 return succeed(payload.sample_revoke_security_group)
1153
1154@@ -662,7 +727,9 @@
1155 way.
1156 """
1157 class StubQuery(object):
1158- def __init__(stub, action, creds, endpoint, other_params=None):
1159+
1160+ def __init__(stub, action="", creds=None, endpoint=None,
1161+ other_params={}):
1162 self.assertEqual(action, "RevokeSecurityGroupIngress")
1163 self.assertEqual(creds.access_key, "foo")
1164 self.assertEqual(creds.secret_key, "bar")
1165@@ -671,6 +738,7 @@
1166 "FromPort": "22", "ToPort": "80",
1167 "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0",
1168 })
1169+
1170 def submit(self):
1171 return succeed(payload.sample_revoke_security_group)
1172
1173@@ -710,7 +778,9 @@
1174 operation.
1175 """
1176 class StubQuery(object):
1177- def __init__(stub, action, creds, endpoint, other_params=None):
1178+
1179+ def __init__(stub, action="", creds=None, endpoint=None,
1180+ other_params={}):
1181 self.assertEqual(action, "RevokeSecurityGroupIngress")
1182 self.assertEqual(creds.access_key, "foo")
1183 self.assertEqual(creds.secret_key, "bar")
1184@@ -719,6 +789,7 @@
1185 "SourceSecurityGroupName": "AppServers",
1186 "SourceSecurityGroupOwnerId": "123456789123",
1187 })
1188+
1189 def submit(self):
1190 return succeed(payload.sample_revoke_security_group)
1191
1192@@ -736,7 +807,9 @@
1193 operation.
1194 """
1195 class StubQuery(object):
1196- def __init__(stub, action, creds, endpoint, other_params=None):
1197+
1198+ def __init__(stub, action="", creds=None, endpoint=None,
1199+ other_params={}):
1200 self.assertEqual(action, "RevokeSecurityGroupIngress")
1201 self.assertEqual(creds.access_key, "foo")
1202 self.assertEqual(creds.secret_key, "bar")
1203@@ -745,6 +818,7 @@
1204 "FromPort": "22", "ToPort": "80",
1205 "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0",
1206 })
1207+
1208 def submit(self):
1209 return succeed(payload.sample_revoke_security_group)
1210
1211@@ -784,11 +858,13 @@
1212 def test_describe_volumes(self):
1213
1214 class StubQuery(object):
1215- def __init__(stub, action, creds, endpoint, params):
1216+
1217+ def __init__(stub, action="", creds=None, endpoint=None,
1218+ other_params={}):
1219 self.assertEqual(action, "DescribeVolumes")
1220 self.assertEqual(self.creds, creds)
1221 self.assertEqual(self.endpoint, endpoint)
1222- self.assertEquals(params, {})
1223+ self.assertEquals(other_params, {})
1224
1225 def submit(self):
1226 return succeed(payload.sample_describe_volumes_result)
1227@@ -802,12 +878,14 @@
1228 def test_describe_specified_volumes(self):
1229
1230 class StubQuery(object):
1231- def __init__(stub, action, creds, endpoint, params):
1232+
1233+ def __init__(stub, action="", creds=None, endpoint=None,
1234+ other_params={}):
1235 self.assertEqual(action, "DescribeVolumes")
1236 self.assertEqual(self.creds, creds)
1237 self.assertEqual(self.endpoint, endpoint)
1238 self.assertEquals(
1239- params,
1240+ other_params,
1241 {"VolumeId.1": "vol-4282672b"})
1242
1243 def submit(self):
1244@@ -832,11 +910,13 @@
1245 def test_describe_snapshots(self):
1246
1247 class StubQuery(object):
1248- def __init__(stub, action, creds, endpoint, params):
1249+
1250+ def __init__(stub, action="", creds=None, endpoint=None,
1251+ other_params={}):
1252 self.assertEqual(action, "DescribeSnapshots")
1253 self.assertEqual(self.creds, creds)
1254 self.assertEqual(self.endpoint, endpoint)
1255- self.assertEquals(params, {})
1256+ self.assertEquals(other_params, {})
1257
1258 def submit(self):
1259 return succeed(payload.sample_describe_snapshots_result)
1260@@ -850,12 +930,14 @@
1261 def test_describe_specified_snapshots(self):
1262
1263 class StubQuery(object):
1264- def __init__(stub, action, creds, endpoint, params):
1265+
1266+ def __init__(stub, action="", creds=None, endpoint=None,
1267+ other_params={}):
1268 self.assertEqual(action, "DescribeSnapshots")
1269 self.assertEqual(self.creds, creds)
1270 self.assertEqual(self.endpoint, endpoint)
1271 self.assertEquals(
1272- params,
1273+ other_params,
1274 {"SnapshotId.1": "snap-78a54011"})
1275
1276 def submit(self):
1277@@ -870,13 +952,15 @@
1278 def test_create_volume(self):
1279
1280 class StubQuery(object):
1281- def __init__(stub, action, creds, endpoint, params):
1282+
1283+ def __init__(stub, action="", creds=None, endpoint=None,
1284+ other_params={}):
1285 self.assertEqual(action, "CreateVolume")
1286 self.assertEqual(self.creds, creds)
1287 self.assertEqual(self.endpoint, endpoint)
1288 self.assertEqual(
1289- {"AvailabilityZone": "us-east-1", "Size": "800"},
1290- params)
1291+ other_params,
1292+ {"AvailabilityZone": "us-east-1", "Size": "800"})
1293
1294 def submit(self):
1295 return succeed(payload.sample_create_volume_result)
1296@@ -897,14 +981,16 @@
1297 def test_create_volume_with_snapshot(self):
1298
1299 class StubQuery(object):
1300- def __init__(stub, action, creds, endpoint, params):
1301+
1302+ def __init__(stub, action="", creds=None, endpoint=None,
1303+ other_params={}):
1304 self.assertEqual(action, "CreateVolume")
1305 self.assertEqual(self.creds, creds)
1306 self.assertEqual(self.endpoint, endpoint)
1307 self.assertEqual(
1308+ other_params,
1309 {"AvailabilityZone": "us-east-1",
1310- "SnapshotId": "snap-12345678"},
1311- params)
1312+ "SnapshotId": "snap-12345678"})
1313
1314 def submit(self):
1315 return succeed(payload.sample_create_volume_result)
1316@@ -939,13 +1025,15 @@
1317 def test_delete_volume(self):
1318
1319 class StubQuery(object):
1320- def __init__(stub, action, creds, endpoint, params):
1321+
1322+ def __init__(stub, action="", creds=None, endpoint=None,
1323+ other_params={}):
1324 self.assertEqual(action, "DeleteVolume")
1325 self.assertEqual(self.creds, creds)
1326 self.assertEqual(self.endpoint, endpoint)
1327 self.assertEqual(
1328- {"VolumeId": "vol-4282672b"},
1329- params)
1330+ other_params,
1331+ {"VolumeId": "vol-4282672b"})
1332
1333 def submit(self):
1334 return succeed(payload.sample_delete_volume_result)
1335@@ -959,13 +1047,15 @@
1336 def test_create_snapshot(self):
1337
1338 class StubQuery(object):
1339- def __init__(stub, action, creds, endpoint, params):
1340+
1341+ def __init__(stub, action="", creds=None, endpoint=None,
1342+ other_params={}):
1343 self.assertEqual(action, "CreateSnapshot")
1344 self.assertEqual(self.creds, creds)
1345 self.assertEqual(self.endpoint, endpoint)
1346 self.assertEqual(
1347- {"VolumeId": "vol-4d826724"},
1348- params)
1349+ other_params,
1350+ {"VolumeId": "vol-4d826724"})
1351
1352 def submit(self):
1353 return succeed(payload.sample_create_snapshot_result)
1354@@ -987,13 +1077,15 @@
1355 def test_delete_snapshot(self):
1356
1357 class StubQuery(object):
1358- def __init__(stub, action, creds, endpoint, params):
1359+
1360+ def __init__(stub, action="", creds=None, endpoint=None,
1361+ other_params={}):
1362 self.assertEqual(action, "DeleteSnapshot")
1363 self.assertEqual(self.creds, creds)
1364 self.assertEqual(self.endpoint, endpoint)
1365 self.assertEqual(
1366- {"SnapshotId": "snap-78a54011"},
1367- params)
1368+ other_params,
1369+ {"SnapshotId": "snap-78a54011"})
1370
1371 def submit(self):
1372 return succeed(payload.sample_delete_snapshot_result)
1373@@ -1007,14 +1099,16 @@
1374 def test_attach_volume(self):
1375
1376 class StubQuery(object):
1377- def __init__(stub, action, creds, endpoint, params):
1378+
1379+ def __init__(stub, action="", creds=None, endpoint=None,
1380+ other_params={}):
1381 self.assertEqual(action, "AttachVolume")
1382 self.assertEqual(self.creds, creds)
1383 self.assertEqual(self.endpoint, endpoint)
1384 self.assertEqual(
1385+ other_params,
1386 {"VolumeId": "vol-4d826724", "InstanceId": "i-6058a509",
1387- "Device": "/dev/sdh"},
1388- params)
1389+ "Device": "/dev/sdh"})
1390
1391 def submit(self):
1392 return succeed(payload.sample_attach_volume_result)
1393@@ -1042,10 +1136,12 @@
1394 def test_single_describe_keypairs(self):
1395
1396 class StubQuery(object):
1397- def __init__(stub, action, creds, endpoint, params):
1398+
1399+ def __init__(stub, action="", creds=None, endpoint=None,
1400+ other_params={}):
1401 self.assertEqual(action, "DescribeKeyPairs")
1402 self.assertEqual("foo", creds)
1403- self.assertEquals(params, {})
1404+ self.assertEquals(other_params, {})
1405
1406 def submit(self):
1407 return succeed(payload.sample_single_describe_keypairs_result)
1408@@ -1070,10 +1166,12 @@
1409 "1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:70")
1410
1411 class StubQuery(object):
1412- def __init__(stub, action, creds, endpoint, params):
1413+
1414+ def __init__(stub, action="", creds=None, endpoint=None,
1415+ other_params={}):
1416 self.assertEqual(action, "DescribeKeyPairs")
1417 self.assertEqual("foo", creds)
1418- self.assertEquals(params, {})
1419+ self.assertEquals(other_params, {})
1420
1421 def submit(self):
1422 return succeed(
1423@@ -1087,11 +1185,13 @@
1424 def test_describe_specified_keypairs(self):
1425
1426 class StubQuery(object):
1427- def __init__(stub, action, creds, endpoint, params):
1428+
1429+ def __init__(stub, action="", creds=None, endpoint=None,
1430+ other_params={}):
1431 self.assertEqual(action, "DescribeKeyPairs")
1432 self.assertEqual("foo", creds)
1433 self.assertEquals(
1434- params,
1435+ other_params,
1436 {"KeyPair.1": "gsg-keypair"})
1437
1438 def submit(self):
1439@@ -1116,11 +1216,13 @@
1440 self.assertEquals(len(keypair.material), 1670)
1441
1442 class StubQuery(object):
1443- def __init__(stub, action, creds, endpoint, params):
1444+
1445+ def __init__(stub, action="", creds=None, endpoint=None,
1446+ other_params={}):
1447 self.assertEqual(action, "CreateKeyPair")
1448 self.assertEqual("foo", creds)
1449 self.assertEquals(
1450- params,
1451+ other_params,
1452 {"KeyName": "example-key-name"})
1453
1454 def submit(self):
1455@@ -1134,12 +1236,14 @@
1456 def test_delete_keypair_true_result(self):
1457
1458 class StubQuery(object):
1459- def __init__(stub, action, creds, endpoint, params):
1460+
1461+ def __init__(stub, action="", creds=None, endpoint=None,
1462+ other_params={}):
1463 self.assertEqual(action, "DeleteKeyPair")
1464 self.assertEqual("foo", creds)
1465 self.assertEqual("http:///", endpoint.get_uri())
1466 self.assertEquals(
1467- params,
1468+ other_params,
1469 {"KeyName": "example-key-name"})
1470
1471 def submit(self):
1472@@ -1153,12 +1257,14 @@
1473 def test_delete_keypair_false_result(self):
1474
1475 class StubQuery(object):
1476- def __init__(stub, action, creds, endpoint, params):
1477+
1478+ def __init__(stub, action="", creds=None, endpoint=None,
1479+ other_params={}):
1480 self.assertEqual(action, "DeleteKeyPair")
1481 self.assertEqual("foo", creds)
1482 self.assertEqual("http:///", endpoint.get_uri())
1483 self.assertEquals(
1484- params,
1485+ other_params,
1486 {"KeyName": "example-key-name"})
1487
1488 def submit(self):
1489@@ -1172,12 +1278,14 @@
1490 def test_delete_keypair_no_result(self):
1491
1492 class StubQuery(object):
1493- def __init__(stub, action, creds, endpoint, params):
1494+
1495+ def __init__(stub, action="", creds=None, endpoint=None,
1496+ other_params={}):
1497 self.assertEqual(action, "DeleteKeyPair")
1498 self.assertEqual("foo", creds)
1499 self.assertEqual("http:///", endpoint.get_uri())
1500 self.assertEquals(
1501- params,
1502+ other_params,
1503 {"KeyName": "example-key-name"})
1504
1505 def submit(self):
1506@@ -1298,40 +1406,38 @@
1507 self.endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US)
1508
1509 def test_init_minimum(self):
1510- query = client.Query("DescribeInstances", self.creds, self.endpoint)
1511+ query = client.Query(
1512+ action="DescribeInstances", creds=self.creds,
1513+ endpoint=self.endpoint)
1514 self.assertTrue("Timestamp" in query.params)
1515 del query.params["Timestamp"]
1516 self.assertEqual(
1517+ query.params,
1518 {"AWSAccessKeyId": "foo",
1519 "Action": "DescribeInstances",
1520 "SignatureMethod": "HmacSHA256",
1521 "SignatureVersion": "2",
1522- "Version": "2008-12-01"},
1523- query.params)
1524-
1525- def test_init_requires_action(self):
1526- self.assertRaises(TypeError, client.Query)
1527-
1528- def test_init_requires_creds(self):
1529- self.assertRaises(TypeError, client.Query, None)
1530+ "Version": "2008-12-01"})
1531
1532 def test_init_other_args_are_params(self):
1533- query = client.Query("DescribeInstances", self.creds, self.endpoint,
1534- {"InstanceId.0": "12345"},
1535+ query = client.Query(
1536+ action="DescribeInstances", creds=self.creds,
1537+ endpoint=self.endpoint, other_params={"InstanceId.0": "12345"},
1538 time_tuple=(2007,11,12,13,14,15,0,0,0))
1539 self.assertEqual(
1540+ query.params,
1541 {"AWSAccessKeyId": "foo",
1542 "Action": "DescribeInstances",
1543 "InstanceId.0": "12345",
1544 "SignatureMethod": "HmacSHA256",
1545 "SignatureVersion": "2",
1546 "Timestamp": "2007-11-12T13:14:15Z",
1547- "Version": "2008-12-01"},
1548- query.params)
1549+ "Version": "2008-12-01"})
1550
1551 def test_sorted_params(self):
1552- query = client.Query("DescribeInstances", self.creds, self.endpoint,
1553- {"fun": "games"},
1554+ query = client.Query(
1555+ action="DescribeInstances", creds=self.creds,
1556+ endpoint=self.endpoint, other_params={"fun": "games"},
1557 time_tuple=(2007,11,12,13,14,15,0,0,0))
1558 self.assertEqual([
1559 ("AWSAccessKeyId", "foo"),
1560@@ -1346,29 +1452,36 @@
1561 def test_encode_unreserved(self):
1562 all_unreserved = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1563 "abcdefghijklmnopqrstuvwxyz0123456789-_.~")
1564- query = client.Query("DescribeInstances", self.creds, self.endpoint)
1565+ query = client.Query(
1566+ action="DescribeInstances", creds=self.creds,
1567+ endpoint=self.endpoint)
1568 self.assertEqual(all_unreserved, query.encode(all_unreserved))
1569
1570 def test_encode_space(self):
1571 """This may be just "url encode", but the AWS manual isn't clear."""
1572- query = client.Query("DescribeInstances", self.creds, self.endpoint)
1573+ query = client.Query(
1574+ action="DescribeInstances", creds=self.creds,
1575+ endpoint=self.endpoint)
1576 self.assertEqual("a%20space", query.encode("a space"))
1577
1578 def test_canonical_query(self):
1579- query = client.Query("DescribeInstances", self.creds, self.endpoint,
1580- {"fu n": "g/ames", "argwithnovalue":"",
1581- "InstanceId.1": "i-1234"},
1582+ query = client.Query(
1583+ action="DescribeInstances", creds=self.creds,
1584+ endpoint=self.endpoint,
1585+ other_params={"fu n": "g/ames", "argwithnovalue":"",
1586+ "InstanceId.1": "i-1234"},
1587 time_tuple=(2007,11,12,13,14,15,0,0,0))
1588 expected_query = ("AWSAccessKeyId=foo&Action=DescribeInstances"
1589 "&InstanceId.1=i-1234"
1590 "&SignatureMethod=HmacSHA256&SignatureVersion=2&"
1591 "Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01&"
1592 "argwithnovalue=&fu%20n=g%2Fames")
1593- self.assertEqual(expected_query, query.canonical_query_params())
1594+ self.assertEqual(expected_query, query.get_canonical_query_params())
1595
1596 def test_signing_text(self):
1597- query = client.Query("DescribeInstances", self.creds, self.endpoint,
1598- time_tuple=(2007,11,12,13,14,15,0,0,0))
1599+ query = client.Query(
1600+ action="DescribeInstances", creds=self.creds,
1601+ endpoint=self.endpoint, time_tuple=(2007,11,12,13,14,15,0,0,0))
1602 signing_text = ("GET\n%s\n/\n" % self.endpoint.host +
1603 "AWSAccessKeyId=foo&Action=DescribeInstances&"
1604 "SignatureMethod=HmacSHA256&SignatureVersion=2&"
1605@@ -1376,7 +1489,9 @@
1606 self.assertEqual(signing_text, query.signing_text())
1607
1608 def test_sign(self):
1609- query = client.Query("DescribeInstances", self.creds, self.endpoint,
1610+ query = client.Query(
1611+ action="DescribeInstances", creds=self.creds,
1612+ endpoint=self.endpoint,
1613 time_tuple=(2007, 11, 12, 13, 14, 15, 0, 0, 0))
1614 query.sign()
1615 self.assertEqual("aDmLr0Ktjsmt17UJD/EZf6DrfKWT1JW0fq2FDUCOPic=",
1616@@ -1401,7 +1516,7 @@
1617 self.assertEquals(error.response, payload.sample_ec2_error_message)
1618
1619 query = client.Query(
1620- 'BadQuery', self.creds, self.endpoint,
1621+ action='BadQuery', creds=self.creds, endpoint=self.endpoint,
1622 time_tuple=(2009,8,15,13,14,15,0,0,0))
1623
1624 failure = query.submit()
1625@@ -1426,7 +1541,7 @@
1626 self.assertEquals(error.status, status)
1627
1628 query = client.Query(
1629- 'BadQuery', self.creds, self.endpoint,
1630+ action='BadQuery', creds=self.creds, endpoint=self.endpoint,
1631 time_tuple=(2009,8,15,13,14,15,0,0,0))
1632
1633 failure = query.submit()
1634@@ -1455,7 +1570,7 @@
1635 "We encountered an internal error. Please try again.")
1636
1637 query = client.Query(
1638- 'BadQuery', self.creds, self.endpoint,
1639+ action='BadQuery', creds=self.creds, endpoint=self.endpoint,
1640 time_tuple=(2009,8,15,13,14,15,0,0,0))
1641
1642 failure = query.submit()
1643@@ -1510,14 +1625,13 @@
1644 def test_get_page(self):
1645 """Copied from twisted.web.test.test_webclient."""
1646 query = client.Query(
1647- 'DummyQuery', self.creds, self.endpoint,
1648+ action="DummyQuery", creds=self.creds, endpoint=self.endpoint,
1649 time_tuple=(2009,8,17,13,14,15,0,0,0))
1650 deferred = query.get_page(self.get_url("file"))
1651 deferred.addCallback(self.assertEquals, "0123456789")
1652 return deferred
1653
1654
1655-
1656 class EC2ClientAddressTestCase(TXAWSTestCase):
1657
1658 def setUp(self):
1659@@ -1528,11 +1642,13 @@
1660 def test_describe_addresses(self):
1661
1662 class StubQuery(object):
1663- def __init__(stub, action, creds, endpoint, params):
1664+
1665+ def __init__(stub, action="", creds=None, endpoint=None,
1666+ other_params={}):
1667 self.assertEqual(action, "DescribeAddresses")
1668 self.assertEqual(self.creds, creds)
1669 self.assertEqual(self.endpoint, endpoint)
1670- self.assertEquals(params, {})
1671+ self.assertEquals(other_params, {})
1672
1673 def submit(self):
1674 return succeed(payload.sample_describe_addresses_result)
1675@@ -1548,12 +1664,14 @@
1676 def test_describe_specified_addresses(self):
1677
1678 class StubQuery(object):
1679- def __init__(stub, action, creds, endpoint, params):
1680+
1681+ def __init__(stub, action="", creds=None, endpoint=None,
1682+ other_params={}):
1683 self.assertEqual(action, "DescribeAddresses")
1684 self.assertEqual(self.creds, creds)
1685 self.assertEqual(self.endpoint, endpoint)
1686 self.assertEquals(
1687- params,
1688+ other_params,
1689 {"PublicIp.1": "67.202.55.255"})
1690
1691 def submit(self):
1692@@ -1570,12 +1688,14 @@
1693 def test_associate_address(self):
1694
1695 class StubQuery(object):
1696- def __init__(stub, action, creds, endpoint, params):
1697+
1698+ def __init__(stub, action="", creds=None, endpoint=None,
1699+ other_params={}):
1700 self.assertEqual(action, "AssociateAddress")
1701 self.assertEqual(self.creds, creds)
1702 self.assertEqual(self.endpoint, endpoint)
1703 self.assertEquals(
1704- params,
1705+ other_params,
1706 {"InstanceId": "i-28a64341", "PublicIp": "67.202.55.255"})
1707
1708 def submit(self):
1709@@ -1590,11 +1710,13 @@
1710 def test_allocate_address(self):
1711
1712 class StubQuery(object):
1713- def __init__(stub, action, creds, endpoint, params):
1714+
1715+ def __init__(stub, action="", creds=None, endpoint=None,
1716+ other_params={}):
1717 self.assertEqual(action, "AllocateAddress")
1718 self.assertEqual(self.creds, creds)
1719 self.assertEqual(self.endpoint, endpoint)
1720- self.assertEquals(params, {})
1721+ self.assertEquals(other_params, {})
1722
1723 def submit(self):
1724 return succeed(payload.sample_allocate_address_result)
1725@@ -1608,11 +1730,13 @@
1726 def test_release_address(self):
1727
1728 class StubQuery(object):
1729- def __init__(stub, action, creds, endpoint, params):
1730+
1731+ def __init__(stub, action="", creds=None, endpoint=None,
1732+ other_params={}):
1733 self.assertEqual(action, "ReleaseAddress")
1734 self.assertEqual(self.creds, creds)
1735 self.assertEqual(self.endpoint, endpoint)
1736- self.assertEquals(params, {"PublicIp": "67.202.55.255"})
1737+ self.assertEquals(other_params, {"PublicIp": "67.202.55.255"})
1738
1739 def submit(self):
1740 return succeed(payload.sample_release_address_result)
1741@@ -1626,11 +1750,13 @@
1742 def test_disassociate_address(self):
1743
1744 class StubQuery(object):
1745- def __init__(stub, action, creds, endpoint, params):
1746+
1747+ def __init__(stub, action="", creds=None, endpoint=None,
1748+ other_params={}):
1749 self.assertEqual(action, "DisassociateAddress")
1750 self.assertEqual(self.creds, creds)
1751 self.assertEqual(self.endpoint, endpoint)
1752- self.assertEquals(params, {"PublicIp": "67.202.55.255"})
1753+ self.assertEquals(other_params, {"PublicIp": "67.202.55.255"})
1754
1755 def submit(self):
1756 return succeed(payload.sample_disassociate_address_result)
1757
1758=== modified file 'txaws/ec2/tests/test_exception.py'
1759--- txaws/ec2/tests/test_exception.py 2009-11-13 23:43:19 +0000
1760+++ txaws/ec2/tests/test_exception.py 2009-11-28 01:10:27 +0000
1761@@ -77,7 +77,7 @@
1762 def test_parse_with_xml(self):
1763 xml1 = "<dummy1 />"
1764 xml2 = "<dummy2 />"
1765- error = EC2Error(xml2)
1766+ error = EC2Error(xml1)
1767 error.parse(xml2)
1768 self.assertEquals(error.original, xml2)
1769
1770
1771=== renamed directory 'txaws/storage' => 'txaws/s3'
1772=== modified file 'txaws/s3/client.py'
1773--- txaws/storage/client.py 2009-09-05 01:13:43 +0000
1774+++ txaws/s3/client.py 2009-11-28 01:10:27 +0000
1775@@ -1,3 +1,8 @@
1776+# Copyright (C) 2008 Tristan Seligmann <mithrandi@mithrandi.net>
1777+# Copyright (C) 2009 Canonical Ltd
1778+# Copyright (C) 2009 Duncan McGreggor <oubiwann@adytum.us>
1779+# Licenced under the txaws licence available at /LICENSE in the txaws source.
1780+
1781 """
1782 Client wrapper for Amazon's Simple Storage Service.
1783
1784@@ -6,173 +11,245 @@
1785 Various API-incompatible changes are planned in order to expose missing
1786 functionality in this wrapper.
1787 """
1788+import mimetypes
1789+
1790+from twisted.web.http import datetimeToString
1791
1792 from epsilon.extime import Time
1793
1794-from twisted.web.client import getPage
1795-from twisted.web.http import datetimeToString
1796-
1797-from txaws.service import AWSServiceEndpoint
1798+from txaws.client.base import BaseClient, BaseQuery
1799+from txaws.s3 import model
1800+from txaws.service import AWSServiceEndpoint, S3_ENDPOINT
1801 from txaws.util import XML, calculate_md5
1802
1803
1804-class S3Request(object):
1805-
1806- def __init__(self, verb, bucket=None, object_name=None, data="",
1807- content_type=None, metadata={}, creds=None, endpoint=None):
1808- self.verb = verb
1809+class URLContext(object):
1810+ """
1811+ The hosts and the paths that form an S3 endpoint change depending upon the
1812+ context in which they are called. Sometimes the bucket name is in the host,
1813+ sometimes in the path. What's more, the behaviour against live AWS
1814+ resources doesn't seem to match the AWS documentation.
1815+ """
1816+ def __init__(self, service_endpoint, bucket="", object_name=""):
1817+ self.endpoint = service_endpoint
1818+ self.bucket = bucket
1819+ self.object_name = object_name
1820+
1821+ def get_host(self):
1822+ if not self.bucket:
1823+ return self.endpoint.get_host()
1824+ else:
1825+ return "%s.%s" % (self.bucket, self.endpoint.get_host())
1826+
1827+ def get_path(self):
1828+ path = "/"
1829+ if self.bucket is not None and self.object_name:
1830+ if self.object_name.startswith("/"):
1831+ path = self.object_name
1832+ else:
1833+ path += self.object_name
1834+ return path
1835+
1836+ def get_url(self):
1837+ return "%s://%s%s" % (
1838+ self.endpoint.scheme, self.get_host(), self.get_path())
1839+
1840+
1841+class BucketURLContext(URLContext):
1842+ """
1843+ This URL context class provides a means of overriding the standard
1844+ behaviour of the URLContext object so that when creating or deleting a
1845+ bucket, the appropriate URL is obtained.
1846+
1847+ When creating and deleting buckets on AWS, if the host is set as documented
1848+ (bucketname.s3.amazonaws.com), a 403 error is returned. When, however, one
1849+ sets the host without the bucket name prefix, the operation is completed
1850+ successfully.
1851+ """
1852+ def get_host(self):
1853+ return self.endpoint.get_host()
1854+
1855+ def get_path(self):
1856+ return "/%s" % (self.bucket)
1857+
1858+
1859+class S3Client(BaseClient):
1860+ """A client for S3."""
1861+
1862+ def __init__(self, creds=None, endpoint=None, query_factory=None):
1863+ if query_factory is None:
1864+ query_factory = Query
1865+ super(S3Client, self).__init__(creds, endpoint, query_factory)
1866+
1867+ def list_buckets(self):
1868+ """
1869+ List all buckets.
1870+
1871+ Returns a list of all the buckets owned by the authenticated sender of
1872+ the request.
1873+ """
1874+ query = self.query_factory(
1875+ action="GET", creds=self.creds, endpoint=self.endpoint)
1876+ d = query.submit()
1877+ return d.addCallback(self._parse_list_buckets)
1878+
1879+ def _parse_list_buckets(self, xml_bytes):
1880+ """
1881+ Parse XML bucket list response.
1882+ """
1883+ root = XML(xml_bytes)
1884+ buckets = []
1885+ for bucket_data in root.find("Buckets"):
1886+ name = bucket_data.findtext("Name")
1887+ date_text = bucket_data.findtext("CreationDate")
1888+ date_time = Time.fromISO8601TimeAndDate(date_text).asDatetime()
1889+ bucket = model.Bucket(name, date_time)
1890+ buckets.append(bucket)
1891+ return buckets
1892+
1893+ def create_bucket(self, bucket):
1894+ """
1895+ Create a new bucket.
1896+ """
1897+ query = self.query_factory(
1898+ action="PUT", creds=self.creds, endpoint=self.endpoint,
1899+ bucket=bucket)
1900+ url_context = BucketURLContext(self.endpoint, bucket)
1901+ return query.submit(url_context)
1902+
1903+ def delete_bucket(self, bucket):
1904+ """
1905+ Delete a bucket.
1906+
1907+ The bucket must be empty before it can be deleted.
1908+ """
1909+ query = self.query_factory(
1910+ action="DELETE", creds=self.creds, endpoint=self.endpoint,
1911+ bucket=bucket)
1912+ url_context = BucketURLContext(self.endpoint, bucket)
1913+ return query.submit(url_context)
1914+
1915+ def put_object(self, bucket, object_name, data, content_type=None,
1916+ metadata={}):
1917+ """
1918+ Put an object in a bucket.
1919+
1920+ Any existing object of the same name will be replaced.
1921+ """
1922+ query = self.query_factory(
1923+ action="PUT", creds=self.creds, endpoint=self.endpoint,
1924+ bucket=bucket, object_name=object_name, data=data,
1925+ content_type=content_type, metadata=metadata)
1926+ return query.submit()
1927+
1928+ def get_object(self, bucket, object_name):
1929+ """
1930+ Get an object from a bucket.
1931+ """
1932+ query = self.query_factory(
1933+ action="GET", creds=self.creds, endpoint=self.endpoint,
1934+ bucket=bucket, object_name=object_name)
1935+ return query.submit()
1936+
1937+ def head_object(self, bucket, object_name):
1938+ """
1939+ Retrieve object metadata only.
1940+ """
1941+ query = self.query_factory(
1942+ action="HEAD", creds=self.creds, endpoint=self.endpoint,
1943+ bucket=bucket, object_name=object_name)
1944+ d = query.submit()
1945+ return d.addCallback(query.get_response_headers)
1946+
1947+ def delete_object(self, bucket, object_name):
1948+ """
1949+ Delete an object from a bucket.
1950+
1951+ Once deleted, there is no method to restore or undelete an object.
1952+ """
1953+ query = self.query_factory(
1954+ action="DELETE", creds=self.creds, endpoint=self.endpoint,
1955+ bucket=bucket, object_name=object_name)
1956+ return query.submit()
1957+
1958+
1959+class Query(BaseQuery):
1960+ """A query for submission to the S3 service."""
1961+
1962+ def __init__(self, bucket=None, object_name=None, data="",
1963+ content_type=None, metadata={}, *args, **kwargs):
1964+ super(Query, self).__init__(*args, **kwargs)
1965 self.bucket = bucket
1966 self.object_name = object_name
1967 self.data = data
1968 self.content_type = content_type
1969 self.metadata = metadata
1970- self.creds = creds
1971- self.endpoint = endpoint
1972 self.date = datetimeToString()
1973-
1974- def get_path(self):
1975- path = "/"
1976- if self.bucket is not None:
1977- path += self.bucket
1978- if self.object_name is not None:
1979- path += "/" + self.object_name
1980- return path
1981-
1982- def get_uri(self):
1983- if self.endpoint is None:
1984- self.endpoint = AWSServiceEndpoint()
1985- self.endpoint.set_path(self.get_path())
1986- return self.endpoint.get_uri()
1987+ if not self.endpoint or not self.endpoint.host:
1988+ self.endpoint = AWSServiceEndpoint(S3_ENDPOINT)
1989+ self.endpoint.set_method(self.action)
1990+
1991+ def set_content_type(self):
1992+ if self.object_name and not self.content_type:
1993+ # XXX nothing is currently done with the encoding... we may
1994+ # need to in the future
1995+ self.content_type, encoding = mimetypes.guess_type(
1996+ self.object_name, strict=False)
1997
1998 def get_headers(self):
1999 headers = {"Content-Length": len(self.data),
2000 "Content-MD5": calculate_md5(self.data),
2001 "Date": self.date}
2002-
2003 for key, value in self.metadata.iteritems():
2004 headers["x-amz-meta-" + key] = value
2005-
2006+ # Before we check if the content type is set, let's see if we can set
2007+ # it by guessing the the mimetype.
2008+ self.set_content_type()
2009 if self.content_type is not None:
2010 headers["Content-Type"] = self.content_type
2011-
2012 if self.creds is not None:
2013- signature = self.get_signature(headers)
2014+ signature = self.sign(headers)
2015 headers["Authorization"] = "AWS %s:%s" % (
2016 self.creds.access_key, signature)
2017 return headers
2018
2019- def get_canonicalized_resource(self):
2020- return self.get_path()
2021-
2022 def get_canonicalized_amz_headers(self, headers):
2023- result = ""
2024- headers = [(name.lower(), value) for name, value in headers.iteritems()
2025+ headers = [
2026+ (name.lower(), value) for name, value in headers.iteritems()
2027 if name.lower().startswith("x-amz-")]
2028 headers.sort()
2029+ # XXX missing spec implementation:
2030+ # 1) txAWS doesn't currently combine headers with the same name
2031+ # 2) txAWS doesn't currently unfold long headers
2032 return "".join("%s:%s\n" % (name, value) for name, value in headers)
2033
2034- def get_signature(self, headers):
2035- text = (self.verb + "\n" +
2036+ def get_canonicalized_resource(self):
2037+ resource = "/"
2038+ if self.bucket:
2039+ resource += self.bucket
2040+ if self.object_name:
2041+ resource += "/%s" % self.object_name
2042+ return resource
2043+
2044+ def sign(self, headers):
2045+
2046+ text = (self.action + "\n" +
2047 headers.get("Content-MD5", "") + "\n" +
2048 headers.get("Content-Type", "") + "\n" +
2049 headers.get("Date", "") + "\n" +
2050 self.get_canonicalized_amz_headers(headers) +
2051 self.get_canonicalized_resource())
2052- return self.creds.sign(text)
2053-
2054- def submit(self):
2055- return self.get_page(url=self.get_uri(), method=self.verb,
2056- postdata=self.data, headers=self.get_headers())
2057-
2058- def get_page(self, *a, **kw):
2059- return getPage(*a, **kw)
2060-
2061-
2062-class S3(object):
2063-
2064- request_factory = S3Request
2065-
2066- def __init__(self, creds, endpoint):
2067- self.creds = creds
2068- self.endpoint = endpoint
2069-
2070- def make_request(self, *a, **kw):
2071- """
2072- Create a request with the arguments passed in.
2073-
2074- This uses the request_factory attribute, adding the creds and endpoint
2075- to the arguments passed in.
2076- """
2077- return self.request_factory(creds=self.creds, endpoint=self.endpoint,
2078- *a, **kw)
2079-
2080- def _parse_bucket_list(self, response):
2081- """
2082- Parse XML bucket list response.
2083- """
2084- root = XML(response)
2085- for bucket in root.find("Buckets"):
2086- timeText = bucket.findtext("CreationDate")
2087- yield {
2088- "name": bucket.findtext("Name"),
2089- "created": Time.fromISO8601TimeAndDate(timeText),
2090- }
2091-
2092- def list_buckets(self):
2093- """
2094- List all buckets.
2095-
2096- Returns a list of all the buckets owned by the authenticated sender of
2097- the request.
2098- """
2099- deferred = self.make_request("GET").submit()
2100- deferred.addCallback(self._parse_bucket_list)
2101- return deferred
2102-
2103- def create_bucket(self, bucket):
2104- """
2105- Create a new bucket.
2106- """
2107- return self.make_request("PUT", bucket).submit()
2108-
2109- def delete_bucket(self, bucket):
2110- """
2111- Delete a bucket.
2112-
2113- The bucket must be empty before it can be deleted.
2114- """
2115- return self.make_request("DELETE", bucket).submit()
2116-
2117- def put_object(self, bucket, object_name, data, content_type=None,
2118- metadata={}):
2119- """
2120- Put an object in a bucket.
2121-
2122- Any existing object of the same name will be replaced.
2123- """
2124- return self.make_request("PUT", bucket, object_name, data,
2125- content_type, metadata).submit()
2126-
2127- def get_object(self, bucket, object_name):
2128- """
2129- Get an object from a bucket.
2130- """
2131- return self.make_request("GET", bucket, object_name).submit()
2132-
2133- def head_object(self, bucket, object_name):
2134- """
2135- Retrieve object metadata only.
2136-
2137- This is like get_object, but the object's content is not retrieved.
2138- Currently the metadata is not returned to the caller either, so this
2139- method is mostly useless, and only provided for completeness.
2140- """
2141- return self.make_request("HEAD", bucket, object_name).submit()
2142-
2143- def delete_object(self, bucket, object_name):
2144- """
2145- Delete an object from a bucket.
2146-
2147- Once deleted, there is no method to restore or undelete an object.
2148- """
2149- return self.make_request("DELETE", bucket, object_name).submit()
2150+ return self.creds.sign(text, hash_type="sha1")
2151+
2152+ def submit(self, url_context=None):
2153+ if not url_context:
2154+ url_context = URLContext(
2155+ self.endpoint, self.bucket, self.object_name)
2156+ d = self.get_page(
2157+ url_context.get_url(), method=self.action, postdata=self.data,
2158+ headers=self.get_headers())
2159+ # XXX - we need an error wrapper like we have for ec2... but let's
2160+ # wait until the new error-wrapper branch has landed, and possibly
2161+ # generalize a base class for all clients.
2162+ #d.addErrback(s3_error_wrapper)
2163+ return d
2164
2165=== added file 'txaws/s3/model.py'
2166--- txaws/s3/model.py 1970-01-01 00:00:00 +0000
2167+++ txaws/s3/model.py 2009-11-28 01:10:27 +0000
2168@@ -0,0 +1,16 @@
2169+class Bucket(object):
2170+ """An Amazon S3 storage bucket."""
2171+
2172+ def __init__(self, name, creation_date):
2173+ self.name = name
2174+ self.creation_date = creation_date
2175+
2176+
2177+class FileChunk(object):
2178+ """
2179+ An Amazon S3 file chunk.
2180+
2181+ S3 returns file chunks, 10 MB at a time, until the entire file is returned.
2182+ These chunks need to be assembled once they are all returned.
2183+ """
2184+
2185
2186=== modified file 'txaws/s3/tests/test_client.py'
2187--- txaws/storage/tests/test_client.py 2009-09-05 01:13:43 +0000
2188+++ txaws/s3/tests/test_client.py 2009-11-28 01:10:27 +0000
2189@@ -1,71 +1,363 @@
2190-from datetime import datetime
2191-
2192-from epsilon.extime import Time
2193-
2194 from twisted.internet.defer import succeed
2195
2196 from txaws.credentials import AWSCredentials
2197+from txaws.s3 import client
2198 from txaws.service import AWSServiceEndpoint
2199-from txaws.storage.client import S3, S3Request
2200+from txaws.testing import payload
2201 from txaws.testing.base import TXAWSTestCase
2202 from txaws.util import calculate_md5
2203
2204
2205-
2206-class StubbedS3Request(S3Request):
2207-
2208- def get_page(self, url, method, postdata, headers):
2209- self.getPageArgs = (url, method, postdata, headers)
2210- return succeed("")
2211-
2212-
2213-class RequestTestCase(TXAWSTestCase):
2214+class URLContextTestCase(TXAWSTestCase):
2215+
2216+ endpoint = AWSServiceEndpoint("https://s3.amazonaws.com/")
2217+
2218+ def test_get_host_with_no_bucket(self):
2219+ url_context = client.URLContext(self.endpoint)
2220+ self.assertEquals(url_context.get_host(), "s3.amazonaws.com")
2221+
2222+ def test_get_host_with_bucket(self):
2223+ url_context = client.URLContext(self.endpoint, "mystuff")
2224+ self.assertEquals(url_context.get_host(), "mystuff.s3.amazonaws.com")
2225+
2226+ def test_get_path_with_no_bucket(self):
2227+ url_context = client.URLContext(self.endpoint)
2228+ self.assertEquals(url_context.get_path(), "/")
2229+
2230+ def test_get_path_with_bucket(self):
2231+ url_context = client.URLContext(self.endpoint, bucket="mystuff")
2232+ self.assertEquals(url_context.get_path(), "/")
2233+
2234+ def test_get_path_with_bucket_and_object(self):
2235+ url_context = client.URLContext(
2236+ self.endpoint, bucket="mystuff", object_name="/images/thing.jpg")
2237+ self.assertEquals(url_context.get_host(), "mystuff.s3.amazonaws.com")
2238+ self.assertEquals(url_context.get_path(), "/images/thing.jpg")
2239+
2240+ def test_get_path_with_bucket_and_object_without_slash(self):
2241+ url_context = client.URLContext(
2242+ self.endpoint, bucket="mystuff", object_name="images/thing.jpg")
2243+ self.assertEquals(url_context.get_host(), "mystuff.s3.amazonaws.com")
2244+ self.assertEquals(url_context.get_path(), "/images/thing.jpg")
2245+
2246+ def test_get_url_with_custom_endpoint(self):
2247+ endpoint = AWSServiceEndpoint("http://localhost/")
2248+ url_context = client.URLContext(endpoint)
2249+ self.assertEquals(url_context.endpoint.get_uri(), "http://localhost/")
2250+ self.assertEquals( url_context.get_url(), "http://localhost/")
2251+
2252+ def test_get_uri_with_endpoint_bucket_and_object(self):
2253+ endpoint = AWSServiceEndpoint("http://localhost/")
2254+ url_context = client.URLContext(
2255+ endpoint, bucket="mydocs", object_name="notes.txt")
2256+ self.assertEquals(
2257+ url_context.get_url(),
2258+ "http://mydocs.localhost/notes.txt")
2259+
2260+
2261+class BucketURLContextTestCase(TXAWSTestCase):
2262+
2263+ endpoint = AWSServiceEndpoint("https://s3.amazonaws.com/")
2264+
2265+ def test_get_host_with_bucket(self):
2266+ url_context = client.BucketURLContext(self.endpoint, "mystuff")
2267+ self.assertEquals(url_context.get_host(), "s3.amazonaws.com")
2268+ self.assertEquals(url_context.get_path(), "/mystuff")
2269+
2270+
2271+class S3ClientTestCase(TXAWSTestCase):
2272+
2273+ def setUp(self):
2274+ TXAWSTestCase.setUp(self)
2275+ self.creds = AWSCredentials(
2276+ access_key="accessKey", secret_key="secretKey")
2277+ self.endpoint = AWSServiceEndpoint()
2278+
2279+ def test_list_buckets(self):
2280+
2281+ class StubQuery(client.Query):
2282+
2283+ def __init__(query, action, creds, endpoint):
2284+ super(StubQuery, query).__init__(
2285+ action=action, creds=creds)
2286+ self.assertEquals(action, "GET")
2287+ self.assertEqual(creds.access_key, "foo")
2288+ self.assertEqual(creds.secret_key, "bar")
2289+ self.assertEqual(query.bucket, None)
2290+ self.assertEqual(query.object_name, None)
2291+ self.assertEqual(query.data, "")
2292+ self.assertEqual(query.metadata, {})
2293+
2294+ def submit(query):
2295+ return succeed(payload.sample_list_buckets_result)
2296+
2297+ def check_list_buckets(results):
2298+ bucket1, bucket2 = results
2299+ self.assertEquals(bucket1.name, "quotes")
2300+ self.assertEquals(
2301+ bucket1.creation_date.timetuple(),
2302+ (2006, 2, 3, 16, 45, 9, 4, 34, 0))
2303+ self.assertEquals(bucket2.name, "samples")
2304+ self.assertEquals(
2305+ bucket2.creation_date.timetuple(),
2306+ (2006, 2, 3, 16, 41, 58, 4, 34, 0))
2307+
2308+ creds = AWSCredentials("foo", "bar")
2309+ s3 = client.S3Client(creds, query_factory=StubQuery)
2310+ d = s3.list_buckets()
2311+ return d.addCallback(check_list_buckets)
2312+
2313+ def test_create_bucket(self):
2314+
2315+ class StubQuery(client.Query):
2316+
2317+ def __init__(query, action, creds, endpoint, bucket=None):
2318+ super(StubQuery, query).__init__(
2319+ action=action, creds=creds, bucket=bucket)
2320+ self.assertEquals(action, "PUT")
2321+ self.assertEqual(creds.access_key, "foo")
2322+ self.assertEqual(creds.secret_key, "bar")
2323+ self.assertEqual(query.bucket, "mybucket")
2324+ self.assertEqual(query.object_name, None)
2325+ self.assertEqual(query.data, "")
2326+ self.assertEqual(query.metadata, {})
2327+
2328+ def submit(query, url_context=None):
2329+ return succeed(None)
2330+
2331+ creds = AWSCredentials("foo", "bar")
2332+ s3 = client.S3Client(creds, query_factory=StubQuery)
2333+ return s3.create_bucket("mybucket")
2334+
2335+ def test_delete_bucket(self):
2336+
2337+ class StubQuery(client.Query):
2338+
2339+ def __init__(query, action, creds, endpoint, bucket=None):
2340+ super(StubQuery, query).__init__(
2341+ action=action, creds=creds, bucket=bucket)
2342+ self.assertEquals(action, "DELETE")
2343+ self.assertEqual(creds.access_key, "foo")
2344+ self.assertEqual(creds.secret_key, "bar")
2345+ self.assertEqual(query.bucket, "mybucket")
2346+ self.assertEqual(query.object_name, None)
2347+ self.assertEqual(query.data, "")
2348+ self.assertEqual(query.metadata, {})
2349+
2350+ def submit(query, url_context=None):
2351+ return succeed(None)
2352+
2353+ creds = AWSCredentials("foo", "bar")
2354+ s3 = client.S3Client(creds, query_factory=StubQuery)
2355+ return s3.delete_bucket("mybucket")
2356+
2357+ def test_put_object(self):
2358+
2359+ class StubQuery(client.Query):
2360+
2361+ def __init__(query, action, creds, endpoint, bucket=None,
2362+ object_name=None, data=None, content_type=None,
2363+ metadata=None):
2364+ super(StubQuery, query).__init__(
2365+ action=action, creds=creds, bucket=bucket,
2366+ object_name=object_name, data=data,
2367+ content_type=content_type, metadata=metadata)
2368+ self.assertEqual(action, "PUT")
2369+ self.assertEqual(creds.access_key, "foo")
2370+ self.assertEqual(creds.secret_key, "bar")
2371+ self.assertEqual(query.bucket, "mybucket")
2372+ self.assertEqual(query.object_name, "objectname")
2373+ self.assertEqual(query.data, "some data")
2374+ self.assertEqual(query.content_type, "text/plain")
2375+ self.assertEqual(query.metadata, {"key": "some meta data"})
2376+
2377+ def submit(query):
2378+ return succeed(None)
2379+
2380+ creds = AWSCredentials("foo", "bar")
2381+ s3 = client.S3Client(creds, query_factory=StubQuery)
2382+ return s3.put_object(
2383+ "mybucket", "objectname", "some data", content_type="text/plain",
2384+ metadata={"key": "some meta data"})
2385+
2386+ def test_get_object(self):
2387+
2388+ class StubQuery(client.Query):
2389+
2390+ def __init__(query, action, creds, endpoint, bucket=None,
2391+ object_name=None, data=None, content_type=None,
2392+ metadata=None):
2393+ super(StubQuery, query).__init__(
2394+ action=action, creds=creds, bucket=bucket,
2395+ object_name=object_name, data=data,
2396+ content_type=content_type, metadata=metadata)
2397+ self.assertEqual(action, "GET")
2398+ self.assertEqual(creds.access_key, "foo")
2399+ self.assertEqual(creds.secret_key, "bar")
2400+ self.assertEqual(query.bucket, "mybucket")
2401+ self.assertEqual(query.object_name, "objectname")
2402+
2403+ def submit(query):
2404+ return succeed(None)
2405+
2406+ creds = AWSCredentials("foo", "bar")
2407+ s3 = client.S3Client(creds, query_factory=StubQuery)
2408+ return s3.get_object("mybucket", "objectname")
2409+
2410+ def test_head_object(self):
2411+
2412+ class StubQuery(client.Query):
2413+
2414+ def __init__(query, action, creds, endpoint, bucket=None,
2415+ object_name=None, data=None, content_type=None,
2416+ metadata=None):
2417+ super(StubQuery, query).__init__(
2418+ action=action, creds=creds, bucket=bucket,
2419+ object_name=object_name, data=data,
2420+ content_type=content_type, metadata=metadata)
2421+ self.assertEqual(action, "HEAD")
2422+ self.assertEqual(creds.access_key, "foo")
2423+ self.assertEqual(creds.secret_key, "bar")
2424+ self.assertEqual(query.bucket, "mybucket")
2425+ self.assertEqual(query.object_name, "objectname")
2426+
2427+ def submit(query):
2428+ return succeed(None)
2429+
2430+ creds = AWSCredentials("foo", "bar")
2431+ s3 = client.S3Client(creds, query_factory=StubQuery)
2432+ return s3.head_object("mybucket", "objectname")
2433+
2434+ def test_delete_object(self):
2435+
2436+ class StubQuery(client.Query):
2437+
2438+ def __init__(query, action, creds, endpoint, bucket=None,
2439+ object_name=None, data=None, content_type=None,
2440+ metadata=None):
2441+ super(StubQuery, query).__init__(
2442+ action=action, creds=creds, bucket=bucket,
2443+ object_name=object_name, data=data,
2444+ content_type=content_type, metadata=metadata)
2445+ self.assertEqual(action, "DELETE")
2446+ self.assertEqual(creds.access_key, "foo")
2447+ self.assertEqual(creds.secret_key, "bar")
2448+ self.assertEqual(query.bucket, "mybucket")
2449+ self.assertEqual(query.object_name, "objectname")
2450+
2451+ def submit(query):
2452+ return succeed(None)
2453+
2454+ creds = AWSCredentials("foo", "bar")
2455+ s3 = client.S3Client(creds, query_factory=StubQuery)
2456+ return s3.delete_object("mybucket", "objectname")
2457+
2458+
2459+class QueryTestCase(TXAWSTestCase):
2460
2461 creds = AWSCredentials(access_key="fookeyid", secret_key="barsecretkey")
2462- endpoint = AWSServiceEndpoint("https://s3.amazonaws.com/")
2463-
2464- def test_get_uri_with_endpoint(self):
2465- endpoint = AWSServiceEndpoint("http://localhost/")
2466- request = S3Request("PUT", endpoint=endpoint)
2467- self.assertEquals(request.endpoint.get_uri(), "http://localhost/")
2468- self.assertEquals(request.get_uri(), "http://localhost/")
2469-
2470- def test_get_uri_with_endpoint_bucket_and_object(self):
2471- endpoint = AWSServiceEndpoint("http://localhost/")
2472- request = S3Request("PUT", bucket="mybucket", object_name="myobject",
2473- endpoint=endpoint)
2474- self.assertEquals(
2475- request.get_uri(),
2476- "http://localhost/mybucket/myobject")
2477-
2478- def test_get_uri_with_no_endpoint(self):
2479- request = S3Request("PUT")
2480- self.assertEquals(request.endpoint, None)
2481- self.assertEquals(request.get_uri(), "http:///")
2482-
2483- def test_get_path_with_bucket_and_object(self):
2484- request = S3Request("PUT", bucket="mybucket", object_name="myobject")
2485- self.assertEquals(request.get_path(), "/mybucket/myobject")
2486-
2487- def test_get_path_with_no_bucket_or_object(self):
2488- request = S3Request("PUT")
2489- self.assertEquals(request.get_path(), "/")
2490-
2491- def test_objectRequest(self):
2492+ endpoint = AWSServiceEndpoint("https://choopy.s3.amazonaws.com/")
2493+
2494+ def test_default_creation(self):
2495+ query = client.Query(action="PUT")
2496+ self.assertEquals(query.bucket, None)
2497+ self.assertEquals(query.object_name, None)
2498+ self.assertEquals(query.data, "")
2499+ self.assertEquals(query.content_type, None)
2500+ self.assertEquals(query.metadata, {})
2501+
2502+ def test_default_endpoint(self):
2503+ query = client.Query(action="PUT")
2504+ self.assertEquals(self.endpoint.host, "choopy.s3.amazonaws.com")
2505+ self.assertEquals(query.endpoint.host, "s3.amazonaws.com")
2506+ self.assertEquals(self.endpoint.method, "GET")
2507+ self.assertEquals(query.endpoint.method, "PUT")
2508+
2509+ def test_set_content_type_no_object_name(self):
2510+ query = client.Query(action="PUT")
2511+ query.set_content_type()
2512+ self.assertEquals(query.content_type, None)
2513+
2514+ def test_set_content_type(self):
2515+ query = client.Query(action="PUT", object_name="advicedog.jpg")
2516+ query.set_content_type()
2517+ self.assertEquals(query.content_type, "image/jpeg")
2518+
2519+ def test_set_content_type_with_content_type_already_set(self):
2520+ query = client.Query(
2521+ action="PUT", object_name="data.txt", content_type="text/csv")
2522+ query.set_content_type()
2523+ self.assertNotEquals(query.content_type, "text/plain")
2524+ self.assertEquals(query.content_type, "text/csv")
2525+
2526+ def test_get_headers(self):
2527+ query = client.Query(
2528+ action="GET", creds=self.creds, bucket="mystuff",
2529+ object_name="/images/thing.jpg")
2530+ headers = query.get_headers()
2531+ self.assertEquals(headers.get("Content-Type"), "image/jpeg")
2532+ self.assertEquals(headers.get("Content-Length"), 0)
2533+ self.assertEquals(
2534+ headers.get("Content-MD5"), "1B2M2Y8AsgTpgAmY7PhCfg==")
2535+ self.assertTrue(len(headers.get("Date")) > 25)
2536+ self.assertTrue(
2537+ headers.get("Authorization").startswith("AWS fookeyid:"))
2538+ self.assertTrue(len(headers.get("Authorization")) > 40)
2539+
2540+ def test_get_headers_with_data(self):
2541+ query = client.Query(
2542+ action="GET", creds=self.creds, bucket="mystuff",
2543+ object_name="/images/thing.jpg", data="BINARY IMAGE DATA")
2544+ headers = query.get_headers()
2545+ self.assertEquals(headers.get("Content-Type"), "image/jpeg")
2546+ self.assertEquals(headers.get("Content-Length"), 17)
2547+ self.assertTrue(len(headers.get("Date")) > 25)
2548+ self.assertTrue(
2549+ headers.get("Authorization").startswith("AWS fookeyid:"))
2550+ self.assertTrue(len(headers.get("Authorization")) > 40)
2551+
2552+ def test_get_canonicalized_amz_headers(self):
2553+ query = client.Query(
2554+ action="SomeThing", metadata={"a": 1, "b": 2, "c": 3})
2555+ headers = query.get_headers()
2556+ self.assertEquals(
2557+ sorted(headers.keys()),
2558+ ["Content-Length", "Content-MD5", "Date", "x-amz-meta-a",
2559+ "x-amz-meta-b", "x-amz-meta-c"])
2560+ amz_headers = query.get_canonicalized_amz_headers(headers)
2561+ self.assertEquals(
2562+ amz_headers,
2563+ "x-amz-meta-a:1\nx-amz-meta-b:2\nx-amz-meta-c:3\n")
2564+
2565+ def test_get_canonicalized_resource(self):
2566+ query = client.Query(action="PUT", bucket="images")
2567+ result = query.get_canonicalized_resource()
2568+ self.assertEquals(result, "/images")
2569+
2570+ def test_get_canonicalized_resource_with_object_name(self):
2571+ query = client.Query(
2572+ action="PUT", bucket="images", object_name="advicedog.jpg")
2573+ result = query.get_canonicalized_resource()
2574+ self.assertEquals(result, "/images/advicedog.jpg")
2575+
2576+ def test_sign(self):
2577+ query = client.Query(action="PUT", creds=self.creds)
2578+ signed = query.sign({})
2579+ self.assertEquals(signed, "H6UJCNHizzXZCGPl7wM6nL6tQdo=")
2580+
2581+ def test_object_query(self):
2582 """
2583 Test that a request addressing an object is created correctly.
2584 """
2585 DATA = "objectData"
2586 DIGEST = "zhdB6gwvocWv/ourYUWMxA=="
2587
2588- request = S3Request("PUT", "somebucket", "object/name/here", DATA,
2589- content_type="text/plain", metadata={"foo": "bar"},
2590- creds=self.creds, endpoint=self.endpoint)
2591- request.get_signature = lambda headers: "TESTINGSIG="
2592- self.assertEqual(request.verb, "PUT")
2593- self.assertEqual(
2594- request.get_uri(),
2595- "https://s3.amazonaws.com/somebucket/object/name/here")
2596+ request = client.Query(
2597+ action="PUT", bucket="somebucket", object_name="object/name/here",
2598+ data=DATA, content_type="text/plain", metadata={"foo": "bar"},
2599+ creds=self.creds, endpoint=self.endpoint)
2600+ request.sign = lambda headers: "TESTINGSIG="
2601+ self.assertEqual(request.action, "PUT")
2602 headers = request.get_headers()
2603 self.assertNotEqual(headers.pop("Date"), "")
2604 self.assertEqual(
2605@@ -77,212 +369,63 @@
2606 "x-amz-meta-foo": "bar"})
2607 self.assertEqual(request.data, "objectData")
2608
2609- def test_bucketRequest(self):
2610+ def test_bucket_query(self):
2611 """
2612 Test that a request addressing a bucket is created correctly.
2613 """
2614 DIGEST = "1B2M2Y8AsgTpgAmY7PhCfg=="
2615
2616- request = S3Request("GET", "somebucket", creds=self.creds,
2617- endpoint=self.endpoint)
2618- request.get_signature = lambda headers: "TESTINGSIG="
2619- self.assertEqual(request.verb, "GET")
2620- self.assertEqual(
2621- request.get_uri(), "https://s3.amazonaws.com/somebucket")
2622- headers = request.get_headers()
2623+ query = client.Query(
2624+ action="GET", bucket="somebucket", creds=self.creds,
2625+ endpoint=self.endpoint)
2626+ query.sign = lambda headers: "TESTINGSIG="
2627+ self.assertEqual(query.action, "GET")
2628+ headers = query.get_headers()
2629 self.assertNotEqual(headers.pop("Date"), "")
2630 self.assertEqual(
2631 headers, {
2632 "Authorization": "AWS fookeyid:TESTINGSIG=",
2633 "Content-Length": 0,
2634 "Content-MD5": DIGEST})
2635- self.assertEqual(request.data, "")
2636+ self.assertEqual(query.data, "")
2637
2638 def test_submit(self):
2639 """
2640 Submitting the request should invoke getPage correctly.
2641 """
2642- request = StubbedS3Request("GET", "somebucket", creds=self.creds,
2643- endpoint=self.endpoint)
2644-
2645- def _postCheck(result):
2646- self.assertEqual(result, "")
2647-
2648- url, method, postdata, headers = request.getPageArgs
2649- self.assertEqual(url, request.get_uri())
2650- self.assertEqual(method, request.verb)
2651- self.assertEqual(postdata, request.data)
2652- self.assertEqual(headers, request.get_headers())
2653-
2654- return request.submit().addCallback(_postCheck)
2655-
2656- def test_authenticationTestCases(self):
2657- request = S3Request("GET", creds=self.creds, endpoint=self.endpoint)
2658- request.get_signature = lambda headers: "TESTINGSIG="
2659- request.date = "Wed, 28 Mar 2007 01:29:59 +0000"
2660-
2661- headers = request.get_headers()
2662+ class StubQuery(client.Query):
2663+
2664+ def __init__(query, action, creds, endpoint, bucket):
2665+ super(StubQuery, query).__init__(
2666+ action=action, creds=creds, bucket=bucket)
2667+ self.assertEquals(action, "GET")
2668+ self.assertEqual(creds.access_key, "fookeyid")
2669+ self.assertEqual(creds.secret_key, "barsecretkey")
2670+ self.assertEqual(query.bucket, "somebucket")
2671+ self.assertEqual(query.object_name, None)
2672+ self.assertEqual(query.data, "")
2673+ self.assertEqual(query.metadata, {})
2674+
2675+ def submit(query):
2676+ return succeed("")
2677+
2678+ query = StubQuery(action="GET", creds=self.creds,
2679+ endpoint=self.endpoint, bucket="somebucket")
2680+ return query.submit()
2681+
2682+ def test_authentication(self):
2683+ query = client.Query(
2684+ action="GET", creds=self.creds, endpoint=self.endpoint)
2685+ query.sign = lambda headers: "TESTINGSIG="
2686+ query.date = "Wed, 28 Mar 2007 01:29:59 +0000"
2687+
2688+ headers = query.get_headers()
2689 self.assertEqual(
2690 headers["Authorization"],
2691 "AWS fookeyid:TESTINGSIG=")
2692
2693
2694-class InertRequest(S3Request):
2695- """
2696- Inert version of S3Request.
2697-
2698- The submission action is stubbed out to return the provided response.
2699- """
2700- submitted = False
2701-
2702- def __init__(self, *a, **kw):
2703- self.response = kw.pop("response")
2704- super(InertRequest, self).__init__(*a, **kw)
2705-
2706- def submit(self):
2707- """
2708- Return the canned result instead of performing a network operation.
2709- """
2710- self.submitted = True
2711- return succeed(self.response)
2712-
2713-
2714-class TestableS3(S3):
2715- """
2716- Testable version of S3.
2717-
2718- This subclass stubs request_factory to use InertRequest, making it easy to
2719- assert things about the requests that are created in response to various
2720- operations.
2721- """
2722- response = None
2723-
2724- def request_factory(self, *a, **kw):
2725- req = InertRequest(response=self.response, *a, **kw)
2726- self._lastRequest = req
2727- return req
2728-
2729-
2730-samples = {
2731- "ListAllMyBucketsResult":
2732- """<?xml version="1.0" encoding="UTF-8"?>
2733-<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
2734- <Owner>
2735- <ID>bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f</ID>
2736- <DisplayName>webfile</DisplayName>
2737- </Owner>
2738- <Buckets>
2739- <Bucket>
2740- <Name>quotes</Name>
2741- <CreationDate>2006-02-03T16:45:09.000Z</CreationDate>
2742- </Bucket>
2743- <Bucket>
2744- <Name>samples</Name>
2745- <CreationDate>2006-02-03T16:41:58.000Z</CreationDate>
2746- </Bucket>
2747- </Buckets>
2748-</ListAllMyBucketsResult>""",
2749- }
2750-
2751-
2752-class WrapperTests(TXAWSTestCase):
2753-
2754- def setUp(self):
2755- TXAWSTestCase.setUp(self)
2756- self.creds = AWSCredentials(
2757- access_key="accessKey", secret_key="secretKey")
2758- self.endpoint = AWSServiceEndpoint()
2759- self.s3 = TestableS3(creds=self.creds, endpoint=self.endpoint)
2760-
2761- def test_make_request(self):
2762- """
2763- Test that make_request passes in the credentials object.
2764- """
2765- marker = object()
2766-
2767- def _cb(*a, **kw):
2768- self.assertEqual(kw["creds"], self.creds)
2769- self.assertEqual(kw["endpoint"], self.endpoint)
2770- return marker
2771-
2772- self.s3.request_factory = _cb
2773- self.assertIdentical(self.s3.make_request("GET"), marker)
2774-
2775- def test_list_buckets(self):
2776- self.s3.response = samples["ListAllMyBucketsResult"]
2777- d = self.s3.list_buckets()
2778-
2779- req = self.s3._lastRequest
2780- self.assertTrue(req.submitted)
2781- self.assertEqual(req.verb, "GET")
2782- self.assertEqual(req.bucket, None)
2783- self.assertEqual(req.object_name, None)
2784-
2785- def _check_result(buckets):
2786- self.assertEqual(
2787- list(buckets),
2788- [{"name": u"quotes",
2789- "created": Time.fromDatetime(
2790- datetime(2006, 2, 3, 16, 45, 9))},
2791- {"name": u"samples",
2792- "created": Time.fromDatetime(
2793- datetime(2006, 2, 3, 16, 41, 58))}])
2794- return d.addCallback(_check_result)
2795-
2796- def test_create_bucket(self):
2797- self.s3.create_bucket("foo")
2798- req = self.s3._lastRequest
2799- self.assertTrue(req.submitted)
2800- self.assertEqual(req.verb, "PUT")
2801- self.assertEqual(req.bucket, "foo")
2802- self.assertEqual(req.object_name, None)
2803-
2804- def test_delete_bucket(self):
2805- self.s3.delete_bucket("foo")
2806- req = self.s3._lastRequest
2807- self.assertTrue(req.submitted)
2808- self.assertEqual(req.verb, "DELETE")
2809- self.assertEqual(req.bucket, "foo")
2810- self.assertEqual(req.object_name, None)
2811-
2812- def test_put_object(self):
2813- self.s3.put_object(
2814- "foobucket", "foo", "data", "text/plain", {"foo": "bar"})
2815- req = self.s3._lastRequest
2816- self.assertTrue(req.submitted)
2817- self.assertEqual(req.verb, "PUT")
2818- self.assertEqual(req.bucket, "foobucket")
2819- self.assertEqual(req.object_name, "foo")
2820- self.assertEqual(req.data, "data")
2821- self.assertEqual(req.content_type, "text/plain")
2822- self.assertEqual(req.metadata, {"foo": "bar"})
2823-
2824- def test_get_object(self):
2825- self.s3.get_object("foobucket", "foo")
2826- req = self.s3._lastRequest
2827- self.assertTrue(req.submitted)
2828- self.assertEqual(req.verb, "GET")
2829- self.assertEqual(req.bucket, "foobucket")
2830- self.assertEqual(req.object_name, "foo")
2831-
2832- def test_head_object(self):
2833- self.s3.head_object("foobucket", "foo")
2834- req = self.s3._lastRequest
2835- self.assertTrue(req.submitted)
2836- self.assertEqual(req.verb, "HEAD")
2837- self.assertEqual(req.bucket, "foobucket")
2838- self.assertEqual(req.object_name, "foo")
2839-
2840- def test_delete_object(self):
2841- self.s3.delete_object("foobucket", "foo")
2842- req = self.s3._lastRequest
2843- self.assertTrue(req.submitted)
2844- self.assertEqual(req.verb, "DELETE")
2845- self.assertEqual(req.bucket, "foobucket")
2846- self.assertEqual(req.object_name, "foo")
2847-
2848-
2849 class MiscellaneousTests(TXAWSTestCase):
2850
2851- def test_contentMD5(self):
2852+ def test_content_md5(self):
2853 self.assertEqual(calculate_md5("somedata"), "rvr3UC1SmUw7AZV2NqPN0g==")
2854
2855=== modified file 'txaws/service.py'
2856--- txaws/service.py 2009-10-12 07:08:05 +0000
2857+++ txaws/service.py 2009-11-28 01:10:27 +0000
2858@@ -12,6 +12,7 @@
2859 REGION_EU = "EU"
2860 EC2_ENDPOINT_US = "https://us-east-1.ec2.amazonaws.com/"
2861 EC2_ENDPOINT_EU = "https://eu-west-1.ec2.amazonaws.com/"
2862+S3_ENDPOINT = "https://s3.amazonaws.com/"
2863 DEFAULT_PORT = 80
2864
2865
2866@@ -38,6 +39,12 @@
2867 self.port = port
2868 self.path = path
2869
2870+ def set_host(self, host):
2871+ self.host = host
2872+
2873+ def get_host(self):
2874+ return self.host
2875+
2876 def set_path(self, path):
2877 self.path = path
2878
2879@@ -48,6 +55,9 @@
2880 uri = "%s:%s" % (uri, self.port)
2881 return uri + self.path
2882
2883+ def set_method(self, method):
2884+ self.method = method
2885+
2886
2887 class AWSServiceRegion(object):
2888 """
2889@@ -65,19 +75,24 @@
2890 @param uri: an endpoint URI that, if provided, will override the region
2891 parameter.
2892 """
2893+ # XXX update unit test to check for both ec2 and s3 endpoints
2894 def __init__(self, creds=None, access_key="", secret_key="",
2895- region=REGION_US, uri=""):
2896+ region=REGION_US, uri="", ec2_uri="", s3_uri=""):
2897 if not creds:
2898 creds = AWSCredentials(access_key, secret_key)
2899 self.creds = creds
2900+ # Provide backwards compatibility for the "uri" parameter.
2901+ if uri and not ec2_uri:
2902+ ec2_uri = uri
2903+ if not ec2_uri and region == REGION_US:
2904+ ec2_uri = EC2_ENDPOINT_US
2905+ elif not ec2_uri and region == REGION_EU:
2906+ ec2_uri = EC2_ENDPOINT_EU
2907+ if not s3_uri:
2908+ s3_uri = S3_ENDPOINT
2909 self._clients = {}
2910- if uri:
2911- ec2_endpoint = uri
2912- elif region == REGION_US:
2913- ec2_endpoint = EC2_ENDPOINT_US
2914- elif region == REGION_EU:
2915- ec2_endpoint = EC2_ENDPOINT_EU
2916- self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_endpoint)
2917+ self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_uri)
2918+ self.s3_endpoint = AWSServiceEndpoint(uri=s3_uri)
2919
2920 def get_client(self, cls, purge_cache=False, *args, **kwds):
2921 """
2922@@ -100,3 +115,11 @@
2923 self.creds = creds
2924 return self.get_client(EC2Client, creds=self.creds,
2925 endpoint=self.ec2_endpoint, query_factory=None)
2926+
2927+ def get_s3_client(self, creds=None):
2928+ from txaws.s3.client import S3Client
2929+
2930+ if creds:
2931+ self.creds = creds
2932+ return self.get_client(S3Client, creds=self.creds,
2933+ endpoint=self.s3_endpoint, query_factory=None)
2934
2935=== modified file 'txaws/testing/payload.py'
2936--- txaws/testing/payload.py 2009-11-13 23:39:02 +0000
2937+++ txaws/testing/payload.py 2009-11-28 01:10:27 +0000
2938@@ -1,4 +1,4 @@
2939-from txaws.version import aws_api
2940+from txaws import version
2941
2942
2943 sample_required_describe_instances_result = """\
2944@@ -34,7 +34,7 @@
2945 </item>
2946 </reservationSet>
2947 </DescribeInstancesResponse>
2948-""" % (aws_api,)
2949+""" % (version.ec2_api,)
2950
2951
2952 sample_describe_instances_result = """\
2953@@ -78,7 +78,7 @@
2954 </item>
2955 </reservationSet>
2956 </DescribeInstancesResponse>
2957-""" % (aws_api,)
2958+""" % (version.ec2_api,)
2959
2960
2961 sample_run_instances_result = """\
2962@@ -145,7 +145,7 @@
2963 </item>
2964 </instancesSet>
2965 </RunInstancesResponse>
2966-""" % (aws_api,)
2967+""" % (version.ec2_api,)
2968
2969 sample_terminate_instances_result = """\
2970 <?xml version="1.0"?>
2971@@ -175,7 +175,7 @@
2972 </item>
2973 </instancesSet>
2974 </TerminateInstancesResponse>
2975-""" % (aws_api,)
2976+""" % (version.ec2_api,)
2977
2978
2979 sample_describe_security_groups_result = """\
2980@@ -203,7 +203,7 @@
2981 </item>
2982 </securityGroupInfo>
2983 </DescribeSecurityGroupsResponse>
2984-""" % (aws_api,)
2985+""" % (version.ec2_api,)
2986
2987
2988 sample_describe_security_groups_multiple_result = """\
2989@@ -265,14 +265,14 @@
2990 </item>
2991 </securityGroupInfo>
2992 </DescribeSecurityGroupsResponse>
2993-""" % (aws_api,)
2994+""" % (version.ec2_api,)
2995
2996
2997 sample_create_security_group = """\
2998 <CreateSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
2999 <return>true</return>
3000 </CreateSecurityGroupResponse>
3001-""" % (aws_api,)
3002+""" % (version.ec2_api,)
3003
3004
3005 sample_duplicate_create_security_group_result = """\
3006@@ -305,7 +305,7 @@
3007 <DeleteSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3008 <return>true</return>
3009 </DeleteSecurityGroupResponse>
3010-""" % (aws_api,)
3011+""" % (version.ec2_api,)
3012
3013
3014 sample_delete_security_group_failure = """\
3015@@ -326,14 +326,14 @@
3016 <AuthorizeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3017 <return>true</return>
3018 </AuthorizeSecurityGroupIngressResponse>
3019-""" % (aws_api,)
3020+""" % (version.ec2_api,)
3021
3022
3023 sample_revoke_security_group = """\
3024 <RevokeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3025 <return>true</return>
3026 </RevokeSecurityGroupIngressResponse>
3027-""" % (aws_api,)
3028+""" % (version.ec2_api,)
3029
3030
3031 sample_describe_volumes_result = """\
3032@@ -360,7 +360,7 @@
3033 </item>
3034 </volumeSet>
3035 </DescribeVolumesResponse>
3036-""" % (aws_api,)
3037+""" % (version.ec2_api,)
3038
3039
3040 sample_describe_snapshots_result = """\
3041@@ -376,7 +376,7 @@
3042 </item>
3043 </snapshotSet>
3044 </DescribeSnapshotsResponse>
3045-""" % (aws_api,)
3046+""" % (version.ec2_api,)
3047
3048
3049 sample_create_volume_result = """\
3050@@ -389,7 +389,7 @@
3051 <availabilityZone>us-east-1a</availabilityZone>
3052 <snapshotId></snapshotId>
3053 </CreateVolumeResponse>
3054-""" % (aws_api,)
3055+""" % (version.ec2_api,)
3056
3057
3058 sample_delete_volume_result = """\
3059@@ -397,7 +397,7 @@
3060 <DeleteVolumeResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3061 <return>true</return>
3062 </DeleteVolumeResponse>
3063-""" % (aws_api,)
3064+""" % (version.ec2_api,)
3065
3066
3067 sample_create_snapshot_result = """\
3068@@ -409,7 +409,7 @@
3069 <startTime>2008-05-07T12:51:50.000Z</startTime>
3070 <progress></progress>
3071 </CreateSnapshotResponse>
3072-""" % (aws_api,)
3073+""" % (version.ec2_api,)
3074
3075
3076 sample_delete_snapshot_result = """\
3077@@ -417,7 +417,7 @@
3078 <DeleteSnapshotResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3079 <return>true</return>
3080 </DeleteSnapshotResponse>
3081-""" % (aws_api,)
3082+""" % (version.ec2_api,)
3083
3084
3085 sample_attach_volume_result = """\
3086@@ -429,7 +429,7 @@
3087 <status>attaching</status>
3088 <attachTime>2008-05-07T11:51:50.000Z</attachTime>
3089 </AttachVolumeResponse>
3090-""" % (aws_api,)
3091+""" % (version.ec2_api,)
3092
3093
3094 sample_ec2_error_message = """\
3095@@ -474,7 +474,7 @@
3096 </item>
3097 </keySet>
3098 </DescribeKeyPairsResponse>
3099-""" % (aws_api,)
3100+""" % (version.ec2_api,)
3101
3102
3103 sample_multiple_describe_keypairs_result = """\
3104@@ -491,7 +491,7 @@
3105 </item>
3106 </keySet>
3107 </DescribeKeyPairsResponse>
3108-""" % (aws_api,)
3109+""" % (version.ec2_api,)
3110
3111
3112 sample_create_keypair_result = """\
3113@@ -523,7 +523,7 @@
3114 2ERKKdwz0ZL9SWq6VTdhr/5G994CK72fy5WhyERbDjUIdHaK3M849JJuf8cSrvSb4g==
3115 -----END RSA PRIVATE KEY-----</keyMaterial>
3116 </CreateKeyPairResponse>
3117-""" % (aws_api,)
3118+""" % (version.ec2_api,)
3119
3120
3121 sample_delete_keypair_true_result = """\
3122@@ -531,7 +531,7 @@
3123 <DeleteKeyPair xmlns="http://ec2.amazonaws.com/doc/%s/">
3124 <return>true</return>
3125 </DeleteKeyPair>
3126-""" % (aws_api,)
3127+""" % (version.ec2_api,)
3128
3129
3130 sample_delete_keypair_false_result = """\
3131@@ -539,14 +539,14 @@
3132 <DeleteKeyPair xmlns="http://ec2.amazonaws.com/doc/%s/">
3133 <return>false</return>
3134 </DeleteKeyPair>
3135-""" % (aws_api,)
3136+""" % (version.ec2_api,)
3137
3138
3139 sample_delete_keypair_no_result = """\
3140 <?xml version="1.0"?>
3141 <DeleteKeyPair xmlns="http://ec2.amazonaws.com/doc/%s/">
3142 </DeleteKeyPair>
3143-""" % (aws_api,)
3144+""" % (version.ec2_api,)
3145
3146
3147 sample_duplicate_keypair_result = """\
3148@@ -568,7 +568,7 @@
3149 <AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3150 <publicIp>67.202.55.255</publicIp>
3151 </AllocateAddressResponse>
3152-""" % (aws_api,)
3153+""" % (version.ec2_api,)
3154
3155
3156 sample_release_address_result = """\
3157@@ -576,7 +576,7 @@
3158 <ReleaseAddressResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3159 <return>true</return>
3160 </ReleaseAddressResponse>
3161-""" % (aws_api,)
3162+""" % (version.ec2_api,)
3163
3164
3165 sample_associate_address_result = """\
3166@@ -584,7 +584,7 @@
3167 <AssociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3168 <return>true</return>
3169 </AssociateAddressResponse>
3170-""" % (aws_api,)
3171+""" % (version.ec2_api,)
3172
3173
3174 sample_disassociate_address_result = """\
3175@@ -592,7 +592,7 @@
3176 <DisassociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/%s/">
3177 <return>true</return>
3178 </DisassociateAddressResponse>
3179-""" % (aws_api,)
3180+""" % (version.ec2_api,)
3181
3182
3183 sample_describe_addresses_result = """\
3184@@ -607,7 +607,7 @@
3185 </item>
3186 </addressesSet>
3187 </DescribeAddressesResponse>
3188-""" % (aws_api,)
3189+""" % (version.ec2_api,)
3190
3191
3192 sample_describe_availability_zones_single_result = """\
3193@@ -619,7 +619,7 @@
3194 </item>
3195 </availabilityZoneInfo>
3196 </DescribeAvailabilityZonesResponse>
3197-""" % (aws_api,)
3198+""" % (version.ec2_api,)
3199
3200
3201 sample_describe_availability_zones_multiple_results = """\
3202@@ -639,7 +639,7 @@
3203 </item>
3204 </availabilityZoneInfo>
3205 </DescribeAvailabilityZonesResponse>
3206-""" % (aws_api,)
3207+""" % (version.ec2_api,)
3208
3209
3210 sample_invalid_client_token_result = """\
3211@@ -656,6 +656,42 @@
3212 """
3213
3214
3215+sample_list_buckets_result = """\
3216+<?xml version="1.0" encoding="UTF-8"?>
3217+<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/%s/">
3218+ <Owner>
3219+ <ID>bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f</ID>
3220+ <DisplayName>webfile</DisplayName>
3221+ </Owner>
3222+ <Buckets>
3223+ <Bucket>
3224+ <Name>quotes</Name>
3225+ <CreationDate>2006-02-03T16:45:09.000Z</CreationDate>
3226+ </Bucket>
3227+ <Bucket>
3228+ <Name>samples</Name>
3229+ <CreationDate>2006-02-03T16:41:58.000Z</CreationDate>
3230+ </Bucket>
3231+ </Buckets>
3232+</ListAllMyBucketsResult>
3233+""" % (version.s3_api,)
3234+
3235+
3236+sample_s3_signature_mismatch = """\
3237+<?xml version="1.0" encoding="UTF-8"?>
3238+<Error>
3239+ <Code>SignatureDoesNotMatch</Code>
3240+ <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
3241+ <StringToSignBytes>47 45 54 0a 31 42 32 4d 32 59 38 41 73 67 54 70 67 41 6d 59 37 50 68 43 66 67 3d 3d 0a 0a 54 68 75 2c 20 30 35 20 4e 6f 76 20 32 30 30 39 20 32 31 3a 33 33 3a 32 39 20 47 4d 54 0a 2f</StringToSignBytes>
3242+ <RequestId>AB9216C8640751B2</RequestId>
3243+ <HostId>sAPBpmFdsOsgUUwtSLsiT6KIwP1mPbmrYY0xUoahzJE263qmABkTaqzGhHddgOq5</HostId>
3244+ <SignatureProvided>ltowhdrbjaQ8dQc9VS5MxzJfsPJZi0BZHEzJC3r9pzU=</SignatureProvided>
3245+ <StringToSign>GET\n1B2M2Y8AsgTpgAmY7PhCfg==\n\nThu, 05 Nov 2009 21:33:29 GMT\n/</StringToSign>
3246+ <AWSAccessKeyId>SOMEKEYID</AWSAccessKeyId>
3247+</Error>
3248+"""
3249+
3250+
3251 sample_server_internal_error_result = """\
3252 <?xml version="1.0" encoding="UTF-8"?>
3253 <Error>
3254
3255=== modified file 'txaws/tests/test_service.py'
3256--- txaws/tests/test_service.py 2009-08-28 20:07:55 +0000
3257+++ txaws/tests/test_service.py 2009-11-28 01:10:27 +0000
3258@@ -3,6 +3,7 @@
3259
3260 from txaws.credentials import AWSCredentials
3261 from txaws.ec2.client import EC2Client
3262+from txaws.s3.client import S3Client
3263 from txaws.service import (AWSServiceEndpoint, AWSServiceRegion,
3264 EC2_ENDPOINT_EU, EC2_ENDPOINT_US, REGION_EU)
3265 from txaws.testing.base import TXAWSTestCase
3266@@ -21,6 +22,11 @@
3267 self.assertEquals(endpoint.path, "/")
3268 self.assertEquals(endpoint.method, "GET")
3269
3270+ def test_custom_method(self):
3271+ endpoint = AWSServiceEndpoint(
3272+ uri="http://service/endpoint", method="PUT")
3273+ self.assertEquals(endpoint.method, "PUT")
3274+
3275 def test_parse_uri(self):
3276 self.assertEquals(self.endpoint.scheme, "http")
3277 self.assertEquals(self.endpoint.host, "my.service")
3278@@ -34,11 +40,6 @@
3279 self.assertEquals(endpoint.port, 8080)
3280 self.assertEquals(endpoint.path, "/endpoint")
3281
3282- def test_custom_method(self):
3283- endpoint = AWSServiceEndpoint(uri="http://service/endpoint",
3284- method="PUT")
3285- self.assertEquals(endpoint.method, "PUT")
3286-
3287 def test_get_uri(self):
3288 uri = self.endpoint.get_uri()
3289 self.assertEquals(uri, "http://my.service/da_endpoint")
3290@@ -49,13 +50,25 @@
3291 new_uri = endpoint.get_uri()
3292 self.assertEquals(new_uri, uri)
3293
3294+ def test_set_host(self):
3295+ self.assertEquals(self.endpoint.host, "my.service")
3296+ self.endpoint.set_host("newhost.com")
3297+ self.assertEquals(self.endpoint.host, "newhost.com")
3298+
3299+ def test_get_host(self):
3300+ self.assertEquals(self.endpoint.host, self.endpoint.get_host())
3301+
3302 def test_set_path(self):
3303- original_path = self.endpoint.path
3304 self.endpoint.set_path("/newpath")
3305 self.assertEquals(
3306 self.endpoint.get_uri(),
3307 "http://my.service/newpath")
3308
3309+ def test_set_method(self):
3310+ self.assertEquals(self.endpoint.method, "GET")
3311+ self.endpoint.set_method("PUT")
3312+ self.assertEquals(self.endpoint.method, "PUT")
3313+
3314
3315 class AWSServiceRegionTestCase(TXAWSTestCase):
3316
3317@@ -74,25 +87,34 @@
3318 self.assertEquals(region.creds.secret_key, "quux")
3319
3320 def test_creation_with_keys_and_creds(self):
3321+ """
3322+ creds take precedence over individual access key/secret key pairs.
3323+ """
3324 region = AWSServiceRegion(self.creds, access_key="baz",
3325 secret_key="quux")
3326- self.assertEquals(self.creds.access_key, "foo")
3327- self.assertEquals(self.creds.secret_key, "bar")
3328+ self.assertEquals(region.creds.access_key, "foo")
3329+ self.assertEquals(region.creds.secret_key, "bar")
3330
3331 def test_creation_with_uri(self):
3332- region = AWSServiceRegion(creds=self.creds, uri="http://foo/bar")
3333+ region = AWSServiceRegion(
3334+ creds=self.creds, ec2_uri="http://foo/bar")
3335+ self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
3336+
3337+ def test_creation_with_uri_backwards_compatible(self):
3338+ region = AWSServiceRegion(
3339+ creds=self.creds, uri="http://foo/bar")
3340 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
3341
3342 def test_creation_with_uri_and_region(self):
3343- region = AWSServiceRegion(creds=self.creds, region=REGION_EU,
3344- uri="http://foo/bar")
3345+ region = AWSServiceRegion(
3346+ creds=self.creds, region=REGION_EU, ec2_uri="http://foo/bar")
3347 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
3348
3349 def test_creation_with_region_override(self):
3350 region = AWSServiceRegion(creds=self.creds, region=REGION_EU)
3351 self.assertEquals(region.ec2_endpoint.get_uri(), EC2_ENDPOINT_EU)
3352
3353- def test_get_client_with_empty_cache(self):
3354+ def test_get_ec2_client_with_empty_cache(self):
3355 key = str(EC2Client) + str(self.creds) + str(self.region.ec2_endpoint)
3356 original_client = self.region._clients.get(key)
3357 new_client = self.region.get_client(
3358@@ -101,7 +123,14 @@
3359 self.assertTrue(isinstance(new_client, EC2Client))
3360 self.assertNotEquals(original_client, new_client)
3361
3362- def test_get_client_from_cache(self):
3363+ def test_get_ec2_client_from_cache_default(self):
3364+ client1 = self.region.get_ec2_client()
3365+ client2 = self.region.get_ec2_client()
3366+ self.assertTrue(isinstance(client1, EC2Client))
3367+ self.assertTrue(isinstance(client2, EC2Client))
3368+ self.assertEquals(client1, client2)
3369+
3370+ def test_get_ec2_client_from_cache(self):
3371 client1 = self.region.get_client(
3372 EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint)
3373 client2 = self.region.get_client(
3374@@ -110,7 +139,7 @@
3375 self.assertTrue(isinstance(client2, EC2Client))
3376 self.assertEquals(client1, client2)
3377
3378- def test_get_client_from_cache_with_purge(self):
3379+ def test_get_ec2_client_from_cache_with_purge(self):
3380 client1 = self.region.get_client(
3381 EC2Client, creds=self.creds, endpoint=self.region.ec2_endpoint,
3382 purge_cache=True)
3383@@ -121,10 +150,11 @@
3384 self.assertTrue(isinstance(client2, EC2Client))
3385 self.assertNotEquals(client1, client2)
3386
3387- def test_get_ec2_client_from_cache(self):
3388- client1 = self.region.get_ec2_client(self.creds)
3389- client2 = self.region.get_ec2_client(self.creds)
3390- self.assertEquals(self.creds, self.region.creds)
3391- self.assertTrue(isinstance(client1, EC2Client))
3392- self.assertTrue(isinstance(client2, EC2Client))
3393- self.assertEquals(client1, client2)
3394+ def test_get_s3_client_with_empty_cache(self):
3395+ key = str(S3Client) + str(self.creds) + str(self.region.s3_endpoint)
3396+ original_client = self.region._clients.get(key)
3397+ new_client = self.region.get_client(
3398+ S3Client, creds=self.creds, endpoint=self.region.s3_endpoint)
3399+ self.assertEquals(original_client, None)
3400+ self.assertTrue(isinstance(new_client, S3Client))
3401+ self.assertNotEquals(original_client, new_client)
3402
3403=== modified file 'txaws/tests/test_util.py'
3404--- txaws/tests/test_util.py 2009-09-05 00:26:12 +0000
3405+++ txaws/tests/test_util.py 2009-11-28 01:10:27 +0000
3406@@ -63,7 +63,7 @@
3407 elements of its return tuple, even when passed an URL which has
3408 previously been passed to L{urlparse} as a C{unicode} string.
3409 """
3410- badInput = u"http://example.com/path"
3411+ badInput = u"http://example1.com/path"
3412 goodInput = badInput.encode("ascii")
3413 urlparse(badInput)
3414 scheme, host, port, path = parse(goodInput)
3415
3416=== modified file 'txaws/util.py'
3417--- txaws/util.py 2009-10-21 19:40:18 +0000
3418+++ txaws/util.py 2009-11-28 01:10:27 +0000
3419@@ -93,4 +93,4 @@
3420 port = defaultPort
3421 if path == "":
3422 path = "/"
3423- return scheme, host, port, path
3424+ return (str(scheme), str(host), port, str(path))
3425
3426=== modified file 'txaws/version.py'
3427--- txaws/version.py 2009-08-31 20:07:58 +0000
3428+++ txaws/version.py 2009-11-28 01:10:27 +0000
3429@@ -1,2 +1,3 @@
3430 txaws = "0.0.1"
3431-aws_api = "2008-12-01"
3432+ec2_api = "2008-12-01"
3433+s3_api = "2006-03-01"

Subscribers

People subscribed via source and target branches

to all changes: