Skip to content

Commit bccbf7e

Browse files
committed
Drop DSTACK_FF_EVENTS
Also fix some unit tests that were patching `uuid.uuid4()` globally, which led to events with duplicate IDs.
1 parent 5b03ca2 commit bccbf7e

File tree

11 files changed

+72
-88
lines changed

11 files changed

+72
-88
lines changed

docs/docs/reference/environment-variables.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,7 @@ For more details on the options below, refer to the [server deployment](../guide
131131
- `DSTACK_SERVER_METRICS_FINISHED_TTL_SECONDS`{ #DSTACK_SERVER_METRICS_FINISHED_TTL_SECONDS } – Maximum age of metrics samples for finished jobs.
132132
- `DSTACK_SERVER_INSTANCE_HEALTH_TTL_SECONDS`{ #DSTACK_SERVER_INSTANCE_HEALTH_TTL_SECONDS } – Maximum age of instance health checks.
133133
- `DSTACK_SERVER_INSTANCE_HEALTH_MIN_COLLECT_INTERVAL_SECONDS`{ #DSTACK_SERVER_INSTANCE_HEALTH_MIN_COLLECT_INTERVAL_SECONDS } – Minimum time interval between consecutive health checks of the same instance.
134-
135-
<!--
136-
TODO: uncomment after dropping DSTACK_FF_EVENTS
137-
138134
- `DSTACK_SERVER_EVENTS_TTL_SECONDS` { #DSTACK_SERVER_EVENTS_TTL_SECONDS } - Maximum age of event records. Set to `0` to disable event storage. Defaults to 30 days.
139-
-->
140135

141136
??? info "Internal environment variables"
142137
The following environment variables are intended for development purposes:

src/dstack/_internal/server/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
get_client_version,
6767
get_server_client_error_details,
6868
)
69-
from dstack._internal.settings import DSTACK_VERSION, FeatureFlags
69+
from dstack._internal.settings import DSTACK_VERSION
7070
from dstack._internal.utils.logging import get_logger
7171
from dstack._internal.utils.ssh import check_required_ssh_version
7272

@@ -229,7 +229,7 @@ def register_routes(app: FastAPI, ui: bool = True):
229229
app.include_router(model_proxy.router, prefix="/proxy/models", tags=["model-proxy"])
230230
app.include_router(prometheus.router)
231231
app.include_router(files.router)
232-
app.include_router(events.root_router, include_in_schema=FeatureFlags.EVENTS)
232+
app.include_router(events.root_router)
233233

234234
@app.exception_handler(ForbiddenError)
235235
async def forbidden_error_handler(request: Request, exc: ForbiddenError):

src/dstack/_internal/server/background/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
process_terminating_jobs,
3434
)
3535
from dstack._internal.server.background.tasks.process_volumes import process_submitted_volumes
36-
from dstack._internal.settings import FeatureFlags
3736

3837
_scheduler = AsyncIOScheduler()
3938

@@ -71,8 +70,7 @@ def start_background_tasks() -> AsyncIOScheduler:
7170
_scheduler.add_job(process_probes, IntervalTrigger(seconds=3, jitter=1))
7271
_scheduler.add_job(collect_metrics, IntervalTrigger(seconds=10), max_instances=1)
7372
_scheduler.add_job(delete_metrics, IntervalTrigger(minutes=5), max_instances=1)
74-
if FeatureFlags.EVENTS:
75-
_scheduler.add_job(delete_events, IntervalTrigger(minutes=7), max_instances=1)
73+
_scheduler.add_job(delete_events, IntervalTrigger(minutes=7), max_instances=1)
7674
if settings.ENABLE_PROMETHEUS_METRICS:
7775
_scheduler.add_job(
7876
collect_prometheus_metrics, IntervalTrigger(seconds=10), max_instances=1

src/dstack/_internal/server/routers/events.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from sqlalchemy.ext.asyncio import AsyncSession
33

44
import dstack._internal.server.services.events as events_services
5-
from dstack._internal.core.errors import ServerClientError
65
from dstack._internal.core.models.events import Event
76
from dstack._internal.server.db import get_session
87
from dstack._internal.server.models import UserModel
@@ -12,7 +11,6 @@
1211
CustomORJSONResponse,
1312
get_base_api_additional_responses,
1413
)
15-
from dstack._internal.settings import FeatureFlags
1614

1715
root_router = APIRouter(
1816
prefix="/api/events",
@@ -36,8 +34,6 @@ async def list_events(
3634
The results are paginated. To get the next page, pass `recorded_at` and `id` of
3735
the last event from the previous page as `prev_recorded_at` and `prev_id`.
3836
"""
39-
if not FeatureFlags.EVENTS:
40-
raise ServerClientError("Events are disabled on this server")
4137
return CustomORJSONResponse(
4238
await events_services.list_events(
4339
session=session,

src/dstack/_internal/server/services/events.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
UserModel,
2323
)
2424
from dstack._internal.server.services.logging import fmt_entity
25-
from dstack._internal.settings import FeatureFlags
2625
from dstack._internal.utils.common import get_current_datetime
2726
from dstack._internal.utils.logging import get_logger
2827

@@ -170,9 +169,6 @@ def emit(session: AsyncSession, message: str, actor: AnyActor, targets: list[Tar
170169
they will see the entire event with all targets. If this is not desired,
171170
consider emitting multiple separate events instead.
172171
"""
173-
if not FeatureFlags.EVENTS:
174-
return
175-
176172
if not targets:
177173
raise ValueError("At least one target must be specified")
178174
if not message:
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import re
2+
import uuid
3+
4+
5+
class SomeUUID4Str:
6+
"""
7+
A matcher that compares equal to any valid UUID4 string
8+
"""
9+
10+
# Simplified UUID regex: just checks the 8-4-4-4-12 hex structure
11+
_uuid_regex = re.compile(
12+
r"^[0-9a-f]{8}-"
13+
r"[0-9a-f]{4}-"
14+
r"[0-9a-f]{4}-"
15+
r"[0-9a-f]{4}-"
16+
r"[0-9a-f]{12}$"
17+
)
18+
19+
def __eq__(self, other):
20+
if isinstance(other, str):
21+
if not self._uuid_regex.match(other):
22+
return False
23+
try:
24+
return uuid.UUID(other).version == 4
25+
except ValueError:
26+
return False
27+
28+
return False
29+
30+
def __repr__(self):
31+
return "SomeUUID4Str()"

src/dstack/_internal/settings.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,3 @@ class FeatureFlags:
3838
# DSTACK_FF_AUTOCREATED_FLEETS_ENABLED enables legacy autocreated fleets:
3939
# If there are no fleet suitable for the run, a new fleet is created automatically instead of an error.
4040
AUTOCREATED_FLEETS_ENABLED = os.getenv("DSTACK_FF_AUTOCREATED_FLEETS_ENABLED") is not None
41-
42-
# Server-side flag to enable event emission and Events API
43-
EVENTS = os.getenv("DSTACK_FF_EVENTS") is not None

src/tests/_internal/server/background/tasks/test_process_events.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from datetime import datetime
2-
from typing import Generator
32
from unittest.mock import patch
43

54
import pytest
@@ -14,12 +13,6 @@
1413
from dstack._internal.server.testing.common import create_user
1514

1615

17-
@pytest.fixture(autouse=True)
18-
def set_feature_flag() -> Generator[None, None, None]:
19-
with patch("dstack._internal.settings.FeatureFlags.EVENTS", True):
20-
yield
21-
22-
2316
@pytest.mark.asyncio
2417
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
2518
async def test_deletes_old_events(test_db, session: AsyncSession) -> None:

src/tests/_internal/server/routers/test_events.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import uuid
22
from datetime import datetime
3-
from typing import Generator
43
from unittest.mock import patch
54

65
import pytest
@@ -29,12 +28,6 @@
2928
]
3029

3130

32-
@pytest.fixture(autouse=True)
33-
def set_feature_flag() -> Generator[None, None, None]:
34-
with patch("dstack._internal.settings.FeatureFlags.EVENTS", True):
35-
yield
36-
37-
3831
class TestListEventsGeneral:
3932
async def test_response_format(self, session: AsyncSession, client: AsyncClient) -> None:
4033
user = await create_user(session=session, name="test_user")

src/tests/_internal/server/routers/test_fleets.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
get_remote_connection_info,
4848
get_ssh_fleet_configuration,
4949
)
50+
from dstack._internal.server.testing.matchers import SomeUUID4Str
5051

5152
pytestmark = pytest.mark.usefixtures("image_config_mock")
5253

@@ -321,16 +322,14 @@ async def test_creates_fleet(self, test_db, session: AsyncSession, client: Async
321322
session=session, project=project, user=user, project_role=ProjectRole.USER
322323
)
323324
spec = get_fleet_spec(conf=get_fleet_configuration())
324-
with patch("uuid.uuid4") as m:
325-
m.return_value = UUID("1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e")
326-
response = await client.post(
327-
f"/api/project/{project.name}/fleets/apply",
328-
headers=get_auth_headers(user.token),
329-
json={"plan": {"spec": spec.dict()}, "force": False},
330-
)
325+
response = await client.post(
326+
f"/api/project/{project.name}/fleets/apply",
327+
headers=get_auth_headers(user.token),
328+
json={"plan": {"spec": spec.dict()}, "force": False},
329+
)
331330
assert response.status_code == 200
332331
assert response.json() == {
333-
"id": "1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e",
332+
"id": SomeUUID4Str(),
334333
"name": spec.configuration.name,
335334
"project_name": project.name,
336335
"spec": {
@@ -390,10 +389,10 @@ async def test_creates_fleet(self, test_db, session: AsyncSession, client: Async
390389
"status_message": None,
391390
"instances": [
392391
{
393-
"id": "1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e",
392+
"id": SomeUUID4Str(),
394393
"project_name": project.name,
395394
"name": f"{spec.configuration.name}-0",
396-
"fleet_id": "1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e",
395+
"fleet_id": SomeUUID4Str(),
397396
"fleet_name": spec.configuration.name,
398397
"instance_num": 0,
399398
"job_name": None,
@@ -413,6 +412,8 @@ async def test_creates_fleet(self, test_db, session: AsyncSession, client: Async
413412
}
414413
],
415414
}
415+
for instance in response.json()["instances"]:
416+
assert instance["fleet_id"] == response.json()["id"]
416417
res = await session.execute(select(FleetModel))
417418
assert res.scalar_one()
418419
res = await session.execute(select(InstanceModel))
@@ -435,16 +436,14 @@ async def test_creates_ssh_fleet(self, test_db, session: AsyncSession, client: A
435436
network=None,
436437
)
437438
spec = get_fleet_spec(conf=conf)
438-
with patch("uuid.uuid4") as m:
439-
m.return_value = UUID("1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e")
440-
response = await client.post(
441-
f"/api/project/{project.name}/fleets/apply",
442-
headers=get_auth_headers(user.token),
443-
json={"plan": {"spec": spec.dict()}, "force": False},
444-
)
439+
response = await client.post(
440+
f"/api/project/{project.name}/fleets/apply",
441+
headers=get_auth_headers(user.token),
442+
json={"plan": {"spec": spec.dict()}, "force": False},
443+
)
445444
assert response.status_code == 200, response.json()
446445
assert response.json() == {
447-
"id": "1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e",
446+
"id": SomeUUID4Str(),
448447
"name": spec.configuration.name,
449448
"project_name": project.name,
450449
"spec": {
@@ -512,7 +511,7 @@ async def test_creates_ssh_fleet(self, test_db, session: AsyncSession, client: A
512511
"status_message": None,
513512
"instances": [
514513
{
515-
"id": "1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e",
514+
"id": SomeUUID4Str(),
516515
"project_name": project.name,
517516
"backend": "remote",
518517
"instance_type": {
@@ -528,7 +527,7 @@ async def test_creates_ssh_fleet(self, test_db, session: AsyncSession, client: A
528527
},
529528
},
530529
"name": f"{spec.configuration.name}-0",
531-
"fleet_id": "1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e",
530+
"fleet_id": SomeUUID4Str(),
532531
"fleet_name": spec.configuration.name,
533532
"instance_num": 0,
534533
"job_name": None,
@@ -546,6 +545,8 @@ async def test_creates_ssh_fleet(self, test_db, session: AsyncSession, client: A
546545
}
547546
],
548547
}
548+
for instance in response.json()["instances"]:
549+
assert instance["fleet_id"] == response.json()["id"]
549550
res = await session.execute(select(FleetModel))
550551
assert res.scalar_one()
551552
res = await session.execute(select(InstanceModel))

0 commit comments

Comments
 (0)