Merge lp:~therve/txaws/run-instances into lp:txaws
- run-instances
- Merge into trunk
Proposed by
Thomas Herve
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~therve/txaws/run-instances |
Merge into: | lp:txaws |
Diff against target: |
307 lines 4 files modified
txaws/ec2/client.py (+81/-29) txaws/ec2/tests/test_client.py (+51/-0) txaws/testing/ec2.py (+6/-0) txaws/testing/payload.py (+66/-0) |
To merge this branch: | bzr merge lp:~therve/txaws/run-instances |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Duncan McGreggor | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Thomas Herve (therve) wrote : | # |
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Duncan McGreggor (oubiwann) wrote : | # |
Looks great!
[1] I see you imported b64encode; is there are reason you didn't use .encode("base64")?
review:
Approve
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Thomas Herve (therve) wrote : | # |
> Looks great!
>
> [1] I see you imported b64encode; is there are reason you didn't use
> .encode("base64")?
It's not really equivalent, .encode("base64") has some quirks.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'txaws/ec2/client.py' |
2 | --- txaws/ec2/client.py 2009-10-12 17:02:33 +0000 |
3 | +++ txaws/ec2/client.py 2009-10-13 09:15:23 +0000 |
4 | @@ -7,6 +7,7 @@ |
5 | |
6 | from datetime import datetime |
7 | from urllib import quote |
8 | +from base64 import b64encode |
9 | |
10 | from twisted.internet import reactor, ssl |
11 | from twisted.web.client import HTTPClientFactory |
12 | @@ -63,6 +64,36 @@ |
13 | d = q.submit() |
14 | return d.addCallback(self._parse_describe_instances) |
15 | |
16 | + def _parse_instances_set(self, root, reservation): |
17 | + instances = [] |
18 | + for instance_data in root.find("instancesSet"): |
19 | + instance_id = instance_data.findtext("instanceId") |
20 | + instance_state = instance_data.find( |
21 | + "instanceState").findtext("name") |
22 | + instance_type = instance_data.findtext("instanceType") |
23 | + image_id = instance_data.findtext("imageId") |
24 | + private_dns_name = instance_data.findtext("privateDnsName") |
25 | + dns_name = instance_data.findtext("dnsName") |
26 | + key_name = instance_data.findtext("keyName") |
27 | + ami_launch_index = instance_data.findtext("amiLaunchIndex") |
28 | + launch_time = instance_data.findtext("launchTime") |
29 | + placement = instance_data.find("placement").findtext( |
30 | + "availabilityZone") |
31 | + products = [] |
32 | + product_codes = instance_data.find("productCodes") |
33 | + if product_codes: |
34 | + for product_data in instance_data.find("productCodes"): |
35 | + products.append(product_data.text) |
36 | + kernel_id = instance_data.findtext("kernelId") |
37 | + ramdisk_id = instance_data.findtext("ramdiskId") |
38 | + instance = model.Instance( |
39 | + instance_id, instance_state, instance_type, image_id, |
40 | + private_dns_name, dns_name, key_name, ami_launch_index, |
41 | + launch_time, placement, products, kernel_id, ramdisk_id, |
42 | + reservation=reservation) |
43 | + instances.append(instance) |
44 | + return instances |
45 | + |
46 | def _parse_describe_instances(self, xml_bytes): |
47 | """ |
48 | Parse the reservations XML payload that is returned from an AWS |
49 | @@ -96,36 +127,58 @@ |
50 | owner_id=reservation_data.findtext("ownerId"), |
51 | groups=groups) |
52 | # Get the list of instances. |
53 | - instances = [] |
54 | - for instance_data in reservation_data.find("instancesSet"): |
55 | - instance_id = instance_data.findtext("instanceId") |
56 | - instance_state = instance_data.find( |
57 | - "instanceState").findtext("name") |
58 | - instance_type = instance_data.findtext("instanceType") |
59 | - image_id = instance_data.findtext("imageId") |
60 | - private_dns_name = instance_data.findtext("privateDnsName") |
61 | - dns_name = instance_data.findtext("dnsName") |
62 | - key_name = instance_data.findtext("keyName") |
63 | - ami_launch_index = instance_data.findtext("amiLaunchIndex") |
64 | - launch_time = instance_data.findtext("launchTime") |
65 | - placement = instance_data.find("placement").findtext( |
66 | - "availabilityZone") |
67 | - products = [] |
68 | - product_codes = instance_data.find("productCodes") |
69 | - if product_codes: |
70 | - for product_data in instance_data.find("productCodes"): |
71 | - products.append(product_data.text) |
72 | - kernel_id = instance_data.findtext("kernelId") |
73 | - ramdisk_id = instance_data.findtext("ramdiskId") |
74 | - instance = model.Instance( |
75 | - instance_id, instance_state, instance_type, image_id, |
76 | - private_dns_name, dns_name, key_name, ami_launch_index, |
77 | - launch_time, placement, products, kernel_id, ramdisk_id, |
78 | - reservation=reservation) |
79 | - instances.append(instance) |
80 | + instances = self._parse_instances_set( |
81 | + reservation_data, reservation) |
82 | results.extend(instances) |
83 | return results |
84 | |
85 | + def run_instances(self, image_id, min_count, max_count, |
86 | + security_groups=None, key_name=None, instance_type=None, |
87 | + user_data=None, availability_zone=None, kernel_id=None, |
88 | + ramdisk_id=None): |
89 | + """Run new instances.""" |
90 | + params = {"ImageId": image_id, "MinCount": min_count, |
91 | + "MaxCount": max_count} |
92 | + if security_groups is not None: |
93 | + for i, name in enumerate(security_groups): |
94 | + params["SecurityGroup.%d" % (i+1)] = name |
95 | + if key_name is not None: |
96 | + params["KeyName"] = key_name |
97 | + if user_data is not None: |
98 | + params["UserData"] = b64encode(user_data) |
99 | + if instance_type is not None: |
100 | + params["InstanceType"] = instance_type |
101 | + if availability_zone is not None: |
102 | + params["Placement.AvailabilityZone"] = availability_zone |
103 | + if kernel_id is not None: |
104 | + params["KernelId"] = kernel_id |
105 | + if ramdisk_id is not None: |
106 | + params["RamdiskId"] = ramdisk_id |
107 | + q = self.query_factory( |
108 | + "RunInstances", self.creds, self.endpoint, params) |
109 | + d = q.submit() |
110 | + return d.addCallback(self._parse_run_instances) |
111 | + |
112 | + def _parse_run_instances(self, xml_bytes): |
113 | + """ |
114 | + Parse the reservations XML payload that is returned from an AWS |
115 | + RunInstances API call. |
116 | + """ |
117 | + root = XML(xml_bytes) |
118 | + # Get the security group information. |
119 | + groups = [] |
120 | + for group_data in root.find("groupSet"): |
121 | + group_id = group_data.findtext("groupId") |
122 | + groups.append(group_id) |
123 | + # Create a reservation object with the parsed data. |
124 | + reservation = model.Reservation( |
125 | + reservation_id=root.findtext("reservationId"), |
126 | + owner_id=root.findtext("ownerId"), |
127 | + groups=groups) |
128 | + # Get the list of instances. |
129 | + instances = self._parse_instances_set(root, reservation) |
130 | + return instances |
131 | + |
132 | def terminate_instances(self, *instance_ids): |
133 | """Terminate some instances. |
134 | |
135 | @@ -219,7 +272,6 @@ |
136 | @return: A C{Deferred} that will fire with a truth value for the |
137 | success of the operation. |
138 | """ |
139 | - group_names = None |
140 | parameters = {"GroupName": name, "GroupDescription": description} |
141 | query = self.query_factory("CreateSecurityGroup", self.creds, |
142 | self.endpoint, parameters) |
143 | @@ -690,7 +742,7 @@ |
144 | self.endpoint, zone_names) |
145 | d = query.submit() |
146 | return d.addCallback(self._parse_describe_availability_zones) |
147 | - |
148 | + |
149 | def _parse_describe_availability_zones(self, xml_bytes): |
150 | results = [] |
151 | root = XML(xml_bytes) |
152 | |
153 | === modified file 'txaws/ec2/tests/test_client.py' |
154 | --- txaws/ec2/tests/test_client.py 2009-10-12 17:02:33 +0000 |
155 | +++ txaws/ec2/tests/test_client.py 2009-10-13 09:15:23 +0000 |
156 | @@ -260,6 +260,57 @@ |
157 | d.addCallback(check_transition) |
158 | return d |
159 | |
160 | + def check_parsed_run_instances(self, results): |
161 | + instance = results[0] |
162 | + # check reservations |
163 | + reservation = instance.reservation |
164 | + self.assertEquals(reservation.reservation_id, "r-47a5402e") |
165 | + self.assertEquals(reservation.owner_id, "495219933132") |
166 | + # check groups |
167 | + group = reservation.groups[0] |
168 | + self.assertEquals(group, "default") |
169 | + # check instance |
170 | + self.assertEquals(instance.instance_id, "i-2ba64342") |
171 | + self.assertEquals(instance.instance_state, "pending") |
172 | + self.assertEquals(instance.instance_type, "m1.small") |
173 | + self.assertEquals(instance.placement, "us-east-1b") |
174 | + instance = results[1] |
175 | + self.assertEquals(instance.instance_id, "i-2bc64242") |
176 | + self.assertEquals(instance.instance_state, "pending") |
177 | + self.assertEquals(instance.instance_type, "m1.small") |
178 | + self.assertEquals(instance.placement, "us-east-1b") |
179 | + instance = results[2] |
180 | + self.assertEquals(instance.instance_id, "i-2be64332") |
181 | + self.assertEquals(instance.instance_state, "pending") |
182 | + self.assertEquals(instance.instance_type, "m1.small") |
183 | + self.assertEquals(instance.placement, "us-east-1b") |
184 | + |
185 | + def test_run_instances(self): |
186 | + |
187 | + class StubQuery(object): |
188 | + def __init__(stub, action, creds, endpoint, params): |
189 | + self.assertEqual(action, "RunInstances") |
190 | + self.assertEqual(creds.access_key, "foo") |
191 | + self.assertEqual(creds.secret_key, "bar") |
192 | + self.assertEquals( |
193 | + params, |
194 | + {"ImageId": "ami-1234", "MaxCount": 2, "MinCount": 1, |
195 | + "SecurityGroup.1": u"group1", "KeyName": u"default", |
196 | + "UserData": "Zm9v", "InstanceType": u"m1.small", |
197 | + "Placement.AvailabilityZone": u"us-east-1b", |
198 | + "KernelId": u"k-1234", "RamdiskId": u"r-1234"}) |
199 | + def submit(self): |
200 | + return succeed( |
201 | + payload.sample_run_instances_result) |
202 | + |
203 | + creds = AWSCredentials("foo", "bar") |
204 | + ec2 = client.EC2Client(creds, query_factory=StubQuery) |
205 | + d = ec2.run_instances("ami-1234", 1, 2, security_groups=[u"group1"], |
206 | + key_name=u"default", user_data=u"foo", instance_type=u"m1.small", |
207 | + availability_zone=u"us-east-1b", kernel_id=u"k-1234", |
208 | + ramdisk_id=u"r-1234") |
209 | + d.addCallback(self.check_parsed_run_instances) |
210 | + |
211 | |
212 | class EC2ClientSecurityGroupsTestCase(TXAWSTestCase): |
213 | |
214 | |
215 | === modified file 'txaws/testing/ec2.py' |
216 | --- txaws/testing/ec2.py 2009-10-13 07:14:42 +0000 |
217 | +++ txaws/testing/ec2.py 2009-10-13 09:15:23 +0000 |
218 | @@ -33,6 +33,12 @@ |
219 | def describe_instances(self, *instances): |
220 | return succeed(self.instances) |
221 | |
222 | + def run_instances(self, image_id, min_count, max_count, |
223 | + security_groups=None, key_name=None, instance_type=None, |
224 | + user_data=None, availability_zone=None, kernel_id=None, |
225 | + ramdisk_id=None): |
226 | + return succeed(self.instances) |
227 | + |
228 | def describe_keypairs(self): |
229 | return succeed(self.keypairs) |
230 | |
231 | |
232 | === modified file 'txaws/testing/payload.py' |
233 | --- txaws/testing/payload.py 2009-10-05 23:50:35 +0000 |
234 | +++ txaws/testing/payload.py 2009-10-13 09:15:23 +0000 |
235 | @@ -81,6 +81,72 @@ |
236 | """ % (aws_api,) |
237 | |
238 | |
239 | +sample_run_instances_result = """\ |
240 | +<?xml version="1.0"?> |
241 | +<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/%s/"> |
242 | + <reservationId>r-47a5402e</reservationId> |
243 | + <ownerId>495219933132</ownerId> |
244 | + <groupSet> |
245 | + <item> |
246 | + <groupId>default</groupId> |
247 | + </item> |
248 | + </groupSet> |
249 | + <instancesSet> |
250 | + <item> |
251 | + <instanceId>i-2ba64342</instanceId> |
252 | + <imageId>ami-60a54009</imageId> |
253 | + <instanceState> |
254 | + <code>0</code> |
255 | + <name>pending</name> |
256 | + </instanceState> |
257 | + <privateDnsName></privateDnsName> |
258 | + <dnsName></dnsName> |
259 | + <keyName>example-key-name</keyName> |
260 | + <amiLaunchIndex>0</amiLaunchIndex> |
261 | + <instanceType>m1.small</instanceType> |
262 | + <launchTime>2007-08-07T11:51:50.000Z</launchTime> |
263 | + <placement> |
264 | + <availabilityZone>us-east-1b</availabilityZone> |
265 | + </placement> |
266 | + </item> |
267 | + <item> |
268 | + <instanceId>i-2bc64242</instanceId> |
269 | + <imageId>ami-60a54009</imageId> |
270 | + <instanceState> |
271 | + <code>0</code> |
272 | + <name>pending</name> |
273 | + </instanceState> |
274 | + <privateDnsName></privateDnsName> |
275 | + <dnsName></dnsName> |
276 | + <keyName>example-key-name</keyName> |
277 | + <amiLaunchIndex>1</amiLaunchIndex> |
278 | + <instanceType>m1.small</instanceType> |
279 | + <launchTime>2007-08-07T11:51:50.000Z</launchTime> |
280 | + <placement> |
281 | + <availabilityZone>us-east-1b</availabilityZone> |
282 | + </placement> |
283 | + </item> |
284 | + <item> |
285 | + <instanceId>i-2be64332</instanceId> |
286 | + <imageId>ami-60a54009</imageId> |
287 | + <instanceState> |
288 | + <code>0</code> |
289 | + <name>pending</name> |
290 | + </instanceState> |
291 | + <privateDnsName></privateDnsName> |
292 | + <dnsName></dnsName> |
293 | + <keyName>example-key-name</keyName> |
294 | + <amiLaunchIndex>2</amiLaunchIndex> |
295 | + <instanceType>m1.small</instanceType> |
296 | + <launchTime>2007-08-07T11:51:50.000Z</launchTime> |
297 | + <placement> |
298 | + <availabilityZone>us-east-1b</availabilityZone> |
299 | + </placement> |
300 | + </item> |
301 | + </instancesSet> |
302 | +</RunInstancesResponse> |
303 | +""" % (aws_api,) |
304 | + |
305 | sample_terminate_instances_result = """\ |
306 | <?xml version="1.0"?> |
307 | <TerminateInstancesResponse xmlns="http://ec2.amazonaws.com/doc/%s/"> |
This add support for the RunInstances operation. Using the code from describe_instances, it's a relatively small change.