mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 06:21:15 +02:00
fix(docker): accept the pinned image id when the stores report different ids
Docker ids are not comparable across image stores: for the same content a classic store reports the config digest while a containerd store (or an image loaded from an OCI archive) reports the manifest digest. The controller pins the id of its own daemon, so a compute whose store reports another id for that content answered ImageMissingError forever: every node creation streamed the image again (save/load round trip) without ever converging, then failed with "The image 'x' is missing" after the six retries. Docker 29 makes this easy to hit, since the id a daemon reports now depends on how the image entered it. The load endpoint now takes the tag and the digest pinned by the controller, and the compute records how its own daemon identifies the image it just received. Node creation accepts the pin through that pair, and only while the tag still resolves to the synced content: a tag moved to some other image, or an image the controller never pushed here, still counts as missing. The pair lives in the compute process, so a restarted compute syncs once more. Computes that predate the new endpoint parameters keep the previous behaviour, as do controllers that do not send them.
This commit is contained in:
8 files changed
+259
-19
No files matched your search
@@ -527,7 +527,9 @@ class Node:
|
||||
res = False
|
||||
if last_missing_image:
|
||||
try:
|
||||
res = await self._upload_missing_image(self._node_type, last_missing_image)
|
||||
res = await self._upload_missing_image(
|
||||
self._node_type, last_missing_image, data.get("image_digest")
|
||||
)
|
||||
except ControllerError as upload_error:
|
||||
# For Docker the image is pulled from a registry: a
|
||||
# failed pull (unknown tag, no network, private image)
|
||||
@@ -1075,14 +1077,18 @@ class Node:
|
||||
f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", **kwargs
|
||||
)
|
||||
|
||||
async def _upload_missing_image(self, type, img):
|
||||
async def _upload_missing_image(self, type, img, image_digest=None):
|
||||
"""
|
||||
Search an image on local computer and upload it to remote compute
|
||||
if the image exists
|
||||
|
||||
:param image_digest: digest pinned for this image in the create payload,
|
||||
forwarded so the compute can translate it (see
|
||||
_sync_missing_docker_image)
|
||||
"""
|
||||
|
||||
if self._node_type == "docker":
|
||||
return await self._sync_missing_docker_image(img)
|
||||
return await self._sync_missing_docker_image(img, image_digest)
|
||||
|
||||
image = self._find_local_image(type, img)
|
||||
if image is None:
|
||||
@@ -1119,11 +1125,17 @@ class Node:
|
||||
if image_id:
|
||||
data["image_digest"] = image_id
|
||||
|
||||
async def _sync_missing_docker_image(self, image):
|
||||
async def _sync_missing_docker_image(self, image, image_digest=None):
|
||||
"""
|
||||
Export a Docker image from the Docker daemon on the controller host and
|
||||
stream it to the remote compute, or ask the compute to pull it when the
|
||||
image is not available locally
|
||||
|
||||
:param image_digest: digest pinned for this image in the create payload.
|
||||
It is sent along with the archive so the compute can remember how its
|
||||
own daemon identifies the image it received: image ids are not
|
||||
comparable across image stores, so the pin alone would keep being
|
||||
reported as a mismatch after every sync.
|
||||
"""
|
||||
|
||||
from gns3server.compute.docker import Docker
|
||||
@@ -1147,8 +1159,11 @@ class Node:
|
||||
self.project.emit_notification(
|
||||
"log.info", {"message": f"Syncing Docker image '{image}' to compute '{self._compute.name}'"}
|
||||
)
|
||||
params = {"image": image}
|
||||
if image_digest:
|
||||
params["digest"] = image_digest
|
||||
try:
|
||||
await self._compute.post("/docker/images/load", data=response.content, timeout=None)
|
||||
await self._compute.post("/docker/images/load", data=response.content, timeout=None, params=params)
|
||||
finally:
|
||||
response.close()
|
||||
self.project.emit_notification(
|
||||
|
||||
Reference in new issue
Block a user