← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:stormify-cve into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:stormify-cve into launchpad:master.

Commit message:
Convert Cve and CveReference to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/388990
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-cve into launchpad:master.
diff --git a/lib/lp/bugs/browser/tests/test_cve.py b/lib/lp/bugs/browser/tests/test_cve.py
index ee760e3..8c4f024 100644
--- a/lib/lp/bugs/browser/tests/test_cve.py
+++ b/lib/lp/bugs/browser/tests/test_cve.py
@@ -1,8 +1,10 @@
-# Copyright 2012-2018 Canonical Ltd.  This software is licensed under the
+# Copyright 2012-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """CVE related tests."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 from functools import partial
 from operator import attrgetter
 import re
diff --git a/lib/lp/bugs/doc/cve-update.txt b/lib/lp/bugs/doc/cve-update.txt
index 542e457..5ae2697 100644
--- a/lib/lp/bugs/doc/cve-update.txt
+++ b/lib/lp/bugs/doc/cve-update.txt
@@ -21,11 +21,13 @@ the case.
     >>> import transaction
     >>> from lp.services.config import config
 
-OK. So now lets import the first XML database. First, lets se how many CVE
-entries are in the database.
+OK. So now let's import the first XML database. First, let's see how many
+CVE entries are in the database.
 
-    >>> from lp.bugs.model.cve import Cve
-    >>> print(Cve.select().count())
+    >>> from zope.component import getUtility
+    >>> from lp.bugs.interfaces.cve import ICveSet
+    >>> cve_set = getUtility(ICveSet)
+    >>> print(cve_set.getAll().count())
     10
 
     >>> script = os.path.join(config.root, 'cronscripts', 'update-cve.py')
@@ -81,14 +83,14 @@ Now run the cronscript.
 And let's make sure we got the right number of CVE entries.
 
     >>> transaction.commit()
-    >>> print(Cve.select().count())
+    >>> print(cve_set.getAll().count())
     18
 
 We will make a note of the CVE modification time of 1999-0002. When we
 update it later, we can use this modification time to check that its
 modification time is being updated correctly.
 
-    >>> c = Cve.bySequence('2005-2734')
+    >>> c = cve_set['2005-2734']
     >>> mod_time = c.datemodified
 
 And while we are here, make a note of the number of references for that CVE
@@ -137,13 +139,14 @@ Now, let's run an import of the update db.
 Let's make sure we got the new CVE's.
 
     >>> transaction.commit()
-    >>> print(Cve.select().count())
+    >>> print(cve_set.getAll().count())
     21
 
 And let's make sure the modification time of 2005-2734 was updated, as were
 the number of comments.
 
-    >>> c.sync()
+    >>> from storm.store import Store
+    >>> Store.of(c).autoreload()
     >>> print(mod_time < c.datemodified)
     True
     >>> print(c.references.count())
diff --git a/lib/lp/bugs/mail/tests/test_commands.py b/lib/lp/bugs/mail/tests/test_commands.py
index fe6d796..00721f8 100644
--- a/lib/lp/bugs/mail/tests/test_commands.py
+++ b/lib/lp/bugs/mail/tests/test_commands.py
@@ -1,6 +1,8 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 from lazr.lifecycle.interfaces import (
     IObjectCreatedEvent,
     IObjectModifiedEvent,
diff --git a/lib/lp/bugs/model/cve.py b/lib/lp/bugs/model/cve.py
index d5329ff..05b5fa8 100644
--- a/lib/lp/bugs/model/cve.py
+++ b/lib/lp/bugs/model/cve.py
@@ -1,6 +1,8 @@
-# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 __all__ = [
@@ -10,13 +12,16 @@ __all__ = [
 
 import operator
 
-from sqlobject import (
-    SQLMultipleJoin,
-    SQLObjectNotFound,
-    StringCol,
+import pytz
+import six
+from storm.locals import (
+    DateTime,
+    Desc,
+    Int,
+    ReferenceSet,
+    Store,
+    Unicode,
     )
-from storm.expr import In
-from storm.store import Store
 from zope.component import getUtility
 from zope.interface import implementer
 
@@ -35,29 +40,36 @@ from lp.bugs.model.buglinktarget import BugLinkTargetMixin
 from lp.bugs.model.cvereference import CveReference
 from lp.services.database import bulk
 from lp.services.database.constants import UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
-from lp.services.database.enumcol import EnumCol
+from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase
+from lp.services.database.stormbase import StormBase
 from lp.services.database.stormexpr import fti_search
 from lp.services.xref.interfaces import IXRefSet
 from lp.services.xref.model import XRef
 
 
 @implementer(ICve, IBugLinkTarget)
-class Cve(SQLBase, BugLinkTargetMixin):
+class Cve(StormBase, BugLinkTargetMixin):
     """A CVE database record."""
 
-    _table = 'Cve'
+    __storm_table__ = 'Cve'
+
+    id = Int(primary=True)
 
-    sequence = StringCol(notNull=True, alternateID=True)
-    status = EnumCol(dbName='status', schema=CveStatus, notNull=True)
-    description = StringCol(notNull=True)
-    datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
-    datemodified = UtcDateTimeCol(notNull=True, default=UTC_NOW)
+    sequence = Unicode(allow_none=False)
+    status = DBEnum(name='status', enum=CveStatus, allow_none=False)
+    description = Unicode(allow_none=False)
+    datecreated = DateTime(tzinfo=pytz.UTC, allow_none=False, default=UTC_NOW)
+    datemodified = DateTime(tzinfo=pytz.UTC, allow_none=False, default=UTC_NOW)
 
-    references = SQLMultipleJoin(
-        'CveReference', joinColumn='cve', orderBy='id')
+    references = ReferenceSet(
+        id, 'CveReference.cve_id', order_by='CveReference.id')
+
+    def __init__(self, sequence, status, description):
+        super(Cve, self).__init__()
+        self.sequence = sequence
+        self.status = status
+        self.description = description
 
     @property
     def url(self):
@@ -89,7 +101,7 @@ class Cve(SQLBase, BugLinkTargetMixin):
 
     def removeReference(self, ref):
         assert ref.cve == self
-        CveReference.delete(ref.id)
+        Store.of(ref).remove(ref)
 
     def createBugLink(self, bug, props=None):
         """See BugLinkTargetMixin."""
@@ -97,18 +109,18 @@ class Cve(SQLBase, BugLinkTargetMixin):
             props = {}
         # XXX: Should set creator.
         getUtility(IXRefSet).create(
-            {(u'cve', self.sequence): {(u'bug', unicode(bug.id)): props}})
+            {(u'cve', self.sequence): {
+                (u'bug', six.text_type(bug.id)): props}})
 
     def deleteBugLink(self, bug):
         """See BugLinkTargetMixin."""
         getUtility(IXRefSet).delete(
-            {(u'cve', self.sequence): [(u'bug', unicode(bug.id))]})
+            {(u'cve', self.sequence): [(u'bug', six.text_type(bug.id))]})
 
 
 @implementer(ICveSet)
 class CveSet:
     """The full set of ICve's."""
-    table = Cve
 
     def __init__(self, bug=None):
         """See ICveSet."""
@@ -120,40 +132,42 @@ class CveSet:
             sequence = sequence[4:]
         if not valid_cve(sequence):
             return None
-        try:
-            return Cve.bySequence(sequence)
-        except SQLObjectNotFound:
-            return None
+        return IStore(Cve).find(Cve, sequence=sequence).one()
 
     def getAll(self):
         """See ICveSet."""
-        return Cve.select(orderBy="-datemodified")
+        return IStore(Cve).find(Cve).order_by(Desc(Cve.datemodified))
 
     def __iter__(self):
         """See ICveSet."""
-        return iter(Cve.select())
+        return iter(IStore(Cve).find(Cve))
 
     def new(self, sequence, description, status=CveStatus.CANDIDATE):
         """See ICveSet."""
-        return Cve(sequence=sequence, status=status,
+        cve = Cve(sequence=sequence, status=status,
             description=description)
+        IStore(Cve).add(cve)
+        return cve
 
     def latest(self, quantity=5):
         """See ICveSet."""
-        return Cve.select(orderBy='-datecreated', limit=quantity)
+        return IStore(Cve).find(Cve).order_by(
+            Desc(Cve.datecreated)).config(limit=quantity)
 
     def latest_modified(self, quantity=5):
         """See ICveSet."""
-        return Cve.select(orderBy='-datemodified', limit=quantity)
+        return IStore(Cve).find(Cve).order_by(
+            Desc(Cve.datemodified)).config(limit=quantity)
 
     def search(self, text):
         """See ICveSet."""
-        return Cve.select(
-            fti_search(Cve, text), distinct=True, orderBy='-datemodified')
+        return IStore(Cve).find(Cve, fti_search(Cve, text)).order_by(
+            Desc(Cve.datemodified)).config(distinct=True)
 
     def inText(self, text):
         """See ICveSet."""
         # let's look for matching entries
+        store = IStore(Cve)
         cves = set()
         for match in CVEREF_PATTERN.finditer(text):
             # let's get the core CVE data
@@ -168,6 +182,7 @@ class CveSet:
                     "are reading this, then this CVE entry is probably "
                     "erroneous, since this text should be replaced by "
                     "the official CVE description automatically.")
+                store.add(cve)
             cves.add(cve)
 
         return sorted(cves, key=lambda a: a.sequence)
@@ -194,7 +209,7 @@ class CveSet:
         store = Store.of(bugtasks[0])
 
         xrefs = getUtility(IXRefSet).findFromMany(
-            [(u'bug', unicode(bug.id)) for bug in bugs], types=[u'cve'])
+            [(u'bug', six.text_type(bug.id)) for bug in bugs], types=[u'cve'])
         bugcve_ids = set()
         for bug_key in xrefs:
             for cve_key in xrefs[bug_key]:
@@ -203,7 +218,7 @@ class CveSet:
         bugcve_ids = list(sorted(bugcve_ids))
 
         cves = store.find(
-            Cve, In(Cve.sequence, [seq for _, seq in bugcve_ids]))
+            Cve, Cve.sequence.is_in([seq for _, seq in bugcve_ids]))
 
         if cve_mapper is None:
             cvemap = dict((cve.sequence, cve) for cve in cves)
diff --git a/lib/lp/bugs/model/cvereference.py b/lib/lp/bugs/model/cvereference.py
index afc357a..129b5a1 100644
--- a/lib/lp/bugs/model/cvereference.py
+++ b/lib/lp/bugs/model/cvereference.py
@@ -1,27 +1,37 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
 __all__ = ['CveReference']
 
-from sqlobject import (
-    ForeignKey,
-    StringCol,
+from storm.locals import (
+    Int,
+    Reference,
+    Unicode,
     )
 from zope.interface import implementer
 
 from lp.bugs.interfaces.cvereference import ICveReference
-from lp.services.database.sqlbase import SQLBase
+from lp.services.database.stormbase import StormBase
 
 
 @implementer(ICveReference)
-class CveReference(SQLBase):
+class CveReference(StormBase):
     """A CVE reference to some other tracking system."""
 
-    _table = 'CveReference'
+    __storm_table__ = 'CveReference'
 
-    # db field names
-    cve = ForeignKey(dbName='cve', foreignKey='Cve', notNull=True)
-    source = StringCol(notNull=True)
-    content = StringCol(notNull=True)
-    url = StringCol(notNull=False, default=None)
+    id = Int(primary=True)
+
+    cve_id = Int(name='cve', allow_none=False)
+    cve = Reference(cve_id, 'Cve.id')
+    source = Unicode(allow_none=False)
+    content = Unicode(allow_none=False)
+    url = Unicode(allow_none=True, default=None)
+
+    def __init__(self, cve, source, content, url=None):
+        super(CveReference, self).__init__()
+        self.cve = cve
+        self.source = source
+        self.content = content
+        self.url = url
diff --git a/lib/lp/bugs/model/tests/test_bugtasksearch.py b/lib/lp/bugs/model/tests/test_bugtasksearch.py
index 60524b8..b5455f6 100644
--- a/lib/lp/bugs/model/tests/test_bugtasksearch.py
+++ b/lib/lp/bugs/model/tests/test_bugtasksearch.py
@@ -1,6 +1,8 @@
-# Copyright 2010-2019 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 from datetime import (
@@ -11,6 +13,7 @@ from operator import attrgetter
 import unittest
 
 import pytz
+import six
 from storm.expr import Or
 from testtools.matchers import Equals
 from testtools.testcase import ExpectedException
@@ -2494,7 +2497,8 @@ def test_suite():
     loader = unittest.TestLoader()
     for bug_target_search_type_class in (
         PreloadBugtaskTargets, NoPreloadBugtaskTargets, QueryBugIDs):
-        class_name = 'Test%s' % bug_target_search_type_class.__name__
+        class_name = six.ensure_str(
+            'Test%s' % bug_target_search_type_class.__name__)
         class_bases = (
             bug_target_search_type_class, ProductTarget, OnceTests,
             SearchTestBase, TestCaseWithFactory)
@@ -2502,9 +2506,10 @@ def test_suite():
         suite.addTest(loader.loadTestsFromTestCase(test_class))
 
         for target_mixin in bug_targets_mixins:
-            class_name = 'Test%s%s' % (
-                bug_target_search_type_class.__name__,
-                target_mixin.__name__)
+            class_name = six.ensure_str(
+                'Test%s%s' % (
+                    bug_target_search_type_class.__name__,
+                    target_mixin.__name__))
             mixins = [
                 target_mixin, bug_target_search_type_class]
             class_bases = (
diff --git a/lib/lp/bugs/scripts/cveimport.py b/lib/lp/bugs/scripts/cveimport.py
index b202974..3730fef 100644
--- a/lib/lp/bugs/scripts/cveimport.py
+++ b/lib/lp/bugs/scripts/cveimport.py
@@ -1,10 +1,12 @@
-# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """A set of functions related to the ability to parse the XML CVE database,
 extract details of known CVE entries, and ensure that all of the known
 CVE's are fully registered in Launchpad."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 import gzip
@@ -13,6 +15,7 @@ import time
 
 import defusedxml.cElementTree as cElementTree
 import requests
+import six
 from zope.component import getUtility
 from zope.event import notify
 from zope.interface import implementer
@@ -42,11 +45,11 @@ CVEDB_NS = '{http://cve.mitre.org/cve/downloads/1.0}'
 
 def getText(elem):
     """Get the text content of the given element"""
-    text = elem.text or ""
+    text = six.ensure_text(elem.text or "")
     for e in elem:
         text += getText(e)
         if e.tail:
-            text += e.tail
+            text += six.ensure_text(e.tail)
     return text.strip()
 
 
@@ -71,8 +74,10 @@ def handle_references(cve_node, cve, log):
 
     # work through the refs in the xml dump
     for ref_node in cve_node.findall('.//%sref' % CVEDB_NS):
-        refsrc = ref_node.get("source")
+        refsrc = six.ensure_text(ref_node.get("source"))
         refurl = ref_node.get("url")
+        if refurl is not None:
+            refurl = six.ensure_text(refurl)
         reftxt = getText(ref_node)
         # compare it to each of the known references
         was_there_previously = False
@@ -105,9 +110,9 @@ def handle_references(cve_node, cve, log):
 def update_one_cve(cve_node, log):
     """Update the state of a single CVE item."""
     # get the sequence number
-    sequence = cve_node.get('seq')
+    sequence = six.ensure_text(cve_node.get('seq'))
     # establish its status
-    status = cve_node.get('type')
+    status = six.ensure_text(cve_node.get('type'))
     # get the description
     description = getText(cve_node.find(CVEDB_NS + 'desc'))
     if not description:
diff --git a/lib/lp/bugs/tests/test_bug.py b/lib/lp/bugs/tests/test_bug.py
index fd833b8..2d76d10 100644
--- a/lib/lp/bugs/tests/test_bug.py
+++ b/lib/lp/bugs/tests/test_bug.py
@@ -1,8 +1,10 @@
-# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for lp.bugs.model.Bug."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 from datetime import timedelta
diff --git a/lib/lp/bugs/tests/test_bugchanges.py b/lib/lp/bugs/tests/test_bugchanges.py
index 48c74b0..7312716 100644
--- a/lib/lp/bugs/tests/test_bugchanges.py
+++ b/lib/lp/bugs/tests/test_bugchanges.py
@@ -1,8 +1,10 @@
-# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for recording changes done to a bug."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 from lazr.lifecycle.event import ObjectCreatedEvent
 from testtools.matchers import (
     MatchesStructure,
diff --git a/lib/lp/bugs/tests/test_buglinktarget.py b/lib/lp/bugs/tests/test_buglinktarget.py
index 2286ffb..ccccc38 100644
--- a/lib/lp/bugs/tests/test_buglinktarget.py
+++ b/lib/lp/bugs/tests/test_buglinktarget.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test harness for running the buglinktarget.txt interface test
@@ -7,6 +7,8 @@ This module will run the interface test against the CVE, Specification,
 Question, and BranchMergeProposal implementations of that interface.
 """
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 __all__ = []
diff --git a/lib/lp/bugs/tests/test_cve.py b/lib/lp/bugs/tests/test_cve.py
index 52cfd44..60d2d61 100644
--- a/lib/lp/bugs/tests/test_cve.py
+++ b/lib/lp/bugs/tests/test_cve.py
@@ -1,8 +1,10 @@
-# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2012-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """CVE related tests."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 from zope.component import getUtility
 
 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 4d93342..4d92b7e 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -4578,7 +4578,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
                 cvestate=CveStatus.CANDIDATE):
         """Create a new CVE record."""
         if description is None:
-            description = self.getUniqueString()
+            description = self.getUniqueUnicode()
         return getUtility(ICveSet).new(sequence, description, cvestate)
 
     def makePublisherConfig(self, distribution=None, root_dir=None,
diff --git a/lib/lp/testing/tests/test_factory.py b/lib/lp/testing/tests/test_factory.py
index 46db466..341a990 100644
--- a/lib/lp/testing/tests/test_factory.py
+++ b/lib/lp/testing/tests/test_factory.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for the Launchpad object factory."""
@@ -630,20 +630,20 @@ class TestFactory(TestCaseWithFactory):
 
     # makeCVE
     def test_makeCVE_returns_cve(self):
-        cve = self.factory.makeCVE(sequence='2000-1234')
+        cve = self.factory.makeCVE(sequence=u'2000-1234')
         self.assertThat(cve, ProvidesAndIsProxied(ICve))
 
     def test_makeCVE_uses_sequence(self):
-        cve = self.factory.makeCVE(sequence='2000-1234')
-        self.assertEqual('2000-1234', cve.sequence)
+        cve = self.factory.makeCVE(sequence=u'2000-1234')
+        self.assertEqual(u'2000-1234', cve.sequence)
 
     def test_makeCVE_uses_description(self):
-        cve = self.factory.makeCVE(sequence='2000-1234', description='foo')
-        self.assertEqual('foo', cve.description)
+        cve = self.factory.makeCVE(sequence=u'2000-1234', description=u'foo')
+        self.assertEqual(u'foo', cve.description)
 
     def test_makeCVE_uses_cve_status(self):
         cve = self.factory.makeCVE(
-            sequence='2000-1234', cvestate=CveStatus.DEPRECATED)
+            sequence=u'2000-1234', cvestate=CveStatus.DEPRECATED)
         self.assertEqual(CveStatus.DEPRECATED, cve.status)
 
     # dir() support.