From f56e0974df20a53f778cf217fd4e7f64adca19c7 Mon Sep 17 00:00:00 2001 From: Gerrod Date: Wed, 9 Aug 2023 07:22:44 -0400 Subject: [PATCH] Fix sync failure on unreachable namespace avatar (#1544) fixes: #1543 --- CHANGES/1543.bugfix | 1 + pulp_ansible/app/models.py | 8 +++++- pulp_ansible/app/tasks/collections.py | 38 +++++++++++++++++++++++---- pulp_ansible/app/viewsets.py | 5 ++-- 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 CHANGES/1543.bugfix diff --git a/CHANGES/1543.bugfix b/CHANGES/1543.bugfix new file mode 100644 index 000000000..a2f579b71 --- /dev/null +++ b/CHANGES/1543.bugfix @@ -0,0 +1 @@ +Stopped collection sync from failing if a namespace's avatar url was unreachable. diff --git a/pulp_ansible/app/models.py b/pulp_ansible/app/models.py index 20fec5628..515c4079b 100644 --- a/pulp_ansible/app/models.py +++ b/pulp_ansible/app/models.py @@ -333,7 +333,13 @@ class AnsibleNamespaceMetadata(Content): @property def avatar_artifact(self): if self.avatar_sha256: - return self._artifacts.get(sha256=self.avatar_sha256) + avatar = self._artifacts.filter(sha256=self.avatar_sha256).first() + if avatar is None: + log.debug( + f"Artifact({self.avatar_sha256}) is missing for namespace avatar " + f"{self.name}:{self.metadata_sha256}" + ) + return avatar return None diff --git a/pulp_ansible/app/tasks/collections.py b/pulp_ansible/app/tasks/collections.py index a4a95de24..3dc1fd8bb 100644 --- a/pulp_ansible/app/tasks/collections.py +++ b/pulp_ansible/app/tasks/collections.py @@ -10,7 +10,7 @@ from uuid import uuid4 import yaml -from aiohttp.client_exceptions import ClientResponseError +from aiohttp.client_exceptions import ClientError, ClientResponseError from asgiref.sync import sync_to_async from async_lru import alru_cache from django.conf import settings @@ -503,7 +503,7 @@ def pipeline_stages(self, new_version): ArtifactSaver(), QueryExistingContents(), DocsBlobDownloader(), - CollectionContentSaver(new_version), + AnsibleContentSaver(new_version), RemoteArtifactSaver(), ResolveContentFutures(), ] @@ -733,8 +733,8 @@ async def _add_namespace(self, name, namespace_sha): da = ( [ - DeclarativeArtifact( - Artifact(sha256=namespace["avatar_sha256"]), + DeclarativeFailsafeArtifact( + Artifact(sha256=namespace.get("avatar_sha256")), url=url, remote=self.remote, relative_path=f"{name}-avatar", @@ -1081,6 +1081,25 @@ async def run(self): pb.total = pb.done +class DeclarativeFailsafeArtifact(DeclarativeArtifact): + """ + Special handling for downloading Namespace Avatar Artifacts. + """ + + async def download(self): + """Allow download to fail, but not stop the sync.""" + artifact_copy = self.artifact + try: + return await super().download() + except ClientError as e: + # Reset DA so that future stages can properly handle it + self.artifact = artifact_copy + self.deferred_download = True + name = self.extra_data.get("namespace") + log.info(f"Failed to download namespace avatar: {name} - {e}, Skipping") + return None + + class DocsBlobDownloader(ArtifactDownloader): """ Stage for downloading docs_blob. @@ -1121,7 +1140,7 @@ async def _handle_content_unit(self, d_content): return downloaded -class CollectionContentSaver(ContentSaver): +class AnsibleContentSaver(ContentSaver): """ A modification of ContentSaver stage that additionally saves Ansible plugin specific items. @@ -1156,6 +1175,15 @@ def _pre_save(self, batch): name = d_content.content.name namespace, created = AnsibleNamespace.objects.get_or_create(name=name) d_content.content.namespace = namespace + if d_content.d_artifacts: + da = d_content.d_artifacts[0] + # Check to see if avatar failed to download, update metadata if so + if da.deferred_download: + d_content.d_artifacts = None + d_content.content.avatar_sha256 = None + # Check to see if upstream didn't have avatar_sha256 set + elif d_content.content.avatar_sha256 is None: + d_content.content.avatar_sha256 = da.artifact.sha256 def _post_save(self, batch): """ diff --git a/pulp_ansible/app/viewsets.py b/pulp_ansible/app/viewsets.py index 49337bbb4..f5537b658 100644 --- a/pulp_ansible/app/viewsets.py +++ b/pulp_ansible/app/viewsets.py @@ -408,11 +408,10 @@ class AnsibleNamespaceViewSet(ReadOnlyContentViewSet): @action(detail=True, methods=["get"], serializer_class=None) def avatar(self, request, pk): """ - Dispatches a collection version rebuild task. + Tries to find a redirect link to the Namespace's avatar """ ns = self.get_object() - artifact = ns.avatar_artifact - if artifact: + if artifact := ns.avatar_artifact: return HttpResponseRedirect(get_artifact_url(artifact)) return HttpResponseNotFound()