Skip to content
Open
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/11643.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enforce `ON DELETE RESTRICT` on `images.registry_id` so deleting a container registry that still has images is blocked at the database level instead of leaving dangling references that surfaced later as a misleading "Image not found in database" error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""add FK to images.registry_id (RESTRICT)
Adds a foreign key from ``images.registry_id`` to ``container_registries.id``
with ``ON DELETE RESTRICT``.
Rationale: the column previously carried a UUID with no DB-level FK, so
deleting a container registry left dangling references on the images
table. The sokovan scheduler would later surface this as a misleading
"Image not found in database" error during session launch. RESTRICT
prevents the registry deletion at the database level, forcing callers
to ``clear_images`` (soft-delete) or ``admin_purge`` the dependent rows
first. CASCADE was rejected because it would also hard-delete image
rows referenced by historical kernels / deployment_revisions via
SET NULL, erasing audit traceability.
Pre-existing dangling rows (image rows whose ``registry_id`` no longer
resolves) are hard-deleted before the constraint is created. The column
remains ``NOT NULL``, so a SET NULL backfill is not an option, and a
sentinel re-assignment would mask the original deletion intent.
Revision ID: 045b0e0e4384
Revises: ba42cb865efe
Create Date: 2026-05-17
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "045b0e0e4384"
down_revision = "ba42cb865efe"
# Part of: 26.5.0
branch_labels = None
depends_on = None

_FK_NAME = "fk_images_registry_id_container_registries"


def upgrade() -> None:
op.execute(
sa.text("DELETE FROM images WHERE registry_id NOT IN (SELECT id FROM container_registries)")
)
Comment on lines +41 to +43
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to reflect Copilot's review

op.create_foreign_key(
_FK_NAME,
source_table="images",
referent_table="container_registries",
local_cols=["registry_id"],
remote_cols=["id"],
ondelete="RESTRICT",
)


def downgrade() -> None:
op.drop_constraint(_FK_NAME, "images", type_="foreignkey")
8 changes: 7 additions & 1 deletion src/ai/backend/manager/models/image/row.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,13 @@ class ImageRow(Base): # type: ignore[misc]
)
tag: Mapped[str | None] = mapped_column("tag", sa.TEXT, nullable=True)
registry: Mapped[str] = mapped_column("registry", sa.String, nullable=False, index=True)
registry_id: Mapped[UUID] = mapped_column("registry_id", GUID, nullable=False, index=True)
registry_id: Mapped[UUID] = mapped_column(
"registry_id",
GUID,
sa.ForeignKey("container_registries.id", ondelete="RESTRICT"),
nullable=False,
index=True,
)
Comment on lines +337 to +343
architecture: Mapped[str] = mapped_column(
"architecture", sa.String, nullable=False, index=True, default="x86_64"
)
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/manager/models/domain/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ai.backend.manager.data.group.types import ProjectType
from ai.backend.manager.data.user.types import UserStatus
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import (
DeploymentAutoScalingPolicyRow,
)
Expand Down Expand Up @@ -70,6 +71,7 @@ def _make_password_info() -> PasswordInfo:
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/manager/models/group/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ai.backend.manager.data.group.types import ProjectType
from ai.backend.manager.data.user.types import UserStatus
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import (
DeploymentAutoScalingPolicyRow,
)
Expand Down Expand Up @@ -62,6 +63,7 @@
KeyPairRow,
GroupRow,
AssocGroupUserRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/manager/models/scaling_group/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sqlalchemy as sa

from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -47,6 +48,7 @@
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import pytest
import sqlalchemy as sa

from ai.backend.common.container_registry import ContainerRegistryType
from ai.backend.common.data.endpoint.types import EndpointLifecycle
from ai.backend.common.types import (
AutoScalingMetricComparator,
Expand All @@ -20,6 +21,7 @@
from ai.backend.manager.data.auth.hash import PasswordHashAlgorithm
from ai.backend.manager.data.image.types import ImageType
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import (
DeploymentAutoScalingPolicyData,
DeploymentAutoScalingPolicyRow,
Expand Down Expand Up @@ -90,6 +92,7 @@ async def db_with_cleanup(
KeyPairRow,
GroupRow,
VFolderRow,
ContainerRegistryRow,
ImageRow,
SessionRow,
KernelRow,
Expand Down Expand Up @@ -224,13 +227,23 @@ async def test_image(
db_with_cleanup: ExtendedAsyncSAEngine,
) -> AsyncGenerator[ImageRow, None]:
"""Create test image."""
registry_id = uuid.uuid4()
async with db_with_cleanup.begin_session() as db_sess:
db_sess.add(
ContainerRegistryRow(
id=registry_id,
url="https://docker.io",
registry_name=f"reg-{uuid.uuid4().hex[:8]}",
type=ContainerRegistryType.DOCKER,
)
)
await db_sess.flush()
image = ImageRow(
name="test-image:latest",
project=str(uuid.uuid4()),
image="test-image",
registry="docker.io",
registry_id=uuid.uuid4(),
registry_id=registry_id,
architecture="x86_64",
is_local=False,
config_digest="sha256:abc123",
Expand Down
15 changes: 14 additions & 1 deletion tests/unit/manager/models/test_deployment_revision.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import sqlalchemy as sa

from ai.backend.common.config import ModelDefinitionDraft
from ai.backend.common.container_registry import ContainerRegistryType
from ai.backend.common.data.endpoint.types import EndpointLifecycle
from ai.backend.common.identifier.vfolder import VFolderUUID
from ai.backend.common.types import (
Expand All @@ -25,6 +26,7 @@
from ai.backend.manager.data.deployment.types import ModelRevisionData
from ai.backend.manager.data.image.types import ImageType
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -95,6 +97,7 @@ async def db_with_cleanup(
GroupRow,
AgentRow,
VFolderRow,
ContainerRegistryRow,
ImageRow,
ResourcePresetRow,
ResourceSlotTypeRow,
Expand Down Expand Up @@ -242,13 +245,23 @@ async def test_image(
db_with_cleanup: ExtendedAsyncSAEngine,
) -> AsyncGenerator[ImageRow, None]:
"""Create test image."""
registry_id = uuid.uuid4()
async with db_with_cleanup.begin_session() as db_sess:
db_sess.add(
ContainerRegistryRow(
id=registry_id,
url="https://docker.io",
registry_name=f"reg-{uuid.uuid4().hex[:8]}",
type=ContainerRegistryType.DOCKER,
)
)
await db_sess.flush()
image = ImageRow(
name="test-image:latest",
project=str(uuid.uuid4()),
image="test-image",
registry="docker.io",
registry_id=uuid.uuid4(),
registry_id=registry_id,
architecture="x86_64",
is_local=False,
config_digest="sha256:abc123",
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/manager/models/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from ai.backend.common.types import BinarySize, ResourceSlot
from ai.backend.manager.data.session.types import SessionStatus
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -80,6 +81,7 @@ async def database_with_tables(
GroupRow,
AgentRow,
VFolderRow,
ContainerRegistryRow,
ImageRow,
ResourcePresetRow,
EndpointRow,
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/manager/models/test_vfolder_prepare_mounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from ai.backend.manager.data.auth.hash import PasswordHashAlgorithm
from ai.backend.manager.errors.api import InvalidAPIParameters
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -128,6 +129,7 @@ async def db_with_cleanup(
AgentRow,
VFolderRow,
VFolderPermissionRow,
ContainerRegistryRow,
ImageRow,
ResourcePresetRow,
RuntimeVariantRow,
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/manager/models/test_vfolder_quota_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from ai.backend.manager.errors.api import InvalidAPIParameters
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -74,6 +75,7 @@ async def db_with_cleanup(
GroupRow,
AgentRow,
VFolderRow,
ContainerRegistryRow,
ImageRow,
ResourcePresetRow,
EndpointRow,
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/manager/models/user/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ai.backend.manager.data.auth.hash import PasswordHashAlgorithm
from ai.backend.manager.data.group.types import ProjectType
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import (
DeploymentAutoScalingPolicyRow,
)
Expand Down Expand Up @@ -60,6 +61,7 @@
KeyPairRow,
GroupRow,
AssocGroupUserRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/manager/repositories/agent/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from ai.backend.manager.data.session.types import SessionStatus
from ai.backend.manager.errors.resource import ScalingGroupNotFound
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -110,6 +111,7 @@ async def db_with_cleanup(
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down Expand Up @@ -620,6 +622,7 @@ async def db_with_tables(
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ai.backend.manager.data.permission.types import RBACElementRef
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.app_config import AppConfigRow, AppConfigScopeType
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -84,6 +85,7 @@ async def db_with_cleanup(
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.artifact import ArtifactRow
from ai.backend.manager.models.artifact_revision import ArtifactRevisionRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -82,6 +83,7 @@ async def db_with_cleanup(
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.artifact import ArtifactRow
from ai.backend.manager.models.artifact_revision import ArtifactRevisionRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -87,6 +88,7 @@ async def db_with_cleanup(
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from ai.backend.manager.data.permission.types import EntityType, ScopeType
from ai.backend.manager.errors.auth import GroupMembershipNotFoundError
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -100,6 +101,7 @@ async def db_with_cleanup(
GroupRow,
AssocGroupUserRow,
AssociationScopesEntitiesRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
DomainHasUsers,
)
from ai.backend.manager.models.agent import AgentRow
from ai.backend.manager.models.container_registry import ContainerRegistryRow
from ai.backend.manager.models.deployment_auto_scaling_policy import DeploymentAutoScalingPolicyRow
from ai.backend.manager.models.deployment_policy import DeploymentPolicyRow
from ai.backend.manager.models.deployment_revision import DeploymentRevisionRow
Expand Down Expand Up @@ -82,6 +83,7 @@ async def db_with_cleanup(
UserRow,
KeyPairRow,
GroupRow,
ContainerRegistryRow,
ImageRow,
VFolderRow,
EndpointRow,
Expand Down
Loading
Loading