Merge lp:~mwhudson/launchpad/less-import-requestMirrors-bug-487357 into lp:launchpad

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
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.

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

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

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
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2010-01-29 17:15:31 +0000
+++ lib/lp/code/browser/branch.py 2010-02-01 05:56:15 +0000
@@ -81,7 +81,8 @@
81 latest_proposals_for_each_branch)81 latest_proposals_for_each_branch)
82from lp.code.enums import (82from lp.code.enums import (
83 BranchLifecycleStatus, BranchType, CodeImportJobState,83 BranchLifecycleStatus, BranchType, CodeImportJobState,
84 CodeImportReviewStatus, RevisionControlSystems, UICreatableBranchType)84 CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems,
85 UICreatableBranchType)
85from lp.code.errors import InvalidBranchMergeProposal86from lp.code.errors import InvalidBranchMergeProposal
86from lp.code.interfaces.branch import (87from lp.code.interfaces.branch import (
87 BranchCreationForbidden, BranchExists, IBranch,88 BranchCreationForbidden, BranchExists, IBranch,
@@ -509,6 +510,13 @@
509 """Return the last 10 CodeImportResults."""510 """Return the last 10 CodeImportResults."""
510 return list(self.context.code_import.results[:10])511 return list(self.context.code_import.results[:10])
511512
513 def iconForCodeImportResultStatus(self, status):
514 """The icon to represent the `CodeImportResultStatus` `status`."""
515 if status in CodeImportResultStatus.successes:
516 return "/@@/yes"
517 else:
518 return "/@@/no"
519
512 @property520 @property
513 def is_svn_import(self):521 def is_svn_import(self):
514 """True if an imported branch is a SVN import."""522 """True if an imported branch is a SVN import."""
515523
=== modified file 'lib/lp/code/enums.py'
--- lib/lp/code/enums.py 2010-01-12 02:07:38 +0000
+++ lib/lp/code/enums.py 2010-02-01 05:56:15 +0000
@@ -799,6 +799,13 @@
799 Import job completed successfully.799 Import job completed successfully.
800 """)800 """)
801801
802 SUCCESS_NOCHANGE = DBItem(110, """
803 Success with no changes
804
805 Import job completed successfully, but there were no new revisions to
806 import.
807 """)
808
802 FAILURE = DBItem(200, """809 FAILURE = DBItem(200, """
803 Failure810 Failure
804811
@@ -857,6 +864,8 @@
857 job, or the deletion of a CodeImport which had a running job.864 job, or the deletion of a CodeImport which had a running job.
858 """)865 """)
859866
867 successes = [SUCCESS, SUCCESS_NOCHANGE]
868
860869
861class CodeReviewVote(DBEnumeratedType):870class CodeReviewVote(DBEnumeratedType):
862 """Code Review Votes871 """Code Review Votes
863872
=== modified file 'lib/lp/code/model/codeimport.py'
--- lib/lp/code/model/codeimport.py 2010-01-13 02:51:30 +0000
+++ lib/lp/code/model/codeimport.py 2010-02-01 05:56:15 +0000
@@ -160,7 +160,8 @@
160 "coalesce",160 "coalesce",
161 Select(161 Select(
162 CodeImportResult.id,162 CodeImportResult.id,
163 And(CodeImportResult.status == CodeImportResultStatus.SUCCESS,163 And(CodeImportResult.status.is_in(
164 CodeImportResultStatus.successes),
164 CodeImportResult.code_import == self),165 CodeImportResult.code_import == self),
165 order_by=Desc(CodeImportResult.id),166 order_by=Desc(CodeImportResult.id),
166 limit=1),167 limit=1),
167168
=== modified file 'lib/lp/code/model/codeimportjob.py'
--- lib/lp/code/model/codeimportjob.py 2009-07-17 00:26:05 +0000
+++ lib/lp/code/model/codeimportjob.py 2010-02-01 05:56:15 +0000
@@ -287,11 +287,14 @@
287 # Only start a new one if the import is still in the REVIEWED state.287 # Only start a new one if the import is still in the REVIEWED state.
288 if code_import.review_status == CodeImportReviewStatus.REVIEWED:288 if code_import.review_status == CodeImportReviewStatus.REVIEWED:
289 self.newJob(code_import)289 self.newJob(code_import)
290 # If the status was successful, update the date_last_successful and290 # If the status was successful, update date_last_successful.
291 # arrange for the branch to be mirrored.291 if status in [CodeImportResultStatus.SUCCESS,
292 if status == CodeImportResultStatus.SUCCESS:292 CodeImportResultStatus.SUCCESS_NOCHANGE]:
293 naked_import = removeSecurityProxy(code_import)293 naked_import = removeSecurityProxy(code_import)
294 naked_import.date_last_successful = result.date_created294 naked_import.date_last_successful = result.date_created
295 # If the status was successful and revisions were imported, arrange
296 # for the branch to be mirrored.
297 if status == CodeImportResultStatus.SUCCESS:
295 code_import.branch.requestMirror()298 code_import.branch.requestMirror()
296 getUtility(ICodeImportEventSet).newFinish(299 getUtility(ICodeImportEventSet).newFinish(
297 code_import, machine)300 code_import, machine)
298301
=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
--- lib/lp/code/model/tests/test_codeimport.py 2010-01-15 23:55:45 +0000
+++ lib/lp/code/model/tests/test_codeimport.py 2010-02-01 05:56:15 +0000
@@ -394,11 +394,15 @@
394 getUtility(ICodeImportJobWorkflow).finishJob(394 getUtility(ICodeImportJobWorkflow).finishJob(
395 running_job, CodeImportResultStatus.FAILURE, None)395 running_job, CodeImportResultStatus.FAILURE, None)
396396
397 def succeedImport(self, code_import):397 def succeedImport(self, code_import,
398 status=CodeImportResultStatus.SUCCESS):
398 """Create if necessary a job for `code_import` and have it succeed."""399 """Create if necessary a job for `code_import` and have it succeed."""
400 if status not in CodeImportResultStatus.successes:
401 raise AssertionError(
402 "succeedImport() should be called with a successful status!")
399 running_job = self.makeRunningJob(code_import)403 running_job = self.makeRunningJob(code_import)
400 getUtility(ICodeImportJobWorkflow).finishJob(404 getUtility(ICodeImportJobWorkflow).finishJob(
401 running_job, CodeImportResultStatus.SUCCESS, None)405 running_job, status, None)
402406
403 def test_consecutive_failure_count_zero_initially(self):407 def test_consecutive_failure_count_zero_initially(self):
404 # A new code import has a consecutive_failure_count of 0.408 # A new code import has a consecutive_failure_count of 0.
@@ -407,7 +411,7 @@
407411
408 def test_consecutive_failure_count_succeed(self):412 def test_consecutive_failure_count_succeed(self):
409 # A code import that has succeeded once has a413 # A code import that has succeeded once has a
410 # consecutive_failure_count of 1.414 # consecutive_failure_count of 0.
411 code_import = self.factory.makeCodeImport()415 code_import = self.factory.makeCodeImport()
412 self.succeedImport(code_import)416 self.succeedImport(code_import)
413 self.assertEqual(0, code_import.consecutive_failure_count)417 self.assertEqual(0, code_import.consecutive_failure_count)
@@ -419,6 +423,15 @@
419 self.failImport(code_import)423 self.failImport(code_import)
420 self.assertEqual(1, code_import.consecutive_failure_count)424 self.assertEqual(1, code_import.consecutive_failure_count)
421425
426 def test_consecutive_failure_count_succeed_succeed_no_changes(self):
427 # A code import that has succeeded then succeeded with no changes has
428 # a consecutive_failure_count of 0.
429 code_import = self.factory.makeCodeImport()
430 self.succeedImport(code_import)
431 self.succeedImport(
432 code_import, CodeImportResultStatus.SUCCESS_NOCHANGE)
433 self.assertEqual(0, code_import.consecutive_failure_count)
434
422 def test_consecutive_failure_count_fail_fail(self):435 def test_consecutive_failure_count_fail_fail(self):
423 # A code import that has failed twice has a consecutive_failure_count436 # A code import that has failed twice has a consecutive_failure_count
424 # of 2.437 # of 2.
425438
=== modified file 'lib/lp/code/model/tests/test_codeimportjob.py'
--- lib/lp/code/model/tests/test_codeimportjob.py 2009-07-17 00:26:05 +0000
+++ lib/lp/code/model/tests/test_codeimportjob.py 2010-02-01 05:56:15 +0000
@@ -32,8 +32,7 @@
32from lp.code.interfaces.codeimportevent import ICodeImportEventSet32from lp.code.interfaces.codeimportevent import ICodeImportEventSet
33from lp.code.interfaces.codeimportjob import (33from lp.code.interfaces.codeimportjob import (
34 ICodeImportJobSet, ICodeImportJobWorkflow)34 ICodeImportJobSet, ICodeImportJobWorkflow)
35from lp.code.interfaces.codeimportresult import (35from lp.code.interfaces.codeimportresult import ICodeImportResult
36 ICodeImportResult, ICodeImportResultSet)
37from lp.registry.interfaces.person import IPersonSet36from lp.registry.interfaces.person import IPersonSet
38from lp.testing import ANONYMOUS, login, logout, TestCaseWithFactory37from lp.testing import ANONYMOUS, login, logout, TestCaseWithFactory
39from canonical.launchpad.testing.codeimporthelpers import (38from canonical.launchpad.testing.codeimporthelpers import (
@@ -274,7 +273,7 @@
274 self.assertReclaimableJobs([stale_job])273 self.assertReclaimableJobs([stale_job])
275 machine = self.factory.makeCodeImportMachine(set_online=True)274 machine = self.factory.makeCodeImportMachine(set_online=True)
276 login(ANONYMOUS)275 login(ANONYMOUS)
277 job = getUtility(ICodeImportJobSet).getJobForMachine(276 getUtility(ICodeImportJobSet).getJobForMachine(
278 machine.hostname)277 machine.hostname)
279 login_for_code_imports()278 login_for_code_imports()
280 # Now there are no reclaimable jobs.279 # Now there are no reclaimable jobs.
@@ -456,7 +455,7 @@
456 # of the job that was deleted when the CodeImport review status455 # of the job that was deleted when the CodeImport review status
457 # changed from REVIEWED. That is the date_job_started of the most456 # changed from REVIEWED. That is the date_job_started of the most
458 # recent CodeImportResult plus the effective update interval.457 # recent CodeImportResult plus the effective update interval.
459 job = getUtility(ICodeImportJobWorkflow).newJob(code_import)458 getUtility(ICodeImportJobWorkflow).newJob(code_import)
460 self.assertSqlAttributeEqualsDate(459 self.assertSqlAttributeEqualsDate(
461 code_import.import_job, 'date_due',460 code_import.import_job, 'date_due',
462 recent_result.date_job_started + interval)461 recent_result.date_job_started + interval)
@@ -690,7 +689,6 @@
690 def test_wrongJobState(self):689 def test_wrongJobState(self):
691 # Calling updateHeartbeat with a job whose state is not RUNNING is an690 # Calling updateHeartbeat with a job whose state is not RUNNING is an
692 # error.691 # error.
693 machine = self.factory.makeCodeImportMachine()
694 code_import = self.factory.makeCodeImport()692 code_import = self.factory.makeCodeImport()
695 job = self.factory.makeCodeImportJob(code_import)693 job = self.factory.makeCodeImportJob(code_import)
696 self.assertFailure(694 self.assertFailure(
@@ -740,7 +738,6 @@
740738
741 def test_wrongJobState(self):739 def test_wrongJobState(self):
742 # Calling finishJob with a job whose state is not RUNNING is an error.740 # Calling finishJob with a job whose state is not RUNNING is an error.
743 machine = self.factory.makeCodeImportMachine()
744 code_import = self.factory.makeCodeImport()741 code_import = self.factory.makeCodeImport()
745 job = self.factory.makeCodeImportJob(code_import)742 job = self.factory.makeCodeImportJob(code_import)
746 self.switchDbUser()743 self.switchDbUser()
@@ -767,7 +764,6 @@
767 # finishJob() creates a new CodeImportJob for the given CodeImport,764 # finishJob() creates a new CodeImportJob for the given CodeImport,
768 # scheduled appropriately far in the future.765 # scheduled appropriately far in the future.
769 running_job = self.makeRunningJob()766 running_job = self.makeRunningJob()
770 running_job_date_due = running_job.date_due
771 code_import = running_job.code_import767 code_import = running_job.code_import
772 self.switchDbUser()768 self.switchDbUser()
773 getUtility(ICodeImportJobWorkflow).finishJob(769 getUtility(ICodeImportJobWorkflow).finishJob(
@@ -784,7 +780,6 @@
784 # finishJob() creates a new CodeImportJob for the given CodeImport,780 # finishJob() creates a new CodeImportJob for the given CodeImport,
785 # unless the CodeImport has been suspended or marked invalid.781 # unless the CodeImport has been suspended or marked invalid.
786 running_job = self.makeRunningJob()782 running_job = self.makeRunningJob()
787 running_job_date_due = running_job.date_due
788 code_import = running_job.code_import783 code_import = running_job.code_import
789 ddaa = getUtility(IPersonSet).getByEmail(784 ddaa = getUtility(IPersonSet).getByEmail(
790 'david.allouche@canonical.com')785 'david.allouche@canonical.com')
@@ -798,9 +793,7 @@
798 def test_createsResultObject(self):793 def test_createsResultObject(self):
799 # finishJob() creates a CodeImportResult object for the given import.794 # finishJob() creates a CodeImportResult object for the given import.
800 running_job = self.makeRunningJob()795 running_job = self.makeRunningJob()
801 running_job_date_due = running_job.date_due
802 code_import = running_job.code_import796 code_import = running_job.code_import
803 result_set = getUtility(ICodeImportResultSet)
804 # Before calling finishJob() there are no CodeImportResults for the797 # Before calling finishJob() there are no CodeImportResults for the
805 # given import...798 # given import...
806 self.assertEqual(len(list(code_import.results)), 0)799 self.assertEqual(len(list(code_import.results)), 0)
@@ -903,7 +896,6 @@
903896
904 self.switchDbUser()897 self.switchDbUser()
905 log_data = 'several\nlines\nof\nlog data'898 log_data = 'several\nlines\nof\nlog data'
906 log_excerpt = log_data.splitlines()[-1]
907 log_alias_id = getUtility(ILibrarianClient).addFile(899 log_alias_id = getUtility(ILibrarianClient).addFile(
908 'import_log.txt', len(log_data),900 'import_log.txt', len(log_data),
909 StringIO.StringIO(log_data), 'text/plain')901 StringIO.StringIO(log_data), 'text/plain')
@@ -939,7 +931,7 @@
939 code_import = job.code_import931 code_import = job.code_import
940 self.assertTrue(code_import.date_last_successful is None)932 self.assertTrue(code_import.date_last_successful is None)
941 getUtility(ICodeImportJobWorkflow).finishJob(job, status, None)933 getUtility(ICodeImportJobWorkflow).finishJob(job, status, None)
942 if status == CodeImportResultStatus.SUCCESS:934 if status in CodeImportResultStatus.successes:
943 self.assertTrue(code_import.date_last_successful is not None)935 self.assertTrue(code_import.date_last_successful is not None)
944 else:936 else:
945 self.assertTrue(code_import.date_last_successful is None)937 self.assertTrue(code_import.date_last_successful is None)
946938
=== modified file 'lib/lp/code/stories/codeimport/xx-codeimport-results.txt'
--- lib/lp/code/stories/codeimport/xx-codeimport-results.txt 2009-03-24 12:43:49 +0000
+++ lib/lp/code/stories/codeimport/xx-codeimport-results.txt 2010-02-01 05:56:15 +0000
@@ -23,6 +23,8 @@
23 >>> browser.open(branch_url)23 >>> browser.open(branch_url)
24 >>> import_results = find_tag_by_id(browser.contents, 'import-results')24 >>> import_results = find_tag_by_id(browser.contents, 'import-results')
25 >>> print extract_text(import_results).replace('—', '--')25 >>> print extract_text(import_results).replace('—', '--')
26 Import started on 2007-12-10 on odin and finished on 2007-12-10
27 taking ten hours -- see the log
26 Import started on 2007-12-09 on odin and finished on 2007-12-0928 Import started on 2007-12-09 on odin and finished on 2007-12-09
27 taking nine hours -- see the log29 taking nine hours -- see the log
28 Import started on 2007-12-08 on odin and finished on 2007-12-0830 Import started on 2007-12-08 on odin and finished on 2007-12-08
@@ -58,4 +60,5 @@
58 <img src="/@@/no" title="Source Checkout Failed" />60 <img src="/@@/no" title="Source Checkout Failed" />
59 <img src="/@@/no" title="Internal Failure" />61 <img src="/@@/no" title="Internal Failure" />
60 <img src="/@@/no" title="Failure" />62 <img src="/@@/no" title="Failure" />
63 <img src="/@@/yes" title="Success with no changes" />
61 <img src="/@@/yes" title="Success" />64 <img src="/@@/yes" title="Success" />
6265
=== modified file 'lib/lp/code/templates/codeimport-macros.pt'
--- lib/lp/code/templates/codeimport-macros.pt 2009-07-17 17:59:07 +0000
+++ lib/lp/code/templates/codeimport-macros.pt 2010-02-01 05:56:15 +0000
@@ -8,10 +8,8 @@
8 Expects a parameter called 'result'.8 Expects a parameter called 'result'.
9 </tal:comment>9 </tal:comment>
1010
11 <img src="/@@/yes" tal:condition="result/status/enumvalue:SUCCESS"11 <img tal:attributes="src python:view.iconForCodeImportResultStatus(result.status);
12 title="Success"/>12 title result/status/title"/>
13 <img src="/@@/no" tal:condition="not: result/status/enumvalue:SUCCESS"
14 tal:attributes="title result/status/title"/>
15 Import started13 Import started
16 <tal:when replace="result/date_job_started/fmt:displaydate">14 <tal:when replace="result/date_job_started/fmt:displaydate">
17 4 hours ago15 4 hours ago
1816
=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
--- lib/lp/codehosting/codeimport/tests/test_worker.py 2010-01-13 02:09:34 +0000
+++ lib/lp/codehosting/codeimport/tests/test_worker.py 2010-02-01 05:56:15 +0000
@@ -30,9 +30,9 @@
3030
31from lp.codehosting import load_optional_plugin31from lp.codehosting import load_optional_plugin
32from lp.codehosting.codeimport.worker import (32from lp.codehosting.codeimport.worker import (
33 BazaarBranchStore, BzrSvnImportWorker, CSCVSImportWorker,33 BazaarBranchStore, BzrSvnImportWorker, CodeImportWorkerExitCode,
34 ForeignTreeStore, GitImportWorker, HgImportWorker, ImportDataStore,34 ForeignTreeStore, GitImportWorker, HgImportWorker, ImportDataStore,
35 ImportWorker, get_default_bazaar_branch_store)35 ImportWorker, CSCVSImportWorker, get_default_bazaar_branch_store)
36from lp.codehosting.codeimport.tests.servers import (36from lp.codehosting.codeimport.tests.servers import (
37 CVSServer, GitServer, MercurialServer, SubversionServer)37 CVSServer, GitServer, MercurialServer, SubversionServer)
38from lp.codehosting.tests.helpers import (38from lp.codehosting.tests.helpers import (
@@ -710,6 +710,28 @@
710 self.assertEqual(710 self.assertEqual(
711 self.foreign_commit_count, len(branch.revision_history()))711 self.foreign_commit_count, len(branch.revision_history()))
712712
713 def test_script_exit_codes(self):
714 # After a successful import that imports revisions, the worker exits
715 # with a code of CodeImportWorkerExitCode.SUCCESS. After a successful
716 # import that does not import revisions, the worker exits with a code
717 # of CodeImportWorkerExitCode.SUCCESS_NOCHANGE.
718 source_details = self.makeSourceDetails(
719 'trunk', [('README', 'Original contents')])
720
721 clean_up_default_stores_for_import(source_details)
722
723 script_path = os.path.join(
724 config.root, 'scripts', 'code-import-worker.py')
725 output = tempfile.TemporaryFile()
726 retcode = subprocess.call(
727 [script_path] + source_details.asArguments(),
728 stderr=output, stdout=output)
729 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS)
730 retcode = subprocess.call(
731 [script_path] + source_details.asArguments(),
732 stderr=output, stdout=output)
733 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE)
734
713735
714class CSCVSActualImportMixin(TestActualImportMixin):736class CSCVSActualImportMixin(TestActualImportMixin):
715737
716738
=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
--- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-01-13 02:09:34 +0000
+++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-02-01 05:56:15 +0000
@@ -38,7 +38,8 @@
38from lp.code.model.codeimportjob import CodeImportJob38from lp.code.model.codeimportjob import CodeImportJob
39from lp.codehosting import load_optional_plugin39from lp.codehosting import load_optional_plugin
40from lp.codehosting.codeimport.worker import (40from lp.codehosting.codeimport.worker import (
41 CodeImportSourceDetails, get_default_bazaar_branch_store)41 CodeImportSourceDetails, CodeImportWorkerExitCode,
42 get_default_bazaar_branch_store)
42from lp.codehosting.codeimport.workermonitor import (43from lp.codehosting.codeimport.workermonitor import (
43 CodeImportWorkerMonitor, CodeImportWorkerMonitorProtocol, ExitQuietly,44 CodeImportWorkerMonitor, CodeImportWorkerMonitorProtocol, ExitQuietly,
44 read_only_transaction)45 read_only_transaction)
@@ -292,7 +293,39 @@
292293
293 def test_callFinishJobCallsFinishJobFailure(self):294 def test_callFinishJobCallsFinishJobFailure(self):
294 # callFinishJob calls finishJob with CodeImportResultStatus.FAILURE295 # callFinishJob calls finishJob with CodeImportResultStatus.FAILURE
295 # and swallows the failure if its argument is a Failure.296 # and swallows the failure if its argument indicates that the
297 # subprocess exited with an exit code of
298 # CodeImportWorkerExitCode.FAILURE.
299 calls = self.patchOutFinishJob()
300 ret = self.worker_monitor.callFinishJob(
301 makeFailure(
302 error.ProcessTerminated,
303 exitCode=CodeImportWorkerExitCode.FAILURE))
304 self.assertEqual(calls, [CodeImportResultStatus.FAILURE])
305 self.assertOopsesLogged([error.ProcessTerminated])
306 # We return the deferred that callFinishJob returns -- if
307 # callFinishJob did not swallow the error, this will fail the test.
308 return ret
309
310 def test_callFinishJobCallsFinishJobSuccessNoChange(self):
311 # If the argument to callFinishJob indicates that the subprocess
312 # exited with a code of CodeImportWorkerExitCode.SUCCESS_NOCHANGE, it
313 # calls finishJob with a status of SUCCESS_NOCHANGE.
314 calls = self.patchOutFinishJob()
315 ret = self.worker_monitor.callFinishJob(
316 makeFailure(
317 error.ProcessTerminated,
318 exitCode=CodeImportWorkerExitCode.SUCCESS_NOCHANGE))
319 self.assertEqual(calls, [CodeImportResultStatus.SUCCESS_NOCHANGE])
320 self.assertOopsesLogged([])
321 # We return the deferred that callFinishJob returns -- if
322 # callFinishJob did not swallow the error, this will fail the test.
323 return ret
324
325 def test_callFinishJobCallsFinishJobArbitraryFailure(self):
326 # If the argument to callFinishJob indicates that there was some other
327 # failure that had nothing to do with the subprocess, it records
328 # failure.
296 calls = self.patchOutFinishJob()329 calls = self.patchOutFinishJob()
297 ret = self.worker_monitor.callFinishJob(makeFailure(RuntimeError))330 ret = self.worker_monitor.callFinishJob(makeFailure(RuntimeError))
298 self.assertEqual(calls, [CodeImportResultStatus.FAILURE])331 self.assertEqual(calls, [CodeImportResultStatus.FAILURE])
299332
=== modified file 'lib/lp/codehosting/codeimport/worker.py'
--- lib/lp/codehosting/codeimport/worker.py 2010-01-13 02:09:34 +0000
+++ lib/lp/codehosting/codeimport/worker.py 2010-02-01 05:56:15 +0000
@@ -9,6 +9,7 @@
9 'BzrSvnImportWorker',9 'BzrSvnImportWorker',
10 'CSCVSImportWorker',10 'CSCVSImportWorker',
11 'CodeImportSourceDetails',11 'CodeImportSourceDetails',
12 'CodeImportWorkerExitCode',
12 'ForeignTreeStore',13 'ForeignTreeStore',
13 'GitImportWorker',14 'GitImportWorker',
14 'HgImportWorker',15 'HgImportWorker',
@@ -29,13 +30,13 @@
29from bzrlib.upgrade import upgrade30from bzrlib.upgrade import upgrade
3031
31from canonical.cachedproperty import cachedproperty32from canonical.cachedproperty import cachedproperty
33from lp.code.enums import RevisionControlSystems
32from lp.codehosting.codeimport.foreigntree import (34from lp.codehosting.codeimport.foreigntree import (
33 CVSWorkingTree, SubversionWorkingTree)35 CVSWorkingTree, SubversionWorkingTree)
34from lp.codehosting.codeimport.tarball import (36from lp.codehosting.codeimport.tarball import (
35 create_tarball, extract_tarball)37 create_tarball, extract_tarball)
36from lp.codehosting.codeimport.uifactory import LoggingUIFactory38from lp.codehosting.codeimport.uifactory import LoggingUIFactory
37from canonical.config import config39from canonical.config import config
38from lp.code.enums import RevisionControlSystems
3940
40from cscvs.cmds import totla41from cscvs.cmds import totla
41import cscvs42import cscvs
@@ -43,6 +44,14 @@
43import SCM44import SCM
4445
4546
47class CodeImportWorkerExitCode:
48 """Exit codes used by the code import worker script."""
49
50 SUCCESS = 0
51 FAILURE = 1
52 SUCCESS_NOCHANGE = 2
53
54
46class BazaarBranchStore:55class BazaarBranchStore:
47 """A place where Bazaar branches of code imports are kept."""56 """A place where Bazaar branches of code imports are kept."""
4857
@@ -77,7 +86,11 @@
77 return BzrDir.open(target_path).open_workingtree()86 return BzrDir.open(target_path).open_workingtree()
7887
79 def push(self, db_branch_id, bzr_tree, required_format):88 def push(self, db_branch_id, bzr_tree, required_format):
80 """Push up `bzr_tree` as the Bazaar branch for `code_import`."""89 """Push up `bzr_tree` as the Bazaar branch for `code_import`.
90
91 :return: A boolean that is true if the push was non-trivial
92 (i.e. actually transferred revisions).
93 """
81 self.transport.create_prefix()94 self.transport.create_prefix()
82 branch_from = bzr_tree.branch95 branch_from = bzr_tree.branch
83 target_url = self._getMirrorURL(db_branch_id)96 target_url = self._getMirrorURL(db_branch_id)
@@ -86,7 +99,8 @@
86 except NotBranchError:99 except NotBranchError:
87 branch_to = BzrDir.create_branch_and_repo(100 branch_to = BzrDir.create_branch_and_repo(
88 target_url, format=required_format)101 target_url, format=required_format)
89 branch_to.pull(branch_from, overwrite=True)102 pull_result = branch_to.pull(branch_from, overwrite=True)
103 return pull_result.old_revid != pull_result.new_revid
90104
91105
92def get_default_bazaar_branch_store():106def get_default_bazaar_branch_store():
@@ -356,8 +370,11 @@
356 self.required_format)370 self.required_format)
357371
358 def pushBazaarWorkingTree(self, bazaar_tree):372 def pushBazaarWorkingTree(self, bazaar_tree):
359 """Push the updated Bazaar working tree to the server."""373 """Push the updated Bazaar working tree to the server.
360 self.bazaar_branch_store.push(374
375 :return: True if revisions were transferred.
376 """
377 return self.bazaar_branch_store.push(
361 self.source_details.branch_id, bazaar_tree, self.required_format)378 self.source_details.branch_id, bazaar_tree, self.required_format)
362379
363 def getWorkingDirectory(self):380 def getWorkingDirectory(self):
@@ -390,12 +407,20 @@
390 saved_pwd = os.getcwd()407 saved_pwd = os.getcwd()
391 os.chdir(working_directory)408 os.chdir(working_directory)
392 try:409 try:
393 self._doImport()410 non_trivial = self._doImport()
411 if non_trivial:
412 return CodeImportWorkerExitCode.SUCCESS
413 else:
414 return CodeImportWorkerExitCode.SUCCESS_NOCHANGE
394 finally:415 finally:
395 shutil.rmtree(working_directory)416 shutil.rmtree(working_directory)
396 os.chdir(saved_pwd)417 os.chdir(saved_pwd)
397418
398 def _doImport(self):419 def _doImport(self):
420 """Perform the import.
421
422 :return: True if the import actually imported some new revisions.
423 """
399 raise NotImplementedError()424 raise NotImplementedError()
400425
401426
@@ -470,8 +495,9 @@
470 foreign_tree = self.getForeignTree()495 foreign_tree = self.getForeignTree()
471 bazaar_tree = self.getBazaarWorkingTree()496 bazaar_tree = self.getBazaarWorkingTree()
472 self.importToBazaar(foreign_tree, bazaar_tree)497 self.importToBazaar(foreign_tree, bazaar_tree)
473 self.pushBazaarWorkingTree(bazaar_tree)498 non_trivial = self.pushBazaarWorkingTree(bazaar_tree)
474 self.foreign_tree_store.archive(foreign_tree)499 self.foreign_tree_store.archive(foreign_tree)
500 return non_trivial
475501
476502
477class PullingImportWorker(ImportWorker):503class PullingImportWorker(ImportWorker):
@@ -506,7 +532,7 @@
506 bazaar_tree.branch.pull(foreign_branch, overwrite=True)532 bazaar_tree.branch.pull(foreign_branch, overwrite=True)
507 finally:533 finally:
508 bzrlib.ui.ui_factory = saved_factory534 bzrlib.ui.ui_factory = saved_factory
509 self.pushBazaarWorkingTree(bazaar_tree)535 return self.pushBazaarWorkingTree(bazaar_tree)
510536
511537
512class GitImportWorker(PullingImportWorker):538class GitImportWorker(PullingImportWorker):
@@ -542,9 +568,11 @@
542 that bzr-git will have created at .bzr/repository/bzr.git into the568 that bzr-git will have created at .bzr/repository/bzr.git into the
543 import data store.569 import data store.
544 """570 """
545 PullingImportWorker.pushBazaarWorkingTree(self, bazaar_tree)571 non_trivial = PullingImportWorker.pushBazaarWorkingTree(
572 self, bazaar_tree)
546 self.import_data_store.put(573 self.import_data_store.put(
547 'git.db', bazaar_tree.branch.repository._transport)574 'git.db', bazaar_tree.branch.repository._transport)
575 return non_trivial
548576
549577
550class HgImportWorker(PullingImportWorker):578class HgImportWorker(PullingImportWorker):
@@ -581,9 +609,11 @@
581 that bzr-hg will have created at .bzr/repository/hg-v2.db into the609 that bzr-hg will have created at .bzr/repository/hg-v2.db into the
582 import data store.610 import data store.
583 """611 """
584 PullingImportWorker.pushBazaarWorkingTree(self, bazaar_tree)612 non_trivial = PullingImportWorker.pushBazaarWorkingTree(
613 self, bazaar_tree)
585 self.import_data_store.put(614 self.import_data_store.put(
586 self.db_file, bazaar_tree.branch.repository._transport)615 self.db_file, bazaar_tree.branch.repository._transport)
616 return non_trivial
587617
588618
589class BzrSvnImportWorker(PullingImportWorker):619class BzrSvnImportWorker(PullingImportWorker):
590620
=== modified file 'lib/lp/codehosting/codeimport/workermonitor.py'
--- lib/lp/codehosting/codeimport/workermonitor.py 2009-06-25 04:06:00 +0000
+++ lib/lp/codehosting/codeimport/workermonitor.py 2010-02-01 05:56:15 +0000
@@ -10,10 +10,9 @@
1010
1111
12import os12import os
13import sys
14import tempfile13import tempfile
1514
16from twisted.internet import defer, reactor, task15from twisted.internet import defer, error, reactor, task
17from twisted.python import failure16from twisted.python import failure
18from twisted.python.util import mergeFunctionMetadata17from twisted.python.util import mergeFunctionMetadata
1918
@@ -32,7 +31,8 @@
32from lp.code.enums import CodeImportResultStatus31from lp.code.enums import CodeImportResultStatus
33from lp.code.interfaces.codeimportjob import (32from lp.code.interfaces.codeimportjob import (
34 ICodeImportJobSet, ICodeImportJobWorkflow)33 ICodeImportJobSet, ICodeImportJobWorkflow)
35from lp.codehosting.codeimport.worker import CodeImportSourceDetails34from lp.codehosting.codeimport.worker import (
35 CodeImportSourceDetails, CodeImportWorkerExitCode)
36from lp.testing import login, logout, ANONYMOUS36from lp.testing import login, logout, ANONYMOUS
3737
3838
@@ -243,19 +243,15 @@
243 def finishJob(self, status):243 def finishJob(self, status):
244 """Call the finishJob method for the job we are working on.244 """Call the finishJob method for the job we are working on.
245245
246 This method uploads the log file to the librarian first. If this246 This method uploads the log file to the librarian first.
247 fails, we still try to call finishJob, but return the librarian's
248 failure if finishJob succeeded (if finishJob fails, that exception
249 'wins').
250 """247 """
251 job = self.getJob()248 job = self.getJob()
252 log_file_size = self._log_file.tell()249 log_file_size = self._log_file.tell()
253 librarian_failure = None
254 if log_file_size > 0:250 if log_file_size > 0:
255 self._log_file.seek(0)251 self._log_file.seek(0)
256 branch = job.code_import.branch252 branch = job.code_import.branch
257 log_file_name = '%s-%s-log.txt' % (253 log_file_name = '%s-%s-%s-log.txt' % (
258 branch.product.name, branch.name)254 branch.owner.name, branch.product.name, branch.name)
259 try:255 try:
260 log_file_alias = self._createLibrarianFileAlias(256 log_file_alias = self._createLibrarianFileAlias(
261 log_file_name, log_file_size, self._log_file,257 log_file_name, log_file_size, self._log_file,
@@ -300,16 +296,25 @@
300 failure.trap(ExitQuietly)296 failure.trap(ExitQuietly)
301 return None297 return None
302298
299 def _reasonToStatus(self, reason):
300 if isinstance(reason, failure.Failure):
301 if reason.check(error.ProcessTerminated):
302 if reason.value.exitCode == \
303 CodeImportWorkerExitCode.SUCCESS_NOCHANGE:
304 return CodeImportResultStatus.SUCCESS_NOCHANGE
305 return CodeImportResultStatus.FAILURE
306 else:
307 return CodeImportResultStatus.SUCCESS
308
303 def callFinishJob(self, reason):309 def callFinishJob(self, reason):
304 """Call finishJob() with the appropriate status."""310 """Call finishJob() with the appropriate status."""
305 if not self._call_finish_job:311 if not self._call_finish_job:
306 return reason312 return reason
307 if isinstance(reason, failure.Failure):313 status = self._reasonToStatus(reason)
314 if status == CodeImportResultStatus.FAILURE:
308 self._log_file.write("Import failed:\n")315 self._log_file.write("Import failed:\n")
309 reason.printTraceback(self._log_file)316 reason.printTraceback(self._log_file)
310 self._logOopsFromFailure(reason)317 self._logOopsFromFailure(reason)
311 status = CodeImportResultStatus.FAILURE
312 else:318 else:
313 self._logger.info('Import succeeded.')319 self._logger.info('Import succeeded.')
314 status = CodeImportResultStatus.SUCCESS
315 return self.finishJob(status)320 return self.finishJob(status)
316321
=== modified file 'scripts/code-import-worker.py'
--- scripts/code-import-worker.py 2009-11-20 07:09:54 +0000
+++ scripts/code-import-worker.py 2010-02-01 05:56:15 +0000
@@ -19,6 +19,7 @@
19import _pythonpath19import _pythonpath
2020
21from optparse import OptionParser21from optparse import OptionParser
22import sys
2223
23from bzrlib.transport import get_transport24from bzrlib.transport import get_transport
2425
@@ -58,9 +59,9 @@
58 source_details,59 source_details,
59 get_transport(config.codeimport.foreign_tree_store),60 get_transport(config.codeimport.foreign_tree_store),
60 get_default_bazaar_branch_store(), self.logger)61 get_default_bazaar_branch_store(), self.logger)
61 import_worker.run()62 return import_worker.run()
6263
6364
64if __name__ == '__main__':65if __name__ == '__main__':
65 script = CodeImportWorker()66 script = CodeImportWorker()
66 script.main()67 sys.exit(script.main())