← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~jugmac00/launchpad:send-proxy-arguments-when-building-rocks into launchpad:master

 

Jürgen Gmach has proposed merging ~jugmac00/launchpad:send-proxy-arguments-when-building-rocks into launchpad:master with ~jugmac00/launchpad:add-build-upload-processing-for-rock-recipes as a prerequisite.

Commit message:
Add missing tests for passing proxy arguments

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~jugmac00/launchpad/+git/launchpad/+merge/473089
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~jugmac00/launchpad:send-proxy-arguments-when-building-rocks into launchpad:master.
diff --git a/lib/lp/rocks/tests/test_rockrecipebuildbehaviour.py b/lib/lp/rocks/tests/test_rockrecipebuildbehaviour.py
index 88cea3a..ede523b 100644
--- a/lib/lp/rocks/tests/test_rockrecipebuildbehaviour.py
+++ b/lib/lp/rocks/tests/test_rockrecipebuildbehaviour.py
@@ -3,17 +3,24 @@
 
 """Test rock recipe build behaviour."""
 
+import base64
 import os.path
+import time
+import uuid
+from datetime import datetime
+from urllib.parse import urlsplit
 
-import transaction
+from fixtures import MockPatch
 from pymacaroons import Macaroon
 from testtools import ExpectedException
 from testtools.matchers import (
+    ContainsDict,
     Equals,
     Is,
     IsInstance,
     MatchesDict,
     MatchesListwise,
+    StartsWith,
 )
 from testtools.twistedsupport import (
     AsynchronousDeferredRunTestForBrokenTwisted,
@@ -28,12 +35,22 @@ from lp.archivepublisher.interfaces.archivegpgsigningkey import (
     IArchiveGPGSigningKey,
 )
 from lp.buildmaster.enums import BuildBaseImageType, BuildStatus
+from lp.buildmaster.interactor import shut_down_default_process_pool
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.interfaces.processor import IProcessorSet
-from lp.buildmaster.tests.mock_workers import MockBuilder, OkWorker
+from lp.buildmaster.tests.builderproxy import (
+    InProcessProxyAuthAPIFixture,
+    ProxyURLMatcher,
+    RevocationEndpointMatcher,
+)
+from lp.buildmaster.tests.mock_workers import (
+    MockBuilder,
+    OkWorker,
+    WorkerTestHelpers,
+)
 from lp.buildmaster.tests.test_buildfarmjobbehaviour import (
     TestGetUploadMethodsMixin,
     TestHandleStatusMixin,
@@ -88,12 +105,7 @@ class TestRockRecipeBuildBehaviourBase(TestCaseWithFactory):
         build = self.factory.makeRockRecipeBuild(
             distro_arch_series=distroarchseries, name="test-rock", **kwargs
         )
-        job = IBuildFarmJobBehaviour(build)
-        if with_builder:
-            builder = MockBuilder()
-            builder.processor = processor
-            job.setBuilder(builder, None)
-        return job
+        return IBuildFarmJobBehaviour(build)
 
 
 class TestRockRecipeBuildBehaviour(TestRockRecipeBuildBehaviourBase):
@@ -115,7 +127,6 @@ class TestRockRecipeBuildBehaviour(TestRockRecipeBuildBehaviourBase):
         # valid builder set.
         job = self.makeJob()
         lfa = self.factory.makeLibraryFileAlias()
-        transaction.commit()
         job.build.distro_arch_series.addOrUpdateChroot(lfa)
         builder = MockBuilder()
         job.setBuilder(builder, OkWorker())
@@ -128,7 +139,6 @@ class TestRockRecipeBuildBehaviour(TestRockRecipeBuildBehaviourBase):
         # build on a non-virtual builder.
         job = self.makeJob()
         lfa = self.factory.makeLibraryFileAlias()
-        transaction.commit()
         job.build.distro_arch_series.addOrUpdateChroot(lfa)
         builder = MockBuilder(virtualized=False)
         job.setBuilder(builder, OkWorker())
@@ -156,13 +166,45 @@ class TestAsyncRockRecipeBuildBehaviour(
         timeout=30
     )
 
+    @defer.inlineCallbacks
     def setUp(self):
         super().setUp()
+        build_username = "OCIBUILD-1"
+        self.token = {
+            "secret": uuid.uuid4().hex,
+            "username": build_username,
+            "timestamp": datetime.utcnow().isoformat(),
+        }
+        self.proxy_url = (
+            "http://{username}:{password}";
+            "@{host}:{port}".format(
+                username=self.token["username"],
+                password=self.token["secret"],
+                host=config.builddmaster.builder_proxy_host,
+                port=config.builddmaster.builder_proxy_port,
+            )
+        )
+        self.proxy_api = self.useFixture(InProcessProxyAuthAPIFixture())
+        yield self.proxy_api.start()
+        self.now = time.time()
+        self.useFixture(MockPatch("time.time", return_value=self.now))
+        self.addCleanup(shut_down_default_process_pool)
         self.setUpStats()
 
+    def makeJob(self, **kwargs):
+        # We need a builder in these tests, in order that requesting a proxy
+        # token can piggyback on its reactor and pool.
+        job = super().makeJob(**kwargs)
+        builder = MockBuilder()
+        builder.processor = job.build.processor
+        worker = self.useFixture(WorkerTestHelpers()).getClientWorker()
+        job.setBuilder(builder, worker)
+        self.addCleanup(worker.pool.closeCachedConnections)
+        return job
+
     @defer.inlineCallbacks
     def test_composeBuildRequest(self):
-        job = self.makeJob(with_builder=True)
+        job = self.makeJob()
         lfa = self.factory.makeLibraryFileAlias(db_only=True)
         job.build.distro_arch_series.addOrUpdateChroot(lfa)
         build_request = yield job.composeBuildRequest(None)
@@ -180,6 +222,66 @@ class TestAsyncRockRecipeBuildBehaviour(
         )
 
     @defer.inlineCallbacks
+    def test_requestProxyToken_unconfigured(self):
+        self.pushConfig("builddmaster", builder_proxy_host=None)
+        job = self.makeJob()
+        with dbuser(config.builddmaster.dbuser):
+            args = yield job.extraBuildArgs()
+        self.assertEqual([], self.proxy_api.tokens.requests)
+        self.assertNotIn("proxy_url", args)
+        self.assertNotIn("revocation_endpoint", args)
+
+    @defer.inlineCallbacks
+    def test_requestProxyToken_no_secret(self):
+        self.pushConfig(
+            "builddmaster", builder_proxy_auth_api_admin_secret=None
+        )
+        job = self.makeJob()
+        expected_exception_msg = (
+            "builder_proxy_auth_api_admin_secret is not configured."
+        )
+        with ExpectedException(CannotBuild, expected_exception_msg):
+            yield job.extraBuildArgs()
+
+    @defer.inlineCallbacks
+    def test_requestProxyToken(self):
+        job = self.makeJob()
+        yield job.extraBuildArgs()
+        expected_uri = urlsplit(
+            config.builddmaster.builder_proxy_auth_api_endpoint
+        ).path.encode("UTF-8")
+        request_matcher = MatchesDict(
+            {
+                "method": Equals(b"POST"),
+                "uri": Equals(expected_uri),
+                "headers": ContainsDict(
+                    {
+                        b"Authorization": MatchesListwise(
+                            [
+                                Equals(
+                                    b"Basic "
+                                    + base64.b64encode(
+                                        b"admin-launchpad.test:admin-secret"
+                                    )
+                                )
+                            ]
+                        ),
+                        b"Content-Type": MatchesListwise(
+                            [Equals(b"application/json")]
+                        ),
+                    }
+                ),
+                "json": MatchesDict(
+                    {"username": StartsWith(job.build.build_cookie + "-")}
+                ),
+            }
+        )
+        self.assertThat(
+            self.proxy_api.tokens.requests,
+            MatchesListwise([request_matcher]),
+        )
+
+    @defer.inlineCallbacks
     def test_extraBuildArgs_git(self):
         # extraBuildArgs returns appropriate arguments if asked to build a
         # job for a Git branch.
@@ -209,8 +311,13 @@ class TestAsyncRockRecipeBuildBehaviour(
                     "git_path": Equals(ref.name),
                     "name": Equals("test-rock"),
                     "private": Is(False),
+                    "proxy_url": ProxyURLMatcher(job, self.now),
+                    "revocation_endpoint": RevocationEndpointMatcher(
+                        job, self.now
+                    ),
                     "series": Equals("unstable"),
                     "trusted_keys": Equals(expected_trusted_keys),
+                    "use_fetch_service": Is(False),
                 }
             ),
         )
@@ -247,8 +354,13 @@ class TestAsyncRockRecipeBuildBehaviour(
                     "git_repository": Equals(ref.repository.git_https_url),
                     "name": Equals("test-rock"),
                     "private": Is(False),
+                    "proxy_url": ProxyURLMatcher(job, self.now),
+                    "revocation_endpoint": RevocationEndpointMatcher(
+                        job, self.now
+                    ),
                     "series": Equals("unstable"),
                     "trusted_keys": Equals(expected_trusted_keys),
+                    "use_fetch_service": Is(False),
                 }
             ),
         )
@@ -257,7 +369,7 @@ class TestAsyncRockRecipeBuildBehaviour(
     def test_extraBuildArgs_prefers_store_name(self):
         # For the "name" argument, extraBuildArgs prefers
         # RockRecipe.store_name over RockRecipe.name if the former is set.
-        job = self.makeJob(store_name="something-else", with_builder=True)
+        job = self.makeJob(store_name="something-else")
         with dbuser(config.builddmaster.dbuser):
             args = yield job.extraBuildArgs()
         self.assertEqual("something-else", args["name"])
@@ -271,7 +383,7 @@ class TestAsyncRockRecipeBuildBehaviour(
         yield IArchiveGPGSigningKey(distribution.main_archive).setSigningKey(
             key_path, async_keyserver=True
         )
-        job = self.makeJob(distribution=distribution, with_builder=True)
+        job = self.makeJob(distribution=distribution)
         self.factory.makeBinaryPackagePublishingHistory(
             distroarchseries=job.build.distro_arch_series,
             pocket=job.build.pocket,
@@ -295,11 +407,6 @@ class TestAsyncRockRecipeBuildBehaviour(
     def test_extraBuildArgs_build_path(self):
         # If the recipe specifies a build path, extraBuildArgs sends it.
         job = self.makeJob(build_path="src", with_builder=True)
-        expected_archives, expected_trusted_keys = (
-            yield get_sources_list_for_building(
-                job, job.build.distro_arch_series, None
-            )
-        )
         with dbuser(config.builddmaster.dbuser):
             args = yield job.extraBuildArgs()
         self.assertEqual("src", args["build_path"])
@@ -308,11 +415,6 @@ class TestAsyncRockRecipeBuildBehaviour(
     def test_extraBuildArgs_channels(self):
         # If the build needs particular channels, extraBuildArgs sends them.
         job = self.makeJob(channels={"rockcraft": "edge"}, with_builder=True)
-        expected_archives, expected_trusted_keys = (
-            yield get_sources_list_for_building(
-                job, job.build.distro_arch_series, None
-            )
-        )
         with dbuser(config.builddmaster.dbuser):
             args = yield job.extraBuildArgs()
         self.assertFalse(isProxy(args["channels"]))
@@ -347,14 +449,21 @@ class TestAsyncRockRecipeBuildBehaviour(
                 }
             )
         )
-        job = self.makeJob(
-            information_type=InformationType.PROPRIETARY, with_builder=True
-        )
+        job = self.makeJob(information_type=InformationType.PROPRIETARY)
         with dbuser(config.builddmaster.dbuser):
             args = yield job.extraBuildArgs()
         self.assertTrue(args["private"])
 
     @defer.inlineCallbacks
+    def test_composeBuildRequest_proxy_url_set(self):
+        job = self.makeJob()
+        build_request = yield job.composeBuildRequest(None)
+        self.assertThat(
+            build_request[4]["proxy_url"], ProxyURLMatcher(job, self.now)
+        )
+        self.assertFalse(build_request[4]["use_fetch_service"])
+
+    @defer.inlineCallbacks
     def test_composeBuildRequest_git_ref_deleted(self):
         # If the source Git reference has been deleted, composeBuildRequest
         # raises CannotBuild.
@@ -367,7 +476,6 @@ class TestAsyncRockRecipeBuildBehaviour(
             owner=owner,
             project=project,
             git_ref=ref,
-            with_builder=True,
         )
         repository.removeRefs([ref.path])
         self.assertIsNone(job.build.recipe.git_ref)
@@ -380,6 +488,7 @@ class TestAsyncRockRecipeBuildBehaviour(
 
     @defer.inlineCallbacks
     def test_dispatchBuildToWorker_prefers_lxd(self):
+        self.pushConfig("builddmaster", builder_proxy_host=None)
         job = self.makeJob()
         builder = MockBuilder()
         builder.processor = job.build.processor
@@ -410,6 +519,7 @@ class TestAsyncRockRecipeBuildBehaviour(
 
     @defer.inlineCallbacks
     def test_dispatchBuildToWorker_falls_back_to_chroot(self):
+        self.pushConfig("builddmaster", builder_proxy_host=None)
         job = self.makeJob()
         builder = MockBuilder()
         builder.processor = job.build.processor