Merge ~twom/launchpad:oci-ocirecipebuild into launchpad:master

Proposed by Tom Wardill
Status: Merged
Approved by: Tom Wardill
Approved revision: e923bacc3f895810d1eab6b9708ee026a9946249
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~twom/launchpad:oci-ocirecipebuild
Merge into: launchpad:master
Prerequisite: ~twom/launchpad:concrete-oci-projects
Diff against target: 517 lines (+336/-11)
6 files modified
lib/lp/oci/interfaces/ocirecipe.py (+3/-0)
lib/lp/oci/interfaces/ocirecipebuild.py (+47/-2)
lib/lp/oci/model/ocirecipe.py (+17/-0)
lib/lp/oci/model/ocirecipebuild.py (+75/-6)
lib/lp/oci/tests/test_ocirecipebuild.py (+168/-0)
lib/lp/testing/factory.py (+26/-3)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+376431@code.launchpad.net

Commit message

Implement more of OCIRecipeBuild

Description of the change

Using SnapBuild as a model, implement more of the required methods to meet the interfaces of IOCIRecipeBuild and IPackageBuild.

Ensure queueBuild works, for further work in to OCIRecipeBuildJob and BuildBehaviour

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve
~twom/launchpad:oci-ocirecipebuild updated
b8a2b9a... by Tom Wardill

Include virtualized tests

1e7cb5c... by Tom Wardill

Remove duplicate attributes

Revision history for this message
Colin Watson (cjwatson) :
review: Approve
~twom/launchpad:oci-ocirecipebuild updated
130aaf6... by Tom Wardill

Rename method, fix comment

9178c04... by Tom Wardill

Format imports

9a3042d... by Tom Wardill

Fix variable name

0427d6c... by Tom Wardill

Better filename tests

e923bac... by Tom Wardill

Better virtualized tests for ocirecipebuild

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/oci/interfaces/ocirecipe.py b/lib/lp/oci/interfaces/ocirecipe.py
index 340adfc..c6ce359 100644
--- a/lib/lp/oci/interfaces/ocirecipe.py
+++ b/lib/lp/oci/interfaces/ocirecipe.py
@@ -240,3 +240,6 @@ class IOCIRecipeSet(Interface):
240240
241 def findByOwner(owner):241 def findByOwner(owner):
242 """Return all OCI Recipes with the given `owner`."""242 """Return all OCI Recipes with the given `owner`."""
243
244 def preloadDataForOCIRecipes(recipes, user):
245 """Load the data reloated to a list of OCI Recipes."""
diff --git a/lib/lp/oci/interfaces/ocirecipebuild.py b/lib/lp/oci/interfaces/ocirecipebuild.py
index 5ee6b46..95af3f6 100644
--- a/lib/lp/oci/interfaces/ocirecipebuild.py
+++ b/lib/lp/oci/interfaces/ocirecipebuild.py
@@ -7,13 +7,14 @@ from __future__ import absolute_import, print_function, unicode_literals
77
8__metaclass__ = type8__metaclass__ = type
9__all__ = [9__all__ = [
10 'IOCIFile',
10 'IOCIRecipeBuild',11 'IOCIRecipeBuild',
11 'IOCIRecipeBuildSet',12 'IOCIRecipeBuildSet',
12 ]13 ]
1314
14from lazr.restful.fields import Reference15from lazr.restful.fields import Reference
15from zope.interface import Interface16from zope.interface import Interface
16from zope.schema import Text17from zope.schema import TextLine
1718
18from lp import _19from lp import _
19from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource20from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
@@ -21,11 +22,20 @@ from lp.buildmaster.interfaces.packagebuild import IPackageBuild
21from lp.oci.interfaces.ocirecipe import IOCIRecipe22from lp.oci.interfaces.ocirecipe import IOCIRecipe
22from lp.services.database.constants import DEFAULT23from lp.services.database.constants import DEFAULT
23from lp.services.fields import PublicPersonChoice24from lp.services.fields import PublicPersonChoice
25from lp.services.librarian.interfaces import ILibraryFileAlias
2426
2527
26class IOCIRecipeBuildEdit(Interface):28class IOCIRecipeBuildEdit(Interface):
29
27 # XXX twom 2020-02-10 This will probably need cancel() implementing30 # XXX twom 2020-02-10 This will probably need cancel() implementing
28 pass31
32 def addFile(lfa, layer_file_digest):
33 """Add an OCI file to this build.
34
35 :param lfa: An `ILibraryFileAlias`.
36 :param layer_file_digest: Digest for this file, used for image layers.
37 :return: An `IOCILayerFile`.
38 """
2939
3040
31class IOCIRecipeBuildView(IPackageBuild):41class IOCIRecipeBuildView(IPackageBuild):
@@ -41,6 +51,21 @@ class IOCIRecipeBuildView(IPackageBuild):
41 required=True,51 required=True,
42 readonly=True)52 readonly=True)
4353
54 def getByFileName():
55 """Retrieve a file by filename
56
57 :return: A result set of (`IOCIFile`, `ILibraryFileAlias`,
58 `ILibraryFileContent`).
59 """
60
61 def getLayerFileByDigest(layer_file_digest):
62 """Retrieve a layer file by the digest.
63
64 :param layer_file_digest: The digest to look up.
65 :raises NotFoundError: if no file exists with the given digest.
66 :return: The corresponding `ILibraryFileAlias`.
67 """
68
4469
45class IOCIRecipeBuildAdmin(Interface):70class IOCIRecipeBuildAdmin(Interface):
46 # XXX twom 2020-02-10 This will probably need rescore() implementing71 # XXX twom 2020-02-10 This will probably need rescore() implementing
@@ -61,3 +86,23 @@ class IOCIRecipeBuildSet(ISpecificBuildFarmJobSource):
6186
62 def preloadBuildsData(builds):87 def preloadBuildsData(builds):
63 """Load the data related to a list of OCI recipe builds."""88 """Load the data related to a list of OCI recipe builds."""
89
90
91class IOCIFile(Interface):
92 """A link between an OCI recipe build and a file in the librarian."""
93
94 build = Reference(
95 IOCIRecipeBuild,
96 title=_("The OCI recipe build producing this file."),
97 required=True, readonly=True)
98
99 library_file = Reference(
100 ILibraryFileAlias, title=_("A file in the librarian."),
101 required=True, readonly=True)
102
103 layer_file_digest = TextLine(
104 title=_("Content-addressable hash of the file''s contents, "
105 "used for reassembling image layers when pushing "
106 "a build to a registry. This hash is in an opaque format "
107 "generated by the OCI build tool."),
108 required=False, readonly=True)
diff --git a/lib/lp/oci/model/ocirecipe.py b/lib/lp/oci/model/ocirecipe.py
index 0d4d20f..8a01b2d 100644
--- a/lib/lp/oci/model/ocirecipe.py
+++ b/lib/lp/oci/model/ocirecipe.py
@@ -30,11 +30,14 @@ from storm.locals import (
30from zope.component import getUtility30from zope.component import getUtility
31from zope.event import notify31from zope.event import notify
32from zope.interface import implementer32from zope.interface import implementer
33from zope.security.proxy import removeSecurityProxy
3334
34from lp.buildmaster.enums import BuildStatus35from lp.buildmaster.enums import BuildStatus
35from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet36from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
36from lp.buildmaster.model.buildfarmjob import BuildFarmJob37from lp.buildmaster.model.buildfarmjob import BuildFarmJob
37from lp.buildmaster.model.buildqueue import BuildQueue38from lp.buildmaster.model.buildqueue import BuildQueue
39from lp.code.model.gitcollection import GenericGitCollection
40from lp.code.model.gitrepository import GitRepository
38from lp.oci.interfaces.ocirecipe import (41from lp.oci.interfaces.ocirecipe import (
39 DuplicateOCIRecipeName,42 DuplicateOCIRecipeName,
40 IOCIRecipe,43 IOCIRecipe,
@@ -46,6 +49,8 @@ from lp.oci.interfaces.ocirecipe import (
46 )49 )
47from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet50from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet
48from lp.oci.model.ocirecipebuild import OCIRecipeBuild51from lp.oci.model.ocirecipebuild import OCIRecipeBuild
52from lp.registry.interfaces.person import IPersonSet
53from lp.services.database.bulk import load_related
49from lp.services.database.constants import DEFAULT54from lp.services.database.constants import DEFAULT
50from lp.services.database.decoratedresultset import DecoratedResultSet55from lp.services.database.decoratedresultset import DecoratedResultSet
51from lp.services.database.interfaces import (56from lp.services.database.interfaces import (
@@ -293,3 +298,15 @@ class OCIRecipeSet:
293 def findByOwner(self, owner):298 def findByOwner(self, owner):
294 """See `IOCIRecipe`."""299 """See `IOCIRecipe`."""
295 return IStore(OCIRecipe).find(OCIRecipe, OCIRecipe.owner == owner)300 return IStore(OCIRecipe).find(OCIRecipe, OCIRecipe.owner == owner)
301
302 def preloadDataForOCIRecipes(self, recipes, user=None):
303 """See `IOCIRecipeSet`."""
304 recipes = [removeSecurityProxy(recipe) for recipe in recipes]
305
306 person_ids = set()
307 for recipe in recipes:
308 person_ids.add(recipe.registrant_id)
309 person_ids.add(recipe.owner_id)
310
311 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
312 person_ids, need_validity=True))
diff --git a/lib/lp/oci/model/ocirecipebuild.py b/lib/lp/oci/model/ocirecipebuild.py
index d069800..307fbd2 100644
--- a/lib/lp/oci/model/ocirecipebuild.py
+++ b/lib/lp/oci/model/ocirecipebuild.py
@@ -7,6 +7,7 @@ from __future__ import absolute_import, print_function, unicode_literals
77
8__metaclass__ = type8__metaclass__ = type
9__all__ = [9__all__ = [
10 'OCIFile',
10 'OCIRecipeBuild',11 'OCIRecipeBuild',
11 'OCIRecipeBuildSet',12 'OCIRecipeBuildSet',
12 ]13 ]
@@ -28,6 +29,7 @@ from storm.store import EmptyResultSet
28from zope.component import getUtility29from zope.component import getUtility
29from zope.interface import implementer30from zope.interface import implementer
3031
32from lp.app.errors import NotFoundError
31from lp.buildmaster.enums import (33from lp.buildmaster.enums import (
32 BuildFarmJobType,34 BuildFarmJobType,
33 BuildStatus,35 BuildStatus,
@@ -35,10 +37,14 @@ from lp.buildmaster.enums import (
35from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource37from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
36from lp.buildmaster.model.buildfarmjob import SpecificBuildFarmJobSourceMixin38from lp.buildmaster.model.buildfarmjob import SpecificBuildFarmJobSourceMixin
37from lp.buildmaster.model.packagebuild import PackageBuildMixin39from lp.buildmaster.model.packagebuild import PackageBuildMixin
40from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
38from lp.oci.interfaces.ocirecipebuild import (41from lp.oci.interfaces.ocirecipebuild import (
42 IOCIFile,
39 IOCIRecipeBuild,43 IOCIRecipeBuild,
40 IOCIRecipeBuildSet,44 IOCIRecipeBuildSet,
41 )45 )
46from lp.registry.model.person import Person
47from lp.services.database.bulk import load_related
42from lp.services.database.constants import DEFAULT48from lp.services.database.constants import DEFAULT
43from lp.services.database.decoratedresultset import DecoratedResultSet49from lp.services.database.decoratedresultset import DecoratedResultSet
44from lp.services.database.enumcol import DBEnum50from lp.services.database.enumcol import DBEnum
@@ -46,6 +52,33 @@ from lp.services.database.interfaces import (
46 IMasterStore,52 IMasterStore,
47 IStore,53 IStore,
48 )54 )
55from lp.services.librarian.model import (
56 LibraryFileAlias,
57 LibraryFileContent,
58 )
59
60
61@implementer(IOCIFile)
62class OCIFile(Storm):
63
64 __storm_table__ = 'OCIFile'
65
66 id = Int(name='id', primary=True)
67
68 build_id = Int(name='build', allow_none=False)
69 build = Reference(build_id, 'OCIRecipeBuild.id')
70
71 library_file_id = Int(name='library_file', allow_none=False)
72 library_file = Reference(library_file_id, 'LibraryFileAlias.id')
73
74 layer_file_digest = Unicode(name='layer_file_digest', allow_none=True)
75
76 def __init__(self, build, library_file, layer_file_digest=None):
77 """Construct a `OCIFile`."""
78 super(OCIFile, self).__init__()
79 self.build = build
80 self.library_file = library_file
81 self.layer_file_digest = layer_file_digest
4982
5083
51@implementer(IOCIRecipeBuild)84@implementer(IOCIRecipeBuild)
@@ -93,6 +126,11 @@ class OCIRecipeBuild(PackageBuildMixin, Storm):
93 build_farm_job_id = Int(name='build_farm_job', allow_none=False)126 build_farm_job_id = Int(name='build_farm_job', allow_none=False)
94 build_farm_job = Reference(build_farm_job_id, 'BuildFarmJob.id')127 build_farm_job = Reference(build_farm_job_id, 'BuildFarmJob.id')
95128
129 # Stub attributes to match the IPackageBuild interface that we
130 # are not using in this implementation at this time.
131 pocket = None
132 distro_series = None
133
96 def __init__(self, build_farm_job, requester, recipe,134 def __init__(self, build_farm_job, requester, recipe,
97 processor, virtualized, date_created):135 processor, virtualized, date_created):
98136
@@ -130,6 +168,34 @@ class OCIRecipeBuild(PackageBuildMixin, Storm):
130 durations.sort()168 durations.sort()
131 return durations[len(durations) // 2]169 return durations[len(durations) // 2]
132170
171 def getByFileName(self, filename):
172 result = Store.of(self).find(
173 (OCIFile, LibraryFileAlias, LibraryFileContent),
174 OCIFile.build == self.id,
175 LibraryFileAlias.id == OCIFile.library_file_id,
176 LibraryFileContent.id == LibraryFileAlias.contentID,
177 LibraryFileAlias.filename == filename).one()
178 if result is not None:
179 return result
180 raise NotFoundError(filename)
181
182 def getLayerFileByDigest(self, layer_file_digest):
183 file_object = Store.of(self).find(
184 (OCIFile, LibraryFileAlias, LibraryFileContent),
185 OCIFile.build == self.id,
186 LibraryFileAlias.id == OCIFile.library_file_id,
187 LibraryFileContent.id == LibraryFileAlias.contentID,
188 OCIFile.layer_file_digest == layer_file_digest).one()
189 if file_object is not None:
190 return file_object
191 raise NotFoundError(layer_file_digest)
192
193 def addFile(self, lfa, layer_file_digest=None):
194 oci_file = OCIFile(
195 build=self, library_file=lfa, layer_file_digest=layer_file_digest)
196 IMasterStore(OCIFile).add(oci_file)
197 return oci_file
198
133 @property199 @property
134 def archive(self):200 def archive(self):
135 # XXX twom 2019-12-05 This may need to change when an OCIProject201 # XXX twom 2019-12-05 This may need to change when an OCIProject
@@ -142,11 +208,6 @@ class OCIRecipeBuild(PackageBuildMixin, Storm):
142 # pillar isn't just a distribution208 # pillar isn't just a distribution
143 return self.recipe.oci_project.distribution209 return self.recipe.oci_project.distribution
144210
145 # Stub attributes to match the IPackageBuild interface that we
146 # will not use in this implementation.
147 pocket = None
148 distro_series = None
149
150211
151@implementer(IOCIRecipeBuildSet)212@implementer(IOCIRecipeBuildSet)
152class OCIRecipeBuildSet(SpecificBuildFarmJobSourceMixin):213class OCIRecipeBuildSet(SpecificBuildFarmJobSourceMixin):
@@ -171,7 +232,15 @@ class OCIRecipeBuildSet(SpecificBuildFarmJobSourceMixin):
171232
172 def preloadBuildsData(self, builds):233 def preloadBuildsData(self, builds):
173 """See `IOCIRecipeBuildSet`."""234 """See `IOCIRecipeBuildSet`."""
174 # XXX twom 2019-12-02 Currently a no-op skeleton, to be filled in235 # Circular import.
236 from lp.oci.model.ocirecipe import OCIRecipe
237 load_related(Person, builds, ["requester_id"])
238 lfas = load_related(LibraryFileAlias, builds, ["log_id"])
239 load_related(LibraryFileContent, lfas, ["contentID"])
240 recipes = load_related(OCIRecipe, builds, ["recipe_id"])
241 getUtility(IOCIRecipeSet).preloadDataForOCIRecipes(recipes)
242 # XXX twom 2019-12-05 This needs to be extended to include
243 # OCIRecipeBuildJob when that exists.
175 return244 return
176245
177 def getByID(self, build_id):246 def getByID(self, build_id):
diff --git a/lib/lp/oci/tests/test_ocirecipebuild.py b/lib/lp/oci/tests/test_ocirecipebuild.py
178new file mode 100644247new file mode 100644
index 0000000..3d51de2
--- /dev/null
+++ b/lib/lp/oci/tests/test_ocirecipebuild.py
@@ -0,0 +1,168 @@
1# Copyright 2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for OCI image building recipe functionality."""
5
6from __future__ import absolute_import, print_function, unicode_literals
7
8from datetime import timedelta
9
10import six
11from zope.component import getUtility
12from zope.security.proxy import removeSecurityProxy
13
14from lp.app.errors import NotFoundError
15from lp.buildmaster.enums import BuildStatus
16from lp.buildmaster.interfaces.buildqueue import IBuildQueue
17from lp.buildmaster.interfaces.packagebuild import IPackageBuild
18from lp.oci.interfaces.ocirecipebuild import (
19 IOCIRecipeBuild,
20 IOCIRecipeBuildSet,
21 )
22from lp.oci.model.ocirecipebuild import OCIRecipeBuildSet
23from lp.testing import (
24 admin_logged_in,
25 TestCaseWithFactory,
26 )
27from lp.testing.layers import (
28 DatabaseFunctionalLayer,
29 LaunchpadZopelessLayer,
30 )
31
32
33class TestOCIRecipeBuild(TestCaseWithFactory):
34
35 layer = LaunchpadZopelessLayer
36
37 def setUp(self):
38 super(TestOCIRecipeBuild, self).setUp()
39 self.build = self.factory.makeOCIRecipeBuild()
40
41 def test_implements_interface(self):
42 with admin_logged_in():
43 self.assertProvides(self.build, IOCIRecipeBuild)
44 self.assertProvides(self.build, IPackageBuild)
45
46 def test_addFile(self):
47 lfa = self.factory.makeLibraryFileAlias()
48 self.build.addFile(lfa)
49 _, result_lfa, _ = self.build.getByFileName(lfa.filename)
50 self.assertEqual(result_lfa, lfa)
51
52 def test_getByFileName(self):
53 files = [self.factory.makeOCIFile(build=self.build) for x in range(3)]
54 result, _, _ = self.build.getByFileName(
55 files[0].library_file.filename)
56 self.assertEqual(result, files[0])
57
58 def test_getByFileName_missing(self):
59 self.assertRaises(
60 NotFoundError,
61 self.build.getByFileName,
62 "missing")
63
64 def test_getLayerFileByDigest(self):
65 files = [self.factory.makeOCIFile(
66 build=self.build, layer_file_digest=six.text_type(x))
67 for x in range(3)]
68 result, _, _ = self.build.getLayerFileByDigest(
69 files[0].layer_file_digest)
70 self.assertEqual(result, files[0])
71
72 def test_getLayerFileByDigest_missing(self):
73 [self.factory.makeOCIFile(
74 build=self.build, layer_file_digest=six.text_type(x))
75 for x in range(3)]
76 self.assertRaises(
77 NotFoundError,
78 self.build.getLayerFileByDigest,
79 'missing')
80
81 def test_estimateDuration(self):
82 # Without previous builds, the default time estimate is 30m.
83 self.assertEqual(1800, self.build.estimateDuration().seconds)
84
85 def test_estimateDuration_with_history(self):
86 # Previous successful builds of the same OCI recipe are used for
87 # estimates.
88 oci_build = self.factory.makeOCIRecipeBuild(
89 status=BuildStatus.FULLYBUILT, duration=timedelta(seconds=335))
90 for i in range(3):
91 self.factory.makeOCIRecipeBuild(
92 requester=oci_build.requester, recipe=oci_build.recipe,
93 status=BuildStatus.FAILEDTOBUILD,
94 duration=timedelta(seconds=20))
95 self.assertEqual(335, oci_build.estimateDuration().seconds)
96
97 def test_queueBuild(self):
98 # OCIRecipeBuild can create the queue entry for itself.
99 bq = self.build.queueBuild()
100 self.assertProvides(bq, IBuildQueue)
101 self.assertEqual(
102 self.build.build_farm_job, removeSecurityProxy(bq)._build_farm_job)
103 self.assertEqual(self.build, bq.specific_build)
104 self.assertEqual(self.build.virtualized, bq.virtualized)
105 self.assertIsNotNone(bq.processor)
106 self.assertEqual(bq, self.build.buildqueue_record)
107
108
109class TestOCIRecipeBuildSet(TestCaseWithFactory):
110
111 layer = DatabaseFunctionalLayer
112
113 def test_implements_interface(self):
114 target = OCIRecipeBuildSet()
115 with admin_logged_in():
116 self.assertProvides(target, IOCIRecipeBuildSet)
117
118 def test_new(self):
119 requester = self.factory.makePerson()
120 recipe = self.factory.makeOCIRecipe()
121 distro_arch_series = self.factory.makeDistroArchSeries()
122 target = getUtility(IOCIRecipeBuildSet).new(
123 requester, recipe, distro_arch_series)
124 with admin_logged_in():
125 self.assertProvides(target, IOCIRecipeBuild)
126
127 def test_getByID(self):
128 builds = [self.factory.makeOCIRecipeBuild() for x in range(3)]
129 result = getUtility(IOCIRecipeBuildSet).getByID(builds[1].id)
130 self.assertEqual(result, builds[1])
131
132 def test_getByBuildFarmJob(self):
133 builds = [self.factory.makeOCIRecipeBuild() for x in range(3)]
134 result = getUtility(IOCIRecipeBuildSet).getByBuildFarmJob(
135 builds[1].build_farm_job)
136 self.assertEqual(result, builds[1])
137
138 def test_getByBuildFarmJobs(self):
139 builds = [self.factory.makeOCIRecipeBuild() for x in range(3)]
140 self.assertContentEqual(
141 builds,
142 getUtility(IOCIRecipeBuildSet).getByBuildFarmJobs(
143 [build.build_farm_job for build in builds]))
144
145 def test_getByBuildFarmJobs_empty(self):
146 self.assertContentEqual(
147 [], getUtility(IOCIRecipeBuildSet).getByBuildFarmJobs([]))
148
149 def test_virtualized_recipe_requires(self):
150 recipe = self.factory.makeOCIRecipe(require_virtualized=True)
151 target = self.factory.makeOCIRecipeBuild(recipe=recipe)
152 self.assertTrue(target.virtualized)
153
154 def test_virtualized_processor_requires(self):
155 distro_arch_series = self.factory.makeDistroArchSeries()
156 distro_arch_series.processor.supports_nonvirtualized = False
157 recipe = self.factory.makeOCIRecipe(require_virtualized=False)
158 target = self.factory.makeOCIRecipeBuild(
159 distro_arch_series=distro_arch_series, recipe=recipe)
160 self.assertTrue(target.virtualized)
161
162 def test_virtualized_no_support(self):
163 recipe = self.factory.makeOCIRecipe(require_virtualized=False)
164 distro_arch_series = self.factory.makeDistroArchSeries()
165 distro_arch_series.processor.supports_nonvirtualized = True
166 target = self.factory.makeOCIRecipeBuild(
167 recipe=recipe, distro_arch_series=distro_arch_series)
168 self.assertFalse(target.virtualized)
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index d0ecd18..f0788d8 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -160,6 +160,7 @@ from lp.hardwaredb.interfaces.hwdb import (
160from lp.oci.interfaces.ocirecipe import IOCIRecipeSet160from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
161from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet161from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet
162from lp.oci.model.ocirecipe import OCIRecipeArch162from lp.oci.model.ocirecipe import OCIRecipeArch
163from lp.oci.model.ocirecipebuild import OCIFile
163from lp.registry.enums import (164from lp.registry.enums import (
164 BranchSharingPolicy,165 BranchSharingPolicy,
165 BugSharingPolicy,166 BugSharingPolicy,
@@ -4979,7 +4980,9 @@ class BareLaunchpadObjectFactory(ObjectFactory):
4979 return OCIRecipeArch(recipe, processor)4980 return OCIRecipeArch(recipe, processor)
49804981
4981 def makeOCIRecipeBuild(self, requester=None, recipe=None,4982 def makeOCIRecipeBuild(self, requester=None, recipe=None,
4982 distro_arch_series=None, date_created=DEFAULT):4983 distro_arch_series=None, date_created=DEFAULT,
4984 status=BuildStatus.NEEDSBUILD, builder=None,
4985 duration=None):
4983 """Make a new OCIRecipeBuild."""4986 """Make a new OCIRecipeBuild."""
4984 if requester is None:4987 if requester is None:
4985 requester = self.makePerson()4988 requester = self.makePerson()
@@ -4987,9 +4990,29 @@ class BareLaunchpadObjectFactory(ObjectFactory):
4987 distro_arch_series = self.makeDistroArchSeries()4990 distro_arch_series = self.makeDistroArchSeries()
4988 if recipe is None:4991 if recipe is None:
4989 recipe = self.makeOCIRecipe()4992 recipe = self.makeOCIRecipe()
49904993 oci_build = getUtility(IOCIRecipeBuildSet).new(
4991 return getUtility(IOCIRecipeBuildSet).new(
4992 requester, recipe, distro_arch_series, date_created)4994 requester, recipe, distro_arch_series, date_created)
4995 if duration is not None:
4996 removeSecurityProxy(oci_build).updateStatus(
4997 BuildStatus.BUILDING, builder=builder,
4998 date_started=oci_build.date_created)
4999 removeSecurityProxy(oci_build).updateStatus(
5000 status, builder=builder,
5001 date_finished=oci_build.date_started + duration)
5002 else:
5003 removeSecurityProxy(oci_build).updateStatus(
5004 status, builder=builder)
5005 return oci_build
5006
5007 def makeOCIFile(self, build=None, library_file=None,
5008 layer_file_digest=None):
5009 """Make a new OCIFile."""
5010 if build is None:
5011 build = self.makeOCIRecipeBuild()
5012 if library_file is None:
5013 library_file = self.makeLibraryFileAlias()
5014 return OCIFile(build=build, library_file=library_file,
5015 layer_file_digest=layer_file_digest)
49935016
49945017
4995# Some factory methods return simple Python types. We don't add5018# Some factory methods return simple Python types. We don't add

Subscribers

People subscribed via source and target branches

to status/vote changes: