← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~julian-edwards/launchpad/publisher-config-db-schema into lp:launchpad

 

Julian Edwards has proposed merging lp:~julian-edwards/launchpad/publisher-config-db-schema into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~julian-edwards/launchpad/publisher-config-db-schema/+merge/52411

= Summary =
New PublisherConfig table

== Proposed fix ==
This branch adds a new PublisherConfig table which will eventually deprecate 
the archivepublisher config section.

The schema allows for separate configurations for each hosted distribution 
which is required as part of the Derived Distributions feature.  When we start 
hosting multiple distributions, the single configuration that currently exists 
for the purposes of publishing Ubuntu will not suffice.  The configured 
options are:

 * The base path for the archive
 * The URL to the archive

It's entirely possible that custom distros will be hosted in an entirely 
different disk area to Ubuntu, so we need to retain this configurability for 
each distro.

The intention is to add a trivial LaunchpadEditForm page after this lands so 
that we can set up the data, fix the rest of the code to use it, and delete 
the original config.

== Implementation details ==
Fairly trivial schema change plus new model code.

== Tests ==
bin/test -cvv test_publisherconfig

== Demo and Q/A ==
n/a yet
-- 
https://code.launchpad.net/~julian-edwards/launchpad/publisher-config-db-schema/+merge/52411
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~julian-edwards/launchpad/publisher-config-db-schema into lp:launchpad.
=== added file 'database/schema/patch-2208-99-0.sql'
--- database/schema/patch-2208-99-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-99-0.sql	2011-03-07 13:21:41 +0000
@@ -0,0 +1,14 @@
+SET client_min_messages=ERROR;
+
+CREATE TABLE PublisherConfig (
+    id serial PRIMARY KEY,
+    distribution integer NOT NULL CONSTRAINT publisherconfig__distribution__fk REFERENCES distribution,
+    root_dir text NOT NULL,
+    base_url text NOT NULL,
+    copy_base_url text NOT NULL
+);
+    
+CREATE UNIQUE INDEX publisherconfig__distribution__idx
+    ON PublisherConfig(distribution);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 99, 0);

=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg	2011-03-07 07:27:56 +0000
+++ database/schema/security.cfg	2011-03-07 13:21:41 +0000
@@ -260,6 +260,7 @@
 public.productrelease                   = SELECT, INSERT, UPDATE, DELETE
 public.productreleasefile               = SELECT, INSERT, DELETE
 public.productseriescodeimport          = SELECT, INSERT, UPDATE
+public.publisherconfig                  = SELECT, INSERT, UPDATE, DELETE
 public.project                          = SELECT
 public.projectbounty                    = SELECT, INSERT, UPDATE
 public.questionbug                      = SELECT, INSERT, DELETE

=== modified file 'lib/lp/archivepublisher/config.py'
--- lib/lp/archivepublisher/config.py	2010-10-17 13:35:20 +0000
+++ lib/lp/archivepublisher/config.py	2011-03-07 13:21:41 +0000
@@ -83,10 +83,6 @@
     return pubconf
 
 
-class LucilleConfigError(Exception):
-    """Lucille configuration was not present."""
-
-
 class Config(object):
     """Manage a publisher configuration from the database. (Read Only)
     This class provides a useful abstraction so that if we change

=== modified file 'lib/lp/archivepublisher/deathrow.py'
--- lib/lp/archivepublisher/deathrow.py	2010-10-17 13:35:20 +0000
+++ lib/lp/archivepublisher/deathrow.py	2011-03-07 13:21:41 +0000
@@ -17,7 +17,6 @@
 from lp.archivepublisher import ELIGIBLE_DOMINATION_STATES
 from lp.archivepublisher.config import (
     getPubConfig,
-    LucilleConfigError,
     )
 from lp.archivepublisher.diskpool import DiskPool
 from lp.archivepublisher.utils import process_in_batches
@@ -40,12 +39,8 @@
          the one provided by the publishing-configuration, it will be only
          used for PRIMARY archives.
     """
-    log.debug("Grab Lucille config.")
-    try:
-        pubconf = getPubConfig(archive)
-    except LucilleConfigError, info:
-        log.error(info)
-        raise
+    log.debug("Grab publisher config.")
+    pubconf = getPubConfig(archive)
 
     if (pool_root_override is not None and
         archive.purpose == ArchivePurpose.PRIMARY):

=== added file 'lib/lp/archivepublisher/interfaces/publisherconfig.py'
--- lib/lp/archivepublisher/interfaces/publisherconfig.py	1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/interfaces/publisherconfig.py	2011-03-07 13:21:41 +0000
@@ -0,0 +1,58 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# pylint: disable-msg=E0211,E0213
+
+"""PublisherConfig interface."""
+
+__metaclass__ = type
+
+__all__ = [
+    'IPublisherConfig',
+    'IPublisherConfigSet',
+    ]
+
+from lazr.restful.fields import Reference
+from zope.interface import Interface
+from zope.schema import (
+    Int,
+    TextLine,
+    )
+
+from canonical.launchpad import _
+from lp.registry.interfaces.distribution import IDistribution
+
+
+class IPublisherConfig(Interface):
+    """`PublisherConfig` interface."""
+
+    id = Int(title=_('ID'), required=True, readonly=True)
+
+    distribution = Reference(
+        IDistribution, title=_("Distribution"), required=True,
+        description=_("The Distribution for this configuration."))
+
+    root_dir = TextLine(
+        title=_("Root Directory"), required=True,
+        description=_("The root directory for published archives."))
+
+    base_url = TextLine(
+        title=_("Base URL"), required=True,
+        description=_("The base URL for published archives"))
+
+    copy_base_url = TextLine(
+        title=_("Copy Base URL"), required=True,
+        description=_("The base URL for published copy archives"))
+
+
+class IPublisherConfigSet(Interface):
+    """`PublisherConfigSet` interface."""
+
+    def new(distribution, root_dir, base_url, copy_base_url):
+        """Create a new `PublisherConfig`."""
+
+    def getByDistribution(distribution):
+        """Get the config for a a distribution.
+
+        :param distribution: An `IDistribution`
+        """

=== added file 'lib/lp/archivepublisher/model/publisherconfig.py'
--- lib/lp/archivepublisher/model/publisherconfig.py	1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/model/publisherconfig.py	2011-03-07 13:21:41 +0000
@@ -0,0 +1,68 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Database class for table PublisherConfig."""
+
+__metaclass__ = type
+
+__all__ = [
+    'PublisherConfig',
+    'PublisherConfigSet',
+    ]
+
+from storm.locals import (
+    Int,
+    Reference,
+    Storm,
+    RawStr,
+    )
+from zope.interface import implements
+
+from canonical.launchpad.interfaces.lpstorm import (
+    IMasterStore,
+    )
+from lp.archivepublisher.interfaces.publisherconfig import (
+    IPublisherConfig,
+    IPublisherConfigSet,
+    )
+
+
+class PublisherConfig(Storm):
+    """See `IArchiveAuthToken`."""
+    implements(IPublisherConfig)
+    __storm_table__ = 'PublisherConfig'
+
+    id = Int(primary=True)
+
+    distribution_id = Int(name='distribution', allow_none=False)
+    distribution = Reference(distribution_id, 'Distribution.id')
+
+    root_dir = RawStr(name='root_dir', allow_none=False)
+
+    base_url = RawStr(name='base_url', allow_none=False)
+
+    copy_base_url = RawStr(name='copy_base_url', allow_none=False)
+
+
+class PublisherConfigSet:
+    """See `IPublisherConfigSet`."""
+    implements(IPublisherConfigSet)
+    title = "Soyuz Publisher Configurations"
+
+    def new(self, distribution, root_dir, base_url, copy_base_url):
+        """Make and return a new `PublisherConfig`."""
+        store = IMasterStore(PublisherConfig)
+        pubconf = PublisherConfig()
+        pubconf.distribution = distribution
+        pubconf.root_dir = root_dir
+        pubconf.base_url = base_url
+        pubconf.copy_base_url = copy_base_url
+        store.add(pubconf)
+        return pubconf
+
+    def getByDistribution(self, distribution):
+        """See `IArchiveAuthTokenSet`."""
+        store = IMasterStore(PublisherConfig)
+        return store.find(
+            PublisherConfig,
+            PublisherConfig.distribution_id == distribution.id).one()

=== modified file 'lib/lp/archivepublisher/publishing.py'
--- lib/lp/archivepublisher/publishing.py	2011-02-04 09:07:36 +0000
+++ lib/lp/archivepublisher/publishing.py	2011-03-07 13:21:41 +0000
@@ -21,7 +21,6 @@
 from lp.archivepublisher import HARDCODED_COMPONENT_ORDER
 from lp.archivepublisher.config import (
     getPubConfig,
-    LucilleConfigError,
     )
 from lp.archivepublisher.diskpool import DiskPool
 from lp.archivepublisher.domination import Dominator
@@ -120,11 +119,7 @@
     else:
         log.debug("Finding configuration for '%s' PPA."
                   % archive.owner.name)
-    try:
-        pubconf = getPubConfig(archive)
-    except LucilleConfigError, info:
-        log.error(info)
-        raise
+    pubconf = getPubConfig(archive)
 
     disk_pool = _getDiskPool(pubconf, log)
 

=== added file 'lib/lp/archivepublisher/tests/test_publisherconfig.py'
--- lib/lp/archivepublisher/tests/test_publisherconfig.py	1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/tests/test_publisherconfig.py	2011-03-07 13:21:41 +0000
@@ -0,0 +1,66 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for publisherConfig model class."""
+
+__metaclass__ = type
+
+
+from storm.store import Store
+from storm.exceptions import IntegrityError
+from zope.component import getUtility
+from zope.interface.verify import verifyObject
+
+from canonical.testing.layers import ZopelessDatabaseLayer
+from lp.archivepublisher.interfaces.publisherconfig import (
+    IPublisherConfig,
+    IPublisherConfigSet,
+    )
+from lp.testing import TestCaseWithFactory
+
+
+class TestPublisherConfig(TestCaseWithFactory):
+    """Test the `PublisherConfig` model."""
+    layer = ZopelessDatabaseLayer
+
+    def setUp(self):
+        TestCaseWithFactory.setUp(self)
+        self.distribution = self.factory.makeDistribution(name='conftest')
+
+    def test_verify_interface(self):
+        # Test the interface for the model.
+        pubconf = self.factory.makePublisherConfig()
+        verified = verifyObject(IPublisherConfig, pubconf)
+        self.assertTrue(verified)
+
+    def test_properties(self):
+        # Test the model properties.
+        ROOT_DIR = "rootdir/test"
+        BASE_URL = "http://base.url";
+        COPY_BASE_URL = "http://base.url";
+        pubconf = self.factory.makePublisherConfig(
+            distribution=self.distribution,
+            root_dir=ROOT_DIR,
+            base_url=BASE_URL,
+            copy_base_url=COPY_BASE_URL,
+            )
+
+        self.assertEqual(self.distribution.name, pubconf.distribution.name)
+        self.assertEqual(ROOT_DIR, pubconf.root_dir)
+        self.assertEqual(BASE_URL, pubconf.base_url)
+        self.assertEqual(COPY_BASE_URL, pubconf.copy_base_url)
+
+    def test_one_config_per_distro(self):
+        # Only one config for each distro is allowed.
+        pubconf = self.factory.makePublisherConfig(self.distribution)
+        pubconf2 = self.factory.makePublisherConfig(self.distribution)
+        store = Store.of(pubconf)
+        self.assertRaises(IntegrityError, store.flush)
+
+    def test_getByDistribution(self):
+        # Test that IPublisherConfigSet.getByDistribution works.
+        pubconf = self.factory.makePublisherConfig(
+            distribution=self.distribution)
+        pubconf = getUtility(IPublisherConfigSet).getByDistribution(
+            self.distribution)
+        self.assertEqual(self.distribution.name, pubconf.distribution.name)

=== modified file 'lib/lp/archivepublisher/zcml/configure.zcml'
--- lib/lp/archivepublisher/zcml/configure.zcml	2009-07-13 18:15:02 +0000
+++ lib/lp/archivepublisher/zcml/configure.zcml	2011-03-07 13:21:41 +0000
@@ -2,8 +2,30 @@
      GNU Affero General Public License version 3 (see the file LICENSE).
 -->
 
-<configure xmlns="http://namespaces.zope.org/zope";>
+<configure
+    xmlns="http://namespaces.zope.org/zope";
+    xmlns:browser="http://namespaces.zope.org/browser";
+    xmlns:i18n="http://namespaces.zope.org/i18n";
+    xmlns:webservice="http://namespaces.canonical.com/webservice";
+    xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc";
+    i18n_domain="launchpad">
+
     <include package="lp.archivepublisher.zcml"
              file="archivesigningkey.zcml" />
+
+    <securedutility
+        class="lp.archivepublisher.model.publisherconfig.PublisherConfigSet"
+        provides="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfigSet">
+        <allow
+            interface="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfigSet"/>
+    </securedutility>
+
+    <class
+        class="lp.archivepublisher.model.publisherconfig.PublisherConfig">
+        <allow
+            interface="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfig" />
+    </class>
+
+
 </configure>
 

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2011-03-06 23:15:05 +0000
+++ lib/lp/testing/factory.py	2011-03-07 13:21:41 +0000
@@ -101,6 +101,7 @@
     )
 from canonical.launchpad.webapp.sorting import sorted_version_numbers
 from lp.app.enums import ServiceUsage
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.archiveuploader.dscfile import DSCFile
 from lp.archiveuploader.uploadpolicy import BuildDaemonUploadPolicy
 from lp.blueprints.enums import (
@@ -3850,6 +3851,20 @@
             description = self.getUniqueString()
         return getUtility(ICveSet).new(sequence, description, cvestate)
 
+    def makePublisherConfig(self, distribution=None, root_dir=None,
+                            base_url=None, copy_base_url=None):
+        """Create a new `PublisherConfig` record."""
+        if distribution is None:
+            distribution = self.makeDistribution()
+        if root_dir is None:
+            root_dir = self.getUniqueString()
+        if base_url is None:
+            base_url = self.getUniqueString()
+        if copy_base_url is None:
+            copy_base_url = self.getUniqueString()
+        return getUtility(IPublisherConfigSet).new(
+            distribution, root_dir, base_url, copy_base_url)
+
 
 # Some factory methods return simple Python types. We don't add
 # security wrappers for them, as well as for objects created by