Merge lp:~jml/launchpad/various-ec2-fixes into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Merged at revision: 12721
Proposed branch: lp:~jml/launchpad/various-ec2-fixes
Merge into: lp:launchpad
Diff against target: 278 lines (+102/-24)
5 files modified
lib/devscripts/autoland.py (+19/-5)
lib/devscripts/ec2test/builtins.py (+26/-8)
lib/devscripts/ec2test/instance.py (+7/-2)
lib/devscripts/ec2test/tests/test_remote.py (+6/-2)
lib/devscripts/tests/test_autoland.py (+44/-7)
To merge this branch: bzr merge lp:~jml/launchpad/various-ec2-fixes
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+55763@code.launchpad.net

Commit message

[r=jcsackett][bug=439348,493774,540016,607434,638468,732029] Many usability improvements to 'ec2 test' and 'ec2 land'.

Description of the change

A whole bunch of fixes for ec2 test. I can't really add much here that's not in the bug reports. The ec2test system isn't super well-tested in general, and I haven't tried very hard to fix that in this branch. I have verified that ec2 update-image and ec2 land still work, that we trust the merge proposal reviewer and that we exclude merge proposals that are approved. I have not verified the EINTR handling (it's tricky) or the bug status fix.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

This looks like a good grabbag of fixes.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/devscripts/autoland.py'
2--- lib/devscripts/autoland.py 2011-02-01 00:43:23 +0000
3+++ lib/devscripts/autoland.py 2011-03-31 16:08:32 +0000
4@@ -67,16 +67,19 @@
5 """Get the merge proposal from the branch."""
6
7 lp_branch = self.get_lp_branch(branch)
8- proposals = lp_branch.landing_targets
9+ proposals = [
10+ mp for mp in lp_branch.landing_targets
11+ if mp.queue_status in ('Needs review', 'Approved')]
12 if len(proposals) == 0:
13 raise BzrCommandError(
14- "The public branch has no source merge proposals. "
15+ "The public branch has no open source merge proposals. "
16 "You must have a merge proposal before attempting to "
17 "land the branch.")
18 elif len(proposals) > 1:
19 raise BzrCommandError(
20- "The public branch has multiple source merge proposals. "
21- "You must provide the URL to the one you wish to use.")
22+ "The public branch has multiple open source merge "
23+ "proposals. You must provide the URL to the one you wish "
24+ "to use.")
25 return MergeProposal(proposals[0])
26
27
28@@ -139,6 +142,8 @@
29 continue
30 reviewers = reviews.setdefault(vote.review_type, [])
31 reviewers.append(vote.reviewer)
32+ if self.is_approved and not reviews:
33+ reviews[None] = [self._mp.reviewer]
34 return reviews
35
36 def get_bugs(self):
37@@ -249,7 +254,16 @@
38 """
39 if not bugs:
40 return ''
41- return '[bug=%s]' % ','.join(str(bug.id) for bug in bugs)
42+ bug_ids = []
43+ for bug in bugs:
44+ for task in bug.bug_tasks:
45+ if (task.bug_target_name == 'launchpad'
46+ and task.status not in ['Fix Committed', 'Fix Released']):
47+ bug_ids.append(str(bug.id))
48+ break
49+ if not bug_ids:
50+ return ''
51+ return '[bug=%s]' % ','.join(bug_ids)
52
53
54 def get_reviewer_handle(reviewer):
55
56=== modified file 'lib/devscripts/ec2test/builtins.py'
57--- lib/devscripts/ec2test/builtins.py 2011-02-24 13:47:13 +0000
58+++ lib/devscripts/ec2test/builtins.py 2011-03-31 16:08:32 +0000
59@@ -10,7 +10,6 @@
60 import os
61 import pdb
62 import socket
63-import subprocess
64
65 from bzrlib.bzrdir import BzrDir
66 from bzrlib.commands import Command
67@@ -314,8 +313,7 @@
68 "supported")
69
70 session_name = EC2SessionName.make(EC2TestRunner.name)
71- instance = EC2Instance.make(
72- session_name, instance_type, machine)
73+ instance = EC2Instance.make(session_name, instance_type, machine)
74
75 runner = EC2TestRunner(
76 test_branch, email=email, file=file,
77@@ -460,15 +458,33 @@
78 print commit_message
79 return
80
81+ emails = mp.get_stakeholder_emails()
82+
83+ target_branch_name = mp.target_branch.split('/')[-1]
84+ branches = [('launchpad', target_branch_name)]
85+
86 landing_command = self._get_landing_command(
87 mp.source_branch, mp.target_branch, commit_message,
88- mp.get_stakeholder_emails(), attached)
89+ emails, attached)
90+
91 if dry_run:
92 print landing_command
93- else:
94- # XXX: JonathanLange 2009-09-24 bug=439348: Call EC2 APIs
95- # directly, rather than spawning a subprocess.
96- return subprocess.call(landing_command)
97+ return
98+
99+ session_name = EC2SessionName.make(EC2TestRunner.name)
100+ instance = EC2Instance.make(
101+ session_name, instance_type, machine)
102+
103+ runner = EC2TestRunner(
104+ mp.source_branch, email=emails,
105+ headless=(not attached),
106+ branches=branches, pqm_message=commit_message,
107+ instance=instance,
108+ launchpad_login=instance._launchpad_login,
109+ test_options=DEFAULT_TEST_OPTIONS,
110+ timeout=480)
111+
112+ instance.set_up_and_run(postmortem, attached, runner.run_tests)
113
114
115 class cmd_demo(EC2Command):
116@@ -616,6 +632,8 @@
117 """
118 # Do NOT accept environment variables via ssh connections.
119 user_connection = instance.connect()
120+ user_connection.perform('sudo apt-get -qqy update')
121+ user_connection.perform('sudo apt-get -qqy upgrade')
122 user_connection.perform(
123 'sudo sed -i "s/^AcceptEnv/#AcceptEnv/" /etc/ssh/sshd_config')
124 user_connection.perform(
125
126=== modified file 'lib/devscripts/ec2test/instance.py'
127--- lib/devscripts/ec2test/instance.py 2010-11-23 17:26:02 +0000
128+++ lib/devscripts/ec2test/instance.py 2011-03-31 16:08:32 +0000
129@@ -1,4 +1,4 @@
130-# Copyright 2009 Canonical Ltd. This software is licensed under the
131+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
132 # GNU Affero General Public License version 3 (see the file LICENSE).
133
134 """Code to represent a single machine instance in EC2."""
135@@ -9,6 +9,7 @@
136 ]
137
138 import code
139+import errno
140 import glob
141 import os
142 import select
143@@ -622,7 +623,11 @@
144 session.exec_command(cmd)
145 session.shutdown_write()
146 while 1:
147- select.select([session], [], [], 0.5)
148+ try:
149+ select.select([session], [], [], 0.5)
150+ except (IOError, select.error), e:
151+ if e.errno == errno.EINTR:
152+ continue
153 if session.recv_ready():
154 data = session.recv(4096)
155 if data:
156
157=== modified file 'lib/devscripts/ec2test/tests/test_remote.py'
158--- lib/devscripts/ec2test/tests/test_remote.py 2011-02-28 00:54:12 +0000
159+++ lib/devscripts/ec2test/tests/test_remote.py 2011-03-31 16:08:32 +0000
160@@ -540,7 +540,9 @@
161 [body, attachment] = email.get_payload()
162 self.assertIsInstance(body, MIMEText)
163 self.assertEqual('inline', body['Content-Disposition'])
164- self.assertEqual('text/plain; charset="utf-8"', body['Content-Type'])
165+ self.assertIn(
166+ body['Content-Type'],
167+ ['text/plain; charset="utf-8"', 'text/plain; charset="utf8"'])
168 self.assertEqual("foo", body.get_payload(decode=True))
169
170 def test_report_email_attachment(self):
171@@ -820,7 +822,9 @@
172 [body, attachment] = email.get_payload()
173 self.assertIsInstance(body, MIMEText)
174 self.assertEqual('inline', body['Content-Disposition'])
175- self.assertEqual('text/plain; charset="utf-8"', body['Content-Type'])
176+ self.assertIn(
177+ body['Content-Type'],
178+ ['text/plain; charset="utf-8"', 'text/plain; charset="utf8"'])
179 self.assertEqual(
180 logger.get_summary_contents(), body.get_payload(decode=True))
181 self.assertIsInstance(attachment, MIMEApplication)
182
183=== modified file 'lib/devscripts/tests/test_autoland.py'
184--- lib/devscripts/tests/test_autoland.py 2011-02-01 00:43:23 +0000
185+++ lib/devscripts/tests/test_autoland.py 2011-03-31 16:08:32 +0000
186@@ -19,14 +19,24 @@
187 MergeProposal)
188
189
190+class FakeBugTask:
191+
192+ def __init__(self, target_name, status):
193+ self.bug_target_name = target_name
194+ self.status = status
195+
196+
197 class FakeBug:
198 """Fake launchpadlib Bug object.
199
200 Only used for the purposes of testing.
201 """
202
203- def __init__(self, id):
204+ def __init__(self, id, bug_tasks=None):
205 self.id = id
206+ if bug_tasks is None:
207+ bug_tasks = [FakeBugTask('launchpad', 'Triaged')]
208+ self.bug_tasks = bug_tasks
209
210
211 class FakePerson:
212@@ -113,7 +123,7 @@
213 self._test_commit_message_match(incr=True, no_qa=False, testfix=False)
214
215
216-class TestBugsClaused(unittest.TestCase):
217+class TestBugsClause(unittest.TestCase):
218 """Tests for `get_bugs_clause`."""
219
220 def test_no_bugs(self):
221@@ -134,6 +144,36 @@
222 bugs_clause = get_bugs_clause([bug1, bug2])
223 self.assertEqual('[bug=20,45]', bugs_clause)
224
225+ def test_fixed_bugs_are_excluded(self):
226+ # If a bug is fixed then it is excluded from the bugs clause.
227+ bug1 = FakeBug(20)
228+ bug2 = FakeBug(45, bug_tasks=[
229+ FakeBugTask('fake-project', 'Fix Released')])
230+ bug3 = FakeBug(67, bug_tasks=[
231+ FakeBugTask('fake-project', 'Fix Committed')])
232+ bugs_clause = get_bugs_clause([bug1, bug2, bug3])
233+ self.assertEqual('[bug=20]', bugs_clause)
234+
235+ def test_bugs_open_on_launchpad_are_included(self):
236+ # If a bug has been fixed on one target but not in launchpad, then it
237+ # is included in the bugs clause, because it's relevant to launchpad
238+ # QA.
239+ bug = FakeBug(20, bug_tasks=[
240+ FakeBugTask('fake-project', 'Fix Released'),
241+ FakeBugTask('launchpad', 'Triaged')])
242+ bugs_clause = get_bugs_clause([bug])
243+ self.assertEqual('[bug=20]', bugs_clause)
244+
245+ def test_bugs_fixed_on_launchpad_but_open_in_others_are_excluded(self):
246+ # If a bug has been fixed in Launchpad but not fixed on a different
247+ # target, then it is excluded from the bugs clause, since we don't
248+ # want to QA it.
249+ bug = FakeBug(20, bug_tasks=[
250+ FakeBugTask('fake-project', 'Triaged'),
251+ FakeBugTask('launchpad', 'Fix Released')])
252+ bugs_clause = get_bugs_clause([bug])
253+ self.assertEqual('', bugs_clause)
254+
255
256 class TestGetCommitMessage(unittest.TestCase):
257
258@@ -229,7 +269,7 @@
259
260 self.assertEqual(
261 "[r=foo][bug=20][no-qa][incr] Foobaring the sbrubble.",
262- self.mp.build_commit_message("Foobaring the sbrubble.",
263+ self.mp.build_commit_message("Foobaring the sbrubble.",
264 testfix, no_qa, incr))
265
266 def test_commit_with_rollback(self):
267@@ -334,10 +374,7 @@
268
269 def test_rollback_and_noqa_and_incr_given(self):
270 bugs = None
271- no_qa = True
272- incr = True
273- self.assertEqual('[rollback=123]',
274- get_qa_clause(bugs, rollback=123))
275+ self.assertEqual('[rollback=123]', get_qa_clause(bugs, rollback=123))
276
277
278 class TestGetReviewerHandle(unittest.TestCase):