Merge lp:~allenap/launchpad/update-ec2-image into lp:launchpad

Proposed by Gavin Panella on 2010-11-22
Status: Merged
Approved by: Aaron Bentley on 2010-11-22
Approved revision: no longer in the source branch.
Merged at revision: 11961
Proposed branch: lp:~allenap/launchpad/update-ec2-image
Merge into: lp:launchpad
Diff against target: 221 lines (+80/-58)
2 files modified
lib/devscripts/ec2test/account.py (+45/-48)
lib/devscripts/ec2test/builtins.py (+35/-10)
To merge this branch: bzr merge lp:~allenap/launchpad/update-ec2-image
Reviewer Review Type Date Requested Status
Aaron Bentley (community) 2010-11-22 Approve on 2010-11-22
Review via email: mp+41485@code.launchpad.net

Commit Message

[r=abentley][ui=none][no-qa] New "images" command for bin/ec2 to display all current test images.

Description of the Change

Add a new command to display all the usable test images for bin/ec2:

{{{
$ bin/ec2 help images
Purpose: Display all available images.
Usage: ec2 images

Options:
  --usage Show usage message and options.
  -v, --verbose Display more information.
  -q, --quiet Only display errors and warnings.
  -h, --help Show help message.

Description:
  The first in the list is the default image.

$ bin/ec2 images
  Rev AMI Owner ID Owner
  499 ami-0288626b 366009196755 salgado
  120 ami-217f9448 038531743404 jelmer
  119 ami-2d8e6044 366009196755 salgado
  117 ami-a9749bc0 036590675370 jml
  116 ami-5d1df134 366009196755 salgado
  114 ami-35806c5c 889698597288 henninge
  113 ami-a909e4c0 200337130613 mwhudson
  112 ami-9212f0fb 200337130613 mwhudson
  111 ami-d228cabb 200337130613 mwhudson
  102 ami-748f6d1d 255383312499 gary
  100 ami-22a1424b 200337130613 mwhudson
}}}

It also adds me to VALID_AMI_OWNERS.

To post a comment you must log in.
Aaron Bentley (abentley) wrote :

VALID_AMI_OWNERS should come after EC2Account in the __all__.

It looks as if it would be simpler to use dict.setdefault rather than sorting, groupby, etc.

e.g. instead of

  search_results.append((revision, image)), make search_results a dict and
do
  search_results.setdefault(revision, []).append(image).

And then instead of
 # Sort and group by revision.
 get_revision = itemgetter(0)
 search_results.sort(key=get_revision, reverse=True)
 for revision, group in groupby(search_results, get_revision):
 yield revision, [image for (revision, image) in group]

do

 return sorted(search_results.items())

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/devscripts/ec2test/account.py'
2--- lib/devscripts/ec2test/account.py 2010-07-30 15:52:05 +0000
3+++ lib/devscripts/ec2test/account.py 2010-11-22 17:04:20 +0000
4@@ -6,30 +6,33 @@
5 __metaclass__ = type
6 __all__ = [
7 'EC2Account',
8+ 'VALID_AMI_OWNERS',
9 ]
10
11+from collections import defaultdict
12 import cStringIO
13+from datetime import datetime
14+from operator import itemgetter
15 import re
16 import sys
17 import urllib
18
19-from datetime import datetime
20-
21 from boto.exception import EC2ResponseError
22 from devscripts.ec2test.session import EC2SessionName
23-
24 import paramiko
25
26-VALID_AMI_OWNERS = (
27- '255383312499', # gary
28- '559320013529', # flacoste
29- '200337130613', # mwhudson
30- '889698597288', # henninge
31- '366009196755', # salgado
32- '036590675370', # jml
33- '038531743404', # jelmer
34+
35+VALID_AMI_OWNERS = {
36+ '255383312499': 'gary',
37+ '559320013529': 'flacoste',
38+ '200337130613': 'mwhudson',
39+ '889698597288': 'henninge',
40+ '366009196755': 'salgado',
41+ '036590675370': 'jml',
42+ '038531743404': 'jelmer',
43+ '444667466231': 'allenap',
44 # ...anyone else want in on the fun?
45- )
46+ }
47
48 AUTH_FAILURE_MESSAGE = """\
49 POSSIBLE CAUSES OF ERROR:
50@@ -160,6 +163,28 @@
51 self.delete_previous_security_groups()
52 self.delete_previous_key_pairs()
53
54+ def find_images(self):
55+ # We are trying to find an image that has a location that matches a
56+ # regex (see definition of _image_match, above). Part of that regex is
57+ # expected to be an integer with the semantics of a revision number.
58+ # The image location with the highest revision number is the one that
59+ # should be chosen. Because AWS does not guarantee that two images
60+ # cannot share a location string, we need to make sure that the search
61+ # result for this image is unique, or throw an error because the
62+ # choice of image is ambiguous.
63+ results = defaultdict(list)
64+
65+ # Find the images with the highest revision numbers and locations that
66+ # match the regex.
67+ images = self.conn.get_all_images(owners=tuple(VALID_AMI_OWNERS))
68+ for image in images:
69+ match = self._image_match(image.location)
70+ if match is not None:
71+ revision = int(match.group(1))
72+ results[revision].append(image)
73+
74+ return sorted(results.iteritems(), key=itemgetter(0), reverse=True)
75+
76 def acquire_image(self, machine_id):
77 """Get the image.
78
79@@ -181,48 +206,20 @@
80 # they can deal with it.
81 return self.conn.get_image(machine_id)
82
83- # We are trying to find an image that has a location that matches a
84- # regex (see definition of _image_match, above). Part of that regex is
85- # expected to be an integer with the semantics of a revision number.
86- # The image location with the highest revision number is the one that
87- # should be chosen. Because AWS does not guarantee that two images
88- # cannot share a location string, we need to make sure that the search
89- # result for this image is unique, or throw an error because the
90- # choice of image is ambiguous.
91- search_results = None
92-
93- # Find the images with the highest revision numbers and locations that
94- # match the regex.
95- for image in self.conn.get_all_images(owners=VALID_AMI_OWNERS):
96- match = self._image_match(image.location)
97- if match:
98- revision = int(match.group(1))
99- if (search_results is None
100- or search_results['revision'] < revision):
101- # Then we have our first, highest match.
102- search_results = {'revision': revision, 'images': [image]}
103- elif search_results['revision'] == revision:
104- # Another image that matches and is equally high.
105- search_results['images'].append(image)
106-
107- # No matching image.
108- if search_results is None:
109+ images_by_revision = self.find_images()
110+ if len(images_by_revision) == 0:
111 raise RuntimeError(
112 "You don't have access to a test-runner image.\n"
113 "Request access and try again.\n")
114
115- # More than one matching image.
116- if len(search_results['images']) > 1:
117+ revision, images = images_by_revision[0]
118+ if len(images) > 1:
119 raise ValueError(
120- ('more than one image of revision %(revision)d found: '
121- '%(images)r') % search_results)
122+ 'More than one image of revision %d found: %r' % (
123+ revision, images))
124
125- # We could put a minimum image version number check here.
126- image = search_results['images'][0]
127- self.log(
128- 'Using machine image version %d\n'
129- % (search_results['revision'],))
130- return image
131+ self.log('Using machine image version %d\n' % revision)
132+ return images[0]
133
134 def get_instance(self, instance_id):
135 """Look in all of our reservations for an instance with the given ID.
136
137=== modified file 'lib/devscripts/ec2test/builtins.py'
138--- lib/devscripts/ec2test/builtins.py 2010-11-05 16:54:24 +0000
139+++ lib/devscripts/ec2test/builtins.py 2010-11-22 17:04:20 +0000
140@@ -8,24 +8,30 @@
141
142 import os
143 import pdb
144+import socket
145 import subprocess
146
147 from bzrlib.bzrdir import BzrDir
148 from bzrlib.commands import Command
149 from bzrlib.errors import BzrCommandError
150 from bzrlib.help import help_commands
151-from bzrlib.option import ListOption, Option
152-
153-import socket
154-
155+from bzrlib.option import (
156+ ListOption,
157+ Option,
158+ )
159 from devscripts import get_launchpad_root
160-
161+from devscripts.ec2test.account import VALID_AMI_OWNERS
162 from devscripts.ec2test.credentials import EC2Credentials
163 from devscripts.ec2test.instance import (
164- AVAILABLE_INSTANCE_TYPES, DEFAULT_INSTANCE_TYPE, EC2Instance)
165+ AVAILABLE_INSTANCE_TYPES,
166+ DEFAULT_INSTANCE_TYPE,
167+ EC2Instance,
168+ )
169 from devscripts.ec2test.session import EC2SessionName
170-from devscripts.ec2test.testrunner import EC2TestRunner, TRUNK_BRANCH
171-
172+from devscripts.ec2test.testrunner import (
173+ EC2TestRunner,
174+ TRUNK_BRANCH,
175+ )
176
177 # Options accepted by more than one command.
178
179@@ -188,7 +194,6 @@
180 return branches, test_branch
181
182
183-
184 DEFAULT_TEST_OPTIONS = '--subunit -vvv'
185
186
187@@ -409,7 +414,8 @@
188 "Commit text not specified. Use --commit-text, or specify a "
189 "message on the merge proposal.")
190 if rollback and (no_qa or incremental):
191- print "--rollback option used. Ignoring --no-qa and --incremental."
192+ print (
193+ "--rollback option used. Ignoring --no-qa and --incremental.")
194 try:
195 commit_message = mp.build_commit_message(
196 commit_text, testfix, no_qa, incremental, rollback=rollback)
197@@ -613,6 +619,25 @@
198 instance.bundle(ami_name, credentials)
199
200
201+class cmd_images(EC2Command):
202+ """Display all available images.
203+
204+ The first in the list is the default image.
205+ """
206+
207+ def run(self):
208+ credentials = EC2Credentials.load_from_file()
209+ session_name = EC2SessionName.make(EC2TestRunner.name)
210+ account = credentials.connect(session_name)
211+ format = "%5s %-12s %-12s %s\n"
212+ self.outf.write(format % ("Rev", "AMI", "Owner ID", "Owner"))
213+ for revision, images in account.find_images():
214+ for image in images:
215+ self.outf.write(format % (
216+ revision, image.id, image.ownerId,
217+ VALID_AMI_OWNERS.get(image.ownerId, "unknown")))
218+
219+
220 class cmd_help(EC2Command):
221 """Show general help or help for a command."""
222