Merge lp:~mwhudson/launchpad/less-import-requestMirrors-bug-487357 into lp:launchpad
- less-import-requestMirrors-bug-487357
- Merge into devel
Proposed by
Michael Hudson-Doyle
Status: | Merged |
---|---|
Approved by: | Michael Hudson-Doyle |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~mwhudson/launchpad/less-import-requestMirrors-bug-487357 |
Merge into: | lp:launchpad |
Diff against target: |
613 lines (+171/-53) 13 files modified
lib/lp/code/browser/branch.py (+9/-1) lib/lp/code/enums.py (+9/-0) lib/lp/code/model/codeimport.py (+2/-1) lib/lp/code/model/codeimportjob.py (+6/-3) lib/lp/code/model/tests/test_codeimport.py (+16/-3) lib/lp/code/model/tests/test_codeimportjob.py (+4/-12) lib/lp/code/stories/codeimport/xx-codeimport-results.txt (+3/-0) lib/lp/code/templates/codeimport-macros.pt (+2/-4) lib/lp/codehosting/codeimport/tests/test_worker.py (+24/-2) lib/lp/codehosting/codeimport/tests/test_workermonitor.py (+35/-2) lib/lp/codehosting/codeimport/worker.py (+40/-10) lib/lp/codehosting/codeimport/workermonitor.py (+18/-13) scripts/code-import-worker.py (+3/-2) |
To merge this branch: | bzr merge lp:~mwhudson/launchpad/less-import-requestMirrors-bug-487357 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Hummer (community) | Approve | ||
Review via email: mp+18361@code.launchpad.net |
Commit message
Extend the contract between the code import 'worker' and 'monitor' scripts by specifying that an exit code of 2 means that the import was successful but didn't import any new revisions so there's no need to request a mirror of the import branch.
Description of the change
To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : | # |
Revision history for this message
Paul Hummer (rockstar) wrote : | # |
This all looks good. Thanks for doing this.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/browser/branch.py' |
2 | --- lib/lp/code/browser/branch.py 2010-01-29 17:15:31 +0000 |
3 | +++ lib/lp/code/browser/branch.py 2010-02-01 05:56:15 +0000 |
4 | @@ -81,7 +81,8 @@ |
5 | latest_proposals_for_each_branch) |
6 | from lp.code.enums import ( |
7 | BranchLifecycleStatus, BranchType, CodeImportJobState, |
8 | - CodeImportReviewStatus, RevisionControlSystems, UICreatableBranchType) |
9 | + CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems, |
10 | + UICreatableBranchType) |
11 | from lp.code.errors import InvalidBranchMergeProposal |
12 | from lp.code.interfaces.branch import ( |
13 | BranchCreationForbidden, BranchExists, IBranch, |
14 | @@ -509,6 +510,13 @@ |
15 | """Return the last 10 CodeImportResults.""" |
16 | return list(self.context.code_import.results[:10]) |
17 | |
18 | + def iconForCodeImportResultStatus(self, status): |
19 | + """The icon to represent the `CodeImportResultStatus` `status`.""" |
20 | + if status in CodeImportResultStatus.successes: |
21 | + return "/@@/yes" |
22 | + else: |
23 | + return "/@@/no" |
24 | + |
25 | @property |
26 | def is_svn_import(self): |
27 | """True if an imported branch is a SVN import.""" |
28 | |
29 | === modified file 'lib/lp/code/enums.py' |
30 | --- lib/lp/code/enums.py 2010-01-12 02:07:38 +0000 |
31 | +++ lib/lp/code/enums.py 2010-02-01 05:56:15 +0000 |
32 | @@ -799,6 +799,13 @@ |
33 | Import job completed successfully. |
34 | """) |
35 | |
36 | + SUCCESS_NOCHANGE = DBItem(110, """ |
37 | + Success with no changes |
38 | + |
39 | + Import job completed successfully, but there were no new revisions to |
40 | + import. |
41 | + """) |
42 | + |
43 | FAILURE = DBItem(200, """ |
44 | Failure |
45 | |
46 | @@ -857,6 +864,8 @@ |
47 | job, or the deletion of a CodeImport which had a running job. |
48 | """) |
49 | |
50 | + successes = [SUCCESS, SUCCESS_NOCHANGE] |
51 | + |
52 | |
53 | class CodeReviewVote(DBEnumeratedType): |
54 | """Code Review Votes |
55 | |
56 | === modified file 'lib/lp/code/model/codeimport.py' |
57 | --- lib/lp/code/model/codeimport.py 2010-01-13 02:51:30 +0000 |
58 | +++ lib/lp/code/model/codeimport.py 2010-02-01 05:56:15 +0000 |
59 | @@ -160,7 +160,8 @@ |
60 | "coalesce", |
61 | Select( |
62 | CodeImportResult.id, |
63 | - And(CodeImportResult.status == CodeImportResultStatus.SUCCESS, |
64 | + And(CodeImportResult.status.is_in( |
65 | + CodeImportResultStatus.successes), |
66 | CodeImportResult.code_import == self), |
67 | order_by=Desc(CodeImportResult.id), |
68 | limit=1), |
69 | |
70 | === modified file 'lib/lp/code/model/codeimportjob.py' |
71 | --- lib/lp/code/model/codeimportjob.py 2009-07-17 00:26:05 +0000 |
72 | +++ lib/lp/code/model/codeimportjob.py 2010-02-01 05:56:15 +0000 |
73 | @@ -287,11 +287,14 @@ |
74 | # Only start a new one if the import is still in the REVIEWED state. |
75 | if code_import.review_status == CodeImportReviewStatus.REVIEWED: |
76 | self.newJob(code_import) |
77 | - # If the status was successful, update the date_last_successful and |
78 | - # arrange for the branch to be mirrored. |
79 | - if status == CodeImportResultStatus.SUCCESS: |
80 | + # If the status was successful, update date_last_successful. |
81 | + if status in [CodeImportResultStatus.SUCCESS, |
82 | + CodeImportResultStatus.SUCCESS_NOCHANGE]: |
83 | naked_import = removeSecurityProxy(code_import) |
84 | naked_import.date_last_successful = result.date_created |
85 | + # If the status was successful and revisions were imported, arrange |
86 | + # for the branch to be mirrored. |
87 | + if status == CodeImportResultStatus.SUCCESS: |
88 | code_import.branch.requestMirror() |
89 | getUtility(ICodeImportEventSet).newFinish( |
90 | code_import, machine) |
91 | |
92 | === modified file 'lib/lp/code/model/tests/test_codeimport.py' |
93 | --- lib/lp/code/model/tests/test_codeimport.py 2010-01-15 23:55:45 +0000 |
94 | +++ lib/lp/code/model/tests/test_codeimport.py 2010-02-01 05:56:15 +0000 |
95 | @@ -394,11 +394,15 @@ |
96 | getUtility(ICodeImportJobWorkflow).finishJob( |
97 | running_job, CodeImportResultStatus.FAILURE, None) |
98 | |
99 | - def succeedImport(self, code_import): |
100 | + def succeedImport(self, code_import, |
101 | + status=CodeImportResultStatus.SUCCESS): |
102 | """Create if necessary a job for `code_import` and have it succeed.""" |
103 | + if status not in CodeImportResultStatus.successes: |
104 | + raise AssertionError( |
105 | + "succeedImport() should be called with a successful status!") |
106 | running_job = self.makeRunningJob(code_import) |
107 | getUtility(ICodeImportJobWorkflow).finishJob( |
108 | - running_job, CodeImportResultStatus.SUCCESS, None) |
109 | + running_job, status, None) |
110 | |
111 | def test_consecutive_failure_count_zero_initially(self): |
112 | # A new code import has a consecutive_failure_count of 0. |
113 | @@ -407,7 +411,7 @@ |
114 | |
115 | def test_consecutive_failure_count_succeed(self): |
116 | # A code import that has succeeded once has a |
117 | - # consecutive_failure_count of 1. |
118 | + # consecutive_failure_count of 0. |
119 | code_import = self.factory.makeCodeImport() |
120 | self.succeedImport(code_import) |
121 | self.assertEqual(0, code_import.consecutive_failure_count) |
122 | @@ -419,6 +423,15 @@ |
123 | self.failImport(code_import) |
124 | self.assertEqual(1, code_import.consecutive_failure_count) |
125 | |
126 | + def test_consecutive_failure_count_succeed_succeed_no_changes(self): |
127 | + # A code import that has succeeded then succeeded with no changes has |
128 | + # a consecutive_failure_count of 0. |
129 | + code_import = self.factory.makeCodeImport() |
130 | + self.succeedImport(code_import) |
131 | + self.succeedImport( |
132 | + code_import, CodeImportResultStatus.SUCCESS_NOCHANGE) |
133 | + self.assertEqual(0, code_import.consecutive_failure_count) |
134 | + |
135 | def test_consecutive_failure_count_fail_fail(self): |
136 | # A code import that has failed twice has a consecutive_failure_count |
137 | # of 2. |
138 | |
139 | === modified file 'lib/lp/code/model/tests/test_codeimportjob.py' |
140 | --- lib/lp/code/model/tests/test_codeimportjob.py 2009-07-17 00:26:05 +0000 |
141 | +++ lib/lp/code/model/tests/test_codeimportjob.py 2010-02-01 05:56:15 +0000 |
142 | @@ -32,8 +32,7 @@ |
143 | from lp.code.interfaces.codeimportevent import ICodeImportEventSet |
144 | from lp.code.interfaces.codeimportjob import ( |
145 | ICodeImportJobSet, ICodeImportJobWorkflow) |
146 | -from lp.code.interfaces.codeimportresult import ( |
147 | - ICodeImportResult, ICodeImportResultSet) |
148 | +from lp.code.interfaces.codeimportresult import ICodeImportResult |
149 | from lp.registry.interfaces.person import IPersonSet |
150 | from lp.testing import ANONYMOUS, login, logout, TestCaseWithFactory |
151 | from canonical.launchpad.testing.codeimporthelpers import ( |
152 | @@ -274,7 +273,7 @@ |
153 | self.assertReclaimableJobs([stale_job]) |
154 | machine = self.factory.makeCodeImportMachine(set_online=True) |
155 | login(ANONYMOUS) |
156 | - job = getUtility(ICodeImportJobSet).getJobForMachine( |
157 | + getUtility(ICodeImportJobSet).getJobForMachine( |
158 | machine.hostname) |
159 | login_for_code_imports() |
160 | # Now there are no reclaimable jobs. |
161 | @@ -456,7 +455,7 @@ |
162 | # of the job that was deleted when the CodeImport review status |
163 | # changed from REVIEWED. That is the date_job_started of the most |
164 | # recent CodeImportResult plus the effective update interval. |
165 | - job = getUtility(ICodeImportJobWorkflow).newJob(code_import) |
166 | + getUtility(ICodeImportJobWorkflow).newJob(code_import) |
167 | self.assertSqlAttributeEqualsDate( |
168 | code_import.import_job, 'date_due', |
169 | recent_result.date_job_started + interval) |
170 | @@ -690,7 +689,6 @@ |
171 | def test_wrongJobState(self): |
172 | # Calling updateHeartbeat with a job whose state is not RUNNING is an |
173 | # error. |
174 | - machine = self.factory.makeCodeImportMachine() |
175 | code_import = self.factory.makeCodeImport() |
176 | job = self.factory.makeCodeImportJob(code_import) |
177 | self.assertFailure( |
178 | @@ -740,7 +738,6 @@ |
179 | |
180 | def test_wrongJobState(self): |
181 | # Calling finishJob with a job whose state is not RUNNING is an error. |
182 | - machine = self.factory.makeCodeImportMachine() |
183 | code_import = self.factory.makeCodeImport() |
184 | job = self.factory.makeCodeImportJob(code_import) |
185 | self.switchDbUser() |
186 | @@ -767,7 +764,6 @@ |
187 | # finishJob() creates a new CodeImportJob for the given CodeImport, |
188 | # scheduled appropriately far in the future. |
189 | running_job = self.makeRunningJob() |
190 | - running_job_date_due = running_job.date_due |
191 | code_import = running_job.code_import |
192 | self.switchDbUser() |
193 | getUtility(ICodeImportJobWorkflow).finishJob( |
194 | @@ -784,7 +780,6 @@ |
195 | # finishJob() creates a new CodeImportJob for the given CodeImport, |
196 | # unless the CodeImport has been suspended or marked invalid. |
197 | running_job = self.makeRunningJob() |
198 | - running_job_date_due = running_job.date_due |
199 | code_import = running_job.code_import |
200 | ddaa = getUtility(IPersonSet).getByEmail( |
201 | 'david.allouche@canonical.com') |
202 | @@ -798,9 +793,7 @@ |
203 | def test_createsResultObject(self): |
204 | # finishJob() creates a CodeImportResult object for the given import. |
205 | running_job = self.makeRunningJob() |
206 | - running_job_date_due = running_job.date_due |
207 | code_import = running_job.code_import |
208 | - result_set = getUtility(ICodeImportResultSet) |
209 | # Before calling finishJob() there are no CodeImportResults for the |
210 | # given import... |
211 | self.assertEqual(len(list(code_import.results)), 0) |
212 | @@ -903,7 +896,6 @@ |
213 | |
214 | self.switchDbUser() |
215 | log_data = 'several\nlines\nof\nlog data' |
216 | - log_excerpt = log_data.splitlines()[-1] |
217 | log_alias_id = getUtility(ILibrarianClient).addFile( |
218 | 'import_log.txt', len(log_data), |
219 | StringIO.StringIO(log_data), 'text/plain') |
220 | @@ -939,7 +931,7 @@ |
221 | code_import = job.code_import |
222 | self.assertTrue(code_import.date_last_successful is None) |
223 | getUtility(ICodeImportJobWorkflow).finishJob(job, status, None) |
224 | - if status == CodeImportResultStatus.SUCCESS: |
225 | + if status in CodeImportResultStatus.successes: |
226 | self.assertTrue(code_import.date_last_successful is not None) |
227 | else: |
228 | self.assertTrue(code_import.date_last_successful is None) |
229 | |
230 | === modified file 'lib/lp/code/stories/codeimport/xx-codeimport-results.txt' |
231 | --- lib/lp/code/stories/codeimport/xx-codeimport-results.txt 2009-03-24 12:43:49 +0000 |
232 | +++ lib/lp/code/stories/codeimport/xx-codeimport-results.txt 2010-02-01 05:56:15 +0000 |
233 | @@ -23,6 +23,8 @@ |
234 | >>> browser.open(branch_url) |
235 | >>> import_results = find_tag_by_id(browser.contents, 'import-results') |
236 | >>> print extract_text(import_results).replace('—', '--') |
237 | + Import started on 2007-12-10 on odin and finished on 2007-12-10 |
238 | + taking ten hours -- see the log |
239 | Import started on 2007-12-09 on odin and finished on 2007-12-09 |
240 | taking nine hours -- see the log |
241 | Import started on 2007-12-08 on odin and finished on 2007-12-08 |
242 | @@ -58,4 +60,5 @@ |
243 | <img src="/@@/no" title="Source Checkout Failed" /> |
244 | <img src="/@@/no" title="Internal Failure" /> |
245 | <img src="/@@/no" title="Failure" /> |
246 | + <img src="/@@/yes" title="Success with no changes" /> |
247 | <img src="/@@/yes" title="Success" /> |
248 | |
249 | === modified file 'lib/lp/code/templates/codeimport-macros.pt' |
250 | --- lib/lp/code/templates/codeimport-macros.pt 2009-07-17 17:59:07 +0000 |
251 | +++ lib/lp/code/templates/codeimport-macros.pt 2010-02-01 05:56:15 +0000 |
252 | @@ -8,10 +8,8 @@ |
253 | Expects a parameter called 'result'. |
254 | </tal:comment> |
255 | |
256 | - <img src="/@@/yes" tal:condition="result/status/enumvalue:SUCCESS" |
257 | - title="Success"/> |
258 | - <img src="/@@/no" tal:condition="not: result/status/enumvalue:SUCCESS" |
259 | - tal:attributes="title result/status/title"/> |
260 | + <img tal:attributes="src python:view.iconForCodeImportResultStatus(result.status); |
261 | + title result/status/title"/> |
262 | Import started |
263 | <tal:when replace="result/date_job_started/fmt:displaydate"> |
264 | 4 hours ago |
265 | |
266 | === modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py' |
267 | --- lib/lp/codehosting/codeimport/tests/test_worker.py 2010-01-13 02:09:34 +0000 |
268 | +++ lib/lp/codehosting/codeimport/tests/test_worker.py 2010-02-01 05:56:15 +0000 |
269 | @@ -30,9 +30,9 @@ |
270 | |
271 | from lp.codehosting import load_optional_plugin |
272 | from lp.codehosting.codeimport.worker import ( |
273 | - BazaarBranchStore, BzrSvnImportWorker, CSCVSImportWorker, |
274 | + BazaarBranchStore, BzrSvnImportWorker, CodeImportWorkerExitCode, |
275 | ForeignTreeStore, GitImportWorker, HgImportWorker, ImportDataStore, |
276 | - ImportWorker, get_default_bazaar_branch_store) |
277 | + ImportWorker, CSCVSImportWorker, get_default_bazaar_branch_store) |
278 | from lp.codehosting.codeimport.tests.servers import ( |
279 | CVSServer, GitServer, MercurialServer, SubversionServer) |
280 | from lp.codehosting.tests.helpers import ( |
281 | @@ -710,6 +710,28 @@ |
282 | self.assertEqual( |
283 | self.foreign_commit_count, len(branch.revision_history())) |
284 | |
285 | + def test_script_exit_codes(self): |
286 | + # After a successful import that imports revisions, the worker exits |
287 | + # with a code of CodeImportWorkerExitCode.SUCCESS. After a successful |
288 | + # import that does not import revisions, the worker exits with a code |
289 | + # of CodeImportWorkerExitCode.SUCCESS_NOCHANGE. |
290 | + source_details = self.makeSourceDetails( |
291 | + 'trunk', [('README', 'Original contents')]) |
292 | + |
293 | + clean_up_default_stores_for_import(source_details) |
294 | + |
295 | + script_path = os.path.join( |
296 | + config.root, 'scripts', 'code-import-worker.py') |
297 | + output = tempfile.TemporaryFile() |
298 | + retcode = subprocess.call( |
299 | + [script_path] + source_details.asArguments(), |
300 | + stderr=output, stdout=output) |
301 | + self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS) |
302 | + retcode = subprocess.call( |
303 | + [script_path] + source_details.asArguments(), |
304 | + stderr=output, stdout=output) |
305 | + self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE) |
306 | + |
307 | |
308 | class CSCVSActualImportMixin(TestActualImportMixin): |
309 | |
310 | |
311 | === modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py' |
312 | --- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-01-13 02:09:34 +0000 |
313 | +++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-02-01 05:56:15 +0000 |
314 | @@ -38,7 +38,8 @@ |
315 | from lp.code.model.codeimportjob import CodeImportJob |
316 | from lp.codehosting import load_optional_plugin |
317 | from lp.codehosting.codeimport.worker import ( |
318 | - CodeImportSourceDetails, get_default_bazaar_branch_store) |
319 | + CodeImportSourceDetails, CodeImportWorkerExitCode, |
320 | + get_default_bazaar_branch_store) |
321 | from lp.codehosting.codeimport.workermonitor import ( |
322 | CodeImportWorkerMonitor, CodeImportWorkerMonitorProtocol, ExitQuietly, |
323 | read_only_transaction) |
324 | @@ -292,7 +293,39 @@ |
325 | |
326 | def test_callFinishJobCallsFinishJobFailure(self): |
327 | # callFinishJob calls finishJob with CodeImportResultStatus.FAILURE |
328 | - # and swallows the failure if its argument is a Failure. |
329 | + # and swallows the failure if its argument indicates that the |
330 | + # subprocess exited with an exit code of |
331 | + # CodeImportWorkerExitCode.FAILURE. |
332 | + calls = self.patchOutFinishJob() |
333 | + ret = self.worker_monitor.callFinishJob( |
334 | + makeFailure( |
335 | + error.ProcessTerminated, |
336 | + exitCode=CodeImportWorkerExitCode.FAILURE)) |
337 | + self.assertEqual(calls, [CodeImportResultStatus.FAILURE]) |
338 | + self.assertOopsesLogged([error.ProcessTerminated]) |
339 | + # We return the deferred that callFinishJob returns -- if |
340 | + # callFinishJob did not swallow the error, this will fail the test. |
341 | + return ret |
342 | + |
343 | + def test_callFinishJobCallsFinishJobSuccessNoChange(self): |
344 | + # If the argument to callFinishJob indicates that the subprocess |
345 | + # exited with a code of CodeImportWorkerExitCode.SUCCESS_NOCHANGE, it |
346 | + # calls finishJob with a status of SUCCESS_NOCHANGE. |
347 | + calls = self.patchOutFinishJob() |
348 | + ret = self.worker_monitor.callFinishJob( |
349 | + makeFailure( |
350 | + error.ProcessTerminated, |
351 | + exitCode=CodeImportWorkerExitCode.SUCCESS_NOCHANGE)) |
352 | + self.assertEqual(calls, [CodeImportResultStatus.SUCCESS_NOCHANGE]) |
353 | + self.assertOopsesLogged([]) |
354 | + # We return the deferred that callFinishJob returns -- if |
355 | + # callFinishJob did not swallow the error, this will fail the test. |
356 | + return ret |
357 | + |
358 | + def test_callFinishJobCallsFinishJobArbitraryFailure(self): |
359 | + # If the argument to callFinishJob indicates that there was some other |
360 | + # failure that had nothing to do with the subprocess, it records |
361 | + # failure. |
362 | calls = self.patchOutFinishJob() |
363 | ret = self.worker_monitor.callFinishJob(makeFailure(RuntimeError)) |
364 | self.assertEqual(calls, [CodeImportResultStatus.FAILURE]) |
365 | |
366 | === modified file 'lib/lp/codehosting/codeimport/worker.py' |
367 | --- lib/lp/codehosting/codeimport/worker.py 2010-01-13 02:09:34 +0000 |
368 | +++ lib/lp/codehosting/codeimport/worker.py 2010-02-01 05:56:15 +0000 |
369 | @@ -9,6 +9,7 @@ |
370 | 'BzrSvnImportWorker', |
371 | 'CSCVSImportWorker', |
372 | 'CodeImportSourceDetails', |
373 | + 'CodeImportWorkerExitCode', |
374 | 'ForeignTreeStore', |
375 | 'GitImportWorker', |
376 | 'HgImportWorker', |
377 | @@ -29,13 +30,13 @@ |
378 | from bzrlib.upgrade import upgrade |
379 | |
380 | from canonical.cachedproperty import cachedproperty |
381 | +from lp.code.enums import RevisionControlSystems |
382 | from lp.codehosting.codeimport.foreigntree import ( |
383 | CVSWorkingTree, SubversionWorkingTree) |
384 | from lp.codehosting.codeimport.tarball import ( |
385 | create_tarball, extract_tarball) |
386 | from lp.codehosting.codeimport.uifactory import LoggingUIFactory |
387 | from canonical.config import config |
388 | -from lp.code.enums import RevisionControlSystems |
389 | |
390 | from cscvs.cmds import totla |
391 | import cscvs |
392 | @@ -43,6 +44,14 @@ |
393 | import SCM |
394 | |
395 | |
396 | +class CodeImportWorkerExitCode: |
397 | + """Exit codes used by the code import worker script.""" |
398 | + |
399 | + SUCCESS = 0 |
400 | + FAILURE = 1 |
401 | + SUCCESS_NOCHANGE = 2 |
402 | + |
403 | + |
404 | class BazaarBranchStore: |
405 | """A place where Bazaar branches of code imports are kept.""" |
406 | |
407 | @@ -77,7 +86,11 @@ |
408 | return BzrDir.open(target_path).open_workingtree() |
409 | |
410 | def push(self, db_branch_id, bzr_tree, required_format): |
411 | - """Push up `bzr_tree` as the Bazaar branch for `code_import`.""" |
412 | + """Push up `bzr_tree` as the Bazaar branch for `code_import`. |
413 | + |
414 | + :return: A boolean that is true if the push was non-trivial |
415 | + (i.e. actually transferred revisions). |
416 | + """ |
417 | self.transport.create_prefix() |
418 | branch_from = bzr_tree.branch |
419 | target_url = self._getMirrorURL(db_branch_id) |
420 | @@ -86,7 +99,8 @@ |
421 | except NotBranchError: |
422 | branch_to = BzrDir.create_branch_and_repo( |
423 | target_url, format=required_format) |
424 | - branch_to.pull(branch_from, overwrite=True) |
425 | + pull_result = branch_to.pull(branch_from, overwrite=True) |
426 | + return pull_result.old_revid != pull_result.new_revid |
427 | |
428 | |
429 | def get_default_bazaar_branch_store(): |
430 | @@ -356,8 +370,11 @@ |
431 | self.required_format) |
432 | |
433 | def pushBazaarWorkingTree(self, bazaar_tree): |
434 | - """Push the updated Bazaar working tree to the server.""" |
435 | - self.bazaar_branch_store.push( |
436 | + """Push the updated Bazaar working tree to the server. |
437 | + |
438 | + :return: True if revisions were transferred. |
439 | + """ |
440 | + return self.bazaar_branch_store.push( |
441 | self.source_details.branch_id, bazaar_tree, self.required_format) |
442 | |
443 | def getWorkingDirectory(self): |
444 | @@ -390,12 +407,20 @@ |
445 | saved_pwd = os.getcwd() |
446 | os.chdir(working_directory) |
447 | try: |
448 | - self._doImport() |
449 | + non_trivial = self._doImport() |
450 | + if non_trivial: |
451 | + return CodeImportWorkerExitCode.SUCCESS |
452 | + else: |
453 | + return CodeImportWorkerExitCode.SUCCESS_NOCHANGE |
454 | finally: |
455 | shutil.rmtree(working_directory) |
456 | os.chdir(saved_pwd) |
457 | |
458 | def _doImport(self): |
459 | + """Perform the import. |
460 | + |
461 | + :return: True if the import actually imported some new revisions. |
462 | + """ |
463 | raise NotImplementedError() |
464 | |
465 | |
466 | @@ -470,8 +495,9 @@ |
467 | foreign_tree = self.getForeignTree() |
468 | bazaar_tree = self.getBazaarWorkingTree() |
469 | self.importToBazaar(foreign_tree, bazaar_tree) |
470 | - self.pushBazaarWorkingTree(bazaar_tree) |
471 | + non_trivial = self.pushBazaarWorkingTree(bazaar_tree) |
472 | self.foreign_tree_store.archive(foreign_tree) |
473 | + return non_trivial |
474 | |
475 | |
476 | class PullingImportWorker(ImportWorker): |
477 | @@ -506,7 +532,7 @@ |
478 | bazaar_tree.branch.pull(foreign_branch, overwrite=True) |
479 | finally: |
480 | bzrlib.ui.ui_factory = saved_factory |
481 | - self.pushBazaarWorkingTree(bazaar_tree) |
482 | + return self.pushBazaarWorkingTree(bazaar_tree) |
483 | |
484 | |
485 | class GitImportWorker(PullingImportWorker): |
486 | @@ -542,9 +568,11 @@ |
487 | that bzr-git will have created at .bzr/repository/bzr.git into the |
488 | import data store. |
489 | """ |
490 | - PullingImportWorker.pushBazaarWorkingTree(self, bazaar_tree) |
491 | + non_trivial = PullingImportWorker.pushBazaarWorkingTree( |
492 | + self, bazaar_tree) |
493 | self.import_data_store.put( |
494 | 'git.db', bazaar_tree.branch.repository._transport) |
495 | + return non_trivial |
496 | |
497 | |
498 | class HgImportWorker(PullingImportWorker): |
499 | @@ -581,9 +609,11 @@ |
500 | that bzr-hg will have created at .bzr/repository/hg-v2.db into the |
501 | import data store. |
502 | """ |
503 | - PullingImportWorker.pushBazaarWorkingTree(self, bazaar_tree) |
504 | + non_trivial = PullingImportWorker.pushBazaarWorkingTree( |
505 | + self, bazaar_tree) |
506 | self.import_data_store.put( |
507 | self.db_file, bazaar_tree.branch.repository._transport) |
508 | + return non_trivial |
509 | |
510 | |
511 | class BzrSvnImportWorker(PullingImportWorker): |
512 | |
513 | === modified file 'lib/lp/codehosting/codeimport/workermonitor.py' |
514 | --- lib/lp/codehosting/codeimport/workermonitor.py 2009-06-25 04:06:00 +0000 |
515 | +++ lib/lp/codehosting/codeimport/workermonitor.py 2010-02-01 05:56:15 +0000 |
516 | @@ -10,10 +10,9 @@ |
517 | |
518 | |
519 | import os |
520 | -import sys |
521 | import tempfile |
522 | |
523 | -from twisted.internet import defer, reactor, task |
524 | +from twisted.internet import defer, error, reactor, task |
525 | from twisted.python import failure |
526 | from twisted.python.util import mergeFunctionMetadata |
527 | |
528 | @@ -32,7 +31,8 @@ |
529 | from lp.code.enums import CodeImportResultStatus |
530 | from lp.code.interfaces.codeimportjob import ( |
531 | ICodeImportJobSet, ICodeImportJobWorkflow) |
532 | -from lp.codehosting.codeimport.worker import CodeImportSourceDetails |
533 | +from lp.codehosting.codeimport.worker import ( |
534 | + CodeImportSourceDetails, CodeImportWorkerExitCode) |
535 | from lp.testing import login, logout, ANONYMOUS |
536 | |
537 | |
538 | @@ -243,19 +243,15 @@ |
539 | def finishJob(self, status): |
540 | """Call the finishJob method for the job we are working on. |
541 | |
542 | - This method uploads the log file to the librarian first. If this |
543 | - fails, we still try to call finishJob, but return the librarian's |
544 | - failure if finishJob succeeded (if finishJob fails, that exception |
545 | - 'wins'). |
546 | + This method uploads the log file to the librarian first. |
547 | """ |
548 | job = self.getJob() |
549 | log_file_size = self._log_file.tell() |
550 | - librarian_failure = None |
551 | if log_file_size > 0: |
552 | self._log_file.seek(0) |
553 | branch = job.code_import.branch |
554 | - log_file_name = '%s-%s-log.txt' % ( |
555 | - branch.product.name, branch.name) |
556 | + log_file_name = '%s-%s-%s-log.txt' % ( |
557 | + branch.owner.name, branch.product.name, branch.name) |
558 | try: |
559 | log_file_alias = self._createLibrarianFileAlias( |
560 | log_file_name, log_file_size, self._log_file, |
561 | @@ -300,16 +296,25 @@ |
562 | failure.trap(ExitQuietly) |
563 | return None |
564 | |
565 | + def _reasonToStatus(self, reason): |
566 | + if isinstance(reason, failure.Failure): |
567 | + if reason.check(error.ProcessTerminated): |
568 | + if reason.value.exitCode == \ |
569 | + CodeImportWorkerExitCode.SUCCESS_NOCHANGE: |
570 | + return CodeImportResultStatus.SUCCESS_NOCHANGE |
571 | + return CodeImportResultStatus.FAILURE |
572 | + else: |
573 | + return CodeImportResultStatus.SUCCESS |
574 | + |
575 | def callFinishJob(self, reason): |
576 | """Call finishJob() with the appropriate status.""" |
577 | if not self._call_finish_job: |
578 | return reason |
579 | - if isinstance(reason, failure.Failure): |
580 | + status = self._reasonToStatus(reason) |
581 | + if status == CodeImportResultStatus.FAILURE: |
582 | self._log_file.write("Import failed:\n") |
583 | reason.printTraceback(self._log_file) |
584 | self._logOopsFromFailure(reason) |
585 | - status = CodeImportResultStatus.FAILURE |
586 | else: |
587 | self._logger.info('Import succeeded.') |
588 | - status = CodeImportResultStatus.SUCCESS |
589 | return self.finishJob(status) |
590 | |
591 | === modified file 'scripts/code-import-worker.py' |
592 | --- scripts/code-import-worker.py 2009-11-20 07:09:54 +0000 |
593 | +++ scripts/code-import-worker.py 2010-02-01 05:56:15 +0000 |
594 | @@ -19,6 +19,7 @@ |
595 | import _pythonpath |
596 | |
597 | from optparse import OptionParser |
598 | +import sys |
599 | |
600 | from bzrlib.transport import get_transport |
601 | |
602 | @@ -58,9 +59,9 @@ |
603 | source_details, |
604 | get_transport(config.codeimport.foreign_tree_store), |
605 | get_default_bazaar_branch_store(), self.logger) |
606 | - import_worker.run() |
607 | + return import_worker.run() |
608 | |
609 | |
610 | if __name__ == '__main__': |
611 | script = CodeImportWorker() |
612 | - script.main() |
613 | + sys.exit(script.main()) |
Hi,
This branch extends the contract between the code import 'worker' and 'monitor' scripts by specifying that an exit code of 2 means that the import was successful but didn't import any new revisions so there's no need to request a mirror of the import branch. I expect this to substantially reduce the load on the puller, as currently about half of all branches pulled are import branches with no new revisions.
I've had some pre-imp discussion with Tim, Jono and Aaron about this.
Cheers,
mwh