Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/+cancel-blob-upload.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for cancelling blob uploads via `DELETE /v2/<name>/blobs/uploads/<uuid>`.
18 changes: 18 additions & 0 deletions pulp_container/app/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ def __init__(self, digest):
)


class BlobUploadUnknown(NotFound):
"""Exception to render a 404 with the code 'BLOB_UPLOAD_UNKNOWN'"""

def __init__(self, uuid):
"""Initialize the exception with the upload uuid."""
super().__init__(
detail={
"errors": [
{
"code": "BLOB_UPLOAD_UNKNOWN",
"message": "blob upload unknown to registry",
"detail": {"uuid": uuid},
}
]
}
)


class ManifestNotFound(NotFound):
"""Exception to render a 404 with the code 'MANIFEST_UNKNOWN'"""

Expand Down
13 changes: 13 additions & 0 deletions pulp_container/app/registry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
BadGateway,
BlobInvalid,
BlobNotFound,
BlobUploadUnknown,
GatewayTimeout,
InvalidRequest,
ManifestInvalid,
Expand Down Expand Up @@ -1014,6 +1015,18 @@ def partial_update(self, request, path, pk=None):

return UploadResponse(upload=upload, path=path, request=request)

def destroy(self, request, path, pk=None):
"""
Cancel an outstanding blob upload.
"""
_, repository = self.get_dr_push(request, path)
try:
upload = models.Upload.objects.get(repository=repository, pk=pk)
except models.Upload.DoesNotExist:
raise BlobUploadUnknown(uuid=pk)
upload.delete()
return Response(status=204)

def put(self, request, path, pk=None):
"""
Create a blob from uploaded chunks.
Expand Down
71 changes: 71 additions & 0 deletions pulp_container/tests/functional/api/test_cancel_blob_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Tests that verify blob uploads can be cancelled via the registry API."""

import uuid

import pytest

from pulp_container.app import models


def test_cancel_blob_upload(local_registry, container_bindings, full_path, add_to_cleanup):
"""Test cancelling an outstanding blob upload."""
repo_name = f"cancel-upload/{uuid.uuid4()}"
upload_path = f"/v2/{full_path(repo_name)}/blobs/uploads/"

response, _ = local_registry.get_response("POST", upload_path)
assert response.status_code == 202
upload_uuid = response.headers["Docker-Upload-UUID"]

distribution = container_bindings.DistributionsContainerApi.list(name=repo_name).results[0]
add_to_cleanup(container_bindings.PulpContainerNamespacesApi, distribution.namespace)

assert models.Upload.objects.filter(pk=upload_uuid).exists()

delete_path = f"/v2/{full_path(repo_name)}/blobs/uploads/{upload_uuid}"
response, _ = local_registry.get_response("DELETE", delete_path)
assert response.status_code == 204
assert response.content == b""
assert not models.Upload.objects.filter(pk=upload_uuid).exists()


def test_cancel_unknown_blob_upload(local_registry, full_path, add_to_cleanup, container_bindings):
"""Test cancelling a blob upload that does not exist."""
repo_name = f"cancel-upload/{uuid.uuid4()}"
upload_path = f"/v2/{full_path(repo_name)}/blobs/uploads/"

response, _ = local_registry.get_response("POST", upload_path)
assert response.status_code == 202

distribution = container_bindings.DistributionsContainerApi.list(name=repo_name).results[0]
add_to_cleanup(container_bindings.PulpContainerNamespacesApi, distribution.namespace)

upload_uuid = uuid.uuid4()
delete_path = f"/v2/{full_path(repo_name)}/blobs/uploads/{upload_uuid}"

response, _ = local_registry.get_response("DELETE", delete_path)
assert response.status_code == 404
assert response.json()["errors"][0]["code"] == "BLOB_UPLOAD_UNKNOWN"


def test_cancel_blob_upload_without_permission(
gen_user, local_registry, full_path, pulp_settings, add_to_cleanup, container_bindings
):
"""Test that cancelling a blob upload requires push permission."""
if pulp_settings.TOKEN_AUTH_DISABLED:
pytest.skip("RBAC cannot be tested when token authentication is disabled")

user_helpless = gen_user()
repo_name = f"cancel-upload/{uuid.uuid4()}"
upload_path = f"/v2/{full_path(repo_name)}/blobs/uploads/"

response, _ = local_registry.get_response("POST", upload_path)
assert response.status_code == 202
upload_uuid = response.headers["Docker-Upload-UUID"]

distribution = container_bindings.DistributionsContainerApi.list(name=repo_name).results[0]
add_to_cleanup(container_bindings.PulpContainerNamespacesApi, distribution.namespace)

delete_path = f"/v2/{full_path(repo_name)}/blobs/uploads/{upload_uuid}"
with user_helpless:
response, _ = local_registry.get_response("DELETE", delete_path)
assert response.status_code == 401
Loading