← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/builderset-current_build into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/builderset-current_build into lp:launchpad.

Commit message:
Add and export Builder.current_build on the webservice.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/builderset-current_build/+merge/223863

I've added and exported Builder.current_build, so we can see more of the build farm state over the API. Additionally, I cleaned up some BuilderSet preloading and made it more efficient.


Anonymous:

In [11]: lp.builders.getByName(name='molybdenum').current_build
Out[11]: <repr(<lazr.restfulclient.resource.Entry at 0x7f1aec045090>) failed: ValueError: You tried to access a resource that you don't have the server-side permission to see.>

In [12]: lp.builders.getByName(name='molybdenum').current_build_link
Out[12]: u'tag:launchpad.net:2008:redacted'


Authenticated as a commercial admin:

In [1]: lp.builders.getByName(name='molybdenum').current_build
Out[1]: <build at https://api.dogfood.paddev.net/devel/~wgrant/+archive/dfp3a/+build/5571092>

In [2]: lp.builders.getByName(name='molybdenum').current_build_link
Out[2]: u'https://api.dogfood.paddev.net/devel/~wgrant/+archive/dfp3a/+build/5571092'

-- 
https://code.launchpad.net/~wgrant/launchpad/builderset-current_build/+merge/223863
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/builderset-current_build into lp:launchpad.
=== modified file 'lib/lp/_schema_circular_imports.py'
--- lib/lp/_schema_circular_imports.py	2014-06-18 18:28:10 +0000
+++ lib/lp/_schema_circular_imports.py	2014-06-20 07:18:25 +0000
@@ -888,6 +888,7 @@
 
 # IBuilder
 patch_entry_explicit_version(IBuilder, 'beta')
+IBuilder['current_build'].schema = IBuildFarmJob
 
 # IBuilderSet
 patch_operations_explicit_version(IBuilderSet, 'beta', "getByName")

=== modified file 'lib/lp/buildmaster/browser/builder.py'
--- lib/lp/buildmaster/browser/builder.py	2014-05-07 10:01:04 +0000
+++ lib/lp/buildmaster/browser/builder.py	2014-06-20 07:18:25 +0000
@@ -37,16 +37,13 @@
     IBuilder,
     IBuilderSet,
     )
+from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
 from lp.buildmaster.model.buildqueue import BuildQueue
 from lp.code.interfaces.sourcepackagerecipebuild import (
     ISourcePackageRecipeBuildSource,
     )
-from lp.services.database.interfaces import IStore
 from lp.services.helpers import english_list
-from lp.services.propertycache import (
-    cachedproperty,
-    get_property_cache,
-    )
+from lp.services.propertycache import cachedproperty
 from lp.services.webapp import (
     ApplicationMenu,
     canonical_url,
@@ -151,19 +148,6 @@
     def builders(self):
         """All active builders"""
         builders = list(self.context.getBuilders())
-
-        # Populate builders' currentjob cachedproperty.
-        queues = IStore(BuildQueue).find(
-            BuildQueue,
-            BuildQueue.builderID.is_in(
-                builder.id for builder in builders))
-        queue_builders = dict(
-            (queue.builderID, queue) for queue in queues)
-        for builder in builders:
-            cache = get_property_cache(builder)
-            cache.currentjob = queue_builders.get(builder.id, None)
-        # Prefetch the jobs' data.
-        BuildQueue.preloadSpecificBuild(queues)
         return list(sorted(
             builders, key=lambda b: (
                 b.virtualized, tuple(p.id for p in b.processors), b.name)))

=== modified file 'lib/lp/buildmaster/interfaces/builder.py'
--- lib/lp/buildmaster/interfaces/builder.py	2014-06-17 12:59:03 +0000
+++ lib/lp/buildmaster/interfaces/builder.py	2014-06-20 07:18:25 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2014 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Builder interfaces."""
@@ -179,6 +179,12 @@
 
     currentjob = Attribute("BuildQueue instance for job being processed.")
 
+    current_build = exported(Reference(
+        title=_("Current build"), required=False, readonly=True,
+        schema=Interface,  # Really IBuildFarmJob.
+        description=_("The job current running on this builder.")),
+        as_of="devel")
+
     failure_count = exported(Int(
         title=_('Failure Count'), required=False, default=0,
        description=_("Number of consecutive failures for this builder.")))

=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmjob.py	2013-11-21 04:13:12 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjob.py	2014-06-20 07:18:25 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2014 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Interface for Soyuz build farm jobs."""
@@ -14,7 +14,10 @@
     ]
 
 from lazr.enum import DBEnumeratedType
-from lazr.restful.declarations import exported
+from lazr.restful.declarations import (
+    export_as_webservice_entry,
+    exported,
+    )
 from lazr.restful.fields import Reference
 from zope.interface import (
     Attribute,
@@ -62,6 +65,8 @@
 class IBuildFarmJob(Interface):
     """Operations that jobs for the build farm must implement."""
 
+    export_as_webservice_entry(as_of='beta')
+
     id = Attribute('The build farm job ID.')
 
     build_farm_job = Attribute('Generic build farm job record')

=== modified file 'lib/lp/buildmaster/interfaces/buildqueue.py'
--- lib/lp/buildmaster/interfaces/buildqueue.py	2013-12-04 06:58:03 +0000
+++ lib/lp/buildmaster/interfaces/buildqueue.py	2014-06-20 07:18:25 +0000
@@ -136,5 +136,8 @@
         builder. If not found, return None.
         """
 
+    def preloadForBuilders(builders):
+        """Preload currentjob for the given IBuilders."""
+
     def preloadForBuildFarmJobs(builds):
         """Preload buildqueue_record for the given IBuildFarmJobs."""

=== modified file 'lib/lp/buildmaster/interfaces/webservice.py'
--- lib/lp/buildmaster/interfaces/webservice.py	2012-01-01 02:58:52 +0000
+++ lib/lp/buildmaster/interfaces/webservice.py	2014-06-20 07:18:25 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2014 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """All the interfaces that are exposed through the webservice.
@@ -12,9 +12,11 @@
 __all__ = [
     'IBuilder',
     'IBuilderSet',
+    'IBuildFarmJob',
     ]
 
 from lp.buildmaster.interfaces.builder import (
     IBuilder,
     IBuilderSet,
     )
+from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob

=== modified file 'lib/lp/buildmaster/model/builder.py'
--- lib/lp/buildmaster/model/builder.py	2014-06-17 12:59:51 +0000
+++ lib/lp/buildmaster/model/builder.py	2014-06-20 07:18:25 +0000
@@ -48,7 +48,10 @@
     specific_build_farm_job_sources,
     )
 from lp.registry.interfaces.person import validate_public_person
-from lp.services.database.bulk import load
+from lp.services.database.bulk import (
+    load,
+    load_related,
+    )
 from lp.services.database.constants import UTC_NOW
 from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.decoratedresultset import DecoratedResultSet
@@ -185,6 +188,12 @@
         """See IBuilder"""
         return getUtility(IBuildQueueSet).getByBuilder(self)
 
+    @property
+    def current_build(self):
+        if self.currentjob is None:
+            return None
+        return self.currentjob.specific_build
+
     def setCleanStatus(self, status):
         """See `IBuilder`."""
         if status != self.clean_status:
@@ -301,7 +310,7 @@
         """See IBuilder."""
         self.gotFailure()
         if self.currentjob is not None:
-            build_farm_job = self.currentjob.specific_build
+            build_farm_job = self.current_build
             build_farm_job.gotFailure()
             logger.info(
                 "Builder %s failure count: %s, job '%s' failure count: %s" % (
@@ -362,10 +371,10 @@
         # Grab (Builder.id, Processor.id) pairs and stuff them into the
         # Builders' processor caches.
         store = IStore(Builder)
-        pairs = store.find(
+        pairs = list(store.find(
             (BuilderProcessor.builder_id, BuilderProcessor.processor_id),
             BuilderProcessor.builder_id.is_in([b.id for b in rows])).order_by(
-                BuilderProcessor.builder_id, BuilderProcessor.processor_id)
+                BuilderProcessor.builder_id, BuilderProcessor.processor_id))
         load(Processor, [pid for bid, pid in pairs])
         for row in rows:
             get_property_cache(row)._processors_cache = []
@@ -375,10 +384,17 @@
 
     def getBuilders(self):
         """See IBuilderSet."""
+        from lp.registry.model.person import Person
         rs = IStore(Builder).find(
             Builder, Builder.active == True).order_by(
                 Builder.virtualized, Builder.name)
-        return DecoratedResultSet(rs, pre_iter_hook=self._preloadProcessors)
+
+        def preload(rows):
+            self._preloadProcessors(rows)
+            load_related(Person, rows, ['ownerID'])
+            bqs = getUtility(IBuildQueueSet).preloadForBuilders(rows)
+            BuildQueue.preloadSpecificBuild(bqs)
+        return DecoratedResultSet(rs, pre_iter_hook=preload)
 
     def getBuildQueueSizes(self):
         """See `IBuilderSet`."""

=== modified file 'lib/lp/buildmaster/model/buildqueue.py'
--- lib/lp/buildmaster/model/buildqueue.py	2013-12-04 07:32:44 +0000
+++ lib/lp/buildmaster/model/buildqueue.py	2014-06-20 07:18:25 +0000
@@ -46,7 +46,10 @@
     IBuildQueue,
     IBuildQueueSet,
     )
-from lp.services.database.bulk import load_related
+from lp.services.database.bulk import (
+    load_referencing,
+    load_related,
+    )
 from lp.services.database.constants import (
     DEFAULT,
     UTC_NOW,
@@ -241,6 +244,16 @@
         """See `IBuildQueueSet`."""
         return BuildQueue.selectOneBy(builder=builder)
 
+    def preloadForBuilders(self, builders):
+        # Populate builders' currentjob cachedproperty.
+        queues = load_referencing(BuildQueue, builders, ['builderID'])
+        queue_builders = dict(
+            (queue.builderID, queue) for queue in queues)
+        for builder in builders:
+            cache = get_property_cache(builder)
+            cache.currentjob = queue_builders.get(builder.id, None)
+        return queues
+
     def preloadForBuildFarmJobs(self, builds):
         """See `IBuildQueueSet`."""
         from lp.buildmaster.model.builder import Builder

=== modified file 'lib/lp/buildmaster/tests/test_manager.py'
--- lib/lp/buildmaster/tests/test_manager.py	2014-06-17 12:59:03 +0000
+++ lib/lp/buildmaster/tests/test_manager.py	2014-06-20 07:18:25 +0000
@@ -374,7 +374,7 @@
         builder = getUtility(IBuilderSet)[scanner.builder_name]
 
         builder.failure_count = builder_count
-        naked_build = removeSecurityProxy(builder.currentjob.specific_build)
+        naked_build = removeSecurityProxy(builder.current_build)
         naked_build.failure_count = job_count
         # The _scanFailed() calls abort, so make sure our existing
         # failure counts are persisted.
@@ -390,7 +390,7 @@
         self.assertEqual(expected_builder_count, builder.failure_count)
         self.assertEqual(
             expected_job_count,
-            builder.currentjob.specific_build.failure_count)
+            builder.current_build.failure_count)
         self.assertEqual(1, manager_module.assessFailureCounts.call_count)
 
     def test_scan_first_fail(self):

=== modified file 'lib/lp/buildmaster/tests/test_webservice.py'
--- lib/lp/buildmaster/tests/test_webservice.py	2014-04-23 10:50:06 +0000
+++ lib/lp/buildmaster/tests/test_webservice.py	2014-06-20 07:18:25 +0000
@@ -1,13 +1,15 @@
-# Copyright 2011-2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2014 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for the builders webservice ."""
 
 __metaclass__ = type
 
+from testtools.matchers import Equals
 from zope.component import getUtility
 
 from lp.registry.interfaces.person import IPersonSet
+from lp.services.webapp import canonical_url
 from lp.services.webapp.interfaces import OAuthPermission
 from lp.testing import (
     admin_logged_in,
@@ -15,7 +17,9 @@
     logout,
     TestCaseWithFactory,
     )
+from lp.testing._webservice import QueryCollector
 from lp.testing.layers import DatabaseFunctionalLayer
+from lp.testing.matchers import HasQueryCount
 from lp.testing.pages import (
     LaunchpadWebServiceCaller,
     webservice_for_person,
@@ -29,6 +33,46 @@
         super(TestBuildersCollection, self).setUp()
         self.webservice = LaunchpadWebServiceCaller()
 
+    def test_list(self):
+        names = ['bob', 'frog']
+        for i in range(3):
+            builder = self.factory.makeBuilder()
+            self.factory.makeBinaryPackageBuild().queueBuild().markAsBuilding(
+                builder)
+            names.append(builder.name)
+        logout()
+        with QueryCollector() as recorder:
+            builders = self.webservice.get(
+                '/builders', api_version='devel').jsonBody()
+        self.assertContentEqual(
+            names, [b['name'] for b in builders['entries']])
+        self.assertThat(recorder, HasQueryCount(Equals(21)))
+
+    def test_list_with_private_builds(self):
+        # Inaccessible private builds aren't linked in builders'
+        # current_build fields.
+        with admin_logged_in():
+            rbpb = self.factory.makeBinaryPackageBuild(
+                archive=self.factory.makeArchive(private=True))
+            rbpb.queueBuild().markAsBuilding(
+                self.factory.makeBuilder(name='restricted'))
+            bpb = self.factory.makeBinaryPackageBuild(
+                archive=self.factory.makeArchive(private=False))
+            bpb.queueBuild().markAsBuilding(
+                self.factory.makeBuilder(name='public'))
+            bpb_url = canonical_url(bpb, path_only_if_possible=True)
+        logout()
+
+        builders = self.webservice.get(
+            '/builders', api_version='devel').jsonBody()
+        current_builds = dict(
+            (b['name'], b['current_build_link']) for b in builders['entries'])
+        self.assertEqual(
+            'tag:launchpad.net:2008:redacted', current_builds['restricted'])
+        self.assertEqual(
+            'http://api.launchpad.dev/devel' + bpb_url,
+            current_builds['public'])
+
     def test_getBuildQueueSizes(self):
         logout()
         results = self.webservice.named_get(


Follow ups