Merge lp:~oubiwann/txaws/474353-storage-ec2-symmetry into lp:txaws
- 474353-storage-ec2-symmetry
- Merge into trunk
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 |
Related bugs: |
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.
Commit message
Description of the change
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 | # |
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" |
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: /blueprints. edge.launchpad. net/txaws/ +spec/improve- s3-support
https:/