Merge lp:~oubiwann/txaws/415486-reservation-object into lp:~txawsteam/txaws/trunk

Proposed by Duncan McGreggor
Status: Merged
Merge reported by: Duncan McGreggor
Merged at revision: not available
Proposed branch: lp:~oubiwann/txaws/415486-reservation-object
Merge into: lp:~txawsteam/txaws/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~oubiwann/txaws/415486-reservation-object
Reviewer Review Type Date Requested Status
Jamu Kakar (community) Approve
Thomas Herve (community) Approve
Review via email: mp+10338@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Duncan McGreggor (oubiwann) wrote :

This is ready for review.

9. By Duncan McGreggor

Updated the gtk client to wrap the new API change.

Revision history for this message
Thomas Herve (therve) wrote :

Nice job, +1!

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

As far as I could tell only one reservation is returned, not N?

If I'm wrong this is fine, if I'm correct, then it would be nicer to
return the reservation rather than an iterable of reservations.

-Rob

Revision history for this message
Jamu Kakar (jkakar) wrote :

[1]

+ def __init__(self, reservation_id, owner_id, groups=[], instances=[]):

The default empty list values for groups and instances will be
inadvertently shared by all Reservation instances that don't provide
explicit values during instantiation. I recommend you use None and
set the value in the body of the constructor as necessary.

[2]

+ def test_parse_reservation(self):
+ ec2 = client.EC2Client(creds='foo')
+ results = ec2._parse_reservation(sample_describe_instances_result)
+ self.check_parsed_reservations(results)

It would be nice to avoid accessing the private _parse_reservation
method by passing a query_factory to EC2Client that returns a
FakeQuery. FakeQuery.submit could return a succeeded deferred and
you could call ec2.describe_instances to get a Deferred with the
result you want to make assertions about.

+1 considering #1.

review: Approve
Revision history for this message
Thomas Herve (therve) wrote :

> As far as I could tell only one reservation is returned, not N?

No, you get one reservation per call to CreateInstances. If you're calling CreateInstances several times, you're getting several reservations.
The reservations are what is holding the information about the security groups used, for example.

10. By Duncan McGreggor

Merged from trunk and resolved conflicts.

Revision history for this message
Robert Collins (lifeless) wrote :

On Tue, 2009-08-18 at 20:34 +0000, Thomas Herve wrote:
> > As far as I could tell only one reservation is returned, not N?
>
> No, you get one reservation per call to CreateInstances. If you're calling CreateInstances several times, you're getting several reservations.
> The reservations are what is holding the information about the security groups used, for example.

Thats 'yes' then - a call to CreateInstances returns one reservation.

Having our thunk to create instances return a _list of reservations_
makes the API harder to work with.

-Rob

11. By Duncan McGreggor

Changed default values in signature so that groups and instances wouldn't be
shared across all Reservation objects.

Revision history for this message
Thomas Herve (therve) wrote :

> On Tue, 2009-08-18 at 20:34 +0000, Thomas Herve wrote:
> > > As far as I could tell only one reservation is returned, not N?
> >
> > No, you get one reservation per call to CreateInstances. If you're calling
> CreateInstances several times, you're getting several reservations.
> > The reservations are what is holding the information about the security
> groups used, for example.
>
> Thats 'yes' then - a call to CreateInstances returns one reservation.
>
> Having our thunk to create instances return a _list of reservations_
> makes the API harder to work with.

We're not *creating* instances, we're describing them. A call to CreateInstances may return a list of instances, but it's not what we're doing right now.

12. By Duncan McGreggor

Finished the fix from the last commit (jkakar 1).

Revision history for this message
Duncan McGreggor (oubiwann) wrote :

> [1]
>
> + def __init__(self, reservation_id, owner_id, groups=[], instances=[]):
>
> The default empty list values for groups and instances will be
> inadvertently shared by all Reservation instances that don't provide
> explicit values during instantiation. I recommend you use None and
> set the value in the body of the constructor as necessary.

Nice catch! Fixed.

> [2]
>
> + def test_parse_reservation(self):
> + ec2 = client.EC2Client(creds='foo')
> + results = ec2._parse_reservation(sample_describe_instances_result)
> + self.check_parsed_reservations(results)
>
> It would be nice to avoid accessing the private _parse_reservation
> method by passing a query_factory to EC2Client that returns a
> FakeQuery. FakeQuery.submit could return a succeeded deferred and
> you could call ec2.describe_instances to get a Deferred with the
> result you want to make assertions about.

This is interesting. I'm not completely sold on it yet... but I'll create a ticket for this.

Revision history for this message
Robert Collins (lifeless) wrote :

We just chatted on IRC.

It would be nice to keep returning a list of instances. We can expose
the reservation as a reservation attribute on each instance.

-Rob

Revision history for this message
Duncan McGreggor (oubiwann) wrote :

> > On Tue, 2009-08-18 at 20:34 +0000, Thomas Herve wrote:
> > > > As far as I could tell only one reservation is returned, not N?
> > >
> > > No, you get one reservation per call to CreateInstances. If you're calling
> > CreateInstances several times, you're getting several reservations.
> > > The reservations are what is holding the information about the security
> > groups used, for example.
> >
> > Thats 'yes' then - a call to CreateInstances returns one reservation.
> >
> > Having our thunk to create instances return a _list of reservations_
> > makes the API harder to work with.
>
> We're not *creating* instances, we're describing them. A call to
> CreateInstances may return a list of instances, but it's not what we're doing
> right now.

There was some confusion about this which got sorted out on IRC. Robert then proposed an excellent idea that we all liked. Here's the IRC chat:

[18:14] <therve`> lifeless: the API we're calling is DescribeInstances
[18:14] <therve`> this one returns a list of reservations
[18:14] <lifeless> ok
[18:14] <lifeless> its still a little ugly to work with but I can see the rationale
[18:14] <therve`> yeah, the problem is that the reservation actually holds important information
[18:15] <therve`> which we can't get anywhere else
[18:15] <lifeless> we could fold that down to the instance
[18:15] <lifeless> if the info is only a field or two
[18:15] <obiwann> lifeless: yeah, that was one of my thoughts too... still not sure how I feel about it
[18:15] <lifeless> that is
[18:15] <lifeless> rather than reservations[N].instances[N]
[18:15] <lifeless> have instances[N].reservation
[18:16] <therve`> this is a valid idea
[18:16] <obiwann> yeah, I kinda like that
[18:16] <obiwann> instances[N].reservation is better than what I was thinking

13. By Duncan McGreggor

Made the reservation for a given instance an attribute on that instance.

14. By Duncan McGreggor

Renamed the functions associated with parsing the AWS reponse, due to the
change in functionality.

15. By Duncan McGreggor

Rolled back the now unnecessary change to the gtk code.

Revision history for this message
Duncan McGreggor (oubiwann) wrote :

> We just chatted on IRC.
>
> It would be nice to keep returning a list of instances. We can expose
> the reservation as a reservation attribute on each instance.
>
> -Rob

Hey Rob, when you get a chance, I've updated the code (and pushed it) according to my understanding of your suggestions. Let me know if it's what you had in mind.

Thanks!

Revision history for this message
Robert Collins (lifeless) wrote :

Looks good to me - thanks.

-Rob

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-04-28 11:52:57 +0000
3+++ txaws/ec2/client.py 2009-08-18 19:08:31 +0000
4@@ -14,22 +14,37 @@
5 from txaws.util import iso8601time, XML
6
7
8+class Reservation(object):
9+ """An Amazon EC2 Reservation.
10+
11+ @attrib reservation_id: Unique ID of the reservation.
12+ @attrib owner_id: AWS Access Key ID of the user who owns the reservation.
13+ @attrib groups: A list of security groups.
14+ @attrib instances: A list of C{Instance}s.
15+ """
16+ def __init__(self, reservation_id, owner_id, groups=[], instances=[]):
17+ self.reservation_id = reservation_id
18+ self.owner_id = owner_id
19+ self.groups = groups
20+ self.instances = instances
21+
22+
23 class Instance(object):
24 """An Amazon EC2 Instance.
25
26- :attrib instanceId: The instance ID of this instance.
27- :attrib instanceState: The state of this instance.
28+ @attrib instance_id: The instance ID of this instance.
29+ @attrib instance_state: The state of this instance.
30 """
31
32- def __init__(self, instanceId, instanceState):
33- self.instanceId = instanceId
34- self.instanceState = instanceState
35+ def __init__(self, instance_id, instance_state):
36+ self.instance_id = instance_id
37+ self.instance_state = instance_state
38
39
40 class EC2Client(object):
41 """A client for EC2."""
42
43- NS = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
44+ name_space = '{http://ec2.amazonaws.com/doc/2008-12-01/}'
45
46 def __init__(self, creds=None, query_factory=None):
47 """Create an EC2Client.
48@@ -50,19 +65,38 @@
49 """Describe current instances."""
50 q = self.query_factory('DescribeInstances', self.creds)
51 d = q.submit()
52- return d.addCallback(self._parse_Reservation)
53+ return d.addCallback(self._parse_reservation)
54
55- def _parse_Reservation(self, xml_bytes):
56+ def _parse_reservation(self, xml_bytes):
57 root = XML(xml_bytes)
58- result = []
59+ results = []
60 # May be a more elegant way to do this:
61- for reservation in root.find(self.NS + 'reservationSet'):
62- for instance in reservation.find(self.NS + 'instancesSet'):
63- instanceId = instance.findtext(self.NS + 'instanceId')
64- instanceState = instance.find(
65- self.NS + 'instanceState').findtext(self.NS + 'name')
66- result.append(Instance(instanceId, instanceState))
67- return result
68+ for reservation_data in root.find(self.name_space + 'reservationSet'):
69+ # Get the security group information.
70+ groups = []
71+ for group_data in reservation_data.find(
72+ self.name_space + 'groupSet'):
73+ group_id = group_data.findtext(self.name_space + 'groupId')
74+ groups.append(group_id)
75+ # Get the list of instances.
76+ instances = []
77+ for instance_data in reservation_data.find(
78+ self.name_space + 'instancesSet'):
79+ instance_id = instance_data.findtext(
80+ self.name_space + 'instanceId')
81+ instance_state = instance_data.find(
82+ self.name_space + 'instanceState').findtext(
83+ self.name_space + 'name')
84+ instances.append(Instance(instance_id, instance_state))
85+ # Create a reservation object with the parsed data.
86+ reservation = Reservation(
87+ reservation_id=reservation_data.findtext(
88+ self.name_space + 'reservationId'),
89+ owner_id=reservation_data.findtext(
90+ self.name_space + 'ownerId'),
91+ groups=groups, instances=instances)
92+ results.append(reservation)
93+ return results
94
95 def terminate_instances(self, *instance_ids):
96 """Terminate some instances.
97@@ -82,12 +116,14 @@
98 root = XML(xml_bytes)
99 result = []
100 # May be a more elegant way to do this:
101- for instance in root.find(self.NS + 'instancesSet'):
102- instanceId = instance.findtext(self.NS + 'instanceId')
103+ for instance in root.find(self.name_space + 'instancesSet'):
104+ instanceId = instance.findtext(self.name_space + 'instanceId')
105 previousState = instance.find(
106- self.NS + 'previousState').findtext(self.NS + 'name')
107+ self.name_space + 'previousState').findtext(
108+ self.name_space + 'name')
109 shutdownState = instance.find(
110- self.NS + 'shutdownState').findtext(self.NS + 'name')
111+ self.name_space + 'shutdownState').findtext(
112+ self.name_space + 'name')
113 result.append((instanceId, previousState, shutdownState))
114 return result
115
116
117=== modified file 'txaws/ec2/tests/test_client.py'
118--- txaws/ec2/tests/test_client.py 2009-04-28 11:52:57 +0000
119+++ txaws/ec2/tests/test_client.py 2009-08-18 19:08:31 +0000
120@@ -9,6 +9,7 @@
121 from txaws.ec2 import client
122 from txaws.tests import TXAWSTestCase
123
124+
125 sample_describe_instances_result = """<?xml version="1.0"?>
126 <DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2008-12-01/">
127 <requestId>52b4c730-f29f-498d-94c1-91efb75994cc</requestId>
128@@ -49,6 +50,7 @@
129 </DescribeInstancesResponse>
130 """
131
132+
133 sample_terminate_instances_result = """<?xml version="1.0"?>
134 <TerminateInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2008-12-01/">
135 <instancesSet>
136@@ -79,6 +81,20 @@
137 """
138
139
140+class ReservationTestCase(TXAWSTestCase):
141+
142+ def test_reservation_creation(self):
143+ reservation = client.Reservation(
144+ "id1", "owner", groups=["one", "two"],
145+ instances=[client.Instance("id2", "state")])
146+ self.assertEquals(reservation.reservation_id, "id1")
147+ self.assertEquals(reservation.owner_id, "owner")
148+ self.assertEquals(reservation.groups, ["one", "two"])
149+ instance = reservation.instances[0]
150+ self.assertEquals(instance.instance_id, "id2")
151+ self.assertEquals(instance.instance_state, "state")
152+
153+
154 class TestEC2Client(TXAWSTestCase):
155
156 def test_init_no_creds(self):
157@@ -95,6 +111,21 @@
158 ec2 = client.EC2Client(creds=creds)
159 self.assertEqual(creds, ec2.creds)
160
161+ def check_parsed_reservations(self, results):
162+ reservation = results[0]
163+ self.assertEquals(reservation.reservation_id, "r-cf24b1a6")
164+ self.assertEquals(reservation.owner_id, "123456789012")
165+ instance = reservation.instances[0]
166+ self.assertEquals(instance.instance_id, "i-abcdef01")
167+ self.assertEquals(instance.instance_state, "running")
168+ group = reservation.groups[0]
169+ self.assertEquals(group, "default")
170+
171+ def test_parse_reservation(self):
172+ ec2 = client.EC2Client(creds='foo')
173+ results = ec2._parse_reservation(sample_describe_instances_result)
174+ self.check_parsed_reservations(results)
175+
176 def test_describe_instances(self):
177 class StubQuery(object):
178 def __init__(stub, action, creds):
179@@ -104,11 +135,7 @@
180 return succeed(sample_describe_instances_result)
181 ec2 = client.EC2Client(creds='foo', query_factory=StubQuery)
182 d = ec2.describe_instances()
183- def check_instances(reservation):
184- self.assertEqual(1, len(reservation))
185- self.assertEqual('i-abcdef01', reservation[0].instanceId)
186- self.assertEqual('running', reservation[0].instanceState)
187- d.addCallback(check_instances)
188+ d.addCallback(self.check_parsed_reservations)
189 return d
190
191 def test_terminate_instances(self):

Subscribers

People subscribed via source and target branches