Merge lp:~jml/launchpad/distribution-mirror-prober into lp:launchpad
- distribution-mirror-prober
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 12151 |
Proposed branch: | lp:~jml/launchpad/distribution-mirror-prober |
Merge into: | lp:launchpad |
Diff against target: |
780 lines (+259/-221) 5 files modified
cronscripts/distributionmirror-prober.py (+13/-136) lib/lp/registry/browser/tests/test_breadcrumbs.py (+2/-2) lib/lp/registry/scripts/distributionmirror_prober.py (+153/-3) lib/lp/registry/tests/test_distributionmirror_prober.py (+88/-79) lib/lp/testing/factory.py (+3/-1) |
To merge this branch: | bzr merge lp:~jml/launchpad/distribution-mirror-prober |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij (community) | code | Approve | |
Review via email: mp+44656@code.launchpad.net |
Commit message
[r=jelmer]
Description of the change
I'd like to make the distribution mirror prober look and behave like normal Twisted code. I want it to return Deferreds from methods and to only do reactor.run/stop things at the very top-most level. I want it to clean up after itself.
This branch doesn't do that though. This branch is a very simple branch that just moves code around in an effort to make the prober easier to change.
I've moved the bulk of the logic out of the cronscript and into the module, so that it can be more easily tested and used by other code.
I've also modernized the tests a little, making them inherit from lp.testing base classes, using factories where I can and generally trimming down setUp methods. I've deleted some of the unnecessary reactor-thwomping tearDowns that I added a while back, leaving only the necessary thwomps.
Jelmer Vernooij (jelmer) : | # |
Preview Diff
1 | === modified file 'cronscripts/distributionmirror-prober.py' |
2 | --- cronscripts/distributionmirror-prober.py 2010-11-08 12:52:43 +0000 |
3 | +++ cronscripts/distributionmirror-prober.py 2010-12-24 17:11:03 +0000 |
4 | @@ -10,53 +10,20 @@ |
5 | import _pythonpath |
6 | |
7 | import os |
8 | -from StringIO import StringIO |
9 | - |
10 | -from twisted.internet import reactor |
11 | - |
12 | -from zope.component import getUtility |
13 | |
14 | from canonical.config import config |
15 | -from canonical.database.sqlbase import ISOLATION_LEVEL_AUTOCOMMIT |
16 | from lp.services.scripts.base import ( |
17 | - LaunchpadCronScript, LaunchpadScriptFailure) |
18 | -from lp.registry.interfaces.distributionmirror import ( |
19 | - IDistributionMirrorSet, |
20 | - MirrorContent, |
21 | + LaunchpadCronScript, |
22 | + LaunchpadScriptFailure, |
23 | ) |
24 | -from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
25 | -from canonical.launchpad.webapp import canonical_url |
26 | -from lp.registry.scripts.distributionmirror_prober import ( |
27 | - get_expected_cdimage_paths, probe_archive_mirror, probe_cdimage_mirror) |
28 | - |
29 | - |
30 | -class DistroMirrorProber(LaunchpadCronScript): |
31 | +from lp.registry.interfaces.distributionmirror import MirrorContent |
32 | +from lp.registry.scripts.distributionmirror_prober import DistroMirrorProber |
33 | + |
34 | + |
35 | +class DistroMirrorProberScript(LaunchpadCronScript): |
36 | usage = ('%prog --content-type=(archive|cdimage) [--force] ' |
37 | '[--no-owner-notification] [--max-mirrors=N]') |
38 | |
39 | - def _sanity_check_mirror(self, mirror): |
40 | - """Check that the given mirror is official and has an http_base_url. |
41 | - """ |
42 | - assert mirror.isOfficial(), ( |
43 | - 'Non-official mirrors should not be probed') |
44 | - if mirror.base_url is None: |
45 | - self.logger.warning( |
46 | - "Mirror '%s' of distribution '%s' doesn't have a base URL; " |
47 | - "we can't probe it." % ( |
48 | - mirror.name, mirror.distribution.name)) |
49 | - return False |
50 | - return True |
51 | - |
52 | - def _create_probe_record(self, mirror, logfile): |
53 | - """Create a probe record for the given mirror with the given logfile. |
54 | - """ |
55 | - logfile.seek(0) |
56 | - filename = '%s-probe-logfile.txt' % mirror.name |
57 | - log_file = getUtility(ILibraryFileAliasSet).create( |
58 | - name=filename, size=len(logfile.getvalue()), |
59 | - file=logfile, contentType='text/plain') |
60 | - mirror.newProbeRecord(log_file) |
61 | - |
62 | def add_my_options(self): |
63 | self.parser.add_option('--content-type', |
64 | dest='content_type', default=None, action='store', |
65 | @@ -76,117 +43,27 @@ |
66 | |
67 | def main(self): |
68 | if self.options.content_type == 'archive': |
69 | - probe_function = probe_archive_mirror |
70 | content_type = MirrorContent.ARCHIVE |
71 | elif self.options.content_type == 'cdimage': |
72 | - probe_function = probe_cdimage_mirror |
73 | content_type = MirrorContent.RELEASE |
74 | else: |
75 | raise LaunchpadScriptFailure( |
76 | 'Wrong value for argument --content-type: %s' |
77 | % self.options.content_type) |
78 | |
79 | - orig_proxy = os.environ.get('http_proxy') |
80 | if config.distributionmirrorprober.use_proxy: |
81 | os.environ['http_proxy'] = config.launchpad.http_proxy |
82 | self.logger.debug("Using %s as proxy." % os.environ['http_proxy']) |
83 | else: |
84 | self.logger.debug("Not using any proxy.") |
85 | |
86 | - self.txn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) |
87 | - self.txn.begin() |
88 | - |
89 | - # Using a script argument to control a config variable is not a great |
90 | - # idea, but to me this seems better than passing the no_remote_hosts |
91 | - # value through a lot of method/function calls, until it reaches the |
92 | - # probe() method. |
93 | - if self.options.no_remote_hosts: |
94 | - localhost_only_conf = """ |
95 | - [distributionmirrorprober] |
96 | - localhost_only: True |
97 | - """ |
98 | - config.push('localhost_only_conf', localhost_only_conf) |
99 | - |
100 | - self.logger.info('Probing %s Mirrors' % content_type.title) |
101 | - |
102 | - mirror_set = getUtility(IDistributionMirrorSet) |
103 | - |
104 | - results = mirror_set.getMirrorsToProbe( |
105 | - content_type, ignore_last_probe=self.options.force, |
106 | - limit=self.options.max_mirrors) |
107 | - mirror_ids = [mirror.id for mirror in results] |
108 | - unchecked_keys = [] |
109 | - logfiles = {} |
110 | - probed_mirrors = [] |
111 | - |
112 | - for mirror_id in mirror_ids: |
113 | - mirror = mirror_set[mirror_id] |
114 | - if not self._sanity_check_mirror(mirror): |
115 | - continue |
116 | - |
117 | - # XXX: salgado 2006-05-26: |
118 | - # Some people registered mirrors on distros other than Ubuntu back |
119 | - # in the old times, so now we need to do this small hack here. |
120 | - if not mirror.distribution.full_functionality: |
121 | - self.logger.info( |
122 | - "Mirror '%s' of distribution '%s' can't be probed --we " |
123 | - "only probe Ubuntu mirrors." |
124 | - % (mirror.name, mirror.distribution.name)) |
125 | - continue |
126 | - |
127 | - probed_mirrors.append(mirror) |
128 | - logfile = StringIO() |
129 | - logfiles[mirror_id] = logfile |
130 | - probe_function(mirror, logfile, unchecked_keys, self.logger) |
131 | - |
132 | - if probed_mirrors: |
133 | - reactor.run() |
134 | - self.logger.info('Probed %d mirrors.' % len(probed_mirrors)) |
135 | - else: |
136 | - self.logger.info('No mirrors to probe.') |
137 | - |
138 | - disabled_mirrors = [] |
139 | - reenabled_mirrors = [] |
140 | - # Now that we finished probing all mirrors, we check if any of these |
141 | - # mirrors appear to have no content mirrored, and, if so, mark them as |
142 | - # disabled and notify their owners. |
143 | - expected_iso_images_count = len(get_expected_cdimage_paths()) |
144 | - notify_owner = not self.options.no_owner_notification |
145 | - for mirror in probed_mirrors: |
146 | - log = logfiles[mirror.id] |
147 | - self._create_probe_record(mirror, log) |
148 | - if mirror.shouldDisable(expected_iso_images_count): |
149 | - if mirror.enabled: |
150 | - log.seek(0) |
151 | - mirror.disable(notify_owner, log.getvalue()) |
152 | - disabled_mirrors.append(canonical_url(mirror)) |
153 | - else: |
154 | - # Ensure the mirror is enabled, so that it shows up on public |
155 | - # mirror listings. |
156 | - if not mirror.enabled: |
157 | - mirror.enabled = True |
158 | - reenabled_mirrors.append(canonical_url(mirror)) |
159 | - |
160 | - if disabled_mirrors: |
161 | - self.logger.info( |
162 | - 'Disabling %s mirror(s): %s' |
163 | - % (len(disabled_mirrors), ", ".join(disabled_mirrors))) |
164 | - if reenabled_mirrors: |
165 | - self.logger.info( |
166 | - 'Re-enabling %s mirror(s): %s' |
167 | - % (len(reenabled_mirrors), ", ".join(reenabled_mirrors))) |
168 | - # XXX: salgado 2007-04-03: |
169 | - # This should be done in LaunchpadScript.lock_and_run() when |
170 | - # the isolation used is ISOLATION_LEVEL_AUTOCOMMIT. Also note |
171 | - # that replacing this with a flush_database_updates() doesn't |
172 | - # have the same effect, it seems. |
173 | - self.txn.commit() |
174 | - |
175 | - self.logger.info('Done.') |
176 | + DistroMirrorProber(self.txn, self.logger).probe( |
177 | + content_type, self.options.no_remote_hosts, self.options.force, |
178 | + self.options.max_mirrors, not self.options.no_owner_notification) |
179 | |
180 | |
181 | if __name__ == '__main__': |
182 | - script = DistroMirrorProber('distributionmirror-prober', |
183 | - dbuser=config.distributionmirrorprober.dbuser) |
184 | + script = DistroMirrorProberScript( |
185 | + 'distributionmirror-prober', |
186 | + dbuser=config.distributionmirrorprober.dbuser) |
187 | script.lock_and_run() |
188 | - |
189 | |
190 | === modified file 'lib/lp/registry/browser/tests/test_breadcrumbs.py' |
191 | --- lib/lp/registry/browser/tests/test_breadcrumbs.py 2010-12-15 22:05:43 +0000 |
192 | +++ lib/lp/registry/browser/tests/test_breadcrumbs.py 2010-12-24 17:11:03 +0000 |
193 | @@ -58,7 +58,7 @@ |
194 | http_url=http_url) |
195 | crumbs = self.getBreadcrumbsForObject(mirror) |
196 | last_crumb = crumbs[-1] |
197 | - self.assertEqual("Example.com-archive", last_crumb.text) |
198 | + self.assertEqual(mirror.displayname, last_crumb.text) |
199 | |
200 | def test_distributionmirror_withFtpUrl(self): |
201 | # If no displayname, the breadcrumb text will be the mirror name, |
202 | @@ -70,7 +70,7 @@ |
203 | ftp_url=ftp_url) |
204 | crumbs = self.getBreadcrumbsForObject(mirror) |
205 | last_crumb = crumbs[-1] |
206 | - self.assertEqual("Example.com-archive", last_crumb.text) |
207 | + self.assertEqual(mirror.displayname, last_crumb.text) |
208 | |
209 | |
210 | class TestMilestoneBreadcrumb(BaseBreadcrumbTestCase): |
211 | |
212 | === modified file 'lib/lp/registry/scripts/distributionmirror_prober.py' |
213 | --- lib/lp/registry/scripts/distributionmirror_prober.py 2010-12-13 15:44:15 +0000 |
214 | +++ lib/lp/registry/scripts/distributionmirror_prober.py 2010-12-24 17:11:03 +0000 |
215 | @@ -1,11 +1,17 @@ |
216 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
217 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
218 | # GNU Affero General Public License version 3 (see the file LICENSE). |
219 | |
220 | +__metaclass__ = type |
221 | +__all__ = [ |
222 | + 'DistroMirrorProber', |
223 | + ] |
224 | + |
225 | from datetime import datetime |
226 | import httplib |
227 | import itertools |
228 | import logging |
229 | import os |
230 | +from StringIO import StringIO |
231 | import urllib2 |
232 | import urlparse |
233 | |
234 | @@ -20,8 +26,13 @@ |
235 | from zope.component import getUtility |
236 | |
237 | from canonical.config import config |
238 | +from canonical.database.sqlbase import ISOLATION_LEVEL_AUTOCOMMIT |
239 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
240 | +from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
241 | +from canonical.launchpad.webapp import canonical_url |
242 | from lp.registry.interfaces.distributionmirror import ( |
243 | + IDistributionMirrorSet, |
244 | + MirrorContent, |
245 | MirrorFreshness, |
246 | UnableToFetchCDImageFileList, |
247 | ) |
248 | @@ -55,8 +66,6 @@ |
249 | # start connecting. |
250 | OVERALL_REQUESTS = 100 |
251 | |
252 | -__metaclass__ = type |
253 | - |
254 | |
255 | class LoggingMixin: |
256 | """Common logging class for archive and releases mirror messages.""" |
257 | @@ -752,3 +761,144 @@ |
258 | assert port.isdigit() |
259 | port = int(port) |
260 | return scheme, host, port, path |
261 | + |
262 | + |
263 | +class DistroMirrorProber: |
264 | + """Main entry point for the distribution mirror prober.""" |
265 | + |
266 | + def __init__(self, txn, logger): |
267 | + self.txn = txn |
268 | + self.logger = logger |
269 | + |
270 | + def _sanity_check_mirror(self, mirror): |
271 | + """Check that the given mirror is official and has an http_base_url. |
272 | + """ |
273 | + assert mirror.isOfficial(), ( |
274 | + 'Non-official mirrors should not be probed') |
275 | + if mirror.base_url is None: |
276 | + self.logger.warning( |
277 | + "Mirror '%s' of distribution '%s' doesn't have a base URL; " |
278 | + "we can't probe it." % ( |
279 | + mirror.name, mirror.distribution.name)) |
280 | + return False |
281 | + return True |
282 | + |
283 | + def _create_probe_record(self, mirror, logfile): |
284 | + """Create a probe record for the given mirror with the given logfile. |
285 | + """ |
286 | + logfile.seek(0) |
287 | + filename = '%s-probe-logfile.txt' % mirror.name |
288 | + log_file = getUtility(ILibraryFileAliasSet).create( |
289 | + name=filename, size=len(logfile.getvalue()), |
290 | + file=logfile, contentType='text/plain') |
291 | + mirror.newProbeRecord(log_file) |
292 | + |
293 | + def probe(self, content_type, no_remote_hosts, ignore_last_probe, |
294 | + max_mirrors, notify_owner): |
295 | + """Probe distribution mirrors. |
296 | + |
297 | + :param content_type: The type of mirrored content, as a |
298 | + `MirrorContent`. |
299 | + :param no_remote_hosts: If True, restrict access to localhost. |
300 | + :param ignore_last_probe: If True, ignore the results of the last |
301 | + probe and probe again anyway. |
302 | + :param max_mirrors: The maximum number of mirrors to probe. If None, |
303 | + no maximum. |
304 | + :param notify_owner: Send failure notification to the owners of the |
305 | + mirrors. |
306 | + """ |
307 | + if content_type == MirrorContent.ARCHIVE: |
308 | + probe_function = probe_archive_mirror |
309 | + elif content_type == MirrorContent.RELEASE: |
310 | + probe_function = probe_cdimage_mirror |
311 | + else: |
312 | + raise ValueError( |
313 | + "Unrecognized content_type: %s" % (content_type,)) |
314 | + |
315 | + self.txn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) |
316 | + self.txn.begin() |
317 | + |
318 | + # To me this seems better than passing the no_remote_hosts value |
319 | + # through a lot of method/function calls, until it reaches the probe() |
320 | + # method. (salgado) |
321 | + if no_remote_hosts: |
322 | + localhost_only_conf = """ |
323 | + [distributionmirrorprober] |
324 | + localhost_only: True |
325 | + """ |
326 | + config.push('localhost_only_conf', localhost_only_conf) |
327 | + |
328 | + self.logger.info('Probing %s Mirrors' % content_type.title) |
329 | + |
330 | + mirror_set = getUtility(IDistributionMirrorSet) |
331 | + results = mirror_set.getMirrorsToProbe( |
332 | + content_type, ignore_last_probe=ignore_last_probe, |
333 | + limit=max_mirrors) |
334 | + mirror_ids = [mirror.id for mirror in results] |
335 | + unchecked_keys = [] |
336 | + logfiles = {} |
337 | + probed_mirrors = [] |
338 | + |
339 | + for mirror_id in mirror_ids: |
340 | + mirror = mirror_set[mirror_id] |
341 | + if not self._sanity_check_mirror(mirror): |
342 | + continue |
343 | + |
344 | + # XXX: salgado 2006-05-26: |
345 | + # Some people registered mirrors on distros other than Ubuntu back |
346 | + # in the old times, so now we need to do this small hack here. |
347 | + if not mirror.distribution.full_functionality: |
348 | + self.logger.info( |
349 | + "Mirror '%s' of distribution '%s' can't be probed --we " |
350 | + "only probe Ubuntu mirrors." |
351 | + % (mirror.name, mirror.distribution.name)) |
352 | + continue |
353 | + |
354 | + probed_mirrors.append(mirror) |
355 | + logfile = StringIO() |
356 | + logfiles[mirror_id] = logfile |
357 | + probe_function(mirror, logfile, unchecked_keys, self.logger) |
358 | + |
359 | + if probed_mirrors: |
360 | + reactor.run() |
361 | + self.logger.info('Probed %d mirrors.' % len(probed_mirrors)) |
362 | + else: |
363 | + self.logger.info('No mirrors to probe.') |
364 | + |
365 | + disabled_mirrors = [] |
366 | + reenabled_mirrors = [] |
367 | + # Now that we finished probing all mirrors, we check if any of these |
368 | + # mirrors appear to have no content mirrored, and, if so, mark them as |
369 | + # disabled and notify their owners. |
370 | + expected_iso_images_count = len(get_expected_cdimage_paths()) |
371 | + for mirror in probed_mirrors: |
372 | + log = logfiles[mirror.id] |
373 | + self._create_probe_record(mirror, log) |
374 | + if mirror.shouldDisable(expected_iso_images_count): |
375 | + if mirror.enabled: |
376 | + log.seek(0) |
377 | + mirror.disable(notify_owner, log.getvalue()) |
378 | + disabled_mirrors.append(canonical_url(mirror)) |
379 | + else: |
380 | + # Ensure the mirror is enabled, so that it shows up on public |
381 | + # mirror listings. |
382 | + if not mirror.enabled: |
383 | + mirror.enabled = True |
384 | + reenabled_mirrors.append(canonical_url(mirror)) |
385 | + |
386 | + if disabled_mirrors: |
387 | + self.logger.info( |
388 | + 'Disabling %s mirror(s): %s' |
389 | + % (len(disabled_mirrors), ", ".join(disabled_mirrors))) |
390 | + if reenabled_mirrors: |
391 | + self.logger.info( |
392 | + 'Re-enabling %s mirror(s): %s' |
393 | + % (len(reenabled_mirrors), ", ".join(reenabled_mirrors))) |
394 | + # XXX: salgado 2007-04-03: |
395 | + # This should be done in LaunchpadScript.lock_and_run() when |
396 | + # the isolation used is ISOLATION_LEVEL_AUTOCOMMIT. Also note |
397 | + # that replacing this with a flush_database_updates() doesn't |
398 | + # have the same effect, it seems. |
399 | + self.txn.commit() |
400 | + |
401 | + self.logger.info('Done.') |
402 | |
403 | === modified file 'lib/lp/registry/tests/test_distributionmirror_prober.py' |
404 | --- lib/lp/registry/tests/test_distributionmirror_prober.py 2010-12-10 21:10:42 +0000 |
405 | +++ lib/lp/registry/tests/test_distributionmirror_prober.py 2010-12-24 17:11:03 +0000 |
406 | @@ -1,8 +1,6 @@ |
407 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
408 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
409 | # GNU Affero General Public License version 3 (see the file LICENSE). |
410 | |
411 | -# pylint: disable-msg=W0703 |
412 | - |
413 | """distributionmirror-prober tests.""" |
414 | |
415 | __metaclass__ = type |
416 | @@ -13,7 +11,6 @@ |
417 | import logging |
418 | import os |
419 | from StringIO import StringIO |
420 | -import unittest |
421 | |
422 | from lazr.uri import URI |
423 | from sqlobject import SQLObjectNotFound |
424 | @@ -25,16 +22,19 @@ |
425 | from twisted.trial.unittest import TestCase as TrialTestCase |
426 | from twisted.web import server |
427 | |
428 | +from zope.component import getUtility |
429 | +from zope.security.proxy import removeSecurityProxy |
430 | + |
431 | import canonical |
432 | from canonical.config import config |
433 | from canonical.launchpad.daemons.tachandler import TacTestSetup |
434 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
435 | from canonical.testing.layers import ( |
436 | - LaunchpadZopelessLayer, |
437 | TwistedLayer, |
438 | + ZopelessDatabaseLayer, |
439 | ) |
440 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
441 | from lp.registry.model.distributionmirror import DistributionMirror |
442 | -from lp.registry.model.distroseries import DistroSeries |
443 | from lp.registry.scripts import distributionmirror_prober |
444 | from lp.registry.scripts.distributionmirror_prober import ( |
445 | _build_request_for_cdimage_file_list, |
446 | @@ -64,6 +64,10 @@ |
447 | from lp.registry.tests.distributionmirror_http_server import ( |
448 | DistributionMirrorTestHTTPServer, |
449 | ) |
450 | +from lp.testing import ( |
451 | + TestCase, |
452 | + TestCaseWithFactory, |
453 | + ) |
454 | |
455 | |
456 | class HTTPServerTestSetup(TacTestSetup): |
457 | @@ -276,7 +280,7 @@ |
458 | self.redirectedTo = url |
459 | |
460 | |
461 | -class TestProberFactoryRequestTimeoutRatioWithoutTwisted(unittest.TestCase): |
462 | +class TestProberFactoryRequestTimeoutRatioWithoutTwisted(TestCase): |
463 | """Tests to ensure we stop issuing requests on a given host if the |
464 | requests/timeouts ratio on that host is too low. |
465 | |
466 | @@ -288,6 +292,7 @@ |
467 | host = 'foo.bar' |
468 | |
469 | def setUp(self): |
470 | + super(TestProberFactoryRequestTimeoutRatioWithoutTwisted, self).setUp() |
471 | self.orig_host_requests = dict( |
472 | distributionmirror_prober.host_requests) |
473 | self.orig_host_timeouts = dict( |
474 | @@ -297,6 +302,7 @@ |
475 | # Restore the globals that our tests fiddle with. |
476 | distributionmirror_prober.host_requests = self.orig_host_requests |
477 | distributionmirror_prober.host_timeouts = self.orig_host_timeouts |
478 | + super(TestProberFactoryRequestTimeoutRatioWithoutTwisted, self).tearDown() |
479 | |
480 | def _createProberStubConnectAndProbe(self, requests, timeouts): |
481 | """Create a ProberFactory object with a URL inside self.host and call |
482 | @@ -431,9 +437,10 @@ |
483 | return self.assertFailure(d, ConnectionSkipped) |
484 | |
485 | |
486 | -class TestMultiLock(unittest.TestCase): |
487 | +class TestMultiLock(TestCase): |
488 | |
489 | def setUp(self): |
490 | + super(TestMultiLock, self).setUp() |
491 | self.lock_one = defer.DeferredLock() |
492 | self.lock_two = defer.DeferredLock() |
493 | self.multi_lock = MultiLock(self.lock_one, self.lock_two) |
494 | @@ -510,7 +517,7 @@ |
495 | self.assertEquals(self.count, 1, "self.callback should have run.") |
496 | |
497 | |
498 | -class TestRedirectAwareProberFactoryAndProtocol(unittest.TestCase): |
499 | +class TestRedirectAwareProberFactoryAndProtocol(TestCase): |
500 | |
501 | def test_redirect_resets_timeout(self): |
502 | prober = RedirectAwareProberFactory('http://foo.bar') |
503 | @@ -599,43 +606,46 @@ |
504 | self.failUnless(protocol.transport.disconnecting) |
505 | |
506 | |
507 | -class TestMirrorCDImageProberCallbacks(unittest.TestCase): |
508 | - layer = LaunchpadZopelessLayer |
509 | - |
510 | - def setUp(self): |
511 | - self.layer.switchDbUser(config.distributionmirrorprober.dbuser) |
512 | - self.logger = logging.getLogger('distributionmirror-prober') |
513 | - self.logger.errorCalled = False |
514 | +class TestMirrorCDImageProberCallbacks(TestCaseWithFactory): |
515 | + |
516 | + layer = ZopelessDatabaseLayer |
517 | + |
518 | + def makeMirrorProberCallbacks(self): |
519 | + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
520 | + distroseries = removeSecurityProxy( |
521 | + self.factory.makeDistroSeries(distribution=ubuntu)) |
522 | + mirror = removeSecurityProxy( |
523 | + self.factory.makeMirror(distroseries.distribution)) |
524 | + callbacks = MirrorCDImageProberCallbacks( |
525 | + mirror, distroseries, 'ubuntu', StringIO()) |
526 | + return callbacks |
527 | + |
528 | + def getLogger(self): |
529 | + logger = logging.getLogger('distributionmirror-prober') |
530 | + logger.errorCalled = False |
531 | def error(msg): |
532 | - self.logger.errorCalled = True |
533 | - self.logger.error = error |
534 | - mirror = DistributionMirror.get(1) |
535 | - warty = DistroSeries.get(1) |
536 | - flavour = 'ubuntu' |
537 | - log_file = StringIO() |
538 | - self.callbacks = MirrorCDImageProberCallbacks( |
539 | - mirror, warty, flavour, log_file) |
540 | - |
541 | - def tearDown(self): |
542 | - # XXX: JonathanLange 2010-11-22: These tests leave stacks of delayed |
543 | - # calls around. They need to be updated to use Twisted correctly. |
544 | - # For the meantime, just blat the reactor. |
545 | - for delayed_call in reactor.getDelayedCalls(): |
546 | - delayed_call.cancel() |
547 | - |
548 | - def test_mirrorcdimageseries_creation_and_deletion(self): |
549 | - callbacks = self.callbacks |
550 | + logger.errorCalled = True |
551 | + logger.error = error |
552 | + return logger |
553 | + |
554 | + def test_mirrorcdimageseries_creation_and_deletion_all_success(self): |
555 | + callbacks = self.makeMirrorProberCallbacks() |
556 | all_success = [(defer.SUCCESS, '200'), (defer.SUCCESS, '200')] |
557 | mirror_cdimage_series = callbacks.ensureOrDeleteMirrorCDImageSeries( |
558 | all_success) |
559 | - self.failUnless( |
560 | - mirror_cdimage_series is not None, |
561 | + self.assertIsNot( |
562 | + mirror_cdimage_series, None, |
563 | "If the prober gets a list of 200 Okay statuses, a new " |
564 | "MirrorCDImageSeries should be created.") |
565 | |
566 | + def test_mirrorcdimageseries_creation_and_deletion_some_404s(self): |
567 | not_all_success = [ |
568 | (defer.FAILURE, Failure(BadResponseCode(str(httplib.NOT_FOUND)))), |
569 | (defer.SUCCESS, '200')] |
570 | + callbacks = self.makeMirrorProberCallbacks() |
571 | + all_success = [(defer.SUCCESS, '200'), (defer.SUCCESS, '200')] |
572 | + mirror_cdimage_series = callbacks.ensureOrDeleteMirrorCDImageSeries( |
573 | + all_success) |
574 | callbacks.ensureOrDeleteMirrorCDImageSeries(not_all_success) |
575 | # If the prober gets at least one 404 status, we need to make sure |
576 | # there's no MirrorCDImageSeries for that series and flavour. |
577 | @@ -648,8 +658,10 @@ |
578 | # ignored by ensureOrDeleteMirrorCDImageSeries() because they've been |
579 | # logged by logMissingURL() already and they're expected to happen |
580 | # some times. |
581 | - self.failUnlessEqual( |
582 | - set(self.callbacks.expected_failures), |
583 | + logger = self.getLogger() |
584 | + callbacks = self.makeMirrorProberCallbacks() |
585 | + self.assertEqual( |
586 | + set(callbacks.expected_failures), |
587 | set([ |
588 | BadResponseCode, |
589 | ProberTimeout, |
590 | @@ -661,65 +673,63 @@ |
591 | ConnectionSkipped(), |
592 | UnknownURLSchemeAfterRedirect('https://localhost')] |
593 | for exception in exceptions: |
594 | - failure = self.callbacks.ensureOrDeleteMirrorCDImageSeries( |
595 | + failure = callbacks.ensureOrDeleteMirrorCDImageSeries( |
596 | [(defer.FAILURE, Failure(exception))]) |
597 | # Twisted callbacks may raise or return a failure; that's why we |
598 | # check the return value. |
599 | self.failIf(isinstance(failure, Failure)) |
600 | # Also, these failures are not logged to stdout/stderr since |
601 | # they're expected to happen. |
602 | - self.failIf(self.logger.errorCalled) |
603 | + self.failIf(logger.errorCalled) |
604 | |
605 | def test_unexpected_failures_are_logged_but_not_raised(self): |
606 | # Errors which are not expected as logged using the |
607 | # prober's logger to make sure people see it while still alowing other |
608 | # mirrors to be probed. |
609 | - failure = self.callbacks.ensureOrDeleteMirrorCDImageSeries( |
610 | + logger = self.getLogger() |
611 | + callbacks = self.makeMirrorProberCallbacks() |
612 | + failure = callbacks.ensureOrDeleteMirrorCDImageSeries( |
613 | [(defer.FAILURE, Failure(ZeroDivisionError()))]) |
614 | # Twisted callbacks may raise or return a failure; that's why we |
615 | # check the return value. |
616 | self.failIf(isinstance(failure, Failure)) |
617 | # Unlike the expected failures, these ones must be logged as errors to |
618 | # stdout/stderr. |
619 | - self.failUnless(self.logger.errorCalled) |
620 | - |
621 | - |
622 | -class TestArchiveMirrorProberCallbacks(unittest.TestCase): |
623 | - layer = LaunchpadZopelessLayer |
624 | - |
625 | - def setUp(self): |
626 | - mirror = DistributionMirror.get(1) |
627 | - warty = DistroSeries.get(1) |
628 | - pocket = PackagePublishingPocket.RELEASE |
629 | - component = warty.components[0] |
630 | - log_file = StringIO() |
631 | - url = 'foo' |
632 | - self.callbacks = ArchiveMirrorProberCallbacks( |
633 | - mirror, warty, pocket, component, url, log_file) |
634 | - |
635 | - def tearDown(self): |
636 | - # XXX: JonathanLange 2010-11-22: These tests leave stacks of delayed |
637 | - # calls around. They need to be updated to use Twisted correctly. |
638 | - # For the meantime, just blat the reactor. |
639 | - for delayed_call in reactor.getDelayedCalls(): |
640 | - delayed_call.cancel() |
641 | + self.failUnless(logger.errorCalled) |
642 | + |
643 | + |
644 | +class TestArchiveMirrorProberCallbacks(TestCaseWithFactory): |
645 | + layer = ZopelessDatabaseLayer |
646 | + |
647 | + def makeMirrorProberCallbacks(self): |
648 | + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
649 | + distroseries = removeSecurityProxy( |
650 | + self.factory.makeDistroSeries(distribution=ubuntu)) |
651 | + mirror = removeSecurityProxy( |
652 | + self.factory.makeMirror(distroseries.distribution)) |
653 | + component = self.factory.makeComponent() |
654 | + callbacks = ArchiveMirrorProberCallbacks( |
655 | + mirror, distroseries, PackagePublishingPocket.RELEASE, |
656 | + component, 'foo', StringIO()) |
657 | + return callbacks |
658 | |
659 | def test_failure_propagation(self): |
660 | # Make sure that deleteMirrorSeries() does not propagate |
661 | # ProberTimeout, BadResponseCode or ConnectionSkipped failures. |
662 | + callbacks = self.makeMirrorProberCallbacks() |
663 | try: |
664 | - self.callbacks.deleteMirrorSeries( |
665 | + callbacks.deleteMirrorSeries( |
666 | Failure(ProberTimeout('http://localhost/', 5))) |
667 | except Exception, e: |
668 | self.fail("A timeout shouldn't be propagated. Got %s" % e) |
669 | try: |
670 | - self.callbacks.deleteMirrorSeries( |
671 | + callbacks.deleteMirrorSeries( |
672 | Failure(BadResponseCode(str(httplib.INTERNAL_SERVER_ERROR)))) |
673 | except Exception, e: |
674 | self.fail( |
675 | "A bad response code shouldn't be propagated. Got %s" % e) |
676 | try: |
677 | - self.callbacks.deleteMirrorSeries(Failure(ConnectionSkipped())) |
678 | + callbacks.deleteMirrorSeries(Failure(ConnectionSkipped())) |
679 | except Exception, e: |
680 | self.fail("A ConnectionSkipped exception shouldn't be " |
681 | "propagated. Got %s" % e) |
682 | @@ -727,7 +737,7 @@ |
683 | # Make sure that deleteMirrorSeries() propagate any failure that is |
684 | # not a ProberTimeout, a BadResponseCode or a ConnectionSkipped. |
685 | d = defer.Deferred() |
686 | - d.addErrback(self.callbacks.deleteMirrorSeries) |
687 | + d.addErrback(callbacks.deleteMirrorSeries) |
688 | def got_result(result): |
689 | self.fail( |
690 | "Any failure that's not a timeout/bad-response/skipped " |
691 | @@ -740,15 +750,16 @@ |
692 | self.assertEqual([1], ok) |
693 | |
694 | def test_mirrorseries_creation_and_deletion(self): |
695 | - mirror_distro_series_source = self.callbacks.ensureMirrorSeries( |
696 | + callbacks = self.makeMirrorProberCallbacks() |
697 | + mirror_distro_series_source = callbacks.ensureMirrorSeries( |
698 | str(httplib.OK)) |
699 | - self.failUnless( |
700 | - mirror_distro_series_source is not None, |
701 | + self.assertIsNot( |
702 | + mirror_distro_series_source, None, |
703 | "If the prober gets a 200 Okay status, a new " |
704 | "MirrorDistroSeriesSource/MirrorDistroArchSeries should be " |
705 | "created.") |
706 | |
707 | - self.callbacks.deleteMirrorSeries( |
708 | + callbacks.deleteMirrorSeries( |
709 | Failure(BadResponseCode(str(httplib.NOT_FOUND)))) |
710 | # If the prober gets a 404 status, we need to make sure there's no |
711 | # MirrorDistroSeriesSource/MirrorDistroArchSeries referent to |
712 | @@ -758,13 +769,14 @@ |
713 | mirror_distro_series_source.id) |
714 | |
715 | |
716 | -class TestProbeFunctionSemaphores(unittest.TestCase): |
717 | +class TestProbeFunctionSemaphores(TestCase): |
718 | """Make sure we use one DeferredSemaphore for each hostname when probing |
719 | mirrors. |
720 | """ |
721 | - layer = LaunchpadZopelessLayer |
722 | + layer = ZopelessDatabaseLayer |
723 | |
724 | def setUp(self): |
725 | + super(TestProbeFunctionSemaphores, self).setUp() |
726 | self.logger = None |
727 | # RequestManager uses a mutable class attribute (host_locks) to ensure |
728 | # all of its instances share the same locks. We don't want our tests |
729 | @@ -778,6 +790,7 @@ |
730 | # For the meantime, just blat the reactor. |
731 | for delayed_call in reactor.getDelayedCalls(): |
732 | delayed_call.cancel() |
733 | + super(TestProbeFunctionSemaphores, self).tearDown() |
734 | |
735 | def test_MirrorCDImageSeries_records_are_deleted_before_probing(self): |
736 | mirror = DistributionMirror.byName('releases-mirror2') |
737 | @@ -854,7 +867,7 @@ |
738 | restore_http_proxy(orig_proxy) |
739 | |
740 | |
741 | -class TestCDImageFileListFetching(unittest.TestCase): |
742 | +class TestCDImageFileListFetching(TestCase): |
743 | |
744 | def test_no_cache(self): |
745 | url = 'http://releases.ubuntu.com/.manifest' |
746 | @@ -863,7 +876,7 @@ |
747 | self.failUnlessEqual(request.headers['Cache-control'], 'no-cache') |
748 | |
749 | |
750 | -class TestLoggingMixin(unittest.TestCase): |
751 | +class TestLoggingMixin(TestCase): |
752 | |
753 | def _fake_gettime(self): |
754 | # Fake the current time. |
755 | @@ -888,7 +901,3 @@ |
756 | logger.log_file.seek(0) |
757 | message = logger.log_file.read() |
758 | self.assertNotEqual(None, message) |
759 | - |
760 | - |
761 | -def test_suite(): |
762 | - return unittest.TestLoader().loadTestsFromName(__name__) |
763 | |
764 | === modified file 'lib/lp/testing/factory.py' |
765 | --- lib/lp/testing/factory.py 2010-12-20 07:51:34 +0000 |
766 | +++ lib/lp/testing/factory.py 2010-12-24 17:11:03 +0000 |
767 | @@ -2651,10 +2651,12 @@ |
768 | proberecord = mirror.newProbeRecord(library_alias) |
769 | return proberecord |
770 | |
771 | - def makeMirror(self, distribution, displayname, country=None, |
772 | + def makeMirror(self, distribution, displayname=None, country=None, |
773 | http_url=None, ftp_url=None, rsync_url=None, |
774 | official_candidate=False): |
775 | """Create a mirror for the distribution.""" |
776 | + if displayname is None: |
777 | + displayname = self.getUniqueString("mirror") |
778 | # If no URL is specified create an HTTP URL. |
779 | if http_url is None and ftp_url is None and rsync_url is None: |
780 | http_url = self.getUniqueURL() |