launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #31093
[Merge] ~ines-almeida/launchpad:fetch-service-update-end-session into launchpad:master
Ines Almeida has proposed merging ~ines-almeida/launchpad:fetch-service-update-end-session into launchpad:master.
Commit message:
Update how to keep track of session_id within a builder session
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/465152
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ines-almeida/launchpad:fetch-service-update-end-session into launchpad:master.
diff --git a/lib/lp/buildmaster/builderproxy.py b/lib/lp/buildmaster/builderproxy.py
index 1fc0d9b..88f1792 100644
--- a/lib/lp/buildmaster/builderproxy.py
+++ b/lib/lp/buildmaster/builderproxy.py
@@ -17,6 +17,7 @@ __all__ = [
import base64
import os
+import re
import time
from typing import Dict, Generator, Type
@@ -56,11 +57,6 @@ class BuilderProxyMixin:
build: BuildFarmJob
_worker: BuilderWorker
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._use_fetch_service = False
- self._proxy_service = None
-
@defer.inlineCallbacks
def startProxySession(
self,
@@ -69,15 +65,15 @@ class BuilderProxyMixin:
use_fetch_service: bool = False,
) -> Generator[None, Dict[str, str], None]:
- self._use_fetch_service = use_fetch_service
-
if not allow_internet:
return
if not use_fetch_service and _get_proxy_config("builder_proxy_host"):
- proxy_service: Type[IProxyService] = BuilderProxy
+ proxy_service: Type[IProxyService] = BuilderProxy(
+ worker=self._worker
+ )
elif use_fetch_service and _get_proxy_config("fetch_service_host"):
- proxy_service = FetchService
+ proxy_service = FetchService(worker=self._worker)
# Append the fetch-service certificate to BuildArgs secrets.
if "secrets" not in args:
@@ -91,17 +87,16 @@ class BuilderProxyMixin:
# non-production environments.
return
- self._proxy_service = proxy_service(
- build_id=self.build.build_cookie, worker=self._worker
+ session_data = yield proxy_service.startSession(
+ build_id=self.build.build_cookie
)
- session_data = yield self._proxy_service.startSession()
args["proxy_url"] = session_data["proxy_url"]
args["revocation_endpoint"] = session_data["revocation_endpoint"]
args["use_fetch_service"] = use_fetch_service
@defer.inlineCallbacks
- def endProxySession(self, upload_path: str):
+ def endProxySession(self, upload_path: str, use_fetch_service: bool):
"""Handles all the necessary cleanup to be done at the end of a build.
For the fetch service case, this means:
@@ -115,30 +110,32 @@ class BuilderProxyMixin:
Sessions will be closed automatically within the Fetch Service after
a certain amount of time configured by its charm (default 6 hours).
"""
- if not self._use_fetch_service:
+ if not use_fetch_service:
# No cleanup needed for the builder proxy
return
- if self._proxy_service is None:
- # A session was never started. This can happen if the proxy configs
- # are not set (see `startProxySession`)
- return
+ proxy_service = FetchService(worker=self._worker)
+ proxy_info = yield self._worker.proxy_info()
+ session_id = proxy_service.parseRevocationEndpoint(
+ proxy_info["revocation_endpoint"]
+ )
metadata_file_name = BUILD_METADATA_FILENAME_FORMAT.format(
build_id=self.build.build_cookie
)
file_path = os.path.join(upload_path, metadata_file_name)
- yield self._proxy_service.retrieveMetadataFromSession(
- save_content_to=file_path
+ yield proxy_service.retrieveMetadataFromSession(
+ session_id=session_id,
+ save_content_to=file_path,
)
- yield self._proxy_service.endSession()
+ yield proxy_service.endSession(session_id=session_id)
class IProxyService:
"""Interface for Proxy Services - either FetchService or BuilderProxy."""
- def __init__(self, build_id: str, worker: BuilderWorker):
+ def __init__(self, worker: BuilderWorker):
pass
@defer.inlineCallbacks
@@ -159,7 +156,7 @@ class BuilderProxy(IProxyService):
making API requests directly to the builder proxy control endpoint.
"""
- def __init__(self, build_id: str, worker: BuilderWorker):
+ def __init__(self, worker: BuilderWorker):
self.control_endpoint = _get_value_from_config(
"builder_proxy_auth_api_endpoint"
)
@@ -168,8 +165,6 @@ class BuilderProxy(IProxyService):
port=_get_value_from_config("builder_proxy_port"),
)
self.auth_header = self._getAuthHeader()
-
- self.build_id = build_id
self.worker = worker
@staticmethod
@@ -187,14 +182,14 @@ class BuilderProxy(IProxyService):
return b"Basic " + base64.b64encode(auth_string.encode("ASCII"))
@defer.inlineCallbacks
- def startSession(self):
+ def startSession(self, build_id: str):
"""Request a token from the builder proxy to be used by the builders.
See IProxyService.
"""
timestamp = int(time.time())
proxy_username = "{build_id}-{timestamp}".format(
- build_id=self.build_id, timestamp=timestamp
+ build_id=build_id, timestamp=timestamp
)
token = yield self.worker.process_pool.doWork(
@@ -233,7 +228,7 @@ class FetchService(IProxyService):
RETRIEVE_METADATA_ENDPOINT = "{control_endpoint}/session/{session_id}"
END_SESSION_ENDPOINT = "{control_endpoint}/session/{session_id}"
- def __init__(self, build_id: str, worker: BuilderWorker):
+ def __init__(self, worker: BuilderWorker):
self.control_endpoint = _get_value_from_config(
"fetch_service_control_endpoint"
)
@@ -242,10 +237,7 @@ class FetchService(IProxyService):
port=_get_value_from_config("fetch_service_port"),
)
self.auth_header = self._getAuthHeader()
-
- self.build_id = build_id
self.worker = worker
- self.session_id = None
@staticmethod
def _getAuthHeader():
@@ -259,7 +251,7 @@ class FetchService(IProxyService):
return b"Basic " + base64.b64encode(auth_string.encode("ASCII"))
@defer.inlineCallbacks
- def startSession(self):
+ def startSession(self, build_id: str):
"""Requests a fetch service session and returns session information.
See IProxyService.
@@ -290,8 +282,20 @@ class FetchService(IProxyService):
"revocation_endpoint": revocation_endpoint,
}
+ def parseRevocationEndpoint(self, revocation_endpoint: str) -> str:
+ """Helper method to get the session_id out of the revocation
+ endpoint."""
+ re_pattern = self.TOKEN_REVOCATION.format(
+ control_endpoint=self.control_endpoint,
+ session_id="(?P<session_id>.*)",
+ )
+ match = re.match(re_pattern, revocation_endpoint)
+ return match["session_id"] if match else None
+
@defer.inlineCallbacks
- def retrieveMetadataFromSession(self, save_content_to: str):
+ def retrieveMetadataFromSession(
+ self, session_id: str, save_content_to: str
+ ):
"""Make request to retrieve metadata from the current session.
Data is stored directly into a file whose path is `save_content_to`
@@ -300,7 +304,7 @@ class FetchService(IProxyService):
"""
url = self.RETRIEVE_METADATA_ENDPOINT.format(
control_endpoint=self.control_endpoint,
- session_id=self.session_id,
+ session_id=session_id,
)
yield self.worker.process_pool.doWork(
RetrieveFetchServiceSessionCommand,
@@ -310,14 +314,14 @@ class FetchService(IProxyService):
)
@defer.inlineCallbacks
- def endSession(self):
+ def endSession(self, session_id: str):
"""End the proxy session and do any cleanup needed.
:raises: RequestException if request to Fetch Service fails
"""
url = self.END_SESSION_ENDPOINT.format(
control_endpoint=self.control_endpoint,
- session_id=self.session_id,
+ session_id=session_id,
)
yield self.worker.process_pool.doWork(
EndFetchServiceSessionCommand,
diff --git a/lib/lp/buildmaster/interactor.py b/lib/lp/buildmaster/interactor.py
index dfcc86b..9bf1a71 100644
--- a/lib/lp/buildmaster/interactor.py
+++ b/lib/lp/buildmaster/interactor.py
@@ -209,6 +209,10 @@ class BuilderWorker:
"""Echo the arguments back."""
return self._with_timeout(self._server.callRemote("echo", *args))
+ def proxy_info(self):
+ """Return the details for the proxy used by the manager."""
+ return self._with_timeout(self._server.callRemote("proxy_info"))
+
def info(self):
"""Return the protocol version and the builder methods supported."""
return self._with_timeout(self._server.callRemote("info"))
diff --git a/lib/lp/buildmaster/tests/mock_workers.py b/lib/lp/buildmaster/tests/mock_workers.py
index a880a9a..0a4a21f 100644
--- a/lib/lp/buildmaster/tests/mock_workers.py
+++ b/lib/lp/buildmaster/tests/mock_workers.py
@@ -151,6 +151,15 @@ class OkWorker:
self.call_log.append("info")
return defer.succeed(("1.0", self.arch_tag, "binarypackage"))
+ def proxy_info(self):
+ self.call_log.append("proxy_info")
+ return defer.succeed(
+ {
+ "revocation_endpoint": "https://proxy.test.net/revoke_token",
+ "use_fetch_service": False,
+ }
+ )
+
def resume(self):
self.call_log.append("resume")
return defer.succeed(("", "", 0))
diff --git a/lib/lp/snappy/model/snapbuildbehaviour.py b/lib/lp/snappy/model/snapbuildbehaviour.py
index 7f0cf6d..3303517 100644
--- a/lib/lp/snappy/model/snapbuildbehaviour.py
+++ b/lib/lp/snappy/model/snapbuildbehaviour.py
@@ -212,4 +212,6 @@ class SnapBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
@defer.inlineCallbacks
def _saveBuildSpecificFiles(self, upload_path):
- yield self.endProxySession(upload_path)
+ yield self.endProxySession(
+ upload_path, self.build.snap.use_fetch_service
+ )
diff --git a/lib/lp/snappy/tests/test_snapbuildbehaviour.py b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
index 81d6926..3f7b1d7 100644
--- a/lib/lp/snappy/tests/test_snapbuildbehaviour.py
+++ b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
@@ -10,6 +10,7 @@ import time
import uuid
from datetime import datetime
from textwrap import dedent
+from unittest.mock import MagicMock
from urllib.parse import urlsplit
import fixtures
@@ -40,7 +41,6 @@ from lp.app.enums import InformationType
from lp.archivepublisher.interfaces.archivegpgsigningkey import (
IArchiveGPGSigningKey,
)
-from lp.buildmaster.builderproxy import BuilderProxy
from lp.buildmaster.enums import BuildBaseImageType, BuildStatus
from lp.buildmaster.interactor import shut_down_default_process_pool
from lp.buildmaster.interfaces.builder import CannotBuild
@@ -431,10 +431,26 @@ class TestAsyncSnapBuildBehaviourFetchService(
snap = self.factory.makeSnap(use_fetch_service=True)
request = self.factory.makeSnapBuildRequest(snap=snap)
job = self.makeJob(snap=snap, build_request=request)
+
+ host = config.builddmaster.fetch_service_host
+ port = config.builddmaster.fetch_service_port
+ session_id = self.fetch_service_api.sessions.session_id
+ revocation_endpoint = (
+ f"http://{host}:{port}/session/{session_id}/token"
+ )
+
+ job._worker.proxy_info = MagicMock(
+ return_value={
+ "revocation_endpoint": revocation_endpoint,
+ "use_fetch_service": True,
+ }
+ )
yield job.extraBuildArgs()
# End the session
- yield job.endProxySession(upload_path=tem_upload_path)
+ yield job.endProxySession(
+ upload_path=tem_upload_path, use_fetch_service=True
+ )
# We expect 3 calls made to the fetch service API, in this order
self.assertEqual(3, len(self.fetch_service_api.sessions.requests))
@@ -443,7 +459,6 @@ class TestAsyncSnapBuildBehaviourFetchService(
start_session_request = self.fetch_service_api.sessions.requests[0]
self.assertEqual(b"POST", start_session_request["method"])
self.assertEqual(b"/session", start_session_request["uri"])
- session_id = self.fetch_service_api.sessions.responses[0]["id"]
# Request retrieve metadata
retrieve_metadata_request = self.fetch_service_api.sessions.requests[1]
@@ -479,29 +494,12 @@ class TestAsyncSnapBuildBehaviourFetchService(
request = self.factory.makeSnapBuildRequest(snap=snap)
job = self.makeJob(snap=snap, build_request=request)
yield job.extraBuildArgs()
- yield job.endProxySession(upload_path="test_path")
-
- # No calls go through to the fetch service
- self.assertEqual(0, len(self.fetch_service_api.sessions.requests))
-
- @defer.inlineCallbacks
- def test_endProxySession_no_proxy_service(self):
- """When the `fetch_service_host` is not set, the calls to the fetch
- service don't go through."""
- self.useFixture(
- FeatureFixture({SNAP_USE_FETCH_SERVICE_FEATURE_FLAG: "on"})
+ yield job.endProxySession(
+ upload_path="test_path", use_fetch_service=False
)
- self.useFixture(FeatureFixture({"fetch_service_host": None}))
-
- snap = self.factory.makeSnap(use_fetch_service=True)
- request = self.factory.makeSnapBuildRequest(snap=snap)
- job = self.makeJob(snap=snap, build_request=request)
- yield job.extraBuildArgs()
- yield job.endProxySession(upload_path="test_path")
# No calls go through to the fetch service
self.assertEqual(0, len(self.fetch_service_api.sessions.requests))
- self.assertEqual(None, job._proxy_service)
class TestAsyncSnapBuildBehaviourBuilderProxy(
@@ -1469,9 +1467,9 @@ class TestAsyncSnapBuildBehaviourBuilderProxy(
yield job.extraBuildArgs()
# End the session
- yield job.endProxySession(upload_path="test_path")
- self.assertFalse(job.use_fetch_service)
- self.assertTrue(isinstance(job.proxy_service, BuilderProxy))
+ yield job.endProxySession(
+ upload_path="test_path", use_fetch_service=False
+ )
@defer.inlineCallbacks
def test_composeBuildRequest_proxy_url_set(self):
Follow ups