Merge lp:~robru/cupstream2distro/prevent-publishing-failures into lp:cupstream2distro

Proposed by Robert Bruce Park
Status: Merged
Approved by: Robert Bruce Park
Approved revision: 1109
Merged at revision: 1108
Proposed branch: lp:~robru/cupstream2distro/prevent-publishing-failures
Merge into: lp:cupstream2distro
Diff against target: 247 lines (+74/-16)
8 files modified
citrain/build.py (+1/-0)
citrain/publisher.py (+6/-7)
citrain/recipes/base.py (+13/-2)
cupstream2distro/errors.py (+5/-0)
cupstream2distro/silomanager.py (+6/-0)
tests/unit/test_recipe_base.py (+20/-1)
tests/unit/test_script_publisher.py (+14/-6)
tests/unit/test_silomanager.py (+9/-0)
To merge this branch: bzr merge lp:~robru/cupstream2distro/prevent-publishing-failures
Reviewer Review Type Date Requested Status
Robert Bruce Park (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+272018@code.launchpad.net

Commit message

Stop publication if silo has bad status.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1108
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/798/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/798/rebuild

review: Approve (continuous-integration)
1109. By Robert Bruce Park

Preserve status since overwriting failed status is self-defeating.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1109
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/799/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/799/rebuild

review: Approve (continuous-integration)
Revision history for this message
Robert Bruce Park (robru) wrote :

Looking good in staging.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'citrain/build.py'
2--- citrain/build.py 2015-09-14 06:52:53 +0000
3+++ citrain/build.py 2015-09-22 18:51:59 +0000
4@@ -166,6 +166,7 @@
5 phases += ['watch', 'diff']
6 try:
7 buildmanager.do(*phases)
8+ silo_state.status = 'Packages built.'
9 except CITrainError as err:
10 logging.error(str(err))
11 silo_state.status = 'Build failed: ' + str(err)
12
13=== modified file 'citrain/publisher.py'
14--- citrain/publisher.py 2015-09-11 00:25:12 +0000
15+++ citrain/publisher.py 2015-09-22 18:51:59 +0000
16@@ -48,7 +48,7 @@
17 from cupstream2distro.archive import Archive
18 from cupstream2distro.branchhandling import Branch
19 from cupstream2distro.packagelist import PackageList
20-from cupstream2distro.errors import CITrainError, PublishError
21+from cupstream2distro.errors import CITrainError, PublishError, NoStatusError
22 from cupstream2distro.silomanager import SiloState
23 from cupstream2distro.launchpadmanager import lp
24 from cupstream2distro.project import DotProject
25@@ -206,14 +206,12 @@
26
27 def main():
28 """Conduct a beautiful orchestra of publication."""
29- silo_state = SiloState(env.SILONAME, primary=True)
30- mergemanager = MergeManager(silo_state)
31 try:
32+ silo_state = SiloState(env.SILONAME, primary=True)
33 dest = silo_state.dest
34 ppa = silo_state.ppa
35- silo_state.status = 'Attempting publication.'
36- silo_state.save_config()
37- mergemanager.do('validate', 'diff', 'checkupload', 'ackaging')
38+ MergeManager(silo_state).do(
39+ 'checkstatus', 'validate', 'diff', 'checkupload', 'ackaging')
40 get_distro_and_series(silo_state.series)
41 silo_state.status = 'Publishing.'
42 silo_state.save_config()
43@@ -226,7 +224,8 @@
44 silo_state.mark_others_dirty()
45 except CITrainError as err:
46 logging.error(str(err))
47- silo_state.status = "Can't publish: " + str(err)
48+ if type(err) is not NoStatusError:
49+ silo_state.status = "Can't publish: " + str(err)
50 return 1
51 finally:
52 silo_state.save_config()
53
54=== modified file 'citrain/recipes/base.py'
55--- citrain/recipes/base.py 2015-09-22 00:58:14 +0000
56+++ citrain/recipes/base.py 2015-09-22 18:51:59 +0000
57@@ -41,6 +41,7 @@
58 BuildError,
59 PublishError,
60 MigrationError,
61+ NoStatusError,
62 )
63 from cupstream2distro.settings import (
64 DEPWAIT_TIMEOUT,
65@@ -88,6 +89,7 @@
66 TWINS.update({v: k for k, v in TWINS.items()})
67
68
69+DO_NOT_PUBLISH = {'failed', 'error', 'dirty'}
70 BUILD_FAILURES = (
71 'Build for superseded Source',
72 'Cancelled build',
73@@ -295,10 +297,13 @@
74 """Check that no builds failed.
75
76 :param silo_state: The SiloState instance representing the silo.
77- :raises BuildError: If any builds in the PPA fail.
78 """
79 silo_state.set_built_checked()
80- silo_state.status = 'Packages built.'
81+
82+ @staticmethod
83+ def pre_diff_phase(silo_state):
84+ """Announce that we are generating diffs since that's a bit slow."""
85+ silo_state.status = 'Generating diffs.'
86 silo_state.save_config()
87
88 def diff_phase(self):
89@@ -314,6 +319,12 @@
90 for new in glob(SILO_DIR(dsc)):
91 self.generate_package_diffs(old, new)
92
93+ @staticmethod
94+ def post_checkstatus_phase(silo_state):
95+ """Prevent publication if silo in bad state."""
96+ if DO_NOT_PUBLISH & silo_state.tokenize():
97+ raise NoStatusError('Silo has bad status.')
98+
99 def checkupload_phase(self):
100 """Confirm that the silo publisher has upload rights."""
101 sourcepub = self.main_archive.getPublishedSources(
102
103=== modified file 'cupstream2distro/errors.py'
104--- cupstream2distro/errors.py 2015-06-23 20:07:17 +0000
105+++ cupstream2distro/errors.py 2015-09-22 18:51:59 +0000
106@@ -65,3 +65,8 @@
107 class RevertError(CITrainError):
108 """Exception raised when there's a problem with reverting."""
109 pass
110+
111+
112+class NoStatusError(PublishError):
113+ """Special Exception that when raised won't set status."""
114+ pass
115
116=== modified file 'cupstream2distro/silomanager.py'
117--- cupstream2distro/silomanager.py 2015-09-21 23:19:20 +0000
118+++ cupstream2distro/silomanager.py 2015-09-22 18:51:59 +0000
119@@ -61,6 +61,7 @@
120
121
122 TOKEN_SCRUB = re.compile('token=[a-z0-9-]+', flags=re.IGNORECASE)
123+WORD = re.compile('\W+')
124
125
126 PPA_DESCRIPTION = """\
127@@ -318,6 +319,11 @@
128 raise PrepError(
129 'Sources and syncs unsupported for dual landings.')
130
131+ def tokenize(self):
132+ """Return a set of words in status & signoff fields."""
133+ return set(
134+ WORD.split('{} {}'.format(self.status, self.qa_signoff).lower()))
135+
136 def summarize(self):
137 """Return a handy summary."""
138 joburl = '{}job/{}-{{}}/build'.format(
139
140=== modified file 'tests/unit/test_recipe_base.py'
141--- tests/unit/test_recipe_base.py 2015-09-22 06:53:49 +0000
142+++ tests/unit/test_recipe_base.py 2015-09-22 18:51:59 +0000
143@@ -369,7 +369,12 @@
144 silo_state = Mock()
145 BuildBase.post_watch_phase(silo_state)
146 silo_state.set_built_checked.assert_called_once_with()
147- self.assertEqual(silo_state.status, 'Packages built.')
148+
149+ def test_buildbase_pre_diff_phase(self):
150+ """Announce that diffing is beginning."""
151+ silo_state = Mock()
152+ BuildBase.pre_diff_phase(silo_state)
153+ self.assertEqual(silo_state.status, 'Generating diffs.')
154 silo_state.save_config.assert_called_once_with()
155
156 @patch('citrain.recipes.base.glob')
157@@ -430,6 +435,20 @@
158 status='Published')
159 build.generate_package_diffs.assert_called_once_with(None, 'new_dsc')
160
161+ def test_buildbase_post_checkstatus_phase(self):
162+ """Silently allow publication when no problems found."""
163+ silo_state = Mock()
164+ silo_state.tokenize.return_value = {'everything', 'is', 'awesome'}
165+ BuildBase.post_checkstatus_phase(silo_state)
166+
167+ def test_buildbase_post_checkstatus_phase_fail(self):
168+ """Ensure that publish is halted if silo in bad state."""
169+ silo_state = Mock()
170+ for problem in ('failed', 'dirty', 'error'):
171+ silo_state.tokenize.return_value = {'everything', 'is', problem}
172+ with self.assertRaisesRegexp(PublishError, 'Silo has bad status'):
173+ BuildBase.post_checkstatus_phase(silo_state)
174+
175 @patch('citrain.recipes.base.lp')
176 def test_buildbase_checkupload_phase(self, lp_mock):
177 """Ensure that we call checkupload correctly."""
178
179=== modified file 'tests/unit/test_script_publisher.py'
180--- tests/unit/test_script_publisher.py 2015-09-11 00:25:12 +0000
181+++ tests/unit/test_script_publisher.py 2015-09-22 18:51:59 +0000
182@@ -23,7 +23,7 @@
183 from tests.unit import CITrainScriptTestCase
184
185 from cupstream2distro.utils import env
186-from cupstream2distro.errors import PublishError, BranchError
187+from cupstream2distro.errors import PublishError, BranchError, NoStatusError
188
189
190 N = '\n'
191@@ -432,7 +432,7 @@
192 self.assertEqual(self.script.main(), 0)
193 self.script.MergeManager.assert_called_once_with(state)
194 self.script.MergeManager.return_value.do.assert_called_once_with(
195- 'validate', 'diff', 'checkupload', 'ackaging')
196+ 'checkstatus', 'validate', 'diff', 'checkupload', 'ackaging')
197 self.assertEqual(state.status, 'Publishing.')
198 self.script.check_versions_at_destination.assert_called_once_with(
199 dest, state.all_projects)
200@@ -442,8 +442,7 @@
201 self.script.initiate_publication.assert_called_once_with(
202 dest, ppa, state.set_package_version_list)
203 state.set_published.assert_called_once_with()
204- self.assertEqual(state.save_config.mock_calls, [
205- call(), call(), call()])
206+ self.assertEqual(state.save_config.mock_calls, [call(), call()])
207 state.mark_others_dirty.assert_called_once_with()
208
209 def test_main_failure(self):
210@@ -461,6 +460,15 @@
211 self.script.push_branches.assert_called_once_with(state.mps)
212 self.assertEqual(self.script.initiate_publication.call_count, 0)
213 self.script.logging.error.assert_called_once_with('Whoops')
214- self.assertEqual(state.save_config.mock_calls, [
215- call(), call(), call()])
216+ self.assertEqual(state.save_config.mock_calls, [call(), call()])
217 self.assertEqual(state.mark_others_dirty.mock_calls, [])
218+
219+ def test_main_failure_nostatus(self):
220+ """publisher.main() shouldn't set status inappropriately."""
221+ self.mock_main()
222+ state = self.script.SiloState()
223+ state.status = 'Preserved'
224+ self.script.MergeManager.return_value.do = Mock(
225+ side_effect=NoStatusError('Whoops'))
226+ self.assertEqual(self.script.main(), 1)
227+ self.assertEqual(state.status, 'Preserved')
228
229=== modified file 'tests/unit/test_silomanager.py'
230--- tests/unit/test_silomanager.py 2015-09-21 22:32:57 +0000
231+++ tests/unit/test_silomanager.py 2015-09-22 18:51:59 +0000
232@@ -698,6 +698,15 @@
233 ])
234 branch_mock.get_package_name_from_branch.assert_called_with('lp:foo')
235
236+ def test_silomanager_tokenize(self):
237+ """Convert silo statuses into tokens sensibly."""
238+ self.state._bileto.update(
239+ status='Hey, you: (job 29) FAILED: broken//#$%^&*',
240+ qa_signoff='Really bad')
241+ self.assertEqual(self.state.tokenize(), {
242+ 'hey', 'you', 'job', '29', 'failed', 'broken', 'really', 'bad',
243+ })
244+
245 def test_signal_handler(self):
246 """Our signal handler is installed globally."""
247 self.assertEqual(

Subscribers

People subscribed via source and target branches