Skip to content

Commit 90b0579

Browse files
authored
Volume events (#3494)
- Support volume events in the API, CLI, and UI - Add the following events: - Volume created - Volume status changed - Volume deleted - Volume deleted due to exceeding `auto_cleanup_duration`
1 parent ca2172c commit 90b0579

File tree

18 files changed

+142
-22
lines changed

18 files changed

+142
-22
lines changed

frontend/src/pages/Events/List/hooks/useColumnDefinitions.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,19 @@ export const useColumnsDefinitions = () => {
112112
</div>
113113
);
114114

115+
case 'volume':
116+
return (
117+
<div>
118+
Volume{' '}
119+
{target.project_name && (
120+
<NavigateLink href={ROUTES.PROJECT.DETAILS.FORMAT(target.project_name)}>
121+
{target.project_name}
122+
</NavigateLink>
123+
)}
124+
/{target.name}
125+
</div>
126+
);
127+
115128
default:
116129
return '---';
117130
}

frontend/src/pages/Events/List/hooks/useFilters.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type RequestParamsKeys = keyof Pick<
1717
| 'target_instances'
1818
| 'target_runs'
1919
| 'target_jobs'
20+
| 'target_volumes'
2021
| 'within_projects'
2122
| 'within_fleets'
2223
| 'within_runs'
@@ -31,6 +32,7 @@ const filterKeys: Record<string, RequestParamsKeys> = {
3132
TARGET_INSTANCES: 'target_instances',
3233
TARGET_RUNS: 'target_runs',
3334
TARGET_JOBS: 'target_jobs',
35+
TARGET_VOLUMES: 'target_volumes',
3436
WITHIN_PROJECTS: 'within_projects',
3537
WITHIN_FLEETS: 'within_fleets',
3638
WITHIN_RUNS: 'within_runs',
@@ -47,6 +49,7 @@ const multipleChoiseKeys: RequestParamsKeys[] = [
4749
'target_instances',
4850
'target_runs',
4951
'target_jobs',
52+
'target_volumes',
5053
'within_projects',
5154
'within_fleets',
5255
'within_runs',
@@ -61,6 +64,7 @@ const targetTypes = [
6164
{ label: 'Instance', value: 'instance' },
6265
{ label: 'Run', value: 'run' },
6366
{ label: 'Job', value: 'job' },
67+
{ label: 'Volume', value: 'volume' },
6468
];
6569

6670
export const useFilters = () => {
@@ -153,6 +157,11 @@ export const useFilters = () => {
153157
operators: ['='],
154158
propertyLabel: 'Target jobs',
155159
},
160+
{
161+
key: filterKeys.TARGET_VOLUMES,
162+
operators: ['='],
163+
propertyLabel: 'Target volumes',
164+
},
156165

157166
{
158167
key: filterKeys.WITHIN_PROJECTS,

frontend/src/types/event.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
declare type TEventTargetType = 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job';
1+
declare type TEventTargetType = 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume';
22

33
declare type TEventListRequestParams = Omit<TBaseRequestListParams, 'prev_created_at'> & {
44
prev_recorded_at?: string;
@@ -8,6 +8,7 @@ declare type TEventListRequestParams = Omit<TBaseRequestListParams, 'prev_create
88
target_instances?: string[];
99
target_runs?: string[];
1010
target_jobs?: string[];
11+
target_volumes?: string[];
1112
within_projects?: string[];
1213
within_fleets?: string[];
1314
within_runs?: string[];
@@ -16,7 +17,7 @@ declare type TEventListRequestParams = Omit<TBaseRequestListParams, 'prev_create
1617
};
1718

1819
declare interface IEventTarget {
19-
type: 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job';
20+
type: 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume';
2021
project_id?: string;
2122
project_name?: string;
2223
id: string;

src/dstack/_internal/cli/commands/event.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ def _register(self):
5252
dest="target_runs",
5353
help="Only show events that target the specified runs",
5454
)
55+
target_filters_group.add_argument(
56+
"--target-volume",
57+
action="append",
58+
metavar="NAME",
59+
dest="target_volumes",
60+
help="Only show events that target the specified volumes",
61+
)
5562
within_filters_group = parser.add_mutually_exclusive_group()
5663
within_filters_group.add_argument(
5764
"--within-fleet",
@@ -108,6 +115,11 @@ def _build_filters(args: argparse.Namespace, api: Client) -> EventListFilters:
108115
filters.target_runs = [
109116
api.client.runs.get(api.project, name).id for name in args.target_runs
110117
]
118+
elif args.target_volumes:
119+
filters.target_volumes = [
120+
api.client.volumes.get(project_name=api.project, name=name).id
121+
for name in args.target_volumes
122+
]
111123

112124
if args.within_fleets:
113125
filters.within_fleets = [

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
class EventListFilters:
1717
target_fleets: Optional[list[uuid.UUID]] = None
1818
target_runs: Optional[list[uuid.UUID]] = None
19+
target_volumes: Optional[list[uuid.UUID]] = None
1920
within_projects: Optional[list[uuid.UUID]] = None
2021
within_fleets: Optional[list[uuid.UUID]] = None
2122
within_runs: Optional[list[uuid.UUID]] = None

src/dstack/_internal/core/models/events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class EventTargetType(str, Enum):
1616
INSTANCE = "instance"
1717
RUN = "run"
1818
JOB = "job"
19+
VOLUME = "volume"
1920

2021

2122
class EventTarget(CoreModel):

src/dstack/_internal/server/background/tasks/process_idle_volumes.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from dstack._internal.server.db import get_db, get_session_ctx
1313
from dstack._internal.server.models import ProjectModel, UserModel, VolumeModel
1414
from dstack._internal.server.services import backends as backends_services
15+
from dstack._internal.server.services import events
1516
from dstack._internal.server.services.locking import get_locker
1617
from dstack._internal.server.services.volumes import (
1718
get_volume_configuration,
@@ -100,8 +101,12 @@ async def _delete_idle_volumes(session: AsyncSession, volumes: List[VolumeModel]
100101

101102
volume_model.deleted = True
102103
volume_model.deleted_at = get_current_datetime()
103-
104-
logger.info("Deleted idle volume %s", volume_model.name)
104+
events.emit(
105+
session=session,
106+
message="Volume deleted due to exceeding auto_cleanup_duration",
107+
actor=events.SystemActor(),
108+
targets=[events.Target.from_model(volume_model)],
109+
)
105110

106111
await session.commit()
107112

src/dstack/_internal/server/background/tasks/process_volumes.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from dstack._internal.server.services import backends as backends_services
1717
from dstack._internal.server.services import volumes as volumes_services
1818
from dstack._internal.server.services.locking import get_locker
19+
from dstack._internal.server.services.volumes import switch_volume_status
1920
from dstack._internal.server.utils import sentry_utils
2021
from dstack._internal.utils.common import get_current_datetime, run_async
2122
from dstack._internal.utils.logging import get_logger
@@ -79,8 +80,8 @@ async def _process_submitted_volume(session: AsyncSession, volume_model: VolumeM
7980
volume.name,
8081
volume.configuration.backend.value,
8182
)
82-
volume_model.status = VolumeStatus.FAILED
8383
volume_model.status_message = "Backend not available"
84+
switch_volume_status(session, volume_model, VolumeStatus.FAILED)
8485
volume_model.last_processed_at = get_current_datetime()
8586
await session.commit()
8687
return
@@ -102,18 +103,18 @@ async def _process_submitted_volume(session: AsyncSession, volume_model: VolumeM
102103
)
103104
except BackendError as e:
104105
logger.info("Failed to create volume %s: %s", volume_model.name, repr(e))
105-
volume_model.status = VolumeStatus.FAILED
106106
status_message = f"Backend error: {repr(e)}"
107107
if len(e.args) > 0:
108108
status_message = str(e.args[0])
109109
volume_model.status_message = status_message
110+
switch_volume_status(session, volume_model, VolumeStatus.FAILED)
110111
volume_model.last_processed_at = get_current_datetime()
111112
await session.commit()
112113
return
113114
except Exception as e:
114115
logger.exception("Got exception when creating volume %s", volume_model.name)
115-
volume_model.status = VolumeStatus.FAILED
116116
volume_model.status_message = f"Unexpected error: {repr(e)}"
117+
switch_volume_status(session, volume_model, VolumeStatus.FAILED)
117118
volume_model.last_processed_at = get_current_datetime()
118119
await session.commit()
119120
return
@@ -123,6 +124,6 @@ async def _process_submitted_volume(session: AsyncSession, volume_model: VolumeM
123124
# Provisioned volumes marked as active since they become available almost immediately in AWS
124125
# TODO: Consider checking volume state
125126
volume_model.volume_provisioning_data = vpd.json()
126-
volume_model.status = VolumeStatus.ACTIVE
127+
switch_volume_status(session, volume_model, VolumeStatus.ACTIVE)
127128
volume_model.last_processed_at = get_current_datetime()
128129
await session.commit()

src/dstack/_internal/server/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,7 @@ class VolumeModel(BaseModel):
745745
deleted: Mapped[bool] = mapped_column(Boolean, default=False)
746746
deleted_at: Mapped[Optional[datetime]] = mapped_column(NaiveDateTime)
747747

748+
# NOTE: `status` must be changed only via `switch_volume_status()`
748749
status: Mapped[VolumeStatus] = mapped_column(EnumAsString(VolumeStatus, 100), index=True)
749750
status_message: Mapped[Optional[str]] = mapped_column(Text)
750751

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ async def list_events(
4444
target_instances=body.target_instances,
4545
target_runs=body.target_runs,
4646
target_jobs=body.target_jobs,
47+
target_volumes=body.target_volumes,
4748
within_projects=body.within_projects,
4849
within_fleets=body.within_fleets,
4950
within_runs=body.within_runs,

0 commit comments

Comments
 (0)