Merge lp:~oubiwann/txaws/432540-more-securitygroup-methods into lp:txaws
- 432540-more-securitygroup-methods
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | not available | ||||
Proposed branch: | lp:~oubiwann/txaws/432540-more-securitygroup-methods | ||||
Merge into: | lp:txaws | ||||
Diff against target: |
1309 lines 4 files modified
txaws/ec2/client.py (+209/-30) txaws/ec2/tests/test_client.py (+601/-261) txaws/exception.py (+1/-0) txaws/testing/payload.py (+42/-0) |
||||
To merge this branch: | bzr merge lp:~oubiwann/txaws/432540-more-securitygroup-methods | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Thomas Herve | Approve | ||
Review via email: mp+12118@code.launchpad.net |
Commit message
Description of the change
Duncan McGreggor (oubiwann) wrote : | # |
- 31. By Duncan McGreggor
-
Added missing newline.
Thomas Herve (therve) wrote : | # |
[1]
+ if success and success.lower() == "true":
+ return True
+ return False
I'm not sure you need to check for the presence of success. At any rate, you could do that:
return (success is not None and success.lower() == "true")
But I would do that:
return root.findtext(
It would handle less cases, but it would like to see it fails when EC2 is not respecting the format of the response (we don't have to be defensive here).
[2]
+ @return: A C{Deferred} that will fire with a turth value for the
typo: truth (in 2 places).
[3] Nice factoring with _parse_
+1!
Duncan McGreggor (oubiwann) wrote : | # |
> [1]
> + if success and success.lower() == "true":
> + return True
> + return False
>
> I'm not sure you need to check for the presence of success. At any rate, you
> could do that:
>
> return (success is not None and success.lower() == "true")
>
> But I would do that:
>
> return root.findtext(
>
> It would handle less cases, but it would like to see it fails when EC2 is not
> respecting the format of the response (we don't have to be defensive here).
Cool. Done.
> [2]
> + @return: A C{Deferred} that will fire with a turth value for the
>
> typo: truth (in 2 places).
Yikes. There were a bunch more, too. Hopefully I got them all ;-)
> [3] Nice factoring with _parse_
> places where similar thing is done?
Thanks! Done.
> +1!
Thanks!
d
- 32. By Duncan McGreggor
-
- Changed other functions that had custom parsing for truth returns to use the
newly refactored method (therve 1).
- Removed debug print from unit test. - 33. By Duncan McGreggor
-
Fixed typos (therve 2).
- 34. By Duncan McGreggor
-
Fixed a bunch more typos.
- 35. By Duncan McGreggor
-
Merged from trunk.
Preview Diff
1 | === modified file 'txaws/ec2/client.py' |
2 | --- txaws/ec2/client.py 2009-09-18 14:51:21 +0000 |
3 | +++ txaws/ec2/client.py 2009-09-25 05:33:10 +0000 |
4 | @@ -57,9 +57,9 @@ |
5 | """Describe current instances.""" |
6 | q = self.query_factory("DescribeInstances", self.creds, self.endpoint) |
7 | d = q.submit() |
8 | - return d.addCallback(self._parse_instances) |
9 | + return d.addCallback(self._parse_describe_instances) |
10 | |
11 | - def _parse_instances(self, xml_bytes): |
12 | + def _parse_describe_instances(self, xml_bytes): |
13 | """ |
14 | Parse the reservations XML payload that is returned from an AWS |
15 | describeInstances API call. |
16 | @@ -165,9 +165,9 @@ |
17 | query = self.query_factory("DescribeSecurityGroups", self.creds, |
18 | self.endpoint, group_names) |
19 | d = query.submit() |
20 | - return d.addCallback(self._parse_security_groups) |
21 | + return d.addCallback(self._parse_describe_security_groups) |
22 | |
23 | - def _parse_security_groups(self, xml_bytes): |
24 | + def _parse_describe_security_groups(self, xml_bytes): |
25 | """Parse the XML returned by the C{DescribeSecurityGroups} function. |
26 | |
27 | @param xml_bytes: XML bytes with a C{DescribeSecurityGroupsResponse} |
28 | @@ -207,6 +207,205 @@ |
29 | result.append(security_group) |
30 | return result |
31 | |
32 | + def create_security_group(self, name, description): |
33 | + """Create security group. |
34 | + |
35 | + @param name: Name of the new security group. |
36 | + @param description: Description of the new security group. |
37 | + @return: A C{Deferred} that will fire with a truth value for the |
38 | + success of the operation. |
39 | + """ |
40 | + group_names = None |
41 | + parameters = {"GroupName": name, "GroupDescription": description} |
42 | + query = self.query_factory("CreateSecurityGroup", self.creds, |
43 | + self.endpoint, parameters) |
44 | + d = query.submit() |
45 | + return d.addCallback(self._parse_truth_return) |
46 | + |
47 | + def _parse_truth_return(self, xml_bytes): |
48 | + root = XML(xml_bytes) |
49 | + return root.findtext("return") == "true" |
50 | + |
51 | + def delete_security_group(self, name): |
52 | + """ |
53 | + @param name: Name of the new security group. |
54 | + @return: A C{Deferred} that will fire with a truth value for the |
55 | + success of the operation. |
56 | + """ |
57 | + parameter = {"GroupName": name} |
58 | + query = self.query_factory("DeleteSecurityGroup", self.creds, |
59 | + self.endpoint, parameter) |
60 | + d = query.submit() |
61 | + return d.addCallback(self._parse_truth_return) |
62 | + |
63 | + def authorize_security_group( |
64 | + self, group_name, source_group_name="", source_group_owner_id="", |
65 | + ip_protocol="", from_port="", to_port="", cidr_ip=""): |
66 | + """ |
67 | + There are two ways to use C{authorize_security_group}: |
68 | + 1) associate an existing group (source group) with the one that you |
69 | + are targeting (group_name) with an authorization update; or |
70 | + 2) associate a set of IP permissions with the group you are |
71 | + targeting with an authorization update. |
72 | + |
73 | + @param group_name: The group you will be modifying with a new |
74 | + authorization. |
75 | + |
76 | + Optionally, the following parameters: |
77 | + @param source_group_name: Name of security group to authorize access to |
78 | + when operating on a user/group pair. |
79 | + @param source_group_owner_id: Owner of security group to authorize |
80 | + access to when operating on a user/group pair. |
81 | + |
82 | + If those parameters are not specified, then the following must be: |
83 | + @param ip_protocol: IP protocol to authorize access to when operating |
84 | + on a CIDR IP. |
85 | + @param from_port: Bottom of port range to authorize access to when |
86 | + operating on a CIDR IP. This contains the ICMP type if ICMP is |
87 | + being authorized. |
88 | + @param to_port: Top of port range to authorize access to when operating |
89 | + on a CIDR IP. This contains the ICMP code if ICMP is being |
90 | + authorized. |
91 | + @param cidr_ip: CIDR IP range to authorize access to when operating on |
92 | + a CIDR IP. |
93 | + |
94 | + @return: A C{Deferred} that will fire with a truth value for the |
95 | + success of the operation. |
96 | + """ |
97 | + if source_group_name and source_group_owner_id: |
98 | + parameters = { |
99 | + "SourceSecurityGroupName": source_group_name, |
100 | + "SourceSecurityGroupOwnerId": source_group_owner_id, |
101 | + } |
102 | + elif ip_protocol and from_port and to_port and cidr_ip: |
103 | + parameters = { |
104 | + "IpProtocol": ip_protocol, |
105 | + "FromPort": from_port, |
106 | + "ToPort": to_port, |
107 | + "CidrIp": cidr_ip, |
108 | + } |
109 | + else: |
110 | + msg = ("You must specify either both group parameters or " |
111 | + "all the ip parameters.") |
112 | + raise ValueError(msg) |
113 | + parameters["GroupName"] = group_name |
114 | + query = self.query_factory("AuthorizeSecurityGroupIngress", self.creds, |
115 | + self.endpoint, parameters) |
116 | + d = query.submit() |
117 | + return d.addCallback(self._parse_truth_return) |
118 | + |
119 | + def authorize_group_permission( |
120 | + self, group_name, source_group_name, source_group_owner_id): |
121 | + """ |
122 | + This is a convenience function that wraps the "authorize group" |
123 | + functionality of the C{authorize_security_group} method. |
124 | + |
125 | + For an explanation of the parameters, see C{authorize_security_group}. |
126 | + """ |
127 | + d = self.authorize_security_group( |
128 | + group_name, |
129 | + source_group_name=source_group_name, |
130 | + source_group_owner_id=source_group_owner_id) |
131 | + return d |
132 | + |
133 | + def authorize_ip_permission( |
134 | + self, group_name, ip_protocol, from_port, to_port, cidr_ip): |
135 | + """ |
136 | + This is a convenience function that wraps the "authorize ip |
137 | + permission" functionality of the C{authorize_security_group} method. |
138 | + |
139 | + For an explanation of the parameters, see C{authorize_security_group}. |
140 | + """ |
141 | + d = self.authorize_security_group( |
142 | + group_name, |
143 | + ip_protocol=ip_protocol, from_port=from_port, to_port=to_port, |
144 | + cidr_ip=cidr_ip) |
145 | + return d |
146 | + |
147 | + def revoke_security_group( |
148 | + self, group_name, source_group_name="", source_group_owner_id="", |
149 | + ip_protocol="", from_port="", to_port="", cidr_ip=""): |
150 | + """ |
151 | + There are two ways to use C{revoke_security_group}: |
152 | + 1) associate an existing group (source group) with the one that you |
153 | + are targeting (group_name) with the revoke update; or |
154 | + 2) associate a set of IP permissions with the group you are |
155 | + targeting with a revoke update. |
156 | + |
157 | + @param group_name: The group you will be modifying with an |
158 | + authorization removal. |
159 | + |
160 | + Optionally, the following parameters: |
161 | + @param source_group_name: Name of security group to revoke access from |
162 | + when operating on a user/group pair. |
163 | + @param source_group_owner_id: Owner of security group to revoke |
164 | + access from when operating on a user/group pair. |
165 | + |
166 | + If those parameters are not specified, then the following must be: |
167 | + @param ip_protocol: IP protocol to revoke access from when operating |
168 | + on a CIDR IP. |
169 | + @param from_port: Bottom of port range to revoke access from when |
170 | + operating on a CIDR IP. This contains the ICMP type if ICMP is |
171 | + being revoked. |
172 | + @param to_port: Top of port range to revoke access from when operating |
173 | + on a CIDR IP. This contains the ICMP code if ICMP is being |
174 | + revoked. |
175 | + @param cidr_ip: CIDR IP range to revoke access from when operating on |
176 | + a CIDR IP. |
177 | + |
178 | + @return: A C{Deferred} that will fire with a truth value for the |
179 | + success of the operation. |
180 | + """ |
181 | + if source_group_name and source_group_owner_id: |
182 | + parameters = { |
183 | + "SourceSecurityGroupName": source_group_name, |
184 | + "SourceSecurityGroupOwnerId": source_group_owner_id, |
185 | + } |
186 | + elif ip_protocol and from_port and to_port and cidr_ip: |
187 | + parameters = { |
188 | + "IpProtocol": ip_protocol, |
189 | + "FromPort": from_port, |
190 | + "ToPort": to_port, |
191 | + "CidrIp": cidr_ip, |
192 | + } |
193 | + else: |
194 | + msg = ("You must specify either both group parameters or " |
195 | + "all the ip parameters.") |
196 | + raise ValueError(msg) |
197 | + parameters["GroupName"] = group_name |
198 | + query = self.query_factory("RevokeSecurityGroupIngress", self.creds, |
199 | + self.endpoint, parameters) |
200 | + d = query.submit() |
201 | + return d.addCallback(self._parse_truth_return) |
202 | + |
203 | + def revoke_group_permission( |
204 | + self, group_name, source_group_name, source_group_owner_id): |
205 | + """ |
206 | + This is a convenience function that wraps the "authorize group" |
207 | + functionality of the C{authorize_security_group} method. |
208 | + |
209 | + For an explanation of the parameters, see C{authorize_security_group}. |
210 | + """ |
211 | + d = self.revoke_security_group( |
212 | + group_name, |
213 | + source_group_name=source_group_name, |
214 | + source_group_owner_id=source_group_owner_id) |
215 | + return d |
216 | + |
217 | + def revoke_ip_permission( |
218 | + self, group_name, ip_protocol, from_port, to_port, cidr_ip): |
219 | + """ |
220 | + This is a convenience function that wraps the "authorize ip |
221 | + permission" functionality of the C{authorize_security_group} method. |
222 | + |
223 | + For an explanation of the parameters, see C{authorize_security_group}. |
224 | + """ |
225 | + d = self.revoke_security_group( |
226 | + group_name, |
227 | + ip_protocol=ip_protocol, from_port=from_port, to_port=to_port, |
228 | + cidr_ip=cidr_ip) |
229 | + return d |
230 | + |
231 | def describe_volumes(self, *volume_ids): |
232 | """Describe available volumes.""" |
233 | volumeset = {} |
234 | @@ -215,9 +414,9 @@ |
235 | q = self.query_factory( |
236 | "DescribeVolumes", self.creds, self.endpoint, volumeset) |
237 | d = q.submit() |
238 | - return d.addCallback(self._parse_volumes) |
239 | + return d.addCallback(self._parse_describe_volumes) |
240 | |
241 | - def _parse_volumes(self, xml_bytes): |
242 | + def _parse_describe_volumes(self, xml_bytes): |
243 | root = XML(xml_bytes) |
244 | result = [] |
245 | for volume_data in root.find("volumeSet"): |
246 | @@ -274,11 +473,7 @@ |
247 | q = self.query_factory( |
248 | "DeleteVolume", self.creds, self.endpoint, {"VolumeId": volume_id}) |
249 | d = q.submit() |
250 | - return d.addCallback(self._parse_delete_volume) |
251 | - |
252 | - def _parse_delete_volume(self, xml_bytes): |
253 | - root = XML(xml_bytes) |
254 | - return root.findtext("return") == "true" |
255 | + return d.addCallback(self._parse_truth_return) |
256 | |
257 | def describe_snapshots(self, *snapshot_ids): |
258 | """Describe available snapshots.""" |
259 | @@ -334,11 +529,7 @@ |
260 | "DeleteSnapshot", self.creds, self.endpoint, |
261 | {"SnapshotId": snapshot_id}) |
262 | d = q.submit() |
263 | - return d.addCallback(self._parse_delete_snapshot) |
264 | - |
265 | - def _parse_delete_snapshot(self, xml_bytes): |
266 | - root = XML(xml_bytes) |
267 | - return root.findtext("return") == "true" |
268 | + return d.addCallback(self._parse_truth_return) |
269 | |
270 | def attach_volume(self, volume_id, instance_id, device): |
271 | """Attach the given volume to the specified instance at C{device}.""" |
272 | @@ -401,19 +592,7 @@ |
273 | "DeleteKeyPair", self.creds, self.endpoint, |
274 | {"KeyName": keypair_name}) |
275 | d = q.submit() |
276 | - return d.addCallback(self._parse_delete_keypair) |
277 | - |
278 | - def _parse_delete_keypair(self, xml_bytes): |
279 | - results = [] |
280 | - keypair_data = XML(xml_bytes) |
281 | - result = keypair_data.findtext("return") |
282 | - if not result: |
283 | - result = False |
284 | - elif result.lower() == "true": |
285 | - result = True |
286 | - else: |
287 | - result = False |
288 | - return result |
289 | + return d.addCallback(self._parse_truth_return) |
290 | |
291 | |
292 | class Query(object): |
293 | @@ -471,7 +650,7 @@ |
294 | self.params["Signature"] = self.creds.sign(self.signing_text()) |
295 | |
296 | def sorted_params(self): |
297 | - """Return the query params sorted appropriately for signing.""" |
298 | + """Return the query parameters sorted appropriately for signing.""" |
299 | return sorted(self.params.items()) |
300 | |
301 | def get_page(self, url, *args, **kwds): |
302 | |
303 | === modified file 'txaws/ec2/tests/test_client.py' |
304 | --- txaws/ec2/tests/test_client.py 2009-09-15 21:12:04 +0000 |
305 | +++ txaws/ec2/tests/test_client.py 2009-09-25 05:33:10 +0000 |
306 | @@ -7,7 +7,7 @@ |
307 | import os |
308 | |
309 | from twisted.internet import reactor |
310 | -from twisted.internet.defer import succeed |
311 | +from twisted.internet.defer import succeed, fail |
312 | from twisted.python.failure import Failure |
313 | from twisted.python.filepath import FilePath |
314 | from twisted.web import server, static, util |
315 | @@ -71,6 +71,9 @@ |
316 | ec2 = client.EC2Client(creds=creds) |
317 | self.assertEqual(creds, ec2.creds) |
318 | |
319 | + |
320 | +class EC2ClientInstancesTestCase(TXAWSTestCase): |
321 | + |
322 | def check_parsed_instances(self, results): |
323 | instance = results[0] |
324 | # check reservations |
325 | @@ -130,7 +133,7 @@ |
326 | def test_parse_reservation(self): |
327 | creds = AWSCredentials("foo", "bar") |
328 | ec2 = client.EC2Client(creds=creds) |
329 | - results = ec2._parse_instances( |
330 | + results = ec2._parse_describe_instances( |
331 | payload.sample_describe_instances_result) |
332 | self.check_parsed_instances(results) |
333 | |
334 | @@ -185,6 +188,9 @@ |
335 | d.addCallback(check_transition) |
336 | return d |
337 | |
338 | + |
339 | +class EC2ClientSecurityGroupsTestCase(TXAWSTestCase): |
340 | + |
341 | def test_describe_security_groups(self): |
342 | """ |
343 | L{EC2Client.describe_security_groups} returns a C{Deferred} that |
344 | @@ -200,7 +206,7 @@ |
345 | def submit(self): |
346 | return succeed(payload.sample_describe_security_groups_result) |
347 | |
348 | - def assert_security_groups(security_groups): |
349 | + def check_results(security_groups): |
350 | [security_group] = security_groups |
351 | self.assertEquals(security_group.owner_id, |
352 | "UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM") |
353 | @@ -214,9 +220,8 @@ |
354 | |
355 | creds = AWSCredentials("foo", "bar") |
356 | ec2 = client.EC2Client(creds, query_factory=StubQuery) |
357 | - security_groups = ec2.describe_security_groups() |
358 | - security_groups.addCallback(assert_security_groups) |
359 | - return security_groups |
360 | + d = ec2.describe_security_groups() |
361 | + return d.addCallback(check_results) |
362 | |
363 | def test_describe_security_groups_with_multiple_results(self): |
364 | """ |
365 | @@ -234,7 +239,7 @@ |
366 | return succeed( |
367 | payload.sample_describe_security_groups_multiple_result) |
368 | |
369 | - def assert_security_groups(security_groups): |
370 | + def check_results(security_groups): |
371 | self.assertEquals(len(security_groups), 2) |
372 | |
373 | security_group = security_groups[0] |
374 | @@ -263,9 +268,8 @@ |
375 | |
376 | creds = AWSCredentials("foo", "bar") |
377 | ec2 = client.EC2Client(creds, query_factory=StubQuery) |
378 | - security_groups = ec2.describe_security_groups() |
379 | - security_groups.addCallback(assert_security_groups) |
380 | - return security_groups |
381 | + d = ec2.describe_security_groups() |
382 | + return d.addCallback(check_results) |
383 | |
384 | def test_describe_security_groups_with_name(self): |
385 | """ |
386 | @@ -281,267 +285,354 @@ |
387 | def submit(self): |
388 | return succeed(payload.sample_describe_security_groups_result) |
389 | |
390 | - def assert_security_groups(security_groups): |
391 | + def check_result(security_groups): |
392 | [security_group] = security_groups |
393 | self.assertEquals(security_group.name, "WebServers") |
394 | |
395 | creds = AWSCredentials("foo", "bar") |
396 | ec2 = client.EC2Client(creds, query_factory=StubQuery) |
397 | - security_groups = ec2.describe_security_groups("WebServers") |
398 | - security_groups.addCallback(assert_security_groups) |
399 | - return security_groups |
400 | - |
401 | - |
402 | -class QueryTestCase(TXAWSTestCase): |
403 | - |
404 | - def setUp(self): |
405 | - TXAWSTestCase.setUp(self) |
406 | - self.creds = AWSCredentials("foo", "bar") |
407 | - self.endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US) |
408 | - |
409 | - def test_init_minimum(self): |
410 | - query = client.Query("DescribeInstances", self.creds, self.endpoint) |
411 | - self.assertTrue("Timestamp" in query.params) |
412 | - del query.params["Timestamp"] |
413 | - self.assertEqual( |
414 | - {"AWSAccessKeyId": "foo", |
415 | - "Action": "DescribeInstances", |
416 | - "SignatureMethod": "HmacSHA1", |
417 | - "SignatureVersion": "2", |
418 | - "Version": "2008-12-01"}, |
419 | - query.params) |
420 | - |
421 | - def test_init_requires_action(self): |
422 | - self.assertRaises(TypeError, client.Query) |
423 | - |
424 | - def test_init_requires_creds(self): |
425 | - self.assertRaises(TypeError, client.Query, None) |
426 | - |
427 | - def test_init_other_args_are_params(self): |
428 | - query = client.Query("DescribeInstances", self.creds, self.endpoint, |
429 | - {"InstanceId.0": "12345"}, |
430 | - time_tuple=(2007,11,12,13,14,15,0,0,0)) |
431 | - self.assertEqual( |
432 | - {"AWSAccessKeyId": "foo", |
433 | - "Action": "DescribeInstances", |
434 | - "InstanceId.0": "12345", |
435 | - "SignatureMethod": "HmacSHA1", |
436 | - "SignatureVersion": "2", |
437 | - "Timestamp": "2007-11-12T13:14:15Z", |
438 | - "Version": "2008-12-01"}, |
439 | - query.params) |
440 | - |
441 | - def test_sorted_params(self): |
442 | - query = client.Query("DescribeInstances", self.creds, self.endpoint, |
443 | - {"fun": "games"}, |
444 | - time_tuple=(2007,11,12,13,14,15,0,0,0)) |
445 | - self.assertEqual([ |
446 | - ("AWSAccessKeyId", "foo"), |
447 | - ("Action", "DescribeInstances"), |
448 | - ("SignatureMethod", "HmacSHA1"), |
449 | - ("SignatureVersion", "2"), |
450 | - ("Timestamp", "2007-11-12T13:14:15Z"), |
451 | - ("Version", "2008-12-01"), |
452 | - ("fun", "games"), |
453 | - ], query.sorted_params()) |
454 | - |
455 | - def test_encode_unreserved(self): |
456 | - all_unreserved = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
457 | - "abcdefghijklmnopqrstuvwxyz0123456789-_.~") |
458 | - query = client.Query("DescribeInstances", self.creds, self.endpoint) |
459 | - self.assertEqual(all_unreserved, query.encode(all_unreserved)) |
460 | - |
461 | - def test_encode_space(self): |
462 | - """This may be just "url encode", but the AWS manual isn't clear.""" |
463 | - query = client.Query("DescribeInstances", self.creds, self.endpoint) |
464 | - self.assertEqual("a%20space", query.encode("a space")) |
465 | - |
466 | - def test_canonical_query(self): |
467 | - query = client.Query("DescribeInstances", self.creds, self.endpoint, |
468 | - {"fu n": "g/ames", "argwithnovalue":"", |
469 | - "InstanceId.1": "i-1234"}, |
470 | - time_tuple=(2007,11,12,13,14,15,0,0,0)) |
471 | - expected_query = ("AWSAccessKeyId=foo&Action=DescribeInstances" |
472 | - "&InstanceId.1=i-1234" |
473 | - "&SignatureMethod=HmacSHA1&SignatureVersion=2&" |
474 | - "Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01&" |
475 | - "argwithnovalue=&fu%20n=g%2Fames") |
476 | - self.assertEqual(expected_query, query.canonical_query_params()) |
477 | - |
478 | - def test_signing_text(self): |
479 | - query = client.Query("DescribeInstances", self.creds, self.endpoint, |
480 | - time_tuple=(2007,11,12,13,14,15,0,0,0)) |
481 | - signing_text = ("GET\n%s\n/\n" % self.endpoint.host + |
482 | - "AWSAccessKeyId=foo&Action=DescribeInstances&" |
483 | - "SignatureMethod=HmacSHA1&SignatureVersion=2&" |
484 | - "Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01") |
485 | - self.assertEqual(signing_text, query.signing_text()) |
486 | - |
487 | - def test_sign(self): |
488 | - query = client.Query("DescribeInstances", self.creds, self.endpoint, |
489 | - time_tuple=(2007,11,12,13,14,15,0,0,0)) |
490 | - query.sign() |
491 | - self.assertEqual("JuCpwFA2H4OVF3Ql/lAQs+V6iMc=", |
492 | - query.params["Signature"]) |
493 | - |
494 | - def test_submit_400(self): |
495 | - """A 4xx response status from EC2 should raise a txAWS EC2Error.""" |
496 | - status = 400 |
497 | - self.addCleanup(setattr, client.Query, "get_page", |
498 | - client.Query.get_page) |
499 | - fake_page_getter = FakePageGetter( |
500 | - status, payload.sample_ec2_error_message) |
501 | - client.Query.get_page = fake_page_getter.get_page_with_exception |
502 | + d = ec2.describe_security_groups("WebServers") |
503 | + return d.addCallback(check_result) |
504 | + |
505 | + def test_create_security_group(self): |
506 | + """ |
507 | + L{EC2Client.create_security_group} returns a C{Deferred} that |
508 | + eventually fires with a true value, indicating the success of the |
509 | + operation. |
510 | + """ |
511 | + class StubQuery(object): |
512 | + def __init__(stub, action, creds, endpoint, other_params=None): |
513 | + self.assertEqual(action, "CreateSecurityGroup") |
514 | + self.assertEqual(creds.access_key, "foo") |
515 | + self.assertEqual(creds.secret_key, "bar") |
516 | + self.assertEqual(other_params, { |
517 | + "GroupName": "WebServers", |
518 | + "GroupDescription": "The group for the web server farm.", |
519 | + }) |
520 | + def submit(self): |
521 | + return succeed(payload.sample_create_security_group) |
522 | + |
523 | + creds = AWSCredentials("foo", "bar") |
524 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
525 | + d = ec2.create_security_group( |
526 | + "WebServers", |
527 | + "The group for the web server farm.") |
528 | + return self.assertTrue(d) |
529 | + |
530 | + def test_delete_security_group(self): |
531 | + """ |
532 | + L{EC2Client.delete_security_group} returns a C{Deferred} that |
533 | + eventually fires with a true value, indicating the success of the |
534 | + operation. |
535 | + """ |
536 | + class StubQuery(object): |
537 | + def __init__(stub, action, creds, endpoint, other_params=None): |
538 | + self.assertEqual(action, "DeleteSecurityGroup") |
539 | + self.assertEqual(creds.access_key, "foo") |
540 | + self.assertEqual(creds.secret_key, "bar") |
541 | + self.assertEqual(other_params, { |
542 | + "GroupName": "WebServers", |
543 | + }) |
544 | + def submit(self): |
545 | + return succeed(payload.sample_delete_security_group) |
546 | + |
547 | + creds = AWSCredentials("foo", "bar") |
548 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
549 | + d = ec2.delete_security_group("WebServers") |
550 | + return self.assertTrue(d) |
551 | + |
552 | + def test_delete_security_group_failure(self): |
553 | + """ |
554 | + L{EC2Client.delete_security_group} returns a C{Deferred} that |
555 | + eventually fires with a failure when EC2 is asked to delete a group |
556 | + that another group uses in that other group's policy. |
557 | + """ |
558 | + class StubQuery(object): |
559 | + def __init__(stub, action, creds, endpoint, other_params=None): |
560 | + self.assertEqual(action, "DeleteSecurityGroup") |
561 | + self.assertEqual(creds.access_key, "foo") |
562 | + self.assertEqual(creds.secret_key, "bar") |
563 | + self.assertEqual(other_params, { |
564 | + "GroupName": "GroupReferredTo", |
565 | + }) |
566 | + def submit(self): |
567 | + error = EC2Error(payload.sample_delete_security_group_failure) |
568 | + return fail(error) |
569 | |
570 | def check_error(error): |
571 | - self.assertTrue(isinstance(error, EC2Error)) |
572 | - self.assertEquals(error.get_error_codes(), "Error.Code") |
573 | self.assertEquals( |
574 | - error.get_error_messages(), |
575 | - "Message for Error.Code") |
576 | - self.assertEquals(error.status, status) |
577 | - self.assertEquals(error.response, payload.sample_ec2_error_message) |
578 | - |
579 | - query = client.Query( |
580 | - 'BadQuery', self.creds, self.endpoint, |
581 | - time_tuple=(2009,8,15,13,14,15,0,0,0)) |
582 | + str(error), |
583 | + ("Error Message: Group groupID1:GroupReferredTo is used by " |
584 | + "groups: groupID2:UsingGroup")) |
585 | |
586 | - failure = query.submit() |
587 | + creds = AWSCredentials("foo", "bar") |
588 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
589 | + failure = ec2.delete_security_group("GroupReferredTo") |
590 | d = self.assertFailure(failure, EC2Error) |
591 | - d.addCallback(check_error) |
592 | - return d |
593 | - |
594 | - def test_submit_500(self): |
595 | - """ |
596 | - A 5xx response status from EC2 should raise the original Twisted |
597 | - exception. |
598 | - """ |
599 | - status = 500 |
600 | - self.addCleanup(setattr, client.Query, "get_page", |
601 | - client.Query.get_page) |
602 | - fake_page_getter = FakePageGetter( |
603 | - status, payload.sample_ec2_error_message) |
604 | - client.Query.get_page = fake_page_getter.get_page_with_exception |
605 | - |
606 | - def check_error(error): |
607 | - self.assertFalse(isinstance(error, EC2Error)) |
608 | - self.assertEquals(error.status, status) |
609 | - self.assertEquals(str(error), "500 There's been an error") |
610 | - |
611 | - query = client.Query( |
612 | - 'BadQuery', self.creds, self.endpoint, |
613 | - time_tuple=(2009,8,15,13,14,15,0,0,0)) |
614 | - |
615 | - failure = query.submit() |
616 | - d = self.assertFailure(failure, Error) |
617 | - d.addCallback(check_error) |
618 | - return d |
619 | - |
620 | - |
621 | -class QueryPageGetterTestCase(TXAWSTestCase): |
622 | - |
623 | - def setUp(self): |
624 | - TXAWSTestCase.setUp(self) |
625 | - self.creds = AWSCredentials("foo", "bar") |
626 | - self.endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US) |
627 | - self.twisted_client_test_setup() |
628 | - self.cleanupServerConnections = 0 |
629 | - |
630 | - def tearDown(self): |
631 | - """Copied from twisted.web.test.test_webclient.""" |
632 | - # If the test indicated it might leave some server-side connections |
633 | - # around, clean them up. |
634 | - connections = self.wrapper.protocols.keys() |
635 | - # If there are fewer server-side connections than requested, |
636 | - # that's okay. Some might have noticed that the client closed |
637 | - # the connection and cleaned up after themselves. |
638 | - for n in range(min(len(connections), self.cleanupServerConnections)): |
639 | - proto = connections.pop() |
640 | - #msg("Closing %r" % (proto,)) |
641 | - proto.transport.loseConnection() |
642 | - if connections: |
643 | - #msg("Some left-over connections; this test is probably buggy.") |
644 | - pass |
645 | - return self.port.stopListening() |
646 | - |
647 | - def _listen(self, site): |
648 | - return reactor.listenTCP(0, site, interface="127.0.0.1") |
649 | - |
650 | - def twisted_client_test_setup(self): |
651 | - name = self.mktemp() |
652 | - os.mkdir(name) |
653 | - FilePath(name).child("file").setContent("0123456789") |
654 | - resource = static.File(name) |
655 | - resource.putChild("redirect", util.Redirect("/file")) |
656 | - self.site = server.Site(resource, timeout=None) |
657 | - self.wrapper = WrappingFactory(self.site) |
658 | - self.port = self._listen(self.wrapper) |
659 | - self.portno = self.port.getHost().port |
660 | - |
661 | - def get_url(self, path): |
662 | - return "http://127.0.0.1:%d/%s" % (self.portno, path) |
663 | - |
664 | - def test_get_page(self): |
665 | - """Copied from twisted.web.test.test_webclient.""" |
666 | - query = client.Query( |
667 | - 'DummyQuery', self.creds, self.endpoint, |
668 | - time_tuple=(2009,8,17,13,14,15,0,0,0)) |
669 | - deferred = query.get_page(self.get_url("file")) |
670 | - deferred.addCallback(self.assertEquals, "0123456789") |
671 | - return deferred |
672 | - |
673 | - |
674 | -class EC2ErrorWrapperTestCase(TXAWSTestCase): |
675 | - |
676 | - def setUp(self): |
677 | - TXAWSTestCase.setUp(self) |
678 | - |
679 | - def get_failure(self, status=None, type=None, message=""): |
680 | - failure = Failure(type(message)) |
681 | - failure.value.response = payload.sample_ec2_error_message |
682 | - failure.value.status = status |
683 | - return failure |
684 | - |
685 | - def test_302_error(self): |
686 | - failure = self.get_failure(302, Exception, "found") |
687 | - error = self.assertRaises(Exception, client.ec2_error_wrapper, failure) |
688 | - self.assertEquals(failure.type, type(error)) |
689 | - self.assertFalse(isinstance(error, EC2Error)) |
690 | - self.assertTrue(isinstance(error, Exception)) |
691 | - self.assertEquals(error.message, "found") |
692 | - |
693 | - def test_400_error(self): |
694 | - failure = self.get_failure(400, Exception) |
695 | - error = self.assertRaises(EC2Error, client.ec2_error_wrapper, failure) |
696 | - self.assertNotEquals(failure.type, type(error)) |
697 | - self.assertTrue(isinstance(error, EC2Error)) |
698 | - self.assertEquals(error.get_error_codes(), "Error.Code") |
699 | - self.assertEquals(error.get_error_messages(), "Message for Error.Code") |
700 | - |
701 | - def test_404_error(self): |
702 | - failure = self.get_failure(404, Exception) |
703 | - error = self.assertRaises(EC2Error, client.ec2_error_wrapper, failure) |
704 | - self.assertNotEquals(failure.type, type(error)) |
705 | - self.assertTrue(isinstance(error, EC2Error)) |
706 | - self.assertEquals(error.get_error_codes(), "Error.Code") |
707 | - self.assertEquals(error.get_error_messages(), "Message for Error.Code") |
708 | - |
709 | - def test_500_error(self): |
710 | - failure = self.get_failure(500, Exception, "A server error occurred") |
711 | - error = self.assertRaises(Exception, client.ec2_error_wrapper, failure) |
712 | - self.assertFalse(isinstance(error, EC2Error)) |
713 | - self.assertTrue(isinstance(error, Exception)) |
714 | - self.assertEquals(error.message, "A server error occurred") |
715 | - |
716 | - def test_timeout_error(self): |
717 | - failure = self.get_failure(type=Exception, message="timeout") |
718 | - error = self.assertRaises(Exception, client.ec2_error_wrapper, failure) |
719 | - self.assertFalse(isinstance(error, EC2Error)) |
720 | - self.assertTrue(isinstance(error, Exception)) |
721 | - self.assertEquals(error.message, "timeout") |
722 | - |
723 | - |
724 | -class EBSTestCase(TXAWSTestCase): |
725 | + return d.addCallback(check_error) |
726 | + |
727 | + def test_authorize_security_group_with_user_group_pair(self): |
728 | + """ |
729 | + L{EC2Client.authorize_security_group} returns a C{Deferred} that |
730 | + eventually fires with a true value, indicating the success of the |
731 | + operation. There are two ways to use the method: set another group's |
732 | + IP permissions or set new IP permissions; this test checks the first |
733 | + way. |
734 | + """ |
735 | + class StubQuery(object): |
736 | + def __init__(stub, action, creds, endpoint, other_params=None): |
737 | + self.assertEqual(action, "AuthorizeSecurityGroupIngress") |
738 | + self.assertEqual(creds.access_key, "foo") |
739 | + self.assertEqual(creds.secret_key, "bar") |
740 | + self.assertEqual(other_params, { |
741 | + "GroupName": "WebServers", |
742 | + "SourceSecurityGroupName": "AppServers", |
743 | + "SourceSecurityGroupOwnerId": "123456789123", |
744 | + }) |
745 | + def submit(self): |
746 | + return succeed(payload.sample_authorize_security_group) |
747 | + |
748 | + creds = AWSCredentials("foo", "bar") |
749 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
750 | + d = ec2.authorize_security_group( |
751 | + "WebServers", source_group_name="AppServers", |
752 | + source_group_owner_id="123456789123") |
753 | + return self.assertTrue(d) |
754 | + |
755 | + def test_authorize_security_group_with_ip_permissions(self): |
756 | + """ |
757 | + L{EC2Client.authorize_security_group} returns a C{Deferred} that |
758 | + eventually fires with a true value, indicating the success of the |
759 | + operation. There are two ways to use the method: set another group's |
760 | + IP permissions or set new IP permissions; this test checks the second |
761 | + way. |
762 | + """ |
763 | + class StubQuery(object): |
764 | + def __init__(stub, action, creds, endpoint, other_params=None): |
765 | + self.assertEqual(action, "AuthorizeSecurityGroupIngress") |
766 | + self.assertEqual(creds.access_key, "foo") |
767 | + self.assertEqual(creds.secret_key, "bar") |
768 | + self.assertEqual(other_params, { |
769 | + "GroupName": "WebServers", |
770 | + "FromPort": "22", "ToPort": "80", |
771 | + "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0", |
772 | + }) |
773 | + def submit(self): |
774 | + return succeed(payload.sample_authorize_security_group) |
775 | + |
776 | + creds = AWSCredentials("foo", "bar") |
777 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
778 | + d = ec2.authorize_security_group( |
779 | + "WebServers", ip_protocol="tcp", from_port="22", to_port="80", |
780 | + cidr_ip="0.0.0.0/0") |
781 | + return self.assertTrue(d) |
782 | + |
783 | + def test_authorize_security_group_with_missing_parameters(self): |
784 | + """ |
785 | + L{EC2Client.authorize_security_group} returns a C{Deferred} that |
786 | + eventually fires with a true value, indicating the success of the |
787 | + operation. There are two ways to use the method: set another group's |
788 | + IP permissions or set new IP permissions. If not all group-setting |
789 | + parameters are set and not all IP permission parameters are set, an |
790 | + error is raised. |
791 | + """ |
792 | + creds = AWSCredentials("foo", "bar") |
793 | + ec2 = client.EC2Client(creds) |
794 | + self.assertRaises(ValueError, ec2.authorize_security_group, |
795 | + "WebServers", ip_protocol="tcp", from_port="22") |
796 | + try: |
797 | + d = ec2.authorize_security_group( |
798 | + "WebServers", ip_protocol="tcp", from_port="22") |
799 | + except Exception, error: |
800 | + self.assertEquals( |
801 | + str(error), |
802 | + ("You must specify either both group parameters or all the " |
803 | + "ip parameters.")) |
804 | + |
805 | + def test_authorize_group_permission(self): |
806 | + """ |
807 | + L{EC2Client.authorize_group_permission} returns a C{Deferred} |
808 | + that eventually fires with a true value, indicating the success of the |
809 | + operation. |
810 | + """ |
811 | + class StubQuery(object): |
812 | + def __init__(stub, action, creds, endpoint, other_params=None): |
813 | + self.assertEqual(action, "AuthorizeSecurityGroupIngress") |
814 | + self.assertEqual(creds.access_key, "foo") |
815 | + self.assertEqual(creds.secret_key, "bar") |
816 | + self.assertEqual(other_params, { |
817 | + "GroupName": "WebServers", |
818 | + "SourceSecurityGroupName": "AppServers", |
819 | + "SourceSecurityGroupOwnerId": "123456789123", |
820 | + }) |
821 | + def submit(self): |
822 | + return succeed(payload.sample_authorize_security_group) |
823 | + |
824 | + creds = AWSCredentials("foo", "bar") |
825 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
826 | + d = ec2.authorize_group_permission( |
827 | + "WebServers", source_group_name="AppServers", |
828 | + source_group_owner_id="123456789123") |
829 | + return self.assertTrue(d) |
830 | + |
831 | + def test_authorize_ip_permission(self): |
832 | + """ |
833 | + L{EC2Client.authorize_ip_permission} returns a C{Deferred} that |
834 | + eventually fires with a true value, indicating the success of the |
835 | + operation. |
836 | + """ |
837 | + class StubQuery(object): |
838 | + def __init__(stub, action, creds, endpoint, other_params=None): |
839 | + self.assertEqual(action, "AuthorizeSecurityGroupIngress") |
840 | + self.assertEqual(creds.access_key, "foo") |
841 | + self.assertEqual(creds.secret_key, "bar") |
842 | + self.assertEqual(other_params, { |
843 | + "GroupName": "WebServers", |
844 | + "FromPort": "22", "ToPort": "80", |
845 | + "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0", |
846 | + }) |
847 | + def submit(self): |
848 | + return succeed(payload.sample_authorize_security_group) |
849 | + |
850 | + creds = AWSCredentials("foo", "bar") |
851 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
852 | + d = ec2.authorize_ip_permission( |
853 | + "WebServers", ip_protocol="tcp", from_port="22", to_port="80", |
854 | + cidr_ip="0.0.0.0/0") |
855 | + return self.assertTrue(d) |
856 | + |
857 | + def test_revoke_security_group_with_user_group_pair(self): |
858 | + """ |
859 | + L{EC2Client.revoke_security_group} returns a C{Deferred} that |
860 | + eventually fires with a true value, indicating the success of the |
861 | + operation. There are two ways to use the method: set another group's |
862 | + IP permissions or set new IP permissions; this test checks the first |
863 | + way. |
864 | + """ |
865 | + class StubQuery(object): |
866 | + def __init__(stub, action, creds, endpoint, other_params=None): |
867 | + self.assertEqual(action, "RevokeSecurityGroupIngress") |
868 | + self.assertEqual(creds.access_key, "foo") |
869 | + self.assertEqual(creds.secret_key, "bar") |
870 | + self.assertEqual(other_params, { |
871 | + "GroupName": "WebServers", |
872 | + "SourceSecurityGroupName": "AppServers", |
873 | + "SourceSecurityGroupOwnerId": "123456789123", |
874 | + }) |
875 | + def submit(self): |
876 | + return succeed(payload.sample_revoke_security_group) |
877 | + |
878 | + creds = AWSCredentials("foo", "bar") |
879 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
880 | + d = ec2.revoke_security_group( |
881 | + "WebServers", source_group_name="AppServers", |
882 | + source_group_owner_id="123456789123") |
883 | + return self.assertTrue(d) |
884 | + |
885 | + def test_revoke_security_group_with_ip_permissions(self): |
886 | + """ |
887 | + L{EC2Client.revoke_security_group} returns a C{Deferred} that |
888 | + eventually fires with a true value, indicating the success of the |
889 | + operation. There are two ways to use the method: set another group's |
890 | + IP permissions or set new IP permissions; this test checks the second |
891 | + way. |
892 | + """ |
893 | + class StubQuery(object): |
894 | + def __init__(stub, action, creds, endpoint, other_params=None): |
895 | + self.assertEqual(action, "RevokeSecurityGroupIngress") |
896 | + self.assertEqual(creds.access_key, "foo") |
897 | + self.assertEqual(creds.secret_key, "bar") |
898 | + self.assertEqual(other_params, { |
899 | + "GroupName": "WebServers", |
900 | + "FromPort": "22", "ToPort": "80", |
901 | + "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0", |
902 | + }) |
903 | + def submit(self): |
904 | + return succeed(payload.sample_revoke_security_group) |
905 | + |
906 | + creds = AWSCredentials("foo", "bar") |
907 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
908 | + d = ec2.revoke_security_group( |
909 | + "WebServers", ip_protocol="tcp", from_port="22", to_port="80", |
910 | + cidr_ip="0.0.0.0/0") |
911 | + return self.assertTrue(d) |
912 | + |
913 | + def test_revoke_security_group_with_missing_parameters(self): |
914 | + """ |
915 | + L{EC2Client.revoke_security_group} returns a C{Deferred} that |
916 | + eventually fires with a true value, indicating the success of the |
917 | + operation. There are two ways to use the method: set another group's |
918 | + IP permissions or set new IP permissions. If not all group-setting |
919 | + parameters are set and not all IP permission parameters are set, an |
920 | + error is raised. |
921 | + """ |
922 | + creds = AWSCredentials("foo", "bar") |
923 | + ec2 = client.EC2Client(creds) |
924 | + self.assertRaises(ValueError, ec2.authorize_security_group, |
925 | + "WebServers", ip_protocol="tcp", from_port="22") |
926 | + try: |
927 | + d = ec2.authorize_security_group( |
928 | + "WebServers", ip_protocol="tcp", from_port="22") |
929 | + except Exception, error: |
930 | + self.assertEquals( |
931 | + str(error), |
932 | + ("You must specify either both group parameters or all the " |
933 | + "ip parameters.")) |
934 | + |
935 | + def test_revoke_group_permission(self): |
936 | + """ |
937 | + L{EC2Client.revoke_group_permission} returns a C{Deferred} that |
938 | + eventually fires with a true value, indicating the success of the |
939 | + operation. |
940 | + """ |
941 | + class StubQuery(object): |
942 | + def __init__(stub, action, creds, endpoint, other_params=None): |
943 | + self.assertEqual(action, "RevokeSecurityGroupIngress") |
944 | + self.assertEqual(creds.access_key, "foo") |
945 | + self.assertEqual(creds.secret_key, "bar") |
946 | + self.assertEqual(other_params, { |
947 | + "GroupName": "WebServers", |
948 | + "SourceSecurityGroupName": "AppServers", |
949 | + "SourceSecurityGroupOwnerId": "123456789123", |
950 | + }) |
951 | + def submit(self): |
952 | + return succeed(payload.sample_revoke_security_group) |
953 | + |
954 | + creds = AWSCredentials("foo", "bar") |
955 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
956 | + d = ec2.revoke_group_permission( |
957 | + "WebServers", source_group_name="AppServers", |
958 | + source_group_owner_id="123456789123") |
959 | + return self.assertTrue(d) |
960 | + |
961 | + def test_revoke_ip_permission(self): |
962 | + """ |
963 | + L{EC2Client.revoke_ip_permission} returns a C{Deferred} that |
964 | + eventually fires with a true value, indicating the success of the |
965 | + operation. |
966 | + """ |
967 | + class StubQuery(object): |
968 | + def __init__(stub, action, creds, endpoint, other_params=None): |
969 | + self.assertEqual(action, "RevokeSecurityGroupIngress") |
970 | + self.assertEqual(creds.access_key, "foo") |
971 | + self.assertEqual(creds.secret_key, "bar") |
972 | + self.assertEqual(other_params, { |
973 | + "GroupName": "WebServers", |
974 | + "FromPort": "22", "ToPort": "80", |
975 | + "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0", |
976 | + }) |
977 | + def submit(self): |
978 | + return succeed(payload.sample_revoke_security_group) |
979 | + |
980 | + creds = AWSCredentials("foo", "bar") |
981 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
982 | + d = ec2.revoke_ip_permission( |
983 | + "WebServers", ip_protocol="tcp", from_port="22", to_port="80", |
984 | + cidr_ip="0.0.0.0/0") |
985 | + return self.assertTrue(d) |
986 | + |
987 | + |
988 | +class EC2ClientEBSTestCase(TXAWSTestCase): |
989 | |
990 | def setUp(self): |
991 | TXAWSTestCase.setUp(self) |
992 | @@ -970,3 +1061,252 @@ |
993 | d = ec2.delete_keypair("example-key-name") |
994 | d.addCallback(self.assertFalse) |
995 | return d |
996 | + |
997 | + |
998 | +class EC2ErrorWrapperTestCase(TXAWSTestCase): |
999 | + |
1000 | + def setUp(self): |
1001 | + TXAWSTestCase.setUp(self) |
1002 | + |
1003 | + def get_failure(self, status=None, type=None, message=""): |
1004 | + failure = Failure(type(message)) |
1005 | + failure.value.response = payload.sample_ec2_error_message |
1006 | + failure.value.status = status |
1007 | + return failure |
1008 | + |
1009 | + def test_302_error(self): |
1010 | + failure = self.get_failure(302, Exception, "found") |
1011 | + error = self.assertRaises(Exception, client.ec2_error_wrapper, failure) |
1012 | + self.assertEquals(failure.type, type(error)) |
1013 | + self.assertFalse(isinstance(error, EC2Error)) |
1014 | + self.assertTrue(isinstance(error, Exception)) |
1015 | + self.assertEquals(error.message, "found") |
1016 | + |
1017 | + def test_400_error(self): |
1018 | + failure = self.get_failure(400, Exception) |
1019 | + error = self.assertRaises(EC2Error, client.ec2_error_wrapper, failure) |
1020 | + self.assertNotEquals(failure.type, type(error)) |
1021 | + self.assertTrue(isinstance(error, EC2Error)) |
1022 | + self.assertEquals(error.get_error_codes(), "Error.Code") |
1023 | + self.assertEquals(error.get_error_messages(), "Message for Error.Code") |
1024 | + |
1025 | + def test_404_error(self): |
1026 | + failure = self.get_failure(404, Exception) |
1027 | + error = self.assertRaises(EC2Error, client.ec2_error_wrapper, failure) |
1028 | + self.assertNotEquals(failure.type, type(error)) |
1029 | + self.assertTrue(isinstance(error, EC2Error)) |
1030 | + self.assertEquals(error.get_error_codes(), "Error.Code") |
1031 | + self.assertEquals(error.get_error_messages(), "Message for Error.Code") |
1032 | + |
1033 | + def test_500_error(self): |
1034 | + failure = self.get_failure(500, Exception, "A server error occurred") |
1035 | + error = self.assertRaises(Exception, client.ec2_error_wrapper, failure) |
1036 | + self.assertFalse(isinstance(error, EC2Error)) |
1037 | + self.assertTrue(isinstance(error, Exception)) |
1038 | + self.assertEquals(error.message, "A server error occurred") |
1039 | + |
1040 | + def test_timeout_error(self): |
1041 | + failure = self.get_failure(type=Exception, message="timeout") |
1042 | + error = self.assertRaises(Exception, client.ec2_error_wrapper, failure) |
1043 | + self.assertFalse(isinstance(error, EC2Error)) |
1044 | + self.assertTrue(isinstance(error, Exception)) |
1045 | + self.assertEquals(error.message, "timeout") |
1046 | + |
1047 | + |
1048 | +class QueryTestCase(TXAWSTestCase): |
1049 | + |
1050 | + def setUp(self): |
1051 | + TXAWSTestCase.setUp(self) |
1052 | + self.creds = AWSCredentials("foo", "bar") |
1053 | + self.endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US) |
1054 | + |
1055 | + def test_init_minimum(self): |
1056 | + query = client.Query("DescribeInstances", self.creds, self.endpoint) |
1057 | + self.assertTrue("Timestamp" in query.params) |
1058 | + del query.params["Timestamp"] |
1059 | + self.assertEqual( |
1060 | + {"AWSAccessKeyId": "foo", |
1061 | + "Action": "DescribeInstances", |
1062 | + "SignatureMethod": "HmacSHA1", |
1063 | + "SignatureVersion": "2", |
1064 | + "Version": "2008-12-01"}, |
1065 | + query.params) |
1066 | + |
1067 | + def test_init_requires_action(self): |
1068 | + self.assertRaises(TypeError, client.Query) |
1069 | + |
1070 | + def test_init_requires_creds(self): |
1071 | + self.assertRaises(TypeError, client.Query, None) |
1072 | + |
1073 | + def test_init_other_args_are_params(self): |
1074 | + query = client.Query("DescribeInstances", self.creds, self.endpoint, |
1075 | + {"InstanceId.0": "12345"}, |
1076 | + time_tuple=(2007,11,12,13,14,15,0,0,0)) |
1077 | + self.assertEqual( |
1078 | + {"AWSAccessKeyId": "foo", |
1079 | + "Action": "DescribeInstances", |
1080 | + "InstanceId.0": "12345", |
1081 | + "SignatureMethod": "HmacSHA1", |
1082 | + "SignatureVersion": "2", |
1083 | + "Timestamp": "2007-11-12T13:14:15Z", |
1084 | + "Version": "2008-12-01"}, |
1085 | + query.params) |
1086 | + |
1087 | + def test_sorted_params(self): |
1088 | + query = client.Query("DescribeInstances", self.creds, self.endpoint, |
1089 | + {"fun": "games"}, |
1090 | + time_tuple=(2007,11,12,13,14,15,0,0,0)) |
1091 | + self.assertEqual([ |
1092 | + ("AWSAccessKeyId", "foo"), |
1093 | + ("Action", "DescribeInstances"), |
1094 | + ("SignatureMethod", "HmacSHA1"), |
1095 | + ("SignatureVersion", "2"), |
1096 | + ("Timestamp", "2007-11-12T13:14:15Z"), |
1097 | + ("Version", "2008-12-01"), |
1098 | + ("fun", "games"), |
1099 | + ], query.sorted_params()) |
1100 | + |
1101 | + def test_encode_unreserved(self): |
1102 | + all_unreserved = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
1103 | + "abcdefghijklmnopqrstuvwxyz0123456789-_.~") |
1104 | + query = client.Query("DescribeInstances", self.creds, self.endpoint) |
1105 | + self.assertEqual(all_unreserved, query.encode(all_unreserved)) |
1106 | + |
1107 | + def test_encode_space(self): |
1108 | + """This may be just "url encode", but the AWS manual isn't clear.""" |
1109 | + query = client.Query("DescribeInstances", self.creds, self.endpoint) |
1110 | + self.assertEqual("a%20space", query.encode("a space")) |
1111 | + |
1112 | + def test_canonical_query(self): |
1113 | + query = client.Query("DescribeInstances", self.creds, self.endpoint, |
1114 | + {"fu n": "g/ames", "argwithnovalue":"", |
1115 | + "InstanceId.1": "i-1234"}, |
1116 | + time_tuple=(2007,11,12,13,14,15,0,0,0)) |
1117 | + expected_query = ("AWSAccessKeyId=foo&Action=DescribeInstances" |
1118 | + "&InstanceId.1=i-1234" |
1119 | + "&SignatureMethod=HmacSHA1&SignatureVersion=2&" |
1120 | + "Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01&" |
1121 | + "argwithnovalue=&fu%20n=g%2Fames") |
1122 | + self.assertEqual(expected_query, query.canonical_query_params()) |
1123 | + |
1124 | + def test_signing_text(self): |
1125 | + query = client.Query("DescribeInstances", self.creds, self.endpoint, |
1126 | + time_tuple=(2007,11,12,13,14,15,0,0,0)) |
1127 | + signing_text = ("GET\n%s\n/\n" % self.endpoint.host + |
1128 | + "AWSAccessKeyId=foo&Action=DescribeInstances&" |
1129 | + "SignatureMethod=HmacSHA1&SignatureVersion=2&" |
1130 | + "Timestamp=2007-11-12T13%3A14%3A15Z&Version=2008-12-01") |
1131 | + self.assertEqual(signing_text, query.signing_text()) |
1132 | + |
1133 | + def test_sign(self): |
1134 | + query = client.Query("DescribeInstances", self.creds, self.endpoint, |
1135 | + time_tuple=(2007,11,12,13,14,15,0,0,0)) |
1136 | + query.sign() |
1137 | + self.assertEqual("JuCpwFA2H4OVF3Ql/lAQs+V6iMc=", |
1138 | + query.params["Signature"]) |
1139 | + |
1140 | + def test_submit_400(self): |
1141 | + """A 4xx response status from EC2 should raise a txAWS EC2Error.""" |
1142 | + status = 400 |
1143 | + self.addCleanup(setattr, client.Query, "get_page", |
1144 | + client.Query.get_page) |
1145 | + fake_page_getter = FakePageGetter( |
1146 | + status, payload.sample_ec2_error_message) |
1147 | + client.Query.get_page = fake_page_getter.get_page_with_exception |
1148 | + |
1149 | + def check_error(error): |
1150 | + self.assertTrue(isinstance(error, EC2Error)) |
1151 | + self.assertEquals(error.get_error_codes(), "Error.Code") |
1152 | + self.assertEquals( |
1153 | + error.get_error_messages(), |
1154 | + "Message for Error.Code") |
1155 | + self.assertEquals(error.status, status) |
1156 | + self.assertEquals(error.response, payload.sample_ec2_error_message) |
1157 | + |
1158 | + query = client.Query( |
1159 | + 'BadQuery', self.creds, self.endpoint, |
1160 | + time_tuple=(2009,8,15,13,14,15,0,0,0)) |
1161 | + |
1162 | + failure = query.submit() |
1163 | + d = self.assertFailure(failure, EC2Error) |
1164 | + d.addCallback(check_error) |
1165 | + return d |
1166 | + |
1167 | + def test_submit_500(self): |
1168 | + """ |
1169 | + A 5xx response status from EC2 should raise the original Twisted |
1170 | + exception. |
1171 | + """ |
1172 | + status = 500 |
1173 | + self.addCleanup(setattr, client.Query, "get_page", |
1174 | + client.Query.get_page) |
1175 | + fake_page_getter = FakePageGetter( |
1176 | + status, payload.sample_ec2_error_message) |
1177 | + client.Query.get_page = fake_page_getter.get_page_with_exception |
1178 | + |
1179 | + def check_error(error): |
1180 | + self.assertFalse(isinstance(error, EC2Error)) |
1181 | + self.assertEquals(error.status, status) |
1182 | + self.assertEquals(str(error), "500 There's been an error") |
1183 | + |
1184 | + query = client.Query( |
1185 | + 'BadQuery', self.creds, self.endpoint, |
1186 | + time_tuple=(2009,8,15,13,14,15,0,0,0)) |
1187 | + |
1188 | + failure = query.submit() |
1189 | + d = self.assertFailure(failure, Error) |
1190 | + d.addCallback(check_error) |
1191 | + return d |
1192 | + |
1193 | + |
1194 | +class QueryPageGetterTestCase(TXAWSTestCase): |
1195 | + |
1196 | + def setUp(self): |
1197 | + TXAWSTestCase.setUp(self) |
1198 | + self.creds = AWSCredentials("foo", "bar") |
1199 | + self.endpoint = AWSServiceEndpoint(uri=EC2_ENDPOINT_US) |
1200 | + self.twisted_client_test_setup() |
1201 | + self.cleanupServerConnections = 0 |
1202 | + |
1203 | + def tearDown(self): |
1204 | + """Copied from twisted.web.test.test_webclient.""" |
1205 | + # If the test indicated it might leave some server-side connections |
1206 | + # around, clean them up. |
1207 | + connections = self.wrapper.protocols.keys() |
1208 | + # If there are fewer server-side connections than requested, |
1209 | + # that's okay. Some might have noticed that the client closed |
1210 | + # the connection and cleaned up after themselves. |
1211 | + for n in range(min(len(connections), self.cleanupServerConnections)): |
1212 | + proto = connections.pop() |
1213 | + #msg("Closing %r" % (proto,)) |
1214 | + proto.transport.loseConnection() |
1215 | + if connections: |
1216 | + #msg("Some left-over connections; this test is probably buggy.") |
1217 | + pass |
1218 | + return self.port.stopListening() |
1219 | + |
1220 | + def _listen(self, site): |
1221 | + return reactor.listenTCP(0, site, interface="127.0.0.1") |
1222 | + |
1223 | + def twisted_client_test_setup(self): |
1224 | + name = self.mktemp() |
1225 | + os.mkdir(name) |
1226 | + FilePath(name).child("file").setContent("0123456789") |
1227 | + resource = static.File(name) |
1228 | + resource.putChild("redirect", util.Redirect("/file")) |
1229 | + self.site = server.Site(resource, timeout=None) |
1230 | + self.wrapper = WrappingFactory(self.site) |
1231 | + self.port = self._listen(self.wrapper) |
1232 | + self.portno = self.port.getHost().port |
1233 | + |
1234 | + def get_url(self, path): |
1235 | + return "http://127.0.0.1:%d/%s" % (self.portno, path) |
1236 | + |
1237 | + def test_get_page(self): |
1238 | + """Copied from twisted.web.test.test_webclient.""" |
1239 | + query = client.Query( |
1240 | + 'DummyQuery', self.creds, self.endpoint, |
1241 | + time_tuple=(2009,8,17,13,14,15,0,0,0)) |
1242 | + deferred = query.get_page(self.get_url("file")) |
1243 | + deferred.addCallback(self.assertEquals, "0123456789") |
1244 | + return deferred |
1245 | |
1246 | === modified file 'txaws/exception.py' |
1247 | --- txaws/exception.py 2009-09-10 00:32:37 +0000 |
1248 | +++ txaws/exception.py 2009-09-25 05:33:10 +0000 |
1249 | @@ -3,6 +3,7 @@ |
1250 | |
1251 | from twisted.web.error import Error |
1252 | |
1253 | + |
1254 | class AWSError(Error): |
1255 | """ |
1256 | A base class for txAWS errors. |
1257 | |
1258 | === modified file 'txaws/testing/payload.py' |
1259 | --- txaws/testing/payload.py 2009-09-15 22:19:14 +0000 |
1260 | +++ txaws/testing/payload.py 2009-09-25 05:33:10 +0000 |
1261 | @@ -202,6 +202,48 @@ |
1262 | """ % (aws_api,) |
1263 | |
1264 | |
1265 | +sample_create_security_group = """\ |
1266 | +<CreateSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/%s/"> |
1267 | + <return>true</return> |
1268 | +</CreateSecurityGroupResponse> |
1269 | +""" % (aws_api,) |
1270 | + |
1271 | + |
1272 | +sample_delete_security_group = """\ |
1273 | +<DeleteSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/%s/"> |
1274 | + <return>true</return> |
1275 | +</DeleteSecurityGroupResponse> |
1276 | +""" % (aws_api,) |
1277 | + |
1278 | + |
1279 | +sample_delete_security_group_failure = """\ |
1280 | +<?xml version="1.0"?> |
1281 | +<Response> |
1282 | + <Errors> |
1283 | + <Error> |
1284 | + <Code>InvalidGroup.InUse</Code> |
1285 | + <Message>Group groupID1:GroupReferredTo is used by groups: groupID2:UsingGroup</Message> |
1286 | + </Error> |
1287 | + </Errors> |
1288 | + <RequestID>9a6df05f-9c27-47aa-81d8-6619689210cc</RequestID> |
1289 | +</Response> |
1290 | +""" |
1291 | + |
1292 | + |
1293 | +sample_authorize_security_group = """\ |
1294 | +<AuthorizeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/%s/"> |
1295 | + <return>true</return> |
1296 | +</AuthorizeSecurityGroupIngressResponse> |
1297 | +""" % (aws_api,) |
1298 | + |
1299 | + |
1300 | +sample_revoke_security_group = """\ |
1301 | +<RevokeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/%s/"> |
1302 | + <return>true</return> |
1303 | +</RevokeSecurityGroupIngressResponse> |
1304 | +""" % (aws_api,) |
1305 | + |
1306 | + |
1307 | sample_describe_volumes_result = """\ |
1308 | <?xml version="1.0"?> |
1309 | <DescribeVolumesResponse xmlns="http://ec2.amazonaws.com/doc/%s/"> |
This one's ready for review. This branch finishes the security group support for txAWS.
Note that I've added 4 convenience functions, two each for revoking and authorizing groups. This is to provide a slightly more intuitive API than what is offered by AWS EC2. The AWS EC2 methods have also been implemented, so nothing's missing. These are just more clear to use, since they split the EC2 methods into distinct areas of functionality (both the authorize and revoke AWS EC2 methods serve dual purpose: they modify a group with another group that has IP permissions or they modify a group with IP permissions).