Merge lp:~cjwatson/launchpad/decouple-code-import-worker-arguments into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18585
Proposed branch: lp:~cjwatson/launchpad/decouple-code-import-worker-arguments
Merge into: lp:launchpad
Diff against target: 808 lines (+238/-255)
8 files modified
lib/lp/code/interfaces/codeimportjob.py (+4/-1)
lib/lp/code/model/codeimportjob.py (+62/-1)
lib/lp/code/model/tests/test_codeimportjob.py (+109/-1)
lib/lp/code/xmlrpc/codeimportscheduler.py (+2/-4)
lib/lp/code/xmlrpc/tests/test_codeimportscheduler.py (+2/-4)
lib/lp/codehosting/codeimport/tests/test_worker.py (+50/-163)
lib/lp/codehosting/codeimport/tests/test_workermonitor.py (+4/-2)
lib/lp/codehosting/codeimport/worker.py (+5/-79)
To merge this branch: bzr merge lp:~cjwatson/launchpad/decouple-code-import-worker-arguments
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+341484@code.launchpad.net

Commit message

Move code import worker argument construction into CodeImportJob.

Description of the change

This can only be used from the main application side anyway because it talks directly to the database, so it's much less confusing to have this in the main application code. Moving this will also help us to split codeimport out to a separate codebase.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/interfaces/codeimportjob.py'
2--- lib/lp/code/interfaces/codeimportjob.py 2013-01-07 02:40:55 +0000
3+++ lib/lp/code/interfaces/codeimportjob.py 2018-03-15 21:30:48 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Interfaces and enumeratrions for CodeImportJobs.
10@@ -97,6 +97,9 @@
11 to the time of the current transaction.
12 """
13
14+ def makeWorkerArguments():
15+ """Return a list of worker arguments for this job."""
16+
17
18 class ICodeImportJobSet(Interface):
19 """The set of pending and active code import jobs."""
20
21=== modified file 'lib/lp/code/model/codeimportjob.py'
22--- lib/lp/code/model/codeimportjob.py 2016-10-20 09:34:02 +0000
23+++ lib/lp/code/model/codeimportjob.py 2018-03-15 21:30:48 +0000
24@@ -1,4 +1,4 @@
25-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
26+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
27 # GNU Affero General Public License version 3 (see the file LICENSE).
28
29 """Database classes for the CodeImportJob table."""
30@@ -31,6 +31,12 @@
31 CodeImportMachineState,
32 CodeImportResultStatus,
33 CodeImportReviewStatus,
34+ RevisionControlSystems,
35+ )
36+from lp.code.interfaces.branch import IBranch
37+from lp.code.interfaces.codehosting import (
38+ branch_id_alias,
39+ compose_public_url,
40 )
41 from lp.code.interfaces.codeimportevent import ICodeImportEventSet
42 from lp.code.interfaces.codeimportjob import (
43@@ -104,6 +110,61 @@
44 "id = %s AND date_due <= %s" % sqlvalues(self.id, UTC_NOW))
45 return import_job is not None
46
47+ def makeWorkerArguments(self):
48+ """See `ICodeImportJob`."""
49+ # Keep this in sync with CodeImportSourceDetails.fromArguments.
50+ code_import = self.code_import
51+ target = code_import.target
52+
53+ if IBranch.providedBy(target):
54+ target_id = target.id
55+ else:
56+ # We don't have a better way to identify the target repository
57+ # than the mutable unique name, but the macaroon constrains
58+ # pushes tightly enough that the worst case is an authentication
59+ # failure.
60+ target_id = target.unique_name
61+
62+ if code_import.rcs_type == RevisionControlSystems.BZR_SVN:
63+ rcs_type = 'bzr-svn'
64+ target_rcs_type = 'bzr'
65+ elif code_import.rcs_type == RevisionControlSystems.CVS:
66+ rcs_type = 'cvs'
67+ target_rcs_type = 'bzr'
68+ elif code_import.rcs_type == RevisionControlSystems.GIT:
69+ rcs_type = 'git'
70+ if IBranch.providedBy(target):
71+ target_rcs_type = 'bzr'
72+ else:
73+ target_rcs_type = 'git'
74+ elif code_import.rcs_type == RevisionControlSystems.BZR:
75+ rcs_type = 'bzr'
76+ target_rcs_type = 'bzr'
77+ else:
78+ raise AssertionError("Unknown rcs_type %r." % code_import.rcs_type)
79+
80+ result = [str(target_id), '%s:%s' % (rcs_type, target_rcs_type)]
81+ if rcs_type in ('bzr-svn', 'git', 'bzr'):
82+ result.append(str(code_import.url))
83+ if (IBranch.providedBy(target) and
84+ target.stacked_on is not None and
85+ not target.stacked_on.private):
86+ stacked_path = branch_id_alias(target.stacked_on)
87+ stacked_on_url = compose_public_url('http', stacked_path)
88+ result.append(stacked_on_url)
89+ elif rcs_type == 'cvs':
90+ result.append(str(code_import.cvs_root))
91+ result.append(str(code_import.cvs_module))
92+ else:
93+ raise AssertionError("Unknown rcs_type %r." % rcs_type)
94+ if target_rcs_type == 'git':
95+ issuer = getUtility(IMacaroonIssuer, 'code-import-job')
96+ macaroon = removeSecurityProxy(issuer).issueMacaroon(self)
97+ # XXX cjwatson 2016-10-12: Consider arranging for this to be
98+ # passed to worker processes in the environment instead.
99+ result.append(macaroon.serialize())
100+ return result
101+
102
103 @implementer(ICodeImportJobSet, ICodeImportJobSetPublic)
104 class CodeImportJobSet(object):
105
106=== modified file 'lib/lp/code/model/tests/test_codeimportjob.py'
107--- lib/lp/code/model/tests/test_codeimportjob.py 2018-01-02 16:10:26 +0000
108+++ lib/lp/code/model/tests/test_codeimportjob.py 2018-03-15 21:30:48 +0000
109@@ -1,4 +1,4 @@
110-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
111+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
112 # GNU Affero General Public License version 3 (see the file LICENSE).
113
114 """Unit tests for CodeImportJob and CodeImportJobWorkflow."""
115@@ -17,13 +17,17 @@
116 from pymacaroons import Macaroon
117 from pytz import UTC
118 from testtools.matchers import (
119+ Equals,
120+ Matcher,
121 MatchesListwise,
122 MatchesStructure,
123+ Mismatch,
124 )
125 import transaction
126 from zope.component import getUtility
127 from zope.security.proxy import removeSecurityProxy
128
129+from lp.app.enums import InformationType
130 from lp.code.enums import (
131 CodeImportEventType,
132 CodeImportJobState,
133@@ -31,6 +35,10 @@
134 CodeImportReviewStatus,
135 TargetRevisionControlSystems,
136 )
137+from lp.code.interfaces.codehosting import (
138+ branch_id_alias,
139+ compose_public_url,
140+ )
141 from lp.code.interfaces.codeimport import ICodeImportSet
142 from lp.code.interfaces.codeimportevent import ICodeImportEventSet
143 from lp.code.interfaces.codeimportjob import (
144@@ -76,6 +84,106 @@
145 return login_celebrity('vcs_imports')
146
147
148+class CodeImportJobMacaroonVerifies(Matcher):
149+ """Matches if a code-import-job macaroon can be verified."""
150+
151+ def __init__(self, code_import):
152+ self.code_import = code_import
153+
154+ def match(self, macaroon_raw):
155+ issuer = getUtility(IMacaroonIssuer, 'code-import-job')
156+ macaroon = Macaroon.deserialize(macaroon_raw)
157+ if not issuer.verifyMacaroon(macaroon, self.code_import.import_job):
158+ return Mismatch("Macaroon '%s' does not verify" % macaroon_raw)
159+
160+
161+class TestCodeImportJob(TestCaseWithFactory):
162+
163+ layer = DatabaseFunctionalLayer
164+
165+ def setUp(self):
166+ super(TestCodeImportJob, self).setUp()
167+ login_for_code_imports()
168+
169+ def assertArgumentsMatch(self, code_import, matcher, start_job=False):
170+ job = self.factory.makeCodeImportJob(code_import=code_import)
171+ if start_job:
172+ machine = self.factory.makeCodeImportMachine(set_online=True)
173+ getUtility(ICodeImportJobWorkflow).startJob(job, machine)
174+ self.assertThat(job.makeWorkerArguments(), matcher)
175+
176+ def test_bzr_arguments(self):
177+ code_import = self.factory.makeCodeImport(
178+ bzr_branch_url="http://example.com/foo")
179+ self.assertArgumentsMatch(
180+ code_import, Equals([
181+ str(code_import.branch.id), 'bzr:bzr',
182+ 'http://example.com/foo']))
183+
184+ def test_git_arguments(self):
185+ code_import = self.factory.makeCodeImport(
186+ git_repo_url="git://git.example.com/project.git")
187+ self.assertArgumentsMatch(
188+ code_import, Equals([
189+ str(code_import.branch.id), 'git:bzr',
190+ 'git://git.example.com/project.git']))
191+
192+ def test_git_to_git_arguments(self):
193+ self.pushConfig('codeimport', macaroon_secret_key='some-secret')
194+ self.useFixture(GitHostingFixture())
195+ code_import = self.factory.makeCodeImport(
196+ git_repo_url="git://git.example.com/project.git",
197+ target_rcs_type=TargetRevisionControlSystems.GIT)
198+ self.assertArgumentsMatch(
199+ code_import, MatchesListwise([
200+ Equals(code_import.git_repository.unique_name),
201+ Equals('git:git'), Equals('git://git.example.com/project.git'),
202+ CodeImportJobMacaroonVerifies(code_import)]),
203+ # Start the job so that the macaroon can be verified.
204+ start_job=True)
205+
206+ def test_cvs_arguments(self):
207+ code_import = self.factory.makeCodeImport(
208+ cvs_root=':pserver:foo@example.com/bar', cvs_module='bar')
209+ self.assertArgumentsMatch(
210+ code_import, Equals([
211+ str(code_import.branch.id), 'cvs:bzr',
212+ ':pserver:foo@example.com/bar', 'bar']))
213+
214+ def test_bzr_svn_arguments(self):
215+ code_import = self.factory.makeCodeImport(
216+ svn_branch_url='svn://svn.example.com/trunk')
217+ self.assertArgumentsMatch(
218+ code_import, Equals([
219+ str(code_import.branch.id), 'bzr-svn:bzr',
220+ 'svn://svn.example.com/trunk']))
221+
222+ def test_bzr_stacked(self):
223+ devfocus = self.factory.makeAnyBranch()
224+ code_import = self.factory.makeCodeImport(
225+ bzr_branch_url='bzr://bzr.example.com/foo',
226+ context=devfocus.target.context)
227+ code_import.branch.stacked_on = devfocus
228+ self.assertArgumentsMatch(
229+ code_import, Equals([
230+ str(code_import.branch.id), 'bzr:bzr',
231+ 'bzr://bzr.example.com/foo',
232+ compose_public_url('http', branch_id_alias(devfocus))]))
233+
234+ def test_bzr_stacked_private(self):
235+ # Code imports can't be stacked on private branches.
236+ devfocus = self.factory.makeAnyBranch(
237+ information_type=InformationType.USERDATA)
238+ code_import = self.factory.makeCodeImport(
239+ context=removeSecurityProxy(devfocus).target.context,
240+ bzr_branch_url='bzr://bzr.example.com/foo')
241+ branch = removeSecurityProxy(code_import.branch)
242+ branch.stacked_on = devfocus
243+ self.assertArgumentsMatch(
244+ code_import, Equals([
245+ str(branch.id), 'bzr:bzr', 'bzr://bzr.example.com/foo']))
246+
247+
248 class TestCodeImportJobSet(TestCaseWithFactory):
249 """Unit tests for the CodeImportJobSet utility."""
250
251
252=== modified file 'lib/lp/code/xmlrpc/codeimportscheduler.py'
253--- lib/lp/code/xmlrpc/codeimportscheduler.py 2016-10-11 13:41:32 +0000
254+++ lib/lp/code/xmlrpc/codeimportscheduler.py 2018-03-15 21:30:48 +0000
255@@ -1,4 +1,4 @@
256-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
257+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
258 # GNU Affero General Public License version 3 (see the file LICENSE).
259
260 """The code import scheduler XML-RPC API."""
261@@ -18,7 +18,6 @@
262 ICodeImportJobWorkflow,
263 )
264 from lp.code.interfaces.codeimportscheduler import ICodeImportScheduler
265-from lp.codehosting.codeimport.worker import CodeImportSourceDetails
266 from lp.services.librarian.interfaces import ILibraryFileAliasSet
267 from lp.services.webapp import (
268 canonical_url,
269@@ -67,8 +66,7 @@
270 @return_fault
271 def _getImportDataForJobID(self, job_id):
272 job = self._getJob(job_id)
273- arguments = CodeImportSourceDetails.fromCodeImportJob(
274- job).asArguments()
275+ arguments = job.makeWorkerArguments()
276 target = job.code_import.target
277 target_url = canonical_url(target)
278 log_file_name = '%s.log' % target.unique_name[1:].replace('/', '-')
279
280=== modified file 'lib/lp/code/xmlrpc/tests/test_codeimportscheduler.py'
281--- lib/lp/code/xmlrpc/tests/test_codeimportscheduler.py 2016-10-11 13:41:32 +0000
282+++ lib/lp/code/xmlrpc/tests/test_codeimportscheduler.py 2018-03-15 21:30:48 +0000
283@@ -1,4 +1,4 @@
284-# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
285+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
286 # GNU Affero General Public License version 3 (see the file LICENSE).
287
288 """Test for the methods of `ICodeImportScheduler`."""
289@@ -15,7 +15,6 @@
290 from lp.code.model.codeimportjob import CodeImportJob
291 from lp.code.tests.codeimporthelpers import make_running_import
292 from lp.code.xmlrpc.codeimportscheduler import CodeImportSchedulerAPI
293-from lp.codehosting.codeimport.worker import CodeImportSourceDetails
294 from lp.services.database.constants import UTC_NOW
295 from lp.services.webapp import canonical_url
296 from lp.testing import (
297@@ -63,8 +62,7 @@
298 code_import = removeSecurityProxy(code_import_job).code_import
299 code_import_arguments, target_url, log_file_name = \
300 self.api.getImportDataForJobID(code_import_job.id)
301- import_as_arguments = CodeImportSourceDetails.fromCodeImportJob(
302- code_import_job).asArguments()
303+ import_as_arguments = code_import_job.makeWorkerArguments()
304 expected_log_file_name = '%s.log' % (
305 code_import.target.unique_name[1:].replace('/', '-'))
306 self.assertEqual(
307
308=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
309--- lib/lp/codehosting/codeimport/tests/test_worker.py 2018-02-14 01:27:28 +0000
310+++ lib/lp/codehosting/codeimport/tests/test_worker.py 2018-03-15 21:30:48 +0000
311@@ -1,4 +1,4 @@
312-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
313+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
314 # GNU Affero General Public License version 3 (see the file LICENSE).
315
316 """Tests for the code import worker."""
317@@ -45,26 +45,10 @@
318 )
319 from dulwich.repo import Repo as GitRepo
320 from fixtures import FakeLogger
321-from pymacaroons import Macaroon
322 import subvertpy
323 import subvertpy.client
324 import subvertpy.ra
325-from testtools.matchers import (
326- Equals,
327- Matcher,
328- MatchesListwise,
329- Mismatch,
330- )
331-from zope.component import getUtility
332
333-from lp.app.enums import InformationType
334-from lp.code.enums import TargetRevisionControlSystems
335-from lp.code.interfaces.codehosting import (
336- branch_id_alias,
337- compose_public_url,
338- )
339-from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow
340-from lp.code.tests.helpers import GitHostingFixture
341 import lp.codehosting
342 from lp.codehosting.codeimport.tarball import (
343 create_tarball,
344@@ -100,16 +84,8 @@
345 from lp.codehosting.tests.helpers import create_branch_with_one_revision
346 from lp.services.config import config
347 from lp.services.log.logger import BufferLogger
348-from lp.services.macaroons.interfaces import IMacaroonIssuer
349-from lp.testing import (
350- celebrity_logged_in,
351- TestCase,
352- TestCaseWithFactory,
353- )
354-from lp.testing.layers import (
355- BaseLayer,
356- DatabaseFunctionalLayer,
357- )
358+from lp.testing import TestCase
359+from lp.testing.layers import BaseLayer
360
361
362 class ForeignBranchPluginLayer(BaseLayer):
363@@ -820,8 +796,8 @@
364 raise NotImplementedError(
365 "Override this with a VCS-specific implementation.")
366
367- def makeSourceDetails(self, module_name, files, stacked_on_url=None):
368- """Make a `CodeImportSourceDetails` that points to a real repository.
369+ def makeWorkerArguments(self, module_name, files, stacked_on_url=None):
370+ """Make a list of worker arguments pointing to a real repository.
371
372 This should set `self.foreign_commit_count` to an appropriate value.
373
374@@ -830,6 +806,11 @@
375 raise NotImplementedError(
376 "Override this with a VCS-specific implementation.")
377
378+ def makeSourceDetails(self, module_name, files, stacked_on_url=None):
379+ """Make a `CodeImportSourceDetails` pointing to a real repository."""
380+ return CodeImportSourceDetails.fromArguments(self.makeWorkerArguments(
381+ module_name, files, stacked_on_url=stacked_on_url))
382+
383 def getStoredBazaarBranch(self, worker):
384 """Get the Bazaar branch 'worker' stored into its BazaarBranchStore.
385 """
386@@ -876,8 +857,9 @@
387 def test_import_script(self):
388 # Like test_import, but using the code-import-worker.py script
389 # to perform the import.
390- source_details = self.makeSourceDetails(
391+ arguments = self.makeWorkerArguments(
392 'trunk', [('README', 'Original contents')])
393+ source_details = CodeImportSourceDetails.fromArguments(arguments)
394
395 clean_up_default_stores_for_import(source_details)
396
397@@ -885,8 +867,7 @@
398 config.root, 'scripts', 'code-import-worker.py')
399 output = tempfile.TemporaryFile()
400 retcode = subprocess.call(
401- [script_path, '--access-policy=anything'] +
402- source_details.asArguments(),
403+ [script_path, '--access-policy=anything'] + arguments,
404 stderr=output, stdout=output)
405 self.assertEqual(retcode, 0)
406
407@@ -914,8 +895,9 @@
408 # with a code of CodeImportWorkerExitCode.SUCCESS. After a successful
409 # import that does not import revisions, the worker exits with a code
410 # of CodeImportWorkerExitCode.SUCCESS_NOCHANGE.
411- source_details = self.makeSourceDetails(
412+ arguments = self.makeWorkerArguments(
413 'trunk', [('README', 'Original contents')])
414+ source_details = CodeImportSourceDetails.fromArguments(arguments)
415
416 clean_up_default_stores_for_import(source_details)
417
418@@ -923,13 +905,11 @@
419 config.root, 'scripts', 'code-import-worker.py')
420 output = tempfile.TemporaryFile()
421 retcode = subprocess.call(
422- [script_path, '--access-policy=anything'] +
423- source_details.asArguments(),
424+ [script_path, '--access-policy=anything'] + arguments,
425 stderr=output, stdout=output)
426 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS)
427 retcode = subprocess.call(
428- [script_path, '--access-policy=anything'] +
429- source_details.asArguments(),
430+ [script_path, '--access-policy=anything'] + arguments,
431 stderr=output, stdout=output)
432 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE)
433
434@@ -971,9 +951,8 @@
435 self.foreign_commit_count += 1
436 shutil.rmtree('working_dir')
437
438- def makeSourceDetails(self, module_name, files, stacked_on_url=None):
439- """Make a CVS `CodeImportSourceDetails` pointing at a real CVS repo.
440- """
441+ def makeWorkerArguments(self, module_name, files, stacked_on_url=None):
442+ """Make CVS worker arguments pointing at a real CVS repo."""
443 cvs_server = CVSServer(self.makeTemporaryDirectory())
444 cvs_server.start_server()
445 self.addCleanup(cvs_server.stop_server)
446@@ -982,9 +961,10 @@
447
448 self.foreign_commit_count = 2
449
450- return self.factory.makeCodeImportSourceDetails(
451- rcstype='cvs', cvs_root=cvs_server.getRoot(), cvs_module='trunk',
452- stacked_on_url=stacked_on_url)
453+ return [
454+ str(self.factory.getUniqueInteger()), 'cvs:bzr',
455+ cvs_server.getRoot(), 'trunk',
456+ ]
457
458
459 class SubversionImportHelpers:
460@@ -1009,9 +989,8 @@
461 self.foreign_commit_count += 1
462 shutil.rmtree('working_tree')
463
464- def makeSourceDetails(self, branch_name, files, stacked_on_url=None):
465- """Make a SVN `CodeImportSourceDetails` pointing at a real SVN repo.
466- """
467+ def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
468+ """Make SVN worker arguments pointing at a real SVN repo."""
469 svn_server = SubversionServer(self.makeTemporaryDirectory())
470 svn_server.start_server()
471 self.addCleanup(svn_server.stop_server)
472@@ -1019,9 +998,13 @@
473 svn_branch_url = svn_server.makeBranch(branch_name, files)
474 svn_branch_url = svn_branch_url.replace('://localhost/', ':///')
475 self.foreign_commit_count = 2
476- return self.factory.makeCodeImportSourceDetails(
477- rcstype=self.rcstype, url=svn_branch_url,
478- stacked_on_url=stacked_on_url)
479+ arguments = [
480+ str(self.factory.getUniqueInteger()), '%s:bzr' % self.rcstype,
481+ svn_branch_url,
482+ ]
483+ if stacked_on_url is not None:
484+ arguments.append(stacked_on_url)
485+ return arguments
486
487
488 class PullingImportWorkerTests:
489@@ -1171,9 +1154,8 @@
490 committer="Joe Random Hacker <joe@example.com>", ref=ref)
491 self.foreign_commit_count += 1
492
493- def makeSourceDetails(self, branch_name, files, stacked_on_url=None):
494- """Make a Git `CodeImportSourceDetails` pointing at a real Git repo.
495- """
496+ def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
497+ """Make Git worker arguments pointing at a real Git repo."""
498 repository_store = self.makeTemporaryDirectory()
499 git_server = GitServer(repository_store)
500 git_server.start_server()
501@@ -1182,9 +1164,13 @@
502 git_server.makeRepo('source', files)
503 self.foreign_commit_count = 1
504
505- return self.factory.makeCodeImportSourceDetails(
506- rcstype='git', url=git_server.get_url('source'),
507- stacked_on_url=stacked_on_url)
508+ arguments = [
509+ str(self.factory.getUniqueInteger()), 'git:bzr',
510+ git_server.get_url('source'),
511+ ]
512+ if stacked_on_url is not None:
513+ arguments.append(stacked_on_url)
514+ return arguments
515
516 def test_non_master(self):
517 # non-master branches can be specified in the import URL.
518@@ -1250,9 +1236,8 @@
519 committer="Joe Random Hacker <joe@example.com>")
520 self.foreign_commit_count += 1
521
522- def makeSourceDetails(self, branch_name, files, stacked_on_url=None):
523- """Make Bzr `CodeImportSourceDetails` pointing at a real Bzr repo.
524- """
525+ def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
526+ """Make Bzr worker arguments pointing at a real Bzr repo."""
527 repository_path = self.makeTemporaryDirectory()
528 bzr_server = BzrServer(repository_path)
529 bzr_server.start_server()
530@@ -1261,9 +1246,13 @@
531 bzr_server.makeRepo(files)
532 self.foreign_commit_count = 1
533
534- return self.factory.makeCodeImportSourceDetails(
535- rcstype='bzr', url=bzr_server.get_url(),
536- stacked_on_url=stacked_on_url)
537+ arguments = [
538+ str(self.factory.getUniqueInteger()), 'bzr:bzr',
539+ bzr_server.get_url(),
540+ ]
541+ if stacked_on_url is not None:
542+ arguments.append(stacked_on_url)
543+ return arguments
544
545 def test_partial(self):
546 self.skipTest(
547@@ -1416,105 +1405,3 @@
548 self.old_server.stop_server()
549 self.assertEqual(
550 CodeImportWorkerExitCode.FAILURE_INVALID, worker.run())
551-
552-
553-class CodeImportJobMacaroonVerifies(Matcher):
554- """Matches if a code-import-job macaroon can be verified."""
555-
556- def __init__(self, code_import):
557- self.code_import = code_import
558-
559- def match(self, macaroon_raw):
560- issuer = getUtility(IMacaroonIssuer, 'code-import-job')
561- macaroon = Macaroon.deserialize(macaroon_raw)
562- if not issuer.verifyMacaroon(macaroon, self.code_import.import_job):
563- return Mismatch("Macaroon '%s' does not verify" % macaroon_raw)
564-
565-
566-class CodeImportSourceDetailsTests(TestCaseWithFactory):
567-
568- layer = DatabaseFunctionalLayer
569-
570- def setUp(self):
571- # Use an admin user as we aren't checking edit permissions here.
572- TestCaseWithFactory.setUp(self, 'admin@canonical.com')
573-
574- def assertArgumentsMatch(self, code_import, matcher, start_job=False):
575- job = self.factory.makeCodeImportJob(code_import=code_import)
576- if start_job:
577- machine = self.factory.makeCodeImportMachine(set_online=True)
578- with celebrity_logged_in("vcs_imports"):
579- getUtility(ICodeImportJobWorkflow).startJob(job, machine)
580- details = CodeImportSourceDetails.fromCodeImportJob(job)
581- self.assertThat(details.asArguments(), matcher)
582-
583- def test_bzr_arguments(self):
584- code_import = self.factory.makeCodeImport(
585- bzr_branch_url="http://example.com/foo")
586- self.assertArgumentsMatch(
587- code_import, Equals([
588- str(code_import.branch.id), 'bzr:bzr',
589- 'http://example.com/foo']))
590-
591- def test_git_arguments(self):
592- code_import = self.factory.makeCodeImport(
593- git_repo_url="git://git.example.com/project.git")
594- self.assertArgumentsMatch(
595- code_import, Equals([
596- str(code_import.branch.id), 'git:bzr',
597- 'git://git.example.com/project.git']))
598-
599- def test_git_to_git_arguments(self):
600- self.pushConfig('codeimport', macaroon_secret_key='some-secret')
601- self.useFixture(GitHostingFixture())
602- code_import = self.factory.makeCodeImport(
603- git_repo_url="git://git.example.com/project.git",
604- target_rcs_type=TargetRevisionControlSystems.GIT)
605- self.assertArgumentsMatch(
606- code_import, MatchesListwise([
607- Equals(code_import.git_repository.unique_name),
608- Equals('git:git'), Equals('git://git.example.com/project.git'),
609- CodeImportJobMacaroonVerifies(code_import)]),
610- # Start the job so that the macaroon can be verified.
611- start_job=True)
612-
613- def test_cvs_arguments(self):
614- code_import = self.factory.makeCodeImport(
615- cvs_root=':pserver:foo@example.com/bar', cvs_module='bar')
616- self.assertArgumentsMatch(
617- code_import, Equals([
618- str(code_import.branch.id), 'cvs:bzr',
619- ':pserver:foo@example.com/bar', 'bar']))
620-
621- def test_bzr_svn_arguments(self):
622- code_import = self.factory.makeCodeImport(
623- svn_branch_url='svn://svn.example.com/trunk')
624- self.assertArgumentsMatch(
625- code_import, Equals([
626- str(code_import.branch.id), 'bzr-svn:bzr',
627- 'svn://svn.example.com/trunk']))
628-
629- def test_bzr_stacked(self):
630- devfocus = self.factory.makeAnyBranch()
631- code_import = self.factory.makeCodeImport(
632- bzr_branch_url='bzr://bzr.example.com/foo',
633- context=devfocus.target.context)
634- code_import.branch.stacked_on = devfocus
635- self.assertArgumentsMatch(
636- code_import, Equals([
637- str(code_import.branch.id), 'bzr:bzr',
638- 'bzr://bzr.example.com/foo',
639- compose_public_url('http', branch_id_alias(devfocus))]))
640-
641- def test_bzr_stacked_private(self):
642- # Code imports can't be stacked on private branches.
643- devfocus = self.factory.makeAnyBranch(
644- information_type=InformationType.USERDATA)
645- code_import = self.factory.makeCodeImport(
646- context=devfocus.target.context,
647- bzr_branch_url='bzr://bzr.example.com/foo')
648- code_import.branch.stacked_on = devfocus
649- self.assertArgumentsMatch(
650- code_import, Equals([
651- str(code_import.branch.id), 'bzr:bzr',
652- 'bzr://bzr.example.com/foo']))
653
654=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
655--- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2017-12-19 17:22:44 +0000
656+++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2018-03-15 21:30:48 +0000
657@@ -1,4 +1,4 @@
658-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
659+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
660 # GNU Affero General Public License version 3 (see the file LICENSE).
661
662 """Tests for the CodeImportWorkerMonitor and related classes."""
663@@ -34,6 +34,7 @@
664 from twisted.python import log
665 from twisted.web import xmlrpc
666 from zope.component import getUtility
667+from zope.security.proxy import removeSecurityProxy
668
669 from lp.code.enums import (
670 CodeImportResultStatus,
671@@ -762,7 +763,8 @@
672 self.factory.makePerson())
673 job = getUtility(ICodeImportJobSet).getJobForMachine('machine', 10)
674 self.assertEqual(code_import, job.code_import)
675- source_details = CodeImportSourceDetails.fromCodeImportJob(job)
676+ source_details = CodeImportSourceDetails.fromArguments(
677+ removeSecurityProxy(job.makeWorkerArguments()))
678 if IBranch.providedBy(code_import.target):
679 clean_up_default_stores_for_import(source_details)
680 self.addCleanup(clean_up_default_stores_for_import, source_details)
681
682=== modified file 'lib/lp/codehosting/codeimport/worker.py'
683--- lib/lp/codehosting/codeimport/worker.py 2017-05-16 11:53:28 +0000
684+++ lib/lp/codehosting/codeimport/worker.py 2018-03-15 21:30:48 +0000
685@@ -1,4 +1,4 @@
686-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
687+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
688 # GNU Affero General Public License version 3 (see the file LICENSE).
689
690 """The code import worker. This imports code from foreign repositories."""
691@@ -73,18 +73,8 @@
692 )
693 from pymacaroons import Macaroon
694 import SCM
695-from zope.component import getUtility
696-from zope.security.proxy import removeSecurityProxy
697
698-from lp.code.enums import RevisionControlSystems
699-from lp.code.interfaces.branch import (
700- get_blacklisted_hostnames,
701- IBranch,
702- )
703-from lp.code.interfaces.codehosting import (
704- branch_id_alias,
705- compose_public_url,
706- )
707+from lp.code.interfaces.branch import get_blacklisted_hostnames
708 from lp.codehosting.codeimport.foreigntree import CVSWorkingTree
709 from lp.codehosting.codeimport.tarball import (
710 create_tarball,
711@@ -97,7 +87,6 @@
712 SafeBranchOpener,
713 )
714 from lp.services.config import config
715-from lp.services.macaroons.interfaces import IMacaroonIssuer
716 from lp.services.propertycache import cachedproperty
717 from lp.services.timeout import urlfetch
718 from lp.services.utils import sanitise_urls
719@@ -285,7 +274,7 @@
720 As the worker doesn't talk to the database, we don't use
721 `CodeImport` objects for this.
722
723- The 'fromArguments' and 'asArguments' methods convert to and from a form
724+ The 'fromArguments' method builds an instance of this class from a form
725 of the information suitable for passing around on executables' command
726 lines.
727
728@@ -319,6 +308,8 @@
729 @classmethod
730 def fromArguments(cls, arguments):
731 """Convert command line-style arguments to an instance."""
732+ # Keep this in sync with CodeImportJob.makeWorkerArguments.
733+ arguments = list(arguments)
734 target_id = arguments.pop(0)
735 rcstype = arguments.pop(0)
736 # XXX cjwatson 2016-10-12: Remove compatibility code once the
737@@ -354,71 +345,6 @@
738 target_id, rcstype, target_rcstype, url, cvs_root, cvs_module,
739 stacked_on_url, macaroon)
740
741- @classmethod
742- def fromCodeImportJob(cls, job):
743- """Convert a `CodeImportJob` to an instance."""
744- code_import = job.code_import
745- target = code_import.target
746- if IBranch.providedBy(target):
747- if target.stacked_on is not None and not target.stacked_on.private:
748- stacked_path = branch_id_alias(target.stacked_on)
749- stacked_on_url = compose_public_url('http', stacked_path)
750- else:
751- stacked_on_url = None
752- target_id = target.id
753- else:
754- # We don't have a better way to identify the target repository
755- # than the mutable unique name, but the macaroon constrains
756- # pushes tightly enough that the worst case is an authentication
757- # failure.
758- target_id = target.unique_name
759- if code_import.rcs_type == RevisionControlSystems.BZR_SVN:
760- return cls(
761- target_id, 'bzr-svn', 'bzr', str(code_import.url),
762- stacked_on_url=stacked_on_url)
763- elif code_import.rcs_type == RevisionControlSystems.CVS:
764- return cls(
765- target_id, 'cvs', 'bzr',
766- cvs_root=str(code_import.cvs_root),
767- cvs_module=str(code_import.cvs_module))
768- elif code_import.rcs_type == RevisionControlSystems.GIT:
769- if IBranch.providedBy(target):
770- return cls(
771- target_id, 'git', 'bzr', str(code_import.url),
772- stacked_on_url=stacked_on_url)
773- else:
774- issuer = getUtility(IMacaroonIssuer, 'code-import-job')
775- macaroon = removeSecurityProxy(issuer).issueMacaroon(job)
776- return cls(
777- target_id, 'git', 'git', str(code_import.url),
778- macaroon=macaroon)
779- elif code_import.rcs_type == RevisionControlSystems.BZR:
780- return cls(
781- target_id, 'bzr', 'bzr', str(code_import.url),
782- stacked_on_url=stacked_on_url)
783- else:
784- raise AssertionError("Unknown rcstype %r." % code_import.rcs_type)
785-
786- def asArguments(self):
787- """Return a list of arguments suitable for passing to a child process.
788- """
789- result = [
790- str(self.target_id), '%s:%s' % (self.rcstype, self.target_rcstype)]
791- if self.rcstype in ['bzr-svn', 'git', 'bzr']:
792- result.append(self.url)
793- if self.stacked_on_url is not None:
794- result.append(self.stacked_on_url)
795- elif self.rcstype == 'cvs':
796- result.append(self.cvs_root)
797- result.append(self.cvs_module)
798- else:
799- raise AssertionError("Unknown rcstype %r." % self.rcstype)
800- if self.target_rcstype == 'git':
801- # XXX cjwatson 2016-10-12: Consider arranging for this to be
802- # passed to worker processes in the environment instead.
803- result.append(self.macaroon.serialize())
804- return result
805-
806
807 class ImportDataStore:
808 """A store for data associated with an import.