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
on 2010-02-01
| Status: | Merged |
|---|---|
| Approved by: | Michael Hudson-Doyle on 2010-02-02 |
| 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) | 2010-02-01 | Approve on 2010-02-02 | |
|
Review via email:
|
|||
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.
| Michael Hudson-Doyle (mwhudson) wrote : | # |
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