Skip to content

Commit 3c0fb4e

Browse files
Refactor topology to use UUIDs instead of integers
Updated the topology models, migrations, and tests to replace integer-based IDs with UUIDs for better scalability and uniqueness. Introduced an `external_id` field where necessary to preserve compatibility with existing data. Adjusted related logic, schemas, and checks accordingly.
1 parent 47ca583 commit 3c0fb4e

File tree

4 files changed

+81
-72
lines changed

4 files changed

+81
-72
lines changed

keep/api/models/db/migrations/versions/2025-03-07-17-49_aaec81b991bd.py

+39-39
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,27 @@ def transfer_data():
2121
session = Session(bind=op.get_bind())
2222
dialect = session.bind.dialect.name
2323

24-
session.execute(sa.text("""
24+
uuid_generation_func = "replace(uuid(),'-','')"
25+
if dialect == "sqlite":
26+
uuid_generation_func = """
27+
lower(
28+
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
29+
substr(hex( randomblob(2)), 2) || '-' ||
30+
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
31+
substr(hex(randomblob(2)), 2) || '-' ||
32+
hex(randomblob(6))
33+
)
34+
"""
35+
elif dialect == "postgresql":
36+
uuid_generation_func = "gen_random_uuid()"
37+
38+
session.execute(sa.text(f"""
2539
INSERT INTO topologyservice (
26-
id, tenant_id, source_provider_id, repository, tags, service, environment, display_name, description,
40+
id, external_id, tenant_id, source_provider_id, repository, tags, service, environment, display_name, description,
2741
team, email, slack, ip_address, mac_address, category, manufacturer, namespace, is_manual
2842
)
2943
SELECT
30-
id, tenant_id, source_provider_id, repository, tags, service, environment, display_name, description,
44+
{uuid_generation_func} as id, id as external_id, tenant_id, source_provider_id, repository, tags, service, environment, display_name, description,
3145
team, email, slack, ip_address, mac_address, category, manufacturer, namespace, is_manual
3246
FROM topologyservice_tmp
3347
"""))
@@ -39,37 +53,17 @@ def transfer_data():
3953

4054
session.execute(sa.text("""
4155
INSERT INTO topologyserviceapplication (service_id, application_id, tenant_id)
42-
SELECT tsa.service_id, tsa.application_id, ts.tenant_id FROM topologyserviceapplication_tmp as tsa
43-
JOIN topologyservice_tmp as ts ON tsa.service_id = ts.id
56+
SELECT ts.id, tsa.application_id, ts.tenant_id FROM topologyserviceapplication_tmp as tsa
57+
JOIN topologyservice as ts ON tsa.service_id = ts.external_id
4458
"""))
4559

46-
if dialect == "sqlite":
47-
session.execute(sa.text("""
48-
INSERT INTO topologyservicedependency (id, service_id, depends_on_service_id, updated_at, protocol, tenant_id)
49-
SELECT lower(
50-
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
51-
substr(hex( randomblob(2)), 2) || '-' ||
52-
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
53-
substr(hex(randomblob(2)), 2) || '-' ||
54-
hex(randomblob(6))
55-
) as id, tsd.service_id, tsd.depends_on_service_id, tsd.updated_at, tsd.protocol, ts.tenant_id
56-
FROM topologyservicedependency_tmp as tsd
57-
JOIN topologyservice_tmp as ts ON tsd.service_id = ts.id
58-
"""))
59-
elif dialect == "postgres":
60-
session.execute(sa.text("""
61-
INSERT INTO topologyservicedependency (id, service_id, depends_on_service_id, updated_at, protocol, tenant_id)
62-
SELECT gen_random_uuid() as id, tsd.service_id, tsd.depends_on_service_id, tsd.updated_at, tsd.protocol, ts.tenant_id
63-
FROM topologyservicedependency_tmp as tsd
64-
JOIN topologyservice_tmp as ts ON tsd.service_id = ts.id
65-
"""))
66-
elif dialect == "mysql":
67-
session.execute(sa.text("""
68-
INSERT INTO topologyservicedependency (id, service_id, depends_on_service_id, updated_at, protocol, tenant_id)
69-
SELECT uuid() as id, tsd.service_id, tsd.depends_on_service_id, tsd.updated_at, tsd.protocol, ts.tenant_id
70-
FROM topologyservicedependency_tmp as tsd
71-
JOIN topologyservice_tmp as ts ON tsd.service_id = ts.id
72-
"""))
60+
session.execute(sa.text(f"""
61+
INSERT INTO topologyservicedependency (id, service_id, depends_on_service_id, updated_at, protocol, tenant_id)
62+
SELECT {uuid_generation_func} as id, ts.id as service_id, ts_dep.id as depends_on_service_id, tsd.updated_at, tsd.protocol, ts.tenant_id
63+
FROM topologyservicedependency_tmp as tsd
64+
JOIN topologyservice as ts ON tsd.service_id = ts.external_id
65+
JOIN topologyservice as ts_dep ON tsd.depends_on_service_id = ts_dep.external_id
66+
"""))
7367

7468

7569
def transfer_data_back():
@@ -80,7 +74,7 @@ def transfer_data_back():
8074
id, tenant_id, source_provider_id, repository, tags, service, environment, display_name, description,
8175
team, email, slack, ip_address, mac_address, category, manufacturer, namespace, is_manual
8276
)
83-
SELECT id, tenant_id, source_provider_id, repository, tags, service, environment, display_name, description,
77+
SELECT external_id as id, tenant_id, source_provider_id, repository, tags, service, environment, display_name, description,
8478
team, email, slack, ip_address, mac_address, category, manufacturer, namespace, is_manual
8579
FROM topologyservice_tmp
8680
"""))
@@ -92,12 +86,17 @@ def transfer_data_back():
9286

9387
session.execute(sa.text("""
9488
INSERT INTO topologyserviceapplication (service_id, application_id)
95-
SELECT service_id, application_id FROM topologyserviceapplication_tmp
89+
SELECT ts.external_id, tsa.application_id FROM topologyserviceapplication_tmp as tsa
90+
JOIN topologyservice_tmp as ts ON tsa.service_id = ts.id
91+
9692
"""))
9793

9894
session.execute(sa.text("""
9995
INSERT INTO topologyservicedependency (service_id, depends_on_service_id, updated_at, protocol)
100-
SELECT service_id, depends_on_service_id, updated_at, protocol FROM topologyservicedependency_tmp
96+
SELECT ts.external_id, ts_dep.external_id, tsd.updated_at, tsd.protocol
97+
FROM topologyservicedependency_tmp as tsd
98+
JOIN topologyservice_tmp as ts ON tsd.service_id = ts.id
99+
JOIN topologyservice_tmp as ts_dep ON tsd.depends_on_service_id = ts_dep.id
101100
"""))
102101

103102
def upgrade():
@@ -121,7 +120,8 @@ def upgrade():
121120
)
122121
op.create_table(
123122
"topologyservice",
124-
sa.Column("id", sa.Integer(), nullable=False),
123+
sa.Column("id", sa.Uuid(), nullable=False),
124+
sa.Column("external_id", sa.Integer(), nullable=True),
125125
sa.Column("tenant_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
126126
sa.Column(
127127
"source_provider_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False
@@ -156,7 +156,7 @@ def upgrade():
156156
op.create_table(
157157
"topologyserviceapplication",
158158
sa.Column("tenant_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
159-
sa.Column("service_id", sa.Integer(), nullable=False),
159+
sa.Column("service_id", sa.Uuid(), nullable=False),
160160
sa.Column("application_id", sa.Uuid(), nullable=False),
161161
sa.ForeignKeyConstraint(
162162
["application_id", "tenant_id"],
@@ -176,8 +176,8 @@ def upgrade():
176176
"topologyservicedependency",
177177
sa.Column("id", sa.Uuid(), nullable=False),
178178
sa.Column("tenant_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
179-
sa.Column("service_id", sa.Integer(), nullable=False),
180-
sa.Column("depends_on_service_id", sa.Integer(), nullable=False),
179+
sa.Column("service_id", sa.Uuid(), nullable=False),
180+
sa.Column("depends_on_service_id", sa.Uuid(), nullable=False),
181181
sa.Column("protocol", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
182182
sa.Column(
183183
"updated_at",

keep/api/models/db/topology.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
class TopologyServiceApplication(SQLModel, table=True):
1111
tenant_id: str = Field(sa_column=Column(ForeignKey("tenant.id"), primary_key=True))
12-
service_id: int = Field(primary_key=True)
12+
service_id: UUID = Field(primary_key=True)
1313
application_id: UUID = Field(primary_key=True)
1414

1515
service: "TopologyService" = Relationship(
@@ -51,7 +51,8 @@ class TopologyApplication(SQLModel, table=True):
5151

5252

5353
class TopologyService(SQLModel, table=True):
54-
id: int
54+
id: UUID = Field(default_factory=uuid4, primary_key=True)
55+
external_id: Optional[int]
5556
tenant_id: str = Field(sa_column=Column(ForeignKey("tenant.id")))
5657
source_provider_id: str = "unknown"
5758
repository: Optional[str]
@@ -103,8 +104,8 @@ class Config:
103104
class TopologyServiceDependency(SQLModel, table=True):
104105
id: UUID = Field(default_factory=uuid4)
105106
tenant_id: str = Field(sa_column=Column(ForeignKey("tenant.id")))
106-
service_id: int
107-
depends_on_service_id: int
107+
service_id: UUID
108+
depends_on_service_id: UUID
108109
protocol: Optional[str] = "unknown"
109110
updated_at: Optional[datetime] = Field(
110111
sa_column=Column(
@@ -169,7 +170,7 @@ class TopologyServiceInDto(TopologyServiceDtoBase):
169170

170171
class TopologyServiceDependencyDto(BaseModel, extra="ignore"):
171172
id: str | UUID
172-
serviceId: str
173+
serviceId: str | UUID
173174
serviceName: str
174175
protocol: Optional[str] = "unknown"
175176

@@ -194,7 +195,7 @@ class TopologyApplicationDto(BaseModel, extra="ignore"):
194195

195196

196197
class TopologyServiceDtoIn(BaseModel, extra="ignore"):
197-
id: int
198+
id: UUID
198199

199200

200201
class TopologyApplicationDtoIn(BaseModel, extra="ignore"):
@@ -239,7 +240,7 @@ def from_orm(
239240

240241

241242
class TopologyServiceDtoOut(TopologyServiceDtoBase):
242-
id: str
243+
id: UUID | str
243244
dependencies: List[TopologyServiceDependencyDto]
244245
application_ids: List[UUID]
245246
updated_at: Optional[datetime]
@@ -298,28 +299,28 @@ class TopologyServiceCreateRequestDTO(BaseModel, extra="ignore"):
298299

299300

300301
class TopologyServiceUpdateRequestDTO(TopologyServiceCreateRequestDTO, extra="ignore"):
301-
id: int
302+
id: UUID | str
302303

303304

304305
class TopologyServiceDependencyCreateRequestDto(BaseModel, extra="ignore"):
305-
service_id: int
306-
depends_on_service_id: int
306+
service_id: UUID | str
307+
depends_on_service_id: UUID | str
307308
protocol: Optional[str] = "unknown"
308309

309310

310311
class TopologyServiceDependencyUpdateRequestDto(
311312
TopologyServiceDependencyCreateRequestDto, extra="ignore"
312313
):
313-
service_id: Optional[int]
314-
depends_on_service_id: Optional[int]
315-
id: int
314+
service_id: Optional[UUID | str]
315+
depends_on_service_id: Optional[UUID | str]
316+
id: UUID | str
316317

317318

318319
class DeleteServicesRequest(BaseModel, extra="ignore"):
319320
service_ids: List[int]
320321

321322

322323
class TopologyServiceYAML(TopologyServiceCreateRequestDTO, extra="ignore"):
323-
id: int
324-
source_provider_id: Optional[str] = None
324+
id: UUID | str
325+
source_provider_id: Optional[UUID | str] = None
325326
is_manual: Optional[bool] = None

tests/test_enrichments.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ def test_disposable_enrichment(db_session, client, test_app, mock_alert_dto):
557557
def test_topology_mapping_rule_enrichment(mock_session, mock_alert_dto):
558558
# Mock a TopologyService with dependencies to simulate the DB structure
559559
mock_topology_service = TopologyService(
560-
id=1, tenant_id="keep", service="test-service", display_name="Test Service"
560+
external_id=1, tenant_id="keep", service="test-service", display_name="Test Service"
561561
)
562562

563563
# Create a mock MappingRule for topology

tests/test_topology.py

+25-17
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
VALID_API_KEY = "valid_api_key"
2424

2525

26-
def create_service(db_session, tenant_id, service_id):
26+
def create_service(db_session, tenant_id, external_id):
2727
service = TopologyService(
28-
id=service_id,
28+
external_id=external_id,
2929
tenant_id=tenant_id,
30-
service="test_service_" + service_id,
31-
display_name=service_id,
30+
service="test_service_" + external_id,
31+
display_name=external_id,
3232
repository="test_repository",
3333
tags=["test_tag"],
3434
description="test_description",
@@ -105,7 +105,7 @@ def test_create_application_by_tenant_id(db_session):
105105
SINGLE_TENANT_UUID, application_dto, db_session
106106
)
107107

108-
application_dto.services.append(TopologyServiceDtoIn(id=123))
108+
application_dto.services.append(TopologyServiceDtoIn(id=uuid.uuid4()))
109109
with pytest.raises(ServiceNotFoundException):
110110
TopologiesService.create_application_by_tenant_id(
111111
SINGLE_TENANT_UUID, application_dto, db_session
@@ -131,8 +131,10 @@ def test_create_application_by_tenant_id(db_session):
131131
assert len(result) == 1
132132
assert result[0].name == "New Application"
133133
assert len(result[0].services) == 2
134-
assert result[0].services[0].service == "test_service_1"
135-
assert result[0].services[1].service == "test_service_2"
134+
135+
services_names = [s.service for s in result[0].services]
136+
assert "test_service_1" in services_names
137+
assert "test_service_2" in services_names
136138

137139

138140
def test_update_application_by_id(db_session):
@@ -213,7 +215,7 @@ def test_create_application(db_session, client, test_app):
213215

214216
service = create_service(db_session, SINGLE_TENANT_UUID, "1")
215217

216-
application_data = {"name": "New Application", "services": [{"id": service.id}]}
218+
application_data = {"name": "New Application", "services": [{"id": str(service.id)}]}
217219

218220
response = client.post(
219221
"/topology/applications",
@@ -256,7 +258,7 @@ def test_update_application(db_session, client, test_app):
256258
assert response.status_code == 200
257259
assert response.json()["name"] == "Updated Application"
258260

259-
invalid_update_data = {"name": "Invalid Application", "services": [{"id": "123"}]}
261+
invalid_update_data = {"name": "Invalid Application", "services": [{"id": str(random_uuid)}]}
260262

261263
response = client.put(
262264
f"/topology/applications/{application.id}",
@@ -335,18 +337,24 @@ def test_import_to_db(db_session):
335337

336338
# Do same operation twice - import and re-import
337339
for i in range(2):
340+
341+
s1_id = str(uuid.uuid4())
342+
s2_id = str(uuid.uuid4())
343+
338344
topology_data = {
339345
"services": [
340346
{
341-
"id": 1,
347+
"id": s1_id,
348+
"external_id": "1",
342349
"service": "test_service_1",
343350
"display_name": "Service 1",
344351
"tags": ["tag1"],
345352
"team": "team1",
346353
"email": "[email protected]",
347354
},
348355
{
349-
"id": 2,
356+
"id": s2_id,
357+
"external_id": "2",
350358
"service": "test_service_2",
351359
"display_name": "Service 2",
352360
"tags": ["tag2"],
@@ -358,18 +366,18 @@ def test_import_to_db(db_session):
358366
{
359367
"name": "Test Application 1",
360368
"description": "Application 1 description",
361-
"services": [1],
369+
"services": [s1_id],
362370
},
363371
{
364372
"name": "Test Application 2",
365373
"description": "Application 2 description",
366-
"services": [2],
374+
"services": [s2_id],
367375
},
368376
],
369377
"dependencies": [
370378
{
371-
"service_id": 1,
372-
"depends_on_service_id": 2,
379+
"service_id": s1_id,
380+
"depends_on_service_id": s2_id,
373381
}
374382
],
375383
}
@@ -388,5 +396,5 @@ def test_import_to_db(db_session):
388396

389397
dependencies = db_session.exec(select(TopologyServiceDependency)).all()
390398
assert len(dependencies) == 1
391-
assert dependencies[0].service_id == 1
392-
assert dependencies[0].depends_on_service_id == 2
399+
assert str(dependencies[0].service_id) == s1_id
400+
assert str(dependencies[0].depends_on_service_id) == s2_id

0 commit comments

Comments
 (0)