From 7f068c3fc37d618940a50d5c8b80535d041edcb6 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 11 Jan 2019 15:24:21 -0500 Subject: [PATCH] Skip parsing the manifest where applicable Instead of always parsing (like we did previously), we now only parse the manifest if conversion is necessary. This should save significant CPU. --- endpoints/v2/manifest.py | 54 ++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/endpoints/v2/manifest.py b/endpoints/v2/manifest.py index 75f818736..4ed8331fd 100644 --- a/endpoints/v2/manifest.py +++ b/endpoints/v2/manifest.py @@ -57,15 +57,9 @@ def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref): # Something went wrong. raise ManifestInvalid() - try: - parsed = manifest.get_parsed_manifest() - except ManifestException: - logger.exception('Got exception when trying to parse manifest `%s`', manifest_ref) - raise ManifestInvalid() - - supported = _rewrite_schema_if_necessary(namespace_name, repo_name, manifest_ref, manifest, - parsed) - if supported is None: + manifest_bytes, manifest_digest, manifest_media_type = _rewrite_schema_if_necessary( + namespace_name, repo_name, manifest_ref, manifest) + if manifest_bytes is None: raise ManifestUnknown() track_and_log('pull_repo', repository_ref, analytics_name='pull_repo_100x', analytics_sample=0.01, @@ -73,11 +67,11 @@ def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref): metric_queue.repository_pull.Inc(labelvalues=[namespace_name, repo_name, 'v2', True]) return Response( - supported.bytes.as_unicode(), + manifest_bytes.as_unicode(), status=200, headers={ - 'Content-Type': '%s; charset=utf-8' % supported.media_type, - 'Docker-Content-Digest': supported.digest, + 'Content-Type': '%s; charset=utf-8' % manifest_media_type, + 'Docker-Content-Digest': manifest_digest, }, ) @@ -96,43 +90,43 @@ def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref): if manifest is None: raise ManifestUnknown() - try: - parsed = manifest.get_parsed_manifest() - except ManifestException: - logger.exception('Got exception when trying to parse manifest `%s`', manifest_ref) - raise ManifestInvalid() - - supported = _rewrite_schema_if_necessary(namespace_name, repo_name, '$digest', manifest, - parsed) - if supported is None: + manifest_bytes, manifest_digest, manifest_media_type = _rewrite_schema_if_necessary( + namespace_name, repo_name, '$digest', manifest) + if manifest_digest is None: raise ManifestUnknown() track_and_log('pull_repo', repository_ref, manifest_digest=manifest_ref) metric_queue.repository_pull.Inc(labelvalues=[namespace_name, repo_name, 'v2', True]) - return Response(supported.bytes.as_unicode(), status=200, headers={ - 'Content-Type': '%s; charset=utf-8' % supported.media_type, - 'Docker-Content-Digest': supported.digest, + return Response(manifest_bytes.as_unicode(), status=200, headers={ + 'Content-Type': '%s; charset=utf-8' % manifest_media_type, + 'Docker-Content-Digest': manifest_digest, }) -def _rewrite_schema_if_necessary(namespace_name, repo_name, tag_name, manifest, parsed): +def _rewrite_schema_if_necessary(namespace_name, repo_name, tag_name, manifest): # As per the Docker protocol, if the manifest is not schema version 1 and the manifest's # media type is not in the Accept header, we return a schema 1 version of the manifest for # the amd64+linux platform, if any, or None if none. # See: https://docs.docker.com/registry/spec/manifest-v2-2 mimetypes = [mimetype for mimetype, _ in request.accept_mimetypes] - if parsed.media_type in mimetypes: - return parsed + if manifest.media_type in mimetypes: + return manifest.internal_manifest_bytes, manifest.digest, manifest.media_type + logger.debug('Manifest `%s` not compatible against %s; checking for conversion', manifest.digest, + request.accept_mimetypes) converted = registry_model.convert_manifest(manifest, namespace_name, repo_name, tag_name, mimetypes, storage) if converted is not None: - return converted + return converted.bytes, converted.digest, converted.media_type # For back-compat, we always default to schema 1 if the manifest could not be converted. - return registry_model.get_schema1_parsed_manifest(manifest, namespace_name, repo_name, tag_name, - storage) + schema1 = registry_model.get_schema1_parsed_manifest(manifest, namespace_name, repo_name, + tag_name, storage) + if schema1 is None: + return None, None, None + + return schema1.bytes, schema1.digest, schema1.media_type def _reject_manifest2_schema2(func):