Merge ~pappacena/launchpad:fix-oci-upload-job-config into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: c8fa04d0fc840c5c9a947fccb4d157fefebf6ad2
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/launchpad:fix-oci-upload-job-config
Merge into: launchpad:master
Diff against target: 239 lines (+127/-41)
2 files modified
lib/lp/oci/model/ocirecipebuildjob.py (+16/-5)
lib/lp/oci/tests/test_ocirecipebuildjob.py (+111/-36)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+392545@code.launchpad.net

Commit message

Fixing OCIRegistryUploadJob.config and avoiding crash logging it

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve
Revision history for this message
Thiago F. Pappacena (pappacena) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/oci/model/ocirecipebuildjob.py b/lib/lp/oci/model/ocirecipebuildjob.py
2index 991b9a4..0db2514 100644
3--- a/lib/lp/oci/model/ocirecipebuildjob.py
4+++ b/lib/lp/oci/model/ocirecipebuildjob.py
5@@ -42,6 +42,7 @@ from lp.oci.interfaces.ociregistryclient import (
6 IOCIRegistryClient,
7 OCIRegistryError,
8 )
9+from lp.services.config import config
10 from lp.services.database.enumcol import DBEnum
11 from lp.services.database.interfaces import (
12 IMasterStore,
13@@ -114,11 +115,19 @@ class OCIRecipeBuildJobDerived(
14
15 def __repr__(self):
16 """An informative representation of the job."""
17- build = self.build
18- return "<%s for ~%s/%s/+oci/%s/+recipe/%s/+build/%d>" % (
19- self.__class__.__name__, build.recipe.owner.name,
20- build.recipe.oci_project.pillar.name,
21- build.recipe.oci_project.name, build.recipe.name, build.id)
22+ try:
23+ build = self.build
24+ return "<%s for ~%s/%s/+oci/%s/+recipe/%s/+build/%d>" % (
25+ self.__class__.__name__, build.recipe.owner.name,
26+ build.recipe.oci_project.pillar.name,
27+ build.recipe.oci_project.name, build.recipe.name, build.id)
28+ except Exception:
29+ # There might be errors while trying to do the full
30+ # representation of this object (database transaction errors,
31+ # for example). There has been some issues in the past trying to
32+ # log this object, so let's not crash everything in case we
33+ # cannot provide a full description of self.
34+ return "<%s ID#%s>" % (self.__class__.__name__, self.job_id)
35
36 @classmethod
37 def get(cls, job_id):
38@@ -180,6 +189,8 @@ class OCIRegistryUploadJob(OCIRecipeBuildJobDerived):
39 retry_error_types = (ManifestListUploadError, )
40 max_retries = 5
41
42+ config = config.IOCIRegistryUploadJobSource
43+
44 @classmethod
45 def create(cls, build):
46 """See `IOCIRegistryUploadJobSource`"""
47diff --git a/lib/lp/oci/tests/test_ocirecipebuildjob.py b/lib/lp/oci/tests/test_ocirecipebuildjob.py
48index 7b66c1d..2b39f4e 100644
49--- a/lib/lp/oci/tests/test_ocirecipebuildjob.py
50+++ b/lib/lp/oci/tests/test_ocirecipebuildjob.py
51@@ -17,11 +17,9 @@ import time
52 from fixtures import FakeLogger
53 from testtools.matchers import (
54 Equals,
55- Is,
56 MatchesDict,
57 MatchesListwise,
58 MatchesStructure,
59- Not,
60 )
61 import transaction
62 from zope.component import getUtility
63@@ -54,6 +52,10 @@ from lp.services.config import config
64 from lp.services.features.testing import FeatureFixture
65 from lp.services.job.interfaces.job import JobStatus
66 from lp.services.job.runner import JobRunner
67+from lp.services.job.tests import (
68+ block_on_job,
69+ pop_remote_notifications,
70+ )
71 from lp.services.webapp import canonical_url
72 from lp.services.webhooks.testing import LogsScheduledWebhooks
73 from lp.testing import (
74@@ -64,6 +66,7 @@ from lp.testing.dbuser import dbuser
75 from lp.testing.fakemethod import FakeMethod
76 from lp.testing.fixture import ZopeUtilityFixture
77 from lp.testing.layers import (
78+ CeleryJobLayer,
79 DatabaseFunctionalLayer,
80 LaunchpadZopelessLayer,
81 )
82@@ -124,7 +127,79 @@ class TestOCIRecipeBuildJob(TestCaseWithFactory):
83 self.assertEqual(expected, oops)
84
85
86-class TestOCIRegistryUploadJob(TestCaseWithFactory):
87+class TestOCIRecipeBuildJobDerived(TestCaseWithFactory):
88+
89+ layer = DatabaseFunctionalLayer
90+
91+ def setUp(self):
92+ super(TestOCIRecipeBuildJobDerived, self).setUp()
93+ self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'}))
94+
95+ def test_repr(self):
96+ build = self.factory.makeOCIRecipeBuild()
97+ job = OCIRecipeBuildJob(
98+ build, OCIRecipeBuildJobType.REGISTRY_UPLOAD, {})
99+ derived_job = OCIRecipeBuildJobDerived(job)
100+ expected_repr = (
101+ "<OCIRecipeBuildJobDerived for "
102+ "~%s/%s/+oci/%s/+recipe/%s/+build/%d>" % (
103+ build.recipe.owner.name,
104+ build.recipe.oci_project.pillar.name,
105+ build.recipe.oci_project.name, build.recipe.name, build.id))
106+ self.assertEqual(expected_repr, repr(derived_job))
107+
108+ def test_repr_fails_to_get_an_attribute(self):
109+ class ErrorOCIRecipeBuildJobDerived(OCIRecipeBuildJobDerived):
110+ def __getattribute__(self, item):
111+ if item == 'build':
112+ raise AttributeError("Somethng is wrong with build")
113+ return super(
114+ ErrorOCIRecipeBuildJobDerived, self).__getattribute__(item)
115+ oci_build = self.factory.makeOCIRecipeBuild()
116+ job = OCIRecipeBuildJob(
117+ oci_build, OCIRecipeBuildJobType.REGISTRY_UPLOAD, {})
118+ derived_job = ErrorOCIRecipeBuildJobDerived(job)
119+ self.assertEqual(
120+ "<ErrorOCIRecipeBuildJobDerived ID#%s>" % derived_job.job_id,
121+ repr(derived_job))
122+
123+
124+class MultiArchRecipeMixin:
125+ def makeRecipe(self, include_i386=True, include_amd64=True):
126+ i386 = getUtility(IProcessorSet).getByName("386")
127+ amd64 = getUtility(IProcessorSet).getByName("amd64")
128+ recipe = self.factory.makeOCIRecipe()
129+ distroseries = self.factory.makeDistroSeries(
130+ distribution=recipe.oci_project.distribution)
131+ distro_i386 = self.factory.makeDistroArchSeries(
132+ distroseries=distroseries, architecturetag="i386",
133+ processor=i386)
134+ distro_i386.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
135+ distro_amd64 = self.factory.makeDistroArchSeries(
136+ distroseries=distroseries, architecturetag="amd64",
137+ processor=amd64)
138+ distro_amd64.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
139+
140+ archs = []
141+ if include_i386:
142+ archs.append(i386)
143+ if include_amd64:
144+ archs.append(amd64)
145+ recipe.setProcessors(archs)
146+ return recipe
147+
148+ def makeBuildRequest(self, include_i386=True, include_amd64=True):
149+ recipe = self.makeRecipe(include_i386, include_amd64)
150+ # Creates a build request with a build in it.
151+ build_request = recipe.requestBuilds(recipe.owner)
152+ with admin_logged_in():
153+ jobs = getUtility(IOCIRecipeRequestBuildsJobSource).iterReady()
154+ with dbuser(config.IOCIRecipeRequestBuildsJobSource.dbuser):
155+ JobRunner(jobs).runAll()
156+ return build_request
157+
158+
159+class TestOCIRegistryUploadJob(TestCaseWithFactory, MultiArchRecipeMixin):
160
161 layer = LaunchpadZopelessLayer
162
163@@ -189,39 +264,6 @@ class TestOCIRegistryUploadJob(TestCaseWithFactory):
164 job = OCIRegistryUploadJob.create(ocibuild)
165 self.assertProvides(job, IOCIRegistryUploadJob)
166
167- def makeRecipe(self, include_i386=True, include_amd64=True):
168- i386 = getUtility(IProcessorSet).getByName("386")
169- amd64 = getUtility(IProcessorSet).getByName("amd64")
170- recipe = self.factory.makeOCIRecipe()
171- distroseries = self.factory.makeDistroSeries(
172- distribution=recipe.oci_project.distribution)
173- distro_i386 = self.factory.makeDistroArchSeries(
174- distroseries=distroseries, architecturetag="i386",
175- processor=i386)
176- distro_i386.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
177- distro_amd64 = self.factory.makeDistroArchSeries(
178- distroseries=distroseries, architecturetag="amd64",
179- processor=amd64)
180- distro_amd64.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
181-
182- archs = []
183- if include_i386:
184- archs.append(i386)
185- if include_amd64:
186- archs.append(amd64)
187- recipe.setProcessors(archs)
188- return recipe
189-
190- def makeBuildRequest(self, include_i386=True, include_amd64=True):
191- recipe = self.makeRecipe(include_i386, include_amd64)
192- # Creates a build request with a build in it.
193- build_request = recipe.requestBuilds(recipe.owner)
194- with admin_logged_in():
195- jobs = getUtility(IOCIRecipeRequestBuildsJobSource).iterReady()
196- with dbuser(config.IOCIRecipeRequestBuildsJobSource.dbuser):
197- JobRunner(jobs).runAll()
198- return build_request
199-
200 def test_run(self):
201 logger = self.useFixture(FakeLogger())
202 build_request = self.makeBuildRequest(include_i386=False)
203@@ -485,3 +527,36 @@ class TestOCIRegistryUploadJob(TestCaseWithFactory):
204 self.assertEqual([], pop_notifications())
205 self.assertWebhookDeliveries(
206 ocibuild, ["Pending", "Failed to upload"], logger)
207+
208+
209+class TestOCIRegistryUploadJobViaCelery(TestCaseWithFactory,
210+ MultiArchRecipeMixin):
211+ """Runs OCIRegistryUploadJob via Celery, to make sure the machinery
212+ around it works.
213+
214+ It's important to have this test specially because this job does some
215+ dodgy things with its own status and the database transaction,
216+ so we should make sure we are not breaking anything in the interaction
217+ with the job lifecycle via celery.
218+ """
219+ layer = CeleryJobLayer
220+
221+ def setUp(self):
222+ super(TestOCIRegistryUploadJobViaCelery, self).setUp()
223+ self.useFixture(FeatureFixture({
224+ 'webhooks.new.enabled': 'true',
225+ OCI_RECIPE_WEBHOOKS_FEATURE_FLAG: 'on',
226+ OCI_RECIPE_ALLOW_CREATE: 'on',
227+ 'jobs.celery.enabled_classes': 'OCIRegistryUploadJob',
228+ }))
229+
230+ def test_run_upload(self):
231+ build_request = self.makeBuildRequest()
232+ builds = build_request.builds
233+ self.assertEqual(2, builds.count())
234+
235+ with block_on_job():
236+ for build in builds:
237+ OCIRegistryUploadJob.create(build)
238+ transaction.commit()
239+ self.assertEqual(0, len(pop_remote_notifications()))