← Back to team overview

launchpad-reviewers team mailing list archive

Re: [Merge] lp:~wgrant/launchpad/xref-buglinks into lp:launchpad

 

Review: Approve



Diff comments:

> 
> === modified file 'lib/lp/answers/model/question.py'
> --- lib/lp/answers/model/question.py	2015-09-28 07:57:17 +0000
> +++ lib/lp/answers/model/question.py	2015-09-29 04:00:42 +0000
> @@ -660,6 +662,20 @@
>          self.status = new_status
>          return tktmsg
>  
> +    @property
> +    def bugs(self):
> +        from lp.bugs.model.bug import Bug
> +        if getFeatureFlag('bugs.xref_buglinks.query'):

Can the flag names used in this branch be in an interface module somewhere rather than repeating it, to guard against typos?

> +            bug_ids = [
> +                int(id) for _, id in getUtility(IXRefSet).findFrom(
> +                    (u'question', unicode(self.id)), types=[u'bug'])]

I feel that perhaps findFrom should handle the unicode() bit; this would also allow changing one place to use from_id_int instead where possible, rather than having to change all the callers.  Same applies to other IXRefSet methods.

> +        else:
> +            bug_ids = list(IStore(QuestionBug).find(
> +                QuestionBug,
> +                QuestionBug.question == self).values(QuestionBug.bugID))
> +        return list(sorted(
> +            bulk.load(Bug, bug_ids), key=operator.attrgetter('id')))
> +
>      # IBugLinkTarget implementation
>      def linkBug(self, bug, user=None):
>          """See `IBugLinkTarget`."""
> 
> === modified file 'lib/lp/scripts/garbo.py'
> --- lib/lp/scripts/garbo.py	2015-07-22 07:09:12 +0000
> +++ lib/lp/scripts/garbo.py	2015-09-29 04:00:42 +0000
> @@ -1678,6 +1679,66 @@
>                  transaction.abort()
>  
>  
> +class BugXRefMigrator(TunableLoop):
> +    """Creates an XRef record for each former IBugLink."""
> +
> +    maximum_chunk_size = 5000
> +
> +    def __init__(self, log, abort_time=None):
> +        super(BugXRefMigrator, self).__init__(log, abort_time)
> +        self.start_at = 1
> +        self.store = IMasterStore(Bug)
> +
> +    def findBugs(self):
> +        if not getFeatureFlag('bugs.xref_buglinks.garbo.enabled'):
> +            return EmptyResultSet()
> +        return self.store.find(
> +            Bug, Bug.id >= self.start_at).order_by(Bug.id)
> +
> +    def isDone(self):
> +        return self.findBugs().is_empty()
> +
> +    def __call__(self, chunk_size):
> +        # Grab a chunk of Bug IDs.
> +        # Find all QuestionBugs, SpecificationBugs and BugCves for each
> +        # of those bugs.
> +        # Compose a list of link IDs that should exist.
> +        # Perform a bulk XRef find for all of those.
> +        # Delete any extras, create any missing.

The actual code does not delete any extras.  Should it, or is this comment wrong?

> +        from lp.blueprints.model.specificationbug import SpecificationBug
> +        from lp.bugs.model.bugcve import BugCve
> +        from lp.bugs.model.cve import Cve
> +        from lp.coop.answersbugs.model import QuestionBug
> +        from lp.services.xref.interfaces import IXRefSet
> +        bug_ids = list(self.findBugs()[:chunk_size].values(Bug.id))
> +        qbs = list(self.store.find(
> +            QuestionBug, QuestionBug.bugID.is_in(bug_ids)))
> +        sbs = list(self.store.find(
> +            SpecificationBug, SpecificationBug.bugID.is_in(bug_ids)))
> +        bcs = list(self.store.find(BugCve, BugCve.bugID.is_in(bug_ids)))
> +        wanted = {(u'bug', unicode(bug_id)): {} for bug_id in bug_ids}

Could be a defaultdict to make it a little more obviously correct.

> +        for qb in qbs:
> +            wanted[(u'bug', unicode(qb.bugID))][
> +                (u'question', unicode(qb.questionID))] = {
> +                    'date_created': qb.date_created}
> +        for sb in sbs:
> +            wanted[(u'bug', unicode(sb.bugID))][
> +                (u'specification', unicode(sb.specificationID))] = {}
> +        load_related(Cve, bcs, ['cveID'])
> +        for bc in bcs:
> +            wanted[(u'bug', unicode(bc.bugID))][
> +                (u'cve', unicode(bc.cve.sequence))] = {}
> +        existing = getUtility(IXRefSet).findFromMany(wanted.keys())
> +        needed = {
> +            bug: {
> +                other: meta for other, meta in others.iteritems()
> +                if other not in existing.get(bug, {})}
> +            for bug, others in wanted.iteritems() if others}
> +        getUtility(IXRefSet).create(needed)
> +        self.start_at = bug_ids[-1] + 1
> +        transaction.commit()
> +
> +
>  class FrequentDatabaseGarbageCollector(BaseDatabaseGarbageCollector):
>      """Run every 5 minutes.
>  
> @@ -1714,6 +1775,7 @@
>          DuplicateSessionPruner,
>          RevisionCachePruner,
>          UnusedSessionPruner,
> +        BugXRefMigrator,
>          ]

This list is otherwise sorted.

>      experimental_tunable_loops = []
>  
> 
> === modified file 'lib/lp/scripts/tests/test_garbo.py'
> --- lib/lp/scripts/tests/test_garbo.py	2015-07-22 07:09:12 +0000
> +++ lib/lp/scripts/tests/test_garbo.py	2015-09-29 04:00:42 +0000
> @@ -1433,6 +1433,94 @@
>          for person in people_enf_true:
>              _assert_enf_by_person(person, True)
>  
> +    def test_BugXRefMigrator(self):
> +        from testtools.matchers import (
> +            Equals,
> +            Is,
> +            MatchesDict,
> +            Not,
> +            )
> +
> +        from lp.bugs.model.bug import Bug
> +        from lp.bugs.model.bugcve import BugCve
> +        from lp.blueprints.model.specificationbug import SpecificationBug
> +        from lp.coop.answersbugs.model import QuestionBug
> +        from lp.services.database.interfaces import IStore
> +        from lp.services.xref.interfaces import IXRefSet
> +
> +        switch_dbuser('testadmin')
> +        self.useFixture(FeatureFixture(
> +            {'bugs.xref_buglinks.garbo.enabled': 'on'}))
> +        store = IStore(Bug)
> +
> +        # The first bug has a spec and a question.
> +        bug1 = self.factory.makeBug()
> +        spec1 = self.factory.makeSpecification()
> +        sb1 = SpecificationBug(specification=spec1, bug=bug1)
> +        store.add(sb1)
> +        question1 = self.factory.makeQuestion()
> +        qb1 = QuestionBug(question=question1, bug=bug1)
> +        store.add(qb1)
> +
> +        # A second bug has a question and a CVE.
> +        bug2 = self.factory.makeBug()
> +        question2 = self.factory.makeQuestion()
> +        qb2 = QuestionBug(question=question2, bug=bug2)
> +        store.add(qb2)
> +        cve2 = self.factory.makeCVE(sequence='2099-1234')
> +        bc2 = BugCve(bug=bug2, cve=cve2)
> +        store.add(bc2)
> +
> +        # Bug the third is all alone.
> +        bug3 = self.factory.makeBug()
> +
> +        # Bug four has just a spec.
> +        bug4 = self.factory.makeBug()
> +        spec4 = self.factory.makeSpecification()
> +        sb4 = SpecificationBug(specification=spec4, bug=bug4)
> +        store.add(sb4)
> +
> +        # Initially the new XRef table has no links for the bugs.
> +        self.assertEqual(
> +            {},
> +            getUtility(IXRefSet).findFromMany(
> +                (u'bug', unicode(bug.id)) for bug in (bug1, bug2, bug3, bug4)))

If you change the code above to delete extras, then there should also be a test with pre-existing extraneous XRefs.

> +
> +        # Garbo fills in links for each QuestionBug, SpecificationBug
> +        # and BugCve.
> +        self.runHourly()
> +        matches_expected = MatchesDict({
> +            (u'bug', unicode(bug1.id)): MatchesDict({
> +                (u'specification', unicode(spec1.id)): MatchesDict({
> +                    'metadata': Is(None), 'creator': Is(None),
> +                    'date_created': Not(Is(None))}),
> +                (u'question', unicode(question1.id)): MatchesDict({
> +                    'metadata': Is(None), 'creator': Is(None),
> +                    'date_created': Equals(qb1.date_created)}),
> +                }),
> +            (u'bug', unicode(bug2.id)): MatchesDict({
> +                (u'question', unicode(question2.id)): MatchesDict({
> +                    'metadata': Is(None), 'creator': Is(None),
> +                    'date_created': Equals(qb2.date_created)}),
> +                (u'cve', cve2.sequence): MatchesDict({
> +                    'metadata': Is(None), 'creator': Is(None),
> +                    'date_created': Not(Is(None))}),
> +                }),
> +            (u'bug', unicode(bug4.id)): MatchesDict({
> +                (u'specification', unicode(spec4.id)): MatchesDict({
> +                    'metadata': Is(None), 'creator': Is(None),
> +                    'date_created': Not(Is(None))}),
> +                }),
> +            })
> +        self.assertThat(
> +            getUtility(IXRefSet).findFromMany(
> +                (u'bug', unicode(bug.id)) for bug in (bug1, bug2, bug3, bug4)),
> +            matches_expected)
> +
> +        # A second run is harmless.
> +        self.runHourly()
> +
> +
>  
>  class TestGarboTasks(TestCaseWithFactory):
>      layer = LaunchpadZopelessLayer


-- 
https://code.launchpad.net/~wgrant/launchpad/xref-buglinks/+merge/272591
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.


References