Skip to content

Commit 75ac361

Browse files
authored
fix: migrate cluster, constraint for k8s_objects (#971)
Adds a missing migration to the k8s_objects table for the watcher. It also modifies the uniqueness constraint to accomodate multiple clusters.
1 parent af63baf commit 75ac361

3 files changed

Lines changed: 97 additions & 2 deletions

File tree

components/renku_data_services/k8s_watcher/orm.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import Any
77

88
from box import Box
9-
from sqlalchemy import ColumnElement, DateTime, MetaData, String, func, text
9+
from sqlalchemy import ColumnElement, DateTime, MetaData, String, UniqueConstraint, func, text
1010
from sqlalchemy.dialects.postgresql import JSONB
1111
from sqlalchemy.ext.hybrid import Comparator, hybrid_property
1212
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
@@ -47,6 +47,17 @@ class K8sObjectORM(BaseORM):
4747
"""Representation of a k8s resource."""
4848

4949
__tablename__ = "k8s_objects"
50+
__table_args__ = (
51+
UniqueConstraint(
52+
"group",
53+
"version",
54+
"kind",
55+
"cluster",
56+
"namespace",
57+
"name",
58+
name="_unique_common_k8s_objects_gvk_cluster_namespace_name",
59+
),
60+
)
5061

5162
id: Mapped[ULID] = mapped_column(
5263
"id",
@@ -56,7 +67,7 @@ class K8sObjectORM(BaseORM):
5667
default_factory=lambda: str(ULID()),
5768
server_default=text("generate_ulid()"),
5869
)
59-
name: Mapped[str] = mapped_column("name", String(), index=True, unique=True)
70+
name: Mapped[str] = mapped_column("name", String(), index=True)
6071
namespace: Mapped[str] = mapped_column("namespace", String(), index=True)
6172
creation_date: Mapped[datetime] = mapped_column(
6273
"creation_date",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""change cluster name and uniuqe constraint
2+
3+
Revision ID: c8061499b966
4+
Revises: e117405fed51
5+
Create Date: 2025-08-14 07:40:10.492620
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "c8061499b966"
14+
down_revision = "e117405fed51"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
op.execute(
21+
sa.text(
22+
"UPDATE common.k8s_objects SET cluster='0RENK1RENK2RENK3RENK4RENK5' where k8s_objects.cluster='renkulab'"
23+
)
24+
)
25+
# ### commands auto generated by Alembic - please adjust! ###
26+
op.drop_index("ix_common_k8s_objects_name", table_name="k8s_objects", schema="common")
27+
op.create_index(op.f("ix_common_k8s_objects_name"), "k8s_objects", ["name"], unique=False, schema="common")
28+
op.create_unique_constraint(
29+
"_unique_common_k8s_objects_gvk_cluster_namespace_name",
30+
"k8s_objects",
31+
["group", "version", "kind", "cluster", "namespace", "name"],
32+
schema="common",
33+
)
34+
# ### end Alembic commands ###
35+
36+
37+
def downgrade() -> None:
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.drop_constraint(
40+
"_unique_common_k8s_objects_gvk_cluster_namespace_name", "k8s_objects", schema="common", type_="unique"
41+
)
42+
op.drop_index(op.f("ix_common_k8s_objects_name"), table_name="k8s_objects", schema="common")
43+
op.create_index("ix_common_k8s_objects_name", "k8s_objects", ["name"], unique=True, schema="common")
44+
# ### end Alembic commands ###

test/bases/renku_data_services/data_api/test_migrations.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,3 +609,43 @@ async def test_migration_to_dcb9648c3c15(app_manager_instance: DependencyManager
609609
assert k8s_objs[1].tuple()[0] == "amalthea.dev"
610610
assert k8s_objs[1].tuple()[1] == "v1alpha1"
611611
assert k8s_objs[1].tuple()[2] == "jupyterserver"
612+
613+
614+
@pytest.mark.asyncio
615+
async def test_migration_to_c8061499b966(app_manager_instance: DependencyManager, admin_user: UserInfo) -> None:
616+
run_migrations_for_app("common", "e117405fed51")
617+
async with app_manager_instance.config.db.async_session_maker() as session, session.begin():
618+
await session.execute(
619+
sa.text(
620+
"INSERT into "
621+
"common.k8s_objects(name, namespace, manifest, deleted, kind, version, cluster, user_id) "
622+
"VALUES ('name_pod', 'ns', '{}', FALSE, 'pod', 'v1', 'renkulab', 'user_id')"
623+
)
624+
)
625+
await session.execute(
626+
sa.text(
627+
"INSERT into "
628+
"common.k8s_objects(name, namespace, manifest, deleted, kind, version, cluster, user_id) "
629+
"VALUES ('name_js', 'ns', '{}', FALSE, 'jupyterserver', 'amalthea.dev/v1alpha1', 'renkulab', 'user_id')"
630+
)
631+
)
632+
run_migrations_for_app("common", "c8061499b966")
633+
async with app_manager_instance.config.db.async_session_maker() as session, session.begin():
634+
k8s_objs = (await session.execute(sa.text("SELECT name, cluster FROM common.k8s_objects"))).all()
635+
assert len(k8s_objs) == 2
636+
# Check that the cluster name was changed
637+
assert k8s_objs[0].tuple()[1] == "0RENK1RENK2RENK3RENK4RENK5"
638+
assert k8s_objs[1].tuple()[1] == "0RENK1RENK2RENK3RENK4RENK5"
639+
id = ULID()
640+
async with app_manager_instance.config.db.async_session_maker() as session, session.begin():
641+
await session.execute(
642+
sa.text(
643+
"INSERT into "
644+
"common.k8s_objects(name, namespace, manifest, deleted, kind, version, cluster, user_id) "
645+
f"VALUES ('name_pod', 'ns', '{{}}', FALSE, 'pod', 'v1', '{id}', 'user_id')"
646+
)
647+
)
648+
async with app_manager_instance.config.db.async_session_maker() as session, session.begin():
649+
k8s_objs = (await session.execute(sa.text("SELECT name, cluster FROM common.k8s_objects"))).all()
650+
# Check that we can insert another object with the same name, gvk, namespace, but a different cluster
651+
assert len(k8s_objs) == 3

0 commit comments

Comments
 (0)