Skip to content
Merged
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
13 changes: 13 additions & 0 deletions frontend/src/pages/Events/List/hooks/useColumnDefinitions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ export const useColumnsDefinitions = () => {
</div>
);

case 'gateway':
return (
<div>
Gateway{' '}
{target.project_name && (
<NavigateLink href={ROUTES.PROJECT.DETAILS.FORMAT(target.project_name)}>
{target.project_name}
</NavigateLink>
)}
/{target.name}
</div>
);

default:
return '---';
}
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/pages/Events/List/hooks/useFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type RequestParamsKeys = keyof Pick<
| 'target_runs'
| 'target_jobs'
| 'target_volumes'
| 'target_gateways'
| 'within_projects'
| 'within_fleets'
| 'within_runs'
Expand All @@ -33,6 +34,7 @@ const filterKeys: Record<string, RequestParamsKeys> = {
TARGET_RUNS: 'target_runs',
TARGET_JOBS: 'target_jobs',
TARGET_VOLUMES: 'target_volumes',
TARGET_GATEWAYS: 'target_gateways',
WITHIN_PROJECTS: 'within_projects',
WITHIN_FLEETS: 'within_fleets',
WITHIN_RUNS: 'within_runs',
Expand All @@ -50,6 +52,7 @@ const multipleChoiseKeys: RequestParamsKeys[] = [
'target_runs',
'target_jobs',
'target_volumes',
'target_gateways',
'within_projects',
'within_fleets',
'within_runs',
Expand All @@ -65,6 +68,7 @@ const targetTypes = [
{ label: 'Run', value: 'run' },
{ label: 'Job', value: 'job' },
{ label: 'Volume', value: 'volume' },
{ label: 'Gateway', value: 'gateway' },
];

export const useFilters = () => {
Expand Down Expand Up @@ -162,6 +166,11 @@ export const useFilters = () => {
operators: ['='],
propertyLabel: 'Target volumes',
},
{
key: filterKeys.TARGET_GATEWAYS,
operators: ['='],
propertyLabel: 'Target gateways',
},

{
key: filterKeys.WITHIN_PROJECTS,
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/types/event.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare type TEventTargetType = 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume';
declare type TEventTargetType = 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume' | 'gateway';

declare type TEventListRequestParams = Omit<TBaseRequestListParams, 'prev_created_at'> & {
prev_recorded_at?: string;
Expand All @@ -9,6 +9,7 @@ declare type TEventListRequestParams = Omit<TBaseRequestListParams, 'prev_create
target_runs?: string[];
target_jobs?: string[];
target_volumes?: string[];
target_gateways?: string[];
within_projects?: string[];
within_fleets?: string[];
within_runs?: string[];
Expand All @@ -17,7 +18,7 @@ declare type TEventListRequestParams = Omit<TBaseRequestListParams, 'prev_create
};

declare interface IEventTarget {
type: 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume';
type: 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume' | 'gateway';
project_id?: string;
project_name?: string;
id: string;
Expand Down
19 changes: 19 additions & 0 deletions src/dstack/_internal/cli/commands/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dstack._internal.cli.utils.common import (
get_start_time,
)
from dstack._internal.core.errors import CLIError
from dstack._internal.core.models.events import EventTargetType
from dstack._internal.server.schemas.events import LIST_EVENTS_DEFAULT_LIMIT
from dstack.api import Client
Expand Down Expand Up @@ -59,6 +60,13 @@ def _register(self):
dest="target_volumes",
help="Only show events that target the specified volumes",
)
target_filters_group.add_argument(
"--target-gateway",
action="append",
metavar="NAME",
dest="target_gateways",
help="Only show events that target the specified gateways",
)
within_filters_group = parser.add_mutually_exclusive_group()
within_filters_group.add_argument(
"--within-fleet",
Expand Down Expand Up @@ -120,6 +128,17 @@ def _build_filters(args: argparse.Namespace, api: Client) -> EventListFilters:
api.client.volumes.get(project_name=api.project, name=name).id
for name in args.target_volumes
]
elif args.target_gateways:
filters.target_gateways = []
for name in args.target_gateways:
id = api.client.gateways.get(api.project, name).id
if id is None:
# TODO(0.21): Remove this check once `Gateway.id` is required.
raise CLIError(
"Cannot determine gateway ID, most likely due to an outdated dstack server."
" Update the server to 0.20.7 or higher or remove --target-gateway."
)
filters.target_gateways.append(id)

if args.within_fleets:
filters.within_fleets = [
Expand Down
1 change: 1 addition & 0 deletions src/dstack/_internal/cli/services/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class EventListFilters:
target_fleets: Optional[list[uuid.UUID]] = None
target_runs: Optional[list[uuid.UUID]] = None
target_volumes: Optional[list[uuid.UUID]] = None
target_gateways: Optional[list[uuid.UUID]] = None
within_projects: Optional[list[uuid.UUID]] = None
within_fleets: Optional[list[uuid.UUID]] = None
within_runs: Optional[list[uuid.UUID]] = None
Expand Down
1 change: 1 addition & 0 deletions src/dstack/_internal/core/models/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class EventTargetType(str, Enum):
RUN = "run"
JOB = "job"
VOLUME = "volume"
GATEWAY = "gateway"


class EventTarget(CoreModel):
Expand Down
4 changes: 4 additions & 0 deletions src/dstack/_internal/core/models/gateways.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import uuid
from enum import Enum
from typing import Dict, Optional, Union

Expand Down Expand Up @@ -93,6 +94,9 @@ class GatewaySpec(CoreModel):


class Gateway(CoreModel):
# ID is only optional on the client side for compatibility with pre-0.20.7 servers.
# TODO(0.21): Make required.
id: Optional[uuid.UUID] = None
name: str
configuration: GatewayConfiguration
created_at: datetime.datetime
Expand Down
1 change: 1 addition & 0 deletions src/dstack/_internal/server/routers/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ async def list_events(
target_runs=body.target_runs,
target_jobs=body.target_jobs,
target_volumes=body.target_volumes,
target_gateways=body.target_gateways,
within_projects=body.within_projects,
within_fleets=body.within_fleets,
within_runs=body.within_runs,
Expand Down
11 changes: 11 additions & 0 deletions src/dstack/_internal/server/schemas/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ class ListEventsRequest(CoreModel):
max_items=MAX_FILTER_ITEMS,
),
] = None
target_gateways: Annotated[
Optional[list[uuid.UUID]],
Field(
description=(
"List of gateway IDs."
" The response will only include events that target the specified gateways"
),
min_items=MIN_FILTER_ITEMS,
max_items=MAX_FILTER_ITEMS,
),
] = None
within_projects: Annotated[
Optional[list[uuid.UUID]],
Field(
Expand Down
17 changes: 17 additions & 0 deletions src/dstack/_internal/server/services/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
EventModel,
EventTargetModel,
FleetModel,
GatewayModel,
InstanceModel,
JobModel,
MemberModel,
Expand Down Expand Up @@ -87,6 +88,7 @@ def __post_init__(self):
def from_model(
model: Union[
FleetModel,
GatewayModel,
InstanceModel,
JobModel,
ProjectModel,
Expand All @@ -102,6 +104,13 @@ def from_model(
id=model.id,
name=model.name,
)
if isinstance(model, GatewayModel):
return Target(
type=EventTargetType.GATEWAY,
project_id=model.project_id or model.project.id,
id=model.id,
name=model.name,
)
if isinstance(model, InstanceModel):
return Target(
type=EventTargetType.INSTANCE,
Expand Down Expand Up @@ -222,6 +231,7 @@ async def list_events(
target_runs: Optional[list[uuid.UUID]],
target_jobs: Optional[list[uuid.UUID]],
target_volumes: Optional[list[uuid.UUID]],
target_gateways: Optional[list[uuid.UUID]],
within_projects: Optional[list[uuid.UUID]],
within_fleets: Optional[list[uuid.UUID]],
within_runs: Optional[list[uuid.UUID]],
Expand Down Expand Up @@ -298,6 +308,13 @@ async def list_events(
EventTargetModel.entity_id.in_(target_volumes),
)
)
if target_gateways is not None:
target_filters.append(
and_(
EventTargetModel.entity_type == EventTargetType.GATEWAY,
EventTargetModel.entity_id.in_(target_gateways),
)
)
if within_projects is not None:
target_filters.append(EventTargetModel.entity_project_id.in_(within_projects))
if within_fleets is not None:
Expand Down
1 change: 1 addition & 0 deletions src/dstack/_internal/server/services/gateways/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ def gateway_model_to_gateway(gateway_model: GatewayModel) -> Gateway:
configuration = get_gateway_configuration(gateway_model)
configuration.default = gateway_model.project.default_gateway_id == gateway_model.id
return Gateway(
id=gateway_model.id,
name=gateway_model.name,
ip_address=ip_address,
instance_id=instance_id,
Expand Down
2 changes: 2 additions & 0 deletions src/dstack/api/server/_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def list(
*,
# NOTE: New parameters go here. Avoid positional parameters, they can break compatibility.
target_volumes: Optional[list[UUID]] = None,
target_gateways: Optional[list[UUID]] = None,
) -> list[Event]:
if prev_recorded_at is not None:
# Time zones other than UTC are misinterpreted by the server:
Expand All @@ -43,6 +44,7 @@ def list(
target_runs=target_runs,
target_jobs=target_jobs,
target_volumes=target_volumes,
target_gateways=target_gateways,
within_projects=within_projects,
within_fleets=within_fleets,
within_runs=within_runs,
Expand Down
8 changes: 8 additions & 0 deletions src/tests/_internal/server/routers/test_gateways.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
create_user,
get_auth_headers,
)
from dstack._internal.server.testing.matchers import SomeUUID4Str


class TestListAndGetGateways:
Expand Down Expand Up @@ -54,6 +55,7 @@ async def test_list(self, test_db, session: AsyncSession, client: AsyncClient):
assert response.status_code == 200
assert response.json() == [
{
"id": SomeUUID4Str(),
"backend": backend.type.value,
"created_at": response.json()[0]["created_at"],
"default": False,
Expand Down Expand Up @@ -107,6 +109,7 @@ async def test_get(self, test_db, session: AsyncSession, client: AsyncClient):
)
assert response.status_code == 200
assert response.json() == {
"id": SomeUUID4Str(),
"backend": backend.type.value,
"created_at": response.json()["created_at"],
"default": False,
Expand Down Expand Up @@ -189,6 +192,7 @@ async def test_create_gateway(self, test_db, session: AsyncSession, client: Asyn
)
assert response.status_code == 200
assert response.json() == {
"id": SomeUUID4Str(),
"name": "test",
"backend": "aws",
"region": "us",
Expand Down Expand Up @@ -243,6 +247,7 @@ async def test_create_gateway_without_name(
g.assert_called_once()
assert response.status_code == 200
assert response.json() == {
"id": SomeUUID4Str(),
"name": "random-name",
"backend": "aws",
"region": "us",
Expand Down Expand Up @@ -347,6 +352,7 @@ async def test_set_default_gateway(self, test_db, session: AsyncSession, client:
)
assert response.status_code == 200
assert response.json() == {
"id": SomeUUID4Str(),
"backend": backend.type.value,
"created_at": response.json()["created_at"],
"default": True,
Expand Down Expand Up @@ -471,6 +477,7 @@ def get_backend(project, backend_type):
assert response.status_code == 200
assert response.json() == [
{
"id": str(gateway_gcp.id),
"backend": backend_gcp.type.value,
"created_at": response.json()[0]["created_at"],
"default": False,
Expand Down Expand Up @@ -542,6 +549,7 @@ async def test_set_wildcard_domain(self, test_db, session: AsyncSession, client:
)
assert response.status_code == 200
assert response.json() == {
"id": SomeUUID4Str(),
"backend": backend.type.value,
"created_at": response.json()["created_at"],
"status": "submitted",
Expand Down