Repository: pulp/pulpcore

Issue:
Orphaned content should not prevent to delete a domain.
Being unable to delete a domain when all there is left in it is orphaned content is unintuitive.


Discussion:

Resolving patch:
diff --git a/pulpcore/app/models/domain.py b/pulpcore/app/models/domain.py
--- a/pulpcore/app/models/domain.py
+++ b/pulpcore/app/models/domain.py
@@ -49,6 +49,15 @@ def get_storage(self):
     def prevent_default_deletion(self):
         raise models.ProtectedError("Default domain can not be updated/deleted.", [self])
 
+    @hook(BEFORE_DELETE, when="name", is_not="default")
+    def _cleanup_orphans_pre_delete(self):
+        if self.content_set.exclude(version_memberships__isnull=True).exists():
+            raise models.ProtectedError("There is active content in the domain.")
+        self.content_set.filter(version_memberships__isnull=True).delete()
+        for artifact in self.artifact_set.all().iterator():
+            # Delete on by one to properly cleanup the storage.
+            artifact.delete()
+
     class Meta:
         permissions = [
             ("manage_roles_domain", "Can manage role assignments on domain"),
diff --git a/pulpcore/app/settings.py b/pulpcore/app/settings.py
--- a/pulpcore/app/settings.py
+++ b/pulpcore/app/settings.py
@@ -294,6 +294,12 @@
 
 DOMAIN_ENABLED = False
 
+SHELL_PLUS_IMPORTS = [
+    "from pulpcore.app.util import get_domain, get_domain_pk, set_domain, get_url, extract_pk",
+    "from pulpcore.tasking.tasks import dispatch",
+    "from pulpcore.tasking.util import cancel_task",
+]
+
 # HERE STARTS DYNACONF EXTENSION LOAD (Keep at the very bottom of settings.py)
 # Read more at https://dynaconf.readthedocs.io/en/latest/guides/django.html
 from dynaconf import DjangoDynaconf, Validator  # noqa


Test patch:
diff --git a/pulpcore/tests/functional/api/test_crud_domains.py b/pulpcore/tests/functional/api/test_crud_domains.py
--- a/pulpcore/tests/functional/api/test_crud_domains.py
+++ b/pulpcore/tests/functional/api/test_crud_domains.py
@@ -125,6 +125,57 @@ def test_active_domain_deletion(domains_api_client, rbac_contentguard_api_client
     assert e.value.status == 404
 
 
+@pytest.mark.parallel
+def test_orphan_domain_deletion(
+    domains_api_client,
+    file_repository_api_client,
+    file_content_api_client,
+    gen_object_with_cleanup,
+    monitor_task,
+    tmp_path,
+):
+    """Test trying to delete a domain that is in use, has objects in it."""
+    if not settings.DOMAIN_ENABLED:
+        pytest.skip("Domains not enabled")
+    body = {
+        "name": str(uuid.uuid4()),
+        "storage_class": "pulpcore.app.models.storage.FileSystem",
+        "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"},
+    }
+    domain = gen_object_with_cleanup(domains_api_client, body)
+
+    repository = gen_object_with_cleanup(
+        file_repository_api_client, {"name": str(uuid.uuid4())}, pulp_domain=domain.name
+    )
+    new_file = tmp_path / "new_file"
+    new_file.write_text("Test file")
+    monitor_task(
+        file_content_api_client.create(
+            relative_path=str(uuid.uuid4()),
+            file=new_file,
+            pulp_domain=domain.name,
+            repository=repository.pulp_href,
+        ).task
+    )
+
+    # Try to delete a domain with the repository in it
+    response = domains_api_client.delete(domain.pulp_href)
+    with pytest.raises(PulpTaskError) as e:
+        monitor_task(response.task)
+
+    assert e.value.task.state == "failed"
+
+    # Delete the repository
+    file_repository_api_client.delete(repository.pulp_href)
+
+    # Now succeed in deleting the domain
+    response = domains_api_client.delete(domain.pulp_href)
+    monitor_task(response.task)
+    with pytest.raises(ApiException) as e:
+        domains_api_client.read(domain.pulp_href)
+    assert e.value.status == 404
+
+
 @pytest.mark.parallel
 def test_special_domain_creation(domains_api_client, gen_object_with_cleanup):
     """Test many possible domain creation scenarios."""