Merge lp:~cjwatson/launchpad/decouple-code-import-worker-arguments into lp:launchpad
- decouple-code-import-worker-arguments
- Merge into devel
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 |
Related bugs: |
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. |