Merge lp:~mwhudson/launchpad/reduce-concurrent-job-count 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/reduce-concurrent-job-count
Merge into: lp:launchpad
Diff against target: 492 lines (+93/-79)
15 files modified
cronscripts/code-import-dispatcher.py (+8/-1)
lib/canonical/config/schema-lazr.conf (+1/-1)
lib/lp/code/doc/xmlrpc-codeimport-scheduler.txt (+3/-6)
lib/lp/code/interfaces/codeimportjob.py (+1/-1)
lib/lp/code/interfaces/codeimportmachine.py (+1/-1)
lib/lp/code/interfaces/codeimportscheduler.py (+1/-1)
lib/lp/code/model/codeimportjob.py (+2/-2)
lib/lp/code/model/codeimportmachine.py (+2/-3)
lib/lp/code/model/tests/test_codeimport.py (+1/-1)
lib/lp/code/model/tests/test_codeimportjob.py (+3/-3)
lib/lp/code/model/tests/test_codeimportmachine.py (+9/-11)
lib/lp/code/xmlrpc/codeimportscheduler.py (+3/-2)
lib/lp/codehosting/codeimport/dispatcher.py (+4/-2)
lib/lp/codehosting/codeimport/tests/test_dispatcher.py (+53/-43)
lib/lp/codehosting/codeimport/tests/test_workermonitor.py (+1/-1)
To merge this branch: bzr merge lp:~mwhudson/launchpad/reduce-concurrent-job-count
Reviewer Review Type Date Requested Status
Tim Penhey (community) Approve
Michael Nelson (community) code Needs Information
Review via email: mp+19574@code.launchpad.net

Commit message

Allow a code import machine to set its own limit for how many jobs to run, and lower the default to 3.

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

As discussed in the linked bug, we should probably do something more sophisticated here to give a machine a reasonable number of jobs to run, but this is much easier for sure.

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Hi Michael,

doesn't this need to be a change on the lp-production-configs? Currently there I see:

production/launchpad-lazr.conf
55:max_jobs_per_machine: 2

review: Needs Information (code)
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Hm, I don't think that config setting takes effect at the moment. I'll investigate further!

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Revision history for this message
Tim Penhey (thumper) :
review: Approve
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

OK, so as discussed in IRC, I've made the worker limit settable on the code-import-dispatcher command line, and lowered the default to 3. Please re-review.

Revision history for this message
Tim Penhey (thumper) wrote :

lib/lp/codehosting/codeimport/dispatcher.py

You have:

    if worker_limit is None:
        self.worker_limit = \
            config.codeimportdispatcher.max_jobs_per_machine

Which doesn't set self.worker_limit if worker_limit is passed in.

    if worker_limit is None:
        worker_limit = config.codeimportdispatcher.max_jobs_per_machine
    self.worker_limit = worker_limit

perhaps.

How hard would it be to add an int Option? That way the OptionParser object does the integer validation and conversion for you.

  --max-jobs

review: Needs Fixing
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Tim,

can you rereview now please?

Cheers,
mwh

Revision history for this message
Tim Penhey (thumper) wrote :

Looks good now

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cronscripts/code-import-dispatcher.py'
2--- cronscripts/code-import-dispatcher.py 2009-10-13 14:38:07 +0000
3+++ cronscripts/code-import-dispatcher.py 2010-02-22 19:42:20 +0000
4@@ -18,6 +18,12 @@
5
6 class CodeImportDispatcherScript(LaunchpadScript):
7
8+ def add_my_options(self):
9+ self.parser.add_option(
10+ "--max-jobs", dest="max_jobs", type=int,
11+ default=config.codeimportdispatcher.max_jobs_per_machine,
12+ help="The maximum number of jobs to run on this machine.")
13+
14 def run(self, use_web_security=False, implicit_begin=True,
15 isolation=None):
16 """See `LaunchpadScript.run`.
17@@ -30,7 +36,8 @@
18 def main(self):
19 globalErrorUtility.configure('codeimportdispatcher')
20
21- CodeImportDispatcher(self.logger).findAndDispatchJob(
22+ dispatcher = CodeImportDispatcher(self.logger, self.options.max_jobs)
23+ dispatcher.findAndDispatchJob(
24 ServerProxy(config.codeimportdispatcher.codeimportscheduler_url))
25
26
27
28=== modified file 'lib/canonical/config/schema-lazr.conf'
29--- lib/canonical/config/schema-lazr.conf 2010-02-20 04:13:47 +0000
30+++ lib/canonical/config/schema-lazr.conf 2010-02-22 19:42:20 +0000
31@@ -449,7 +449,7 @@
32 codeimportscheduler_url: http://xmlrpc-private.launchpad.dev:8087/codeimportscheduler
33
34 # The maximum number of jobs to run on a machine at one time.
35-max_jobs_per_machine: 10
36+max_jobs_per_machine: 3
37
38 # See [error_reports].
39 copy_to_zlog: False
40
41=== modified file 'lib/lp/code/doc/xmlrpc-codeimport-scheduler.txt'
42--- lib/lp/code/doc/xmlrpc-codeimport-scheduler.txt 2009-10-22 11:55:51 +0000
43+++ lib/lp/code/doc/xmlrpc-codeimport-scheduler.txt 2010-02-22 19:42:20 +0000
44@@ -32,12 +32,9 @@
45 getJobForMachine(), that returns the id of the job that the code
46 import slave should next run.
47
48- >>> codeimportscheduler_api.getJobForMachine('bazaar-importer')
49+ >>> codeimportscheduler_api.getJobForMachine('bazaar-importer', 2)
50 1
51
52- >>> from canonical.database.sqlbase import flush_database_updates
53- >>> flush_database_updates()
54-
55 The method just calls the 'getJobForMachine' method from the
56 ICodeImportJobSet interface, and tests all the details of what it does
57 can be found in the tests for ICodeImportJobSet.
58@@ -49,7 +46,7 @@
59 >>> codeimportscheduler = xmlrpclib.ServerProxy(
60 ... 'http://xmlrpc-private.launchpad.dev:8087/codeimportscheduler',
61 ... transport=XMLRPCTestTransport())
62- >>> codeimportscheduler.getJobForMachine('bazaar-importer')
63+ >>> codeimportscheduler.getJobForMachine('bazaar-importer', 2)
64 0
65
66 This includes the behaviour of auto-creating machine rows for
67@@ -60,7 +57,7 @@
68 >>> print getUtility(ICodeImportMachineSet).getByHostname(
69 ... 'doesnt-exist-yet')
70 None
71- >>> codeimportscheduler.getJobForMachine('doesnt-exist-yet')
72+ >>> codeimportscheduler.getJobForMachine('doesnt-exist-yet', 1)
73 0
74 >>> new_machine = getUtility(ICodeImportMachineSet).getByHostname(
75 ... 'doesnt-exist-yet')
76
77=== modified file 'lib/lp/code/interfaces/codeimportjob.py'
78--- lib/lp/code/interfaces/codeimportjob.py 2009-06-25 04:06:00 +0000
79+++ lib/lp/code/interfaces/codeimportjob.py 2010-02-22 19:42:20 +0000
80@@ -122,7 +122,7 @@
81 # we implement endpoint specific authentication for the private xml-rpc
82 # server.
83
84- def getJobForMachine(hostname):
85+ def getJobForMachine(hostname, worker_limit):
86 """Select a job for the given machine to run and mark it as started.
87
88 If there is not already a CodeImportMachine with the given hostname,
89
90=== modified file 'lib/lp/code/interfaces/codeimportmachine.py'
91--- lib/lp/code/interfaces/codeimportmachine.py 2009-06-25 04:06:00 +0000
92+++ lib/lp/code/interfaces/codeimportmachine.py 2010-02-22 19:42:20 +0000
93@@ -47,7 +47,7 @@
94 description=_("When the controller deamon last recorded it was"
95 " running."))
96
97- def shouldLookForJob():
98+ def shouldLookForJob(worker_limit):
99 """Should we look for a job to run on this machine?
100
101 There are three reasons we might not look for a job:
102
103=== modified file 'lib/lp/code/interfaces/codeimportscheduler.py'
104--- lib/lp/code/interfaces/codeimportscheduler.py 2009-06-25 04:06:00 +0000
105+++ lib/lp/code/interfaces/codeimportscheduler.py 2010-02-22 19:42:20 +0000
106@@ -28,7 +28,7 @@
107 when they need more work to do.
108 """
109
110- def getJobForMachine(hostname):
111+ def getJobForMachine(hostname, worker_limit):
112 """Get a job to run on the slave 'hostname'.
113
114 This method selects the most appropriate job for the machine,
115
116=== modified file 'lib/lp/code/model/codeimportjob.py'
117--- lib/lp/code/model/codeimportjob.py 2010-01-27 02:48:13 +0000
118+++ lib/lp/code/model/codeimportjob.py 2010-02-22 19:42:20 +0000
119@@ -102,7 +102,7 @@
120 except SQLObjectNotFound:
121 return None
122
123- def getJobForMachine(self, hostname):
124+ def getJobForMachine(self, hostname, worker_limit):
125 """See `ICodeImportJobSet`."""
126 job_workflow = getUtility(ICodeImportJobWorkflow)
127 for job in self.getReclaimableJobs():
128@@ -111,7 +111,7 @@
129 if machine is None:
130 machine = getUtility(ICodeImportMachineSet).new(
131 hostname, CodeImportMachineState.ONLINE)
132- elif not machine.shouldLookForJob():
133+ elif not machine.shouldLookForJob(worker_limit):
134 return None
135 job = CodeImportJob.selectOne(
136 """id IN (SELECT id FROM CodeImportJob
137
138=== modified file 'lib/lp/code/model/codeimportmachine.py'
139--- lib/lp/code/model/codeimportmachine.py 2009-06-25 04:06:00 +0000
140+++ lib/lp/code/model/codeimportmachine.py 2010-02-22 19:42:20 +0000
141@@ -51,7 +51,7 @@
142 'CodeImportEvent', joinColumn='machine',
143 orderBy=['-date_created', '-id'])
144
145- def shouldLookForJob(self):
146+ def shouldLookForJob(self, worker_limit):
147 """See `ICodeImportMachine`."""
148 job_count = self.current_jobs.count()
149
150@@ -64,8 +64,7 @@
151 CodeImportMachineOfflineReason.QUIESCED)
152 return False
153 elif self.state == CodeImportMachineState.ONLINE:
154- max_jobs = config.codeimportdispatcher.max_jobs_per_machine
155- return job_count < max_jobs
156+ return job_count < worker_limit
157 else:
158 raise AssertionError(
159 "Unknown machine state %r??" % self.state)
160
161=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
162--- lib/lp/code/model/tests/test_codeimport.py 2010-02-01 03:55:59 +0000
163+++ lib/lp/code/model/tests/test_codeimport.py 2010-02-22 19:42:20 +0000
164@@ -195,7 +195,7 @@
165
166 def makeApprovedImportWithRunningJob(self):
167 code_import = self.makeApprovedImportWithPendingJob()
168- job = CodeImportJobSet().getJobForMachine('machine')
169+ job = CodeImportJobSet().getJobForMachine('machine', 10)
170 self.assertEqual(code_import.import_job, job)
171 return code_import
172
173
174=== modified file 'lib/lp/code/model/tests/test_codeimportjob.py'
175--- lib/lp/code/model/tests/test_codeimportjob.py 2010-02-01 03:55:59 +0000
176+++ lib/lp/code/model/tests/test_codeimportjob.py 2010-02-22 19:42:20 +0000
177@@ -114,7 +114,7 @@
178 def assertJobIsSelected(self, desired_job):
179 """Assert that the expected job is chosen by getJobForMachine."""
180 observed_job = getUtility(ICodeImportJobSet).getJobForMachine(
181- self.machine.hostname)
182+ self.machine.hostname, 10)
183 self.assert_(observed_job is not None, "No job was selected.")
184 self.assertEqual(desired_job, observed_job,
185 "Expected job not selected.")
186@@ -122,7 +122,7 @@
187 def assertNoJobSelected(self):
188 """Assert that no job is selected."""
189 observed_job = getUtility(ICodeImportJobSet).getJobForMachine(
190- 'machine')
191+ 'machine', 10)
192 self.assert_(observed_job is None, "Job unexpectedly selected.")
193
194 def test_nothingSelectedIfNothingCreated(self):
195@@ -274,7 +274,7 @@
196 machine = self.factory.makeCodeImportMachine(set_online=True)
197 login(ANONYMOUS)
198 getUtility(ICodeImportJobSet).getJobForMachine(
199- machine.hostname)
200+ machine.hostname, 10)
201 login_for_code_imports()
202 # Now there are no reclaimable jobs.
203 self.assertReclaimableJobs([])
204
205=== modified file 'lib/lp/code/model/tests/test_codeimportmachine.py'
206--- lib/lp/code/model/tests/test_codeimportmachine.py 2009-06-25 04:06:00 +0000
207+++ lib/lp/code/model/tests/test_codeimportmachine.py 2010-02-22 19:42:20 +0000
208@@ -9,7 +9,6 @@
209
210 from zope.component import getUtility
211
212-from canonical.config import config
213 from canonical.database.constants import UTC_NOW
214 from lp.code.enums import (
215 CodeImportMachineOfflineReason, CodeImportMachineState)
216@@ -39,13 +38,13 @@
217 def test_machineIsOffline(self):
218 # When the machine is offline, we shouldn't look for any jobs.
219 self.machine.setOffline(CodeImportMachineOfflineReason.STOPPED)
220- self.assertFalse(self.machine.shouldLookForJob())
221+ self.assertFalse(self.machine.shouldLookForJob(10))
222
223 def test_machineIsQuiescingNoJobsRunning(self):
224 # When the machine is quiescing and no jobs are running on this
225 # machine, we should set the machine to OFFLINE and not look for jobs.
226 self.machine.setQuiescing(self.factory.makePerson())
227- self.assertFalse(self.machine.shouldLookForJob())
228+ self.assertFalse(self.machine.shouldLookForJob(10))
229 self.assertEqual(self.machine.state, CodeImportMachineState.OFFLINE)
230
231 def test_machineIsQuiescingWithJobsRunning(self):
232@@ -53,20 +52,19 @@
233 # machine, we shouldn't look for any more jobs.
234 self.createJobRunningOnMachine(self.machine)
235 self.machine.setQuiescing(self.factory.makePerson())
236- self.assertFalse(self.machine.shouldLookForJob())
237+ self.assertFalse(self.machine.shouldLookForJob(10))
238 self.assertEqual(self.machine.state, CodeImportMachineState.QUIESCING)
239
240 def test_enoughJobsRunningOnMachine(self):
241 # When there are already enough jobs running on this machine, we
242 # shouldn't look for any more jobs.
243- for i in range(config.codeimportdispatcher.max_jobs_per_machine):
244- self.createJobRunningOnMachine(self.machine)
245- self.assertFalse(self.machine.shouldLookForJob())
246+ self.createJobRunningOnMachine(self.machine)
247+ self.assertFalse(self.machine.shouldLookForJob(worker_limit=1))
248
249 def test_shouldLook(self):
250 # If the machine is online and there are not already
251 # max_jobs_per_machine jobs running, then we should look for a job.
252- self.assertTrue(self.machine.shouldLookForJob())
253+ self.assertTrue(self.machine.shouldLookForJob(worker_limit=1))
254
255 def test_noHeartbeatWhenCreated(self):
256 # Machines are created with a NULL heartbeat.
257@@ -75,18 +73,18 @@
258 def test_noHeartbeatUpdateWhenOffline(self):
259 # When the machine is offline, the heartbeat is not updated.
260 self.machine.setOffline(CodeImportMachineOfflineReason.STOPPED)
261- self.machine.shouldLookForJob()
262+ self.machine.shouldLookForJob(10)
263 self.assertTrue(self.machine.heartbeat is None)
264
265 def test_heartbeatUpdateWhenQuiescing(self):
266 # When the machine is quiescing, the heartbeat is updated.
267 self.machine.setQuiescing(self.factory.makePerson())
268- self.machine.shouldLookForJob()
269+ self.machine.shouldLookForJob(10)
270 self.assertSqlAttributeEqualsDate(self.machine, 'heartbeat', UTC_NOW)
271
272 def test_heartbeatUpdateWhenOnline(self):
273 # When the machine is online, the heartbeat is updated.
274- self.machine.shouldLookForJob()
275+ self.machine.shouldLookForJob(10)
276 self.assertSqlAttributeEqualsDate(self.machine, 'heartbeat', UTC_NOW)
277
278
279
280=== modified file 'lib/lp/code/xmlrpc/codeimportscheduler.py'
281--- lib/lp/code/xmlrpc/codeimportscheduler.py 2009-06-25 04:06:00 +0000
282+++ lib/lp/code/xmlrpc/codeimportscheduler.py 2010-02-22 19:42:20 +0000
283@@ -22,9 +22,10 @@
284
285 implements(ICodeImportScheduler)
286
287- def getJobForMachine(self, hostname):
288+ def getJobForMachine(self, hostname, worker_limit):
289 """See `ICodeImportScheduler`."""
290- job = getUtility(ICodeImportJobSet).getJobForMachine(hostname)
291+ job = getUtility(ICodeImportJobSet).getJobForMachine(
292+ hostname, worker_limit)
293 if job is not None:
294 return job.id
295 else:
296
297=== modified file 'lib/lp/codehosting/codeimport/dispatcher.py'
298--- lib/lp/codehosting/codeimport/dispatcher.py 2009-06-25 04:06:00 +0000
299+++ lib/lp/codehosting/codeimport/dispatcher.py 2010-02-22 19:42:20 +0000
300@@ -32,12 +32,13 @@
301 worker_script = os.path.join(
302 config.root, 'scripts', 'code-import-worker-db.py')
303
304- def __init__(self, logger):
305+ def __init__(self, logger, worker_limit):
306 """Initialize an instance.
307
308 :param logger: A `Logger` object.
309 """
310 self.logger = logger
311+ self.worker_limit = worker_limit
312
313 def getHostname(self):
314 """Return the hostname of this machine.
315@@ -66,7 +67,8 @@
316 def findAndDispatchJob(self, scheduler_client):
317 """Check for and dispatch a job if necessary."""
318
319- job_id = scheduler_client.getJobForMachine(self.getHostname())
320+ job_id = scheduler_client.getJobForMachine(
321+ self.getHostname(), self.worker_limit)
322
323 if job_id == 0:
324 self.logger.info("No jobs pending.")
325
326=== modified file 'lib/lp/codehosting/codeimport/tests/test_dispatcher.py'
327--- lib/lp/codehosting/codeimport/tests/test_dispatcher.py 2009-12-14 18:11:07 +0000
328+++ lib/lp/codehosting/codeimport/tests/test_dispatcher.py 2010-02-22 19:42:20 +0000
329@@ -11,64 +11,60 @@
330 import shutil
331 import socket
332 import tempfile
333-from textwrap import dedent
334 from unittest import TestLoader
335
336-from twisted.trial.unittest import TestCase
337-
338-from canonical.config import config
339-from lp.codehosting.codeimport.dispatcher import CodeImportDispatcher
340 from canonical.launchpad import scripts
341 from canonical.launchpad.scripts.logger import QuietFakeLogger
342-from canonical.testing.layers import TwistedLaunchpadZopelessLayer
343+from canonical.testing.layers import BaseLayer
344+
345+from lp.codehosting.codeimport.dispatcher import CodeImportDispatcher
346+from lp.testing import TestCase
347
348
349 class StubSchedulerClient:
350- """A stub scheduler client that returns a pre-arranged answer."""
351+ """A scheduler client that returns a pre-arranged answer."""
352
353 def __init__(self, id_to_return):
354 self.id_to_return = id_to_return
355
356- def getJobForMachine(self, machine):
357+ def getJobForMachine(self, machine, limit):
358 return self.id_to_return
359
360
361+class MockSchedulerClient:
362+ """A scheduler client that records calls to `getJobForMachine`."""
363+
364+ def __init__(self):
365+ self.calls = []
366+
367+ def getJobForMachine(self, machine, limit):
368+ self.calls.append((machine, limit))
369+ return 0
370+
371+
372 class TestCodeImportDispatcherUnit(TestCase):
373 """Unit tests for `CodeImportDispatcher`."""
374
375- layer = TwistedLaunchpadZopelessLayer
376+ layer = BaseLayer
377
378 def setUp(self):
379- self.config_count = 0
380- self.pushConfig(forced_hostname='none')
381- self.dispatcher = CodeImportDispatcher(QuietFakeLogger())
382-
383- def pushConfig(self, **args):
384- """Push some key-value pairs into the codeimportdispatcher config.
385-
386- The config values will be restored during test tearDown.
387- """
388- self.config_count += 1
389- name = 'test%d' % self.config_count
390- body = '\n'.join(["%s: %s"%(k, v) for k, v in args.iteritems()])
391- config.push(name, dedent("""
392- [codeimportdispatcher]
393- %s
394- """ % body))
395- self.addCleanup(config.pop, name)
396+ TestCase.setUp(self)
397+ self.pushConfig('codeimportdispatcher', forced_hostname='none')
398+
399+ def makeDispatcher(self, worker_limit=10):
400+ """Make a `CodeImportDispatcher`."""
401+ return CodeImportDispatcher(QuietFakeLogger(), worker_limit)
402
403 def test_getHostname(self):
404 # By default, getHostname return the same as socket.gethostname()
405- self.assertEqual(
406- self.dispatcher.getHostname(),
407- socket.gethostname())
408+ dispatcher = self.makeDispatcher()
409+ self.assertEqual(socket.gethostname(), dispatcher.getHostname())
410
411 def test_getHostnameOverride(self):
412 # getHostname can be overriden by the config for testing, however.
413- self.pushConfig(forced_hostname='test-value')
414- self.assertEqual(
415- self.dispatcher.getHostname(),
416- 'test-value')
417+ dispatcher = self.makeDispatcher()
418+ self.pushConfig('codeimportdispatcher', forced_hostname='test-value')
419+ self.assertEqual('test-value', dispatcher.getHostname())
420
421 def writePythonScript(self, script_path, script_body):
422 """Write out an executable Python script.
423@@ -94,6 +90,7 @@
424
425 # We create a script that writes its command line arguments to
426 # some a temporary file and examine that.
427+ dispatcher = self.makeDispatcher()
428 tmpdir = tempfile.mkdtemp()
429 self.addCleanup(shutil.rmtree, tmpdir)
430 script_path = os.path.join(tmpdir, 'script.py')
431@@ -102,26 +99,39 @@
432 script_path,
433 ['import sys',
434 'open(%r, "w").write(str(sys.argv[1:]))' % output_path])
435- self.dispatcher.worker_script = script_path
436- proc = self.dispatcher.dispatchJob(10)
437+ dispatcher.worker_script = script_path
438+ proc = dispatcher.dispatchJob(10)
439 proc.wait()
440 arglist = self.filterOutLoggingOptions(eval(open(output_path).read()))
441- self.assertEqual(arglist, ['10'])
442+ self.assertEqual(['10'], arglist)
443
444 def test_findAndDispatchJob_jobWaiting(self):
445- # If there is a job to dispatch, then we call dispatchJob with its id.
446+ # If there is a job to dispatch, then we call dispatchJob with its id
447+ # and the worker_limit supplied to the dispatcher.
448 calls = []
449- self.dispatcher.dispatchJob = lambda job_id: calls.append(job_id)
450- self.dispatcher.findAndDispatchJob(StubSchedulerClient(10))
451- self.assertEqual(calls, [10])
452+ dispatcher = self.makeDispatcher()
453+ dispatcher.dispatchJob = lambda job_id: calls.append(job_id)
454+ dispatcher.findAndDispatchJob(StubSchedulerClient(10))
455+ self.assertEqual([10], calls)
456
457 def test_findAndDispatchJob_noJobWaiting(self):
458 # If there is no job to dispatch, then we just exit quietly.
459 calls = []
460- self.dispatcher.dispatchJob = lambda job_id: calls.append(job_id)
461- self.dispatcher.findAndDispatchJob(StubSchedulerClient(0))
462- self.assertEqual(calls, [])
463+ dispatcher = self.makeDispatcher()
464+ dispatcher.dispatchJob = lambda job_id: calls.append(job_id)
465+ dispatcher.findAndDispatchJob(StubSchedulerClient(0))
466+ self.assertEqual([], calls)
467
468+ def test_findAndDispatchJob_calls_getJobForMachine_with_limit(self):
469+ # findAndDispatchJob calls getJobForMachine on the scheduler client
470+ # with the hostname and supplied worker limit.
471+ worker_limit = self.factory.getUniqueInteger()
472+ dispatcher = self.makeDispatcher(worker_limit)
473+ scheduler_client = MockSchedulerClient()
474+ dispatcher.findAndDispatchJob(scheduler_client)
475+ self.assertEqual(
476+ [(dispatcher.getHostname(), worker_limit)],
477+ scheduler_client.calls)
478
479 def test_suite():
480 return TestLoader().loadTestsFromName(__name__)
481
482=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
483--- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-02-03 19:29:27 +0000
484+++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2010-02-22 19:42:20 +0000
485@@ -566,7 +566,7 @@
486 code_import.updateFromData(
487 {'review_status': CodeImportReviewStatus.REVIEWED},
488 self.factory.makePerson())
489- job = getUtility(ICodeImportJobSet).getJobForMachine('machine')
490+ job = getUtility(ICodeImportJobSet).getJobForMachine('machine', 10)
491 self.assertEqual(code_import, job.code_import)
492 return job
493