launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #16957
[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