launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #31570
[Merge] ~jugmac00/launchpad:enable-filtering-when-requesting-rock-builds into launchpad:master
Jürgen Gmach has proposed merging ~jugmac00/launchpad:enable-filtering-when-requesting-rock-builds into launchpad:master.
Commit message:
Enable requesting a subset of architectures for rock builds
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jugmac00/launchpad/+git/launchpad/+merge/473987
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~jugmac00/launchpad:enable-filtering-when-requesting-rock-builds into launchpad:master.
diff --git a/lib/lp/rocks/adapters/buildarch.py b/lib/lp/rocks/adapters/buildarch.py
index 57ce120..d78c7f0 100644
--- a/lib/lp/rocks/adapters/buildarch.py
+++ b/lib/lp/rocks/adapters/buildarch.py
@@ -131,7 +131,9 @@ class UnifiedRockBaseConfiguration:
self.run_on = list(build_on) if run_on is None else run_on
@classmethod
- def from_dict(cls, rockcraft_data, supported_arches):
+ def from_dict(
+ cls, rockcraft_data, supported_arches, requested_architectures
+ ):
base = rockcraft_data["base"]
if isinstance(base, str):
if base == "bare" and "build-base" not in rockcraft_data:
@@ -155,9 +157,6 @@ class UnifiedRockBaseConfiguration:
# interpreted as a float. So we convert it to a string.
base_channel = str(base["channel"])
- # XXX jugmac00 2024-09-18: Find out if we need 'build-base' or not.
- # There is no existing code that is using that.
-
platforms = rockcraft_data.get("platforms")
if not platforms:
raise MissingPropertyError(
@@ -165,6 +164,13 @@ class UnifiedRockBaseConfiguration:
)
configs = []
for platform, configuration in platforms.items():
+ # when we request specific architectures, we need to check
+ # whether they are defined in the rockcraft.yaml configuration
+ # when we do not request specific arches, we build for all
+ # platforms defined in the configuration files
+ if requested_architectures:
+ if platform not in requested_architectures:
+ continue
# The 'platforms' property and its values look like
# platforms:
# ubuntu-amd64:
@@ -209,17 +215,22 @@ class UnifiedRockBaseConfiguration:
return configs
-def determine_instances_to_build(rockcraft_data, supported_arches):
+def determine_instances_to_build(
+ rockcraft_data, supported_arches, requested_architectures=None
+):
"""Return a list of instances to build based on rockcraft.yaml.
:param rockcraft_data: A parsed rockcraft.yaml.
:param supported_arches: An ordered list of all `DistroArchSeries` that
we can create builds for. Note that these may span multiple
`DistroSeries`.
+ :param requested_architectures: A list of requested architectures, or None;
+ for the latter case we build all architectures specified in the
+ rockcraft.yaml configuration file.
:return: A list of `DistroArchSeries`.
"""
configs = UnifiedRockBaseConfiguration.from_dict(
- rockcraft_data, supported_arches
+ rockcraft_data, supported_arches, requested_architectures
)
# Ensure that multiple `run-on` items don't overlap; this is ambiguous
# and forbidden by rockcraft.
diff --git a/lib/lp/rocks/interfaces/rockrecipe.py b/lib/lp/rocks/interfaces/rockrecipe.py
index 4f1f677..ca87032 100644
--- a/lib/lp/rocks/interfaces/rockrecipe.py
+++ b/lib/lp/rocks/interfaces/rockrecipe.py
@@ -69,6 +69,7 @@ from lp.app.errors import NameLookupFailed
from lp.app.interfaces.informationtype import IInformationType
from lp.app.validators.name import name_validator
from lp.app.validators.path import path_does_not_escape
+from lp.buildmaster.interfaces.processor import IProcessor
from lp.code.interfaces.gitref import IGitRef
from lp.code.interfaces.gitrepository import IGitRepository
from lp.registry.interfaces.person import IPerson
@@ -377,7 +378,12 @@ class IRockRecipeView(Interface):
),
key_type=TextLine(),
required=False,
- )
+ ),
+ architectures=List(
+ title=_("The list of architectures to build for this recipe."),
+ value_type=Reference(schema=IProcessor),
+ required=False,
+ ),
)
@export_factory_operation(IRockRecipeBuildRequest, [])
@operation_for_version("devel")
diff --git a/lib/lp/rocks/model/rockrecipe.py b/lib/lp/rocks/model/rockrecipe.py
index b4057e7..0a94562 100644
--- a/lib/lp/rocks/model/rockrecipe.py
+++ b/lib/lp/rocks/model/rockrecipe.py
@@ -532,27 +532,18 @@ class RockRecipe(StormBase):
rockcraft_data = removeSecurityProxy(
getUtility(IRockRecipeSet).getRockcraftYaml(self)
)
-
- # Sort by (Distribution.id, DistroSeries.id, Processor.id) for
- # determinism. This is chosen to be a similar order as in
- # BinaryPackageBuildSet.createForSource, to minimize confusion.
- supported_arches = [
- das
- for das in sorted(
- self.getAllowedArchitectures(),
- key=attrgetter(
- "distroseries.distribution.id",
- "distroseries.id",
- "processor.id",
- ),
- )
- if (
- architectures is None
- or das.architecturetag in architectures
- )
- ]
+ supported_arches = sorted(
+ self.getAllowedArchitectures(),
+ key=attrgetter(
+ "distroseries.distribution.id",
+ "distroseries.id",
+ "processor.id",
+ ),
+ )
instances_to_build = determine_instances_to_build(
- rockcraft_data, supported_arches
+ rockcraft_data,
+ supported_arches=supported_arches,
+ requested_architectures=architectures,
)
except Exception as e:
if not allow_failures:
diff --git a/lib/lp/rocks/model/rockrecipejob.py b/lib/lp/rocks/model/rockrecipejob.py
index f21df96..7587e63 100644
--- a/lib/lp/rocks/model/rockrecipejob.py
+++ b/lib/lp/rocks/model/rockrecipejob.py
@@ -174,6 +174,14 @@ class RockRecipeRequestBuildsJob(RockRecipeJobDerived):
@classmethod
def create(cls, recipe, requester, channels=None, architectures=None):
"""See `IRockRecipeRequestBuildsJobSource`."""
+ # architectures can be a iterable of strings or Processors
+ # in the latter case, we need to convert them to strings
+ if architectures and all(
+ not (isinstance(arch, str)) for arch in architectures
+ ):
+ architectures = [
+ architecture.name for architecture in architectures
+ ]
metadata = {
"requester": requester.id,
"channels": channels,
diff --git a/lib/lp/rocks/tests/test_rockrecipe.py b/lib/lp/rocks/tests/test_rockrecipe.py
index ae56494..dbda557 100644
--- a/lib/lp/rocks/tests/test_rockrecipe.py
+++ b/lib/lp/rocks/tests/test_rockrecipe.py
@@ -1860,6 +1860,121 @@ class TestRockRecipeWebservice(TestCaseWithFactory):
),
)
+ def test_requestBuilds_with_architectures(self):
+ # Requests for builds for all relevant architectures can be
+ # performed over the webservice, and the returned entry indicates
+ # the status of the asynchronous job.
+ distroseries = self.factory.makeDistroSeries(
+ distribution=getUtility(ILaunchpadCelebrities).ubuntu,
+ registrant=self.person,
+ )
+ amd640 = self.factory.makeProcessor(
+ name="amd640", supports_virtualized=True
+ )
+ risc500 = self.factory.makeProcessor(
+ name="risc500", supports_virtualized=True
+ )
+ s400x = self.factory.makeProcessor(
+ name="s400x", supports_virtualized=True
+ )
+ processors = [amd640, risc500, s400x]
+ for processor in processors:
+ self.makeBuildableDistroArchSeries(
+ distroseries=distroseries,
+ architecturetag=processor.name,
+ processor=processor,
+ owner=self.person,
+ )
+ [git_ref] = self.factory.makeGitRefs()
+ recipe = self.makeRockRecipe(git_ref=git_ref)
+ now = get_transaction_timestamp(IStore(distroseries))
+ # api_base = "http://api.launchpad.test/devel"
+ # amd640_api_url = api_base + api_url(amd640)
+ response = self.webservice.named_post(
+ recipe["self_link"],
+ "requestBuilds",
+ channels={"rockcraft": "edge"},
+ architectures=[api_url(amd640), api_url(risc500)],
+ )
+ self.assertEqual(201, response.status)
+ build_request_url = response.getHeader("Location")
+ build_request = self.webservice.get(build_request_url).jsonBody()
+ self.assertThat(
+ build_request,
+ ContainsDict(
+ {
+ "date_requested": AfterPreprocessing(
+ iso8601.parse_date, GreaterThan(now)
+ ),
+ "date_finished": Is(None),
+ "recipe_link": Equals(recipe["self_link"]),
+ "status": Equals("Pending"),
+ "error_message": Is(None),
+ "builds_collection_link": Equals(
+ build_request_url + "/builds"
+ ),
+ }
+ ),
+ )
+ self.assertEqual([], self.getCollectionLinks(build_request, "builds"))
+ with person_logged_in(self.person):
+ rockcraft_yaml = (
+ "base: ubuntu@%s\nplatforms:\n" % distroseries.version
+ )
+ for processor in processors:
+ rockcraft_yaml += " %s:\n" % processor.name
+ self.useFixture(GitHostingFixture(blob=rockcraft_yaml))
+ [job] = getUtility(IRockRecipeRequestBuildsJobSource).iterReady()
+ with dbuser(config.IRockRecipeRequestBuildsJobSource.dbuser):
+ JobRunner([job]).runAll()
+ date_requested = iso8601.parse_date(build_request["date_requested"])
+ now = get_transaction_timestamp(IStore(distroseries))
+ build_request = self.webservice.get(
+ build_request["self_link"]
+ ).jsonBody()
+ self.assertThat(
+ build_request,
+ ContainsDict(
+ {
+ "date_requested": AfterPreprocessing(
+ iso8601.parse_date, Equals(date_requested)
+ ),
+ "date_finished": AfterPreprocessing(
+ iso8601.parse_date,
+ MatchesAll(GreaterThan(date_requested), LessThan(now)),
+ ),
+ "recipe_link": Equals(recipe["self_link"]),
+ "status": Equals("Completed"),
+ "error_message": Is(None),
+ "builds_collection_link": Equals(
+ build_request_url + "/builds"
+ ),
+ }
+ ),
+ )
+ builds = self.webservice.get(
+ build_request["builds_collection_link"]
+ ).jsonBody()["entries"]
+ with person_logged_in(self.person):
+ self.assertThat(
+ builds,
+ MatchesSetwise(
+ *(
+ ContainsDict(
+ {
+ "recipe_link": Equals(recipe["self_link"]),
+ "archive_link": Equals(
+ self.getURL(distroseries.main_archive)
+ ),
+ "arch_tag": Equals(processor.name),
+ "channels": Equals({"rockcraft": "edge"}),
+ }
+ )
+ for processor in (amd640, risc500) # requested arches
+ )
+ ),
+ )
+
def test_requestBuilds_failure(self):
# If the asynchronous build request job fails, this is reflected in
# the build request entry.