Merge lp:~jtv/launchpad/bug-733245 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 12599
Proposed branch: lp:~jtv/launchpad/bug-733245
Merge into: lp:launchpad
Diff against target: 288 lines (+175/-8)
3 files modified
lib/lp/registry/model/distroseriesdifference.py (+1/-2)
lib/lp/registry/scripts/populate_distroseriesdiff.py (+88/-4)
lib/lp/registry/scripts/tests/test_populate_distroseriesdiff.py (+86/-2)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-733245
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+53230@code.launchpad.net

Commit message

[r=benji][bug=733245] Initialize DSD.base_version in populating script.

Description of the change

= Summary =

The DistroSeriesDifference population script creates DSDs, but leaves base_version uninitialized. That part needs to be done from python.

== Proposed fix ==

Fire off a DBLoopTuner to fix up any DSDs for a freshly-populated DistroSeries. The work is likely to take long enough to warrant a DBLoopTuner; that way we know it won't cause undue stress on the backend.

If we enable the feature before the fixup is done, we'll start out with DSDs where it's impossible to request a diff. But that may be better than leaving the feature disabled until all the work is finished.

== Pre-implementation notes ==

This work is subject to more or less continuous discussion. In particular in this case, I discussed with StevenK.

== Implementation details ==

A new option --ignore-base-version lets the script ignore base_version. If desired, we can run the script with this option to create all required DSDs very quickly, and then run it again without the option to fix up the base_versions.

Conversely, there is no mode to skip creation of DSDs; that part is fast and unintrusive enough to run multiple times — especially since it does not update or re-create existing DSDs. Just detecting which DSDs there should be takes a few seconds at worst.

== Tests ==

{{{
./bin/test -vvc lp.registry.scripts.tests.test_populate_distroseriesdiff
}}}

== Demo and Q/A ==

We'll run this on a full derived distroseries and check that some of the base_versions get updated. It won't be all, since it's not always possible to determine a usable base_version.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/scripts/populate_distroseriesdiff.py
  lib/lp/registry/model/distroseriesdifference.py
  lib/lp/registry/scripts/tests/test_populate_distroseriesdiff.py

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

Looks good.

Line 206 of lib/lp/registry/scripts/populate_distroseriesdiff.py (line
49 of the diff): I think you want to wrap temp_table in a call to
quote_identifier.

The use of the expression "3 - 1" in test_cutChunk_one_cuts_exactly_one
is creative and increases the clarity of the test.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/registry/model/distroseriesdifference.py'
--- lib/lp/registry/model/distroseriesdifference.py 2011-03-10 04:00:08 +0000
+++ lib/lp/registry/model/distroseriesdifference.py 2011-03-14 12:23:49 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the1# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Database classes for a difference between two distribution series."""4"""Database classes for a difference between two distribution series."""
@@ -29,7 +29,6 @@
29 IMasterStore,29 IMasterStore,
30 IStore,30 IStore,
31 )31 )
32from lp.archivepublisher.debversion import Version
33from lp.registry.enum import (32from lp.registry.enum import (
34 DistroSeriesDifferenceStatus,33 DistroSeriesDifferenceStatus,
35 DistroSeriesDifferenceType,34 DistroSeriesDifferenceType,
3635
=== modified file 'lib/lp/registry/scripts/populate_distroseriesdiff.py'
--- lib/lp/registry/scripts/populate_distroseriesdiff.py 2011-03-04 05:50:01 +0000
+++ lib/lp/registry/scripts/populate_distroseriesdiff.py 2011-03-14 12:23:49 +0000
@@ -33,9 +33,11 @@
33 DistroSeriesDifferenceStatus,33 DistroSeriesDifferenceStatus,
34 DistroSeriesDifferenceType,34 DistroSeriesDifferenceType,
35 )35 )
36from canonical.launchpad.utilities.looptuner import TunableLoop
36from lp.registry.interfaces.distribution import IDistributionSet37from lp.registry.interfaces.distribution import IDistributionSet
37from lp.registry.interfaces.pocket import PackagePublishingPocket38from lp.registry.interfaces.pocket import PackagePublishingPocket
38from lp.registry.model.distroseries import DistroSeries39from lp.registry.model.distroseries import DistroSeries
40from lp.registry.model.distroseriesdifference import DistroSeriesDifference
39from lp.services.scripts.base import LaunchpadScript41from lp.services.scripts.base import LaunchpadScript
40from lp.soyuz.interfaces.publishing import active_publishing_status42from lp.soyuz.interfaces.publishing import active_publishing_status
4143
@@ -188,7 +190,7 @@
188 store.execute("DROP TABLE IF EXISTS %s" % quote_identifier(table))190 store.execute("DROP TABLE IF EXISTS %s" % quote_identifier(table))
189191
190192
191def populate_distroseriesdiff(derived_distroseries):193def populate_distroseriesdiff(logger, derived_distroseries):
192 """Compare `derived_distroseries` to parent, and register differences.194 """Compare `derived_distroseries` to parent, and register differences.
193195
194 The differences are registered by creating `DistroSeriesDifference`196 The differences are registered by creating `DistroSeriesDifference`
@@ -201,6 +203,9 @@
201 store.execute("CREATE TEMP TABLE %s AS %s" % (203 store.execute("CREATE TEMP TABLE %s AS %s" % (
202 quote_identifier(temp_table),204 quote_identifier(temp_table),
203 compose_sql_find_differences(derived_distroseries)))205 compose_sql_find_differences(derived_distroseries)))
206 logger.info(
207 "Found %d potential difference(s).",
208 store.execute("SELECT count(*) FROM %s" % temp_table).get_one()[0])
204 store.execute(209 store.execute(
205 compose_sql_populate_distroseriesdiff(210 compose_sql_populate_distroseriesdiff(
206 derived_distroseries, temp_table))211 derived_distroseries, temp_table))
@@ -221,6 +226,59 @@
221 (DistroSeries.parent_seriesID, DistroSeries.id))226 (DistroSeries.parent_seriesID, DistroSeries.id))
222227
223228
229class BaseVersionFixer(TunableLoop):
230 """Fix up `DistroSeriesDifference.base_version` in the database.
231
232 The code that creates `DistroSeriesDifference`s does not set the
233 `base_version`. In cases where there may actually be a real base
234 version, this needs to be fixed up.
235
236 Since this is likely to be much, much slower than the rest of the
237 work of creating and initializing `DistroSeriesDifference`s, it is
238 done in a `DBLoopTuner`.
239 """
240
241 def __init__(self, log, store, commit, ids):
242 """See `TunableLoop`.
243
244 :param log: A logger.
245 :param store: Database store to work on.
246 :param commit: A commit function to call after each batch.
247 :param ids: Sequence of `DistroSeriesDifference` ids to fix.
248 """
249 super(BaseVersionFixer, self).__init__(log)
250 self.minimum_chunk_size = 2
251 self.maximum_chunk_size = 1000
252 self.store = store
253 self.commit = commit
254 self.ids = sorted(ids, reverse=True)
255
256 def isDone(self):
257 """See `ITunableLoop`."""
258 return len(self.ids) == 0
259
260 def _cutChunk(self, chunk_size):
261 """Cut a chunk of up to `chunk_size` items from the remaining work.
262
263 Removes the items to be processed in this chunk from the list of
264 remaining work, and returns those.
265 """
266 todo = self.ids[-chunk_size:]
267 self.ids = self.ids[:-chunk_size]
268 return todo
269
270 def _getBatch(self, ids):
271 """Retrieve a batch of `DistroSeriesDifference`s with given ids."""
272 return self.store.find(
273 DistroSeriesDifference, DistroSeriesDifference.id.is_in(ids))
274
275 def __call__(self, chunk_size):
276 """See `ITunableLoop`."""
277 for dsd in self._getBatch(self._cutChunk(int(chunk_size))):
278 dsd._updateBaseVersion()
279 self.commit()
280
281
224class PopulateDistroSeriesDiff(LaunchpadScript):282class PopulateDistroSeriesDiff(LaunchpadScript):
225 """Populate `DistroSeriesDifference` for pre-existing differences."""283 """Populate `DistroSeriesDifference` for pre-existing differences."""
226284
@@ -264,7 +322,10 @@
264 def processDistroSeries(self, distroseries):322 def processDistroSeries(self, distroseries):
265 """Generate `DistroSeriesDifference`s for `distroseries`."""323 """Generate `DistroSeriesDifference`s for `distroseries`."""
266 self.logger.info("Looking for differences in %s.", distroseries)324 self.logger.info("Looking for differences in %s.", distroseries)
267 populate_distroseriesdiff(distroseries)325 populate_distroseriesdiff(self.logger, distroseries)
326 self.commit()
327 self.logger.info("Updating base_versions.")
328 self.fixBaseVersions(distroseries)
268 self.commit()329 self.commit()
269 self.logger.info("Done with %s.", distroseries)330 self.logger.info("Done with %s.", distroseries)
270331
@@ -288,10 +349,10 @@
288 specified_series = (self.options.series is not None)349 specified_series = (self.options.series is not None)
289 if specified_distro != specified_series:350 if specified_distro != specified_series:
290 raise OptionValueError(351 raise OptionValueError(
291 "Specify neither a distribution or a series, or both.")352 "Specify both a distribution and a series, or use --all.")
292 if specified_distro == self.options.all:353 if specified_distro == self.options.all:
293 raise OptionValueError(354 raise OptionValueError(
294 "Either specify a distribution series, or use --all.")355 "Either specify a distribution and series, or use --all.")
295356
296 def main(self):357 def main(self):
297 """Do the script's work."""358 """Do the script's work."""
@@ -307,3 +368,26 @@
307368
308 for series in self.getDistroSeries():369 for series in self.getDistroSeries():
309 self.processDistroSeries(series)370 self.processDistroSeries(series)
371
372 def fixBaseVersions(self, distroseries):
373 """Fix up `DistroSeriesDifference.base_version` where appropriate.
374
375 The `DistroSeriesDifference` records we create don't have their
376 `base_version` fields set yet. This is a shame because it's the
377 only thing we need to figure out python-side.
378
379 Only instances where the source package is published in both the
380 parent series and the derived series need to have this done.
381 """
382 self.logger.info(
383 "Fixing up base_versions for %s.", distroseries.title)
384 store = IStore(distroseries)
385 dsd_ids = store.find(
386 DistroSeriesDifference.id,
387 DistroSeriesDifference.derived_series == distroseries,
388 DistroSeriesDifference.status ==
389 DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
390 DistroSeriesDifference.difference_type ==
391 DistroSeriesDifferenceType.DIFFERENT_VERSIONS,
392 DistroSeriesDifference.base_version == None)
393 BaseVersionFixer(self.logger, store, self.commit, dsd_ids).run()
310394
=== modified file 'lib/lp/registry/scripts/tests/test_populate_distroseriesdiff.py'
--- lib/lp/registry/scripts/tests/test_populate_distroseriesdiff.py 2011-03-11 00:29:38 +0000
+++ lib/lp/registry/scripts/tests/test_populate_distroseriesdiff.py 2011-03-14 12:23:49 +0000
@@ -24,6 +24,7 @@
24from lp.registry.interfaces.pocket import PackagePublishingPocket24from lp.registry.interfaces.pocket import PackagePublishingPocket
25from lp.registry.model.distroseriesdifference import DistroSeriesDifference25from lp.registry.model.distroseriesdifference import DistroSeriesDifference
26from lp.registry.scripts.populate_distroseriesdiff import (26from lp.registry.scripts.populate_distroseriesdiff import (
27 BaseVersionFixer,
27 compose_sql_difference_type,28 compose_sql_difference_type,
28 compose_sql_find_latest_source_package_releases,29 compose_sql_find_latest_source_package_releases,
29 compose_sql_find_differences,30 compose_sql_find_differences,
@@ -42,7 +43,10 @@
42 )43 )
43from lp.soyuz.model.archive import Archive44from lp.soyuz.model.archive import Archive
44from lp.soyuz.enums import ArchivePurpose45from lp.soyuz.enums import ArchivePurpose
45from lp.testing import TestCaseWithFactory46from lp.testing import (
47 TestCase,
48 TestCaseWithFactory,
49 )
46from lp.testing.fakemethod import FakeMethod50from lp.testing.fakemethod import FakeMethod
4751
4852
@@ -409,7 +413,7 @@
409 def test_creates_distroseriesdifference(self):413 def test_creates_distroseriesdifference(self):
410 distroseries = self.makeDerivedDistroSeries()414 distroseries = self.makeDerivedDistroSeries()
411 spph = self.makeSPPH(distroseries=distroseries)415 spph = self.makeSPPH(distroseries=distroseries)
412 populate_distroseriesdiff(distroseries)416 populate_distroseriesdiff(DevNullLogger(), distroseries)
413 store = Store.of(distroseries)417 store = Store.of(distroseries)
414 dsd = self.getDistroSeriesDiff(distroseries).one()418 dsd = self.getDistroSeriesDiff(distroseries).one()
415 spr = spph.sourcepackagerelease419 spr = spph.sourcepackagerelease
@@ -445,6 +449,50 @@
445 self.assertEqual(existing_versions['derived'], dsd.source_version)449 self.assertEqual(existing_versions['derived'], dsd.source_version)
446450
447451
452class FakeDSD:
453 _updateBaseVersion = FakeMethod()
454
455
456class TestBaseVersionFixer(TestCase):
457 """Test the poignant parts of `BaseVersionFixer`."""
458
459 def makeFixer(self, ids):
460 fixer = BaseVersionFixer(DevNullLogger(), None, FakeMethod(), ids)
461 fixer._getBatch = FakeMethod()
462 return fixer
463
464 def test_isDone_is_done_when_ids_is_empty(self):
465 self.assertTrue(self.makeFixer([]).isDone())
466
467 def test_isDone_is_not_done_until_ids_is_empty(self):
468 self.assertFalse(self.makeFixer([1]).isDone())
469
470 def test_cutChunk_one_cuts_exactly_one(self):
471 fixer = self.makeFixer(range(3))
472 chunk = fixer._cutChunk(1)
473 self.assertEqual([0], chunk)
474 self.assertEqual(3 - 1, len(fixer.ids))
475
476 def test_cutChunk_over_remaining_size_completes_loop(self):
477 fixer = self.makeFixer(range(3))
478 chunk = fixer._cutChunk(100)
479 self.assertContentEqual(range(3), chunk)
480 self.assertEqual([], fixer.ids)
481
482 def test_updatesBaseVersion(self):
483 fake_dsd = FakeDSD()
484 fixer = self.makeFixer([fake_dsd])
485 fixer._getBatch.result = fixer.ids
486 fixer(1)
487 self.assertNotEqual(0, fake_dsd._updateBaseVersion.call_count)
488
489 def test_loop_commits(self):
490 fixer = self.makeFixer([FakeDSD()])
491 fixer._getBatch = FakeMethod(result=fixer.ids)
492 fixer(1)
493 self.assertNotEqual(0, fixer.commit.call_count)
494
495
448class TestPopulateDistroSeriesDiffScript(TestCaseWithFactory, FactoryHelper):496class TestPopulateDistroSeriesDiffScript(TestCaseWithFactory, FactoryHelper):
449 """Test the `populate-distroseriesdiff` script."""497 """Test the `populate-distroseriesdiff` script."""
450498
@@ -522,3 +570,39 @@
522 expected_series_name = "%s %s" % (570 expected_series_name = "%s %s" % (
523 spph.distroseries.distribution.name, spph.distroseries.name)571 spph.distroseries.distribution.name, spph.distroseries.name)
524 self.assertIn(expected_series_name, script.logger.getLogBuffer())572 self.assertIn(expected_series_name, script.logger.getLogBuffer())
573
574 def test_calls_fixBaseVersions(self):
575 distroseries = self.makeDerivedDistroSeries()
576 self.makeSPPH(distroseries=distroseries)
577 script = self.makeScript([
578 '--distribution', distroseries.distribution.name,
579 '--series', distroseries.name,
580 ])
581 script.fixBaseVersions = FakeMethod()
582 script.main()
583 self.assertEqual(
584 [((distroseries,), {})], script.fixBaseVersions.calls)
585
586 def test_fixes_base_versions(self):
587 distroseries = self.makeDerivedDistroSeries()
588 package = self.factory.makeSourcePackageName()
589 derived_spr = self.factory.makeSourcePackageRelease(
590 distroseries=distroseries, sourcepackagename=package)
591 self.makeSPPH(
592 distroseries=distroseries,
593 sourcepackagerelease=derived_spr)
594 parent_spr = self.factory.makeSourcePackageRelease(
595 distroseries=distroseries.parent_series,
596 sourcepackagename=package)
597 self.makeSPPH(
598 distroseries=distroseries.parent_series,
599 sourcepackagerelease=parent_spr)
600 script = self.makeScript([
601 '--distribution', distroseries.distribution.name,
602 '--series', distroseries.name,
603 ])
604 script.main()
605 dsd = self.getDistroSeriesDiff(distroseries)[0]
606 dsd._updateBaseVersion = FakeMethod()
607 script.fixBaseVersions(distroseries)
608 self.assertEqual(1, dsd._updateBaseVersion.call_count)