dstack 0.19.18__py3-none-any.whl → 0.19.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dstack might be problematic. Click here for more details.
- dstack/_internal/cli/services/configurators/fleet.py +99 -1
- dstack/_internal/cli/services/profile.py +1 -1
- dstack/_internal/core/backends/cloudrift/api_client.py +13 -1
- dstack/_internal/core/backends/oci/resources.py +5 -5
- dstack/_internal/core/compatibility/runs.py +12 -1
- dstack/_internal/core/compatibility/volumes.py +2 -0
- dstack/_internal/core/models/common.py +38 -2
- dstack/_internal/core/models/configurations.py +9 -1
- dstack/_internal/core/models/fleets.py +2 -1
- dstack/_internal/core/models/profiles.py +8 -5
- dstack/_internal/core/models/resources.py +15 -8
- dstack/_internal/core/models/runs.py +41 -138
- dstack/_internal/core/models/volumes.py +14 -0
- dstack/_internal/core/services/diff.py +30 -10
- dstack/_internal/core/services/ssh/attach.py +2 -0
- dstack/_internal/server/app.py +17 -9
- dstack/_internal/server/background/__init__.py +5 -3
- dstack/_internal/server/background/tasks/process_gateways.py +46 -28
- dstack/_internal/server/background/tasks/process_idle_volumes.py +139 -0
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +2 -0
- dstack/_internal/server/migrations/versions/35e90e1b0d3e_add_rolling_deployment_fields.py +6 -6
- dstack/_internal/server/migrations/versions/d5863798bf41_add_volumemodel_last_job_processed_at.py +40 -0
- dstack/_internal/server/models.py +1 -0
- dstack/_internal/server/routers/backends.py +23 -16
- dstack/_internal/server/routers/files.py +7 -6
- dstack/_internal/server/routers/fleets.py +47 -36
- dstack/_internal/server/routers/gateways.py +27 -18
- dstack/_internal/server/routers/instances.py +18 -13
- dstack/_internal/server/routers/logs.py +7 -3
- dstack/_internal/server/routers/metrics.py +14 -8
- dstack/_internal/server/routers/projects.py +33 -22
- dstack/_internal/server/routers/repos.py +7 -6
- dstack/_internal/server/routers/runs.py +49 -28
- dstack/_internal/server/routers/secrets.py +20 -15
- dstack/_internal/server/routers/server.py +7 -4
- dstack/_internal/server/routers/users.py +22 -19
- dstack/_internal/server/routers/volumes.py +34 -25
- dstack/_internal/server/schemas/logs.py +3 -11
- dstack/_internal/server/schemas/runs.py +17 -5
- dstack/_internal/server/services/fleets.py +354 -72
- dstack/_internal/server/services/gateways/__init__.py +13 -4
- dstack/_internal/server/services/gateways/client.py +5 -3
- dstack/_internal/server/services/instances.py +8 -0
- dstack/_internal/server/services/jobs/__init__.py +45 -0
- dstack/_internal/server/services/jobs/configurators/base.py +7 -0
- dstack/_internal/server/services/locking.py +3 -1
- dstack/_internal/server/services/logging.py +4 -2
- dstack/_internal/server/services/logs/__init__.py +15 -2
- dstack/_internal/server/services/logs/aws.py +47 -7
- dstack/_internal/server/services/logs/filelog.py +148 -32
- dstack/_internal/server/services/logs/gcp.py +3 -5
- dstack/_internal/server/services/prometheus/custom_metrics.py +20 -0
- dstack/_internal/server/services/proxy/repo.py +4 -1
- dstack/_internal/server/services/runs.py +115 -32
- dstack/_internal/server/services/services/__init__.py +2 -1
- dstack/_internal/server/services/users.py +3 -1
- dstack/_internal/server/services/volumes.py +13 -0
- dstack/_internal/server/settings.py +7 -2
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-d1ac2e8c38ed5f08a114.js → main-39a767528976f8078166.js} +11 -30
- dstack/_internal/server/statics/{main-d1ac2e8c38ed5f08a114.js.map → main-39a767528976f8078166.js.map} +1 -1
- dstack/_internal/server/statics/{main-d58fc0460cb0eae7cb5c.css → main-8f9ee218d3eb45989682.css} +2 -2
- dstack/_internal/server/testing/common.py +41 -5
- dstack/_internal/server/utils/routers.py +31 -8
- dstack/_internal/utils/common.py +10 -21
- dstack/_internal/utils/json_utils.py +54 -0
- dstack/api/_public/runs.py +13 -2
- dstack/api/server/_runs.py +12 -2
- dstack/version.py +1 -1
- {dstack-0.19.18.dist-info → dstack-0.19.20.dist-info}/METADATA +7 -5
- {dstack-0.19.18.dist-info → dstack-0.19.20.dist-info}/RECORD +74 -71
- {dstack-0.19.18.dist-info → dstack-0.19.20.dist-info}/WHEEL +0 -0
- {dstack-0.19.18.dist-info → dstack-0.19.20.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.18.dist-info → dstack-0.19.20.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -27,7 +27,10 @@ from dstack._internal.server.services.config import (
|
|
|
27
27
|
get_backend_config_yaml,
|
|
28
28
|
update_backend_config_yaml,
|
|
29
29
|
)
|
|
30
|
-
from dstack._internal.server.utils.routers import
|
|
30
|
+
from dstack._internal.server.utils.routers import (
|
|
31
|
+
CustomORJSONResponse,
|
|
32
|
+
get_base_api_additional_responses,
|
|
33
|
+
)
|
|
31
34
|
|
|
32
35
|
root_router = APIRouter(
|
|
33
36
|
prefix="/api/backends",
|
|
@@ -41,35 +44,37 @@ project_router = APIRouter(
|
|
|
41
44
|
)
|
|
42
45
|
|
|
43
46
|
|
|
44
|
-
@root_router.post("/list_types")
|
|
45
|
-
async def list_backend_types()
|
|
46
|
-
return
|
|
47
|
+
@root_router.post("/list_types", response_model=List[BackendType])
|
|
48
|
+
async def list_backend_types():
|
|
49
|
+
return CustomORJSONResponse(
|
|
50
|
+
dstack._internal.core.backends.configurators.list_available_backend_types()
|
|
51
|
+
)
|
|
47
52
|
|
|
48
53
|
|
|
49
|
-
@project_router.post("/create")
|
|
54
|
+
@project_router.post("/create", response_model=AnyBackendConfigWithCreds)
|
|
50
55
|
async def create_backend(
|
|
51
56
|
body: AnyBackendConfigWithCreds,
|
|
52
57
|
session: AsyncSession = Depends(get_session),
|
|
53
58
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
54
|
-
)
|
|
59
|
+
):
|
|
55
60
|
_, project = user_project
|
|
56
61
|
config = await backends.create_backend(session=session, project=project, config=body)
|
|
57
62
|
if settings.SERVER_CONFIG_ENABLED:
|
|
58
63
|
await ServerConfigManager().sync_config(session=session)
|
|
59
|
-
return config
|
|
64
|
+
return CustomORJSONResponse(config)
|
|
60
65
|
|
|
61
66
|
|
|
62
|
-
@project_router.post("/update")
|
|
67
|
+
@project_router.post("/update", response_model=AnyBackendConfigWithCreds)
|
|
63
68
|
async def update_backend(
|
|
64
69
|
body: AnyBackendConfigWithCreds,
|
|
65
70
|
session: AsyncSession = Depends(get_session),
|
|
66
71
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
67
|
-
)
|
|
72
|
+
):
|
|
68
73
|
_, project = user_project
|
|
69
74
|
config = await backends.update_backend(session=session, project=project, config=body)
|
|
70
75
|
if settings.SERVER_CONFIG_ENABLED:
|
|
71
76
|
await ServerConfigManager().sync_config(session=session)
|
|
72
|
-
return config
|
|
77
|
+
return CustomORJSONResponse(config)
|
|
73
78
|
|
|
74
79
|
|
|
75
80
|
@project_router.post("/delete")
|
|
@@ -86,16 +91,16 @@ async def delete_backends(
|
|
|
86
91
|
await ServerConfigManager().sync_config(session=session)
|
|
87
92
|
|
|
88
93
|
|
|
89
|
-
@project_router.post("/{backend_name}/config_info")
|
|
94
|
+
@project_router.post("/{backend_name}/config_info", response_model=AnyBackendConfigWithCreds)
|
|
90
95
|
async def get_backend_config_info(
|
|
91
96
|
backend_name: BackendType,
|
|
92
97
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
93
|
-
)
|
|
98
|
+
):
|
|
94
99
|
_, project = user_project
|
|
95
100
|
config = await backends.get_backend_config(project=project, backend_type=backend_name)
|
|
96
101
|
if config is None:
|
|
97
102
|
raise ResourceNotExistsError()
|
|
98
|
-
return config
|
|
103
|
+
return CustomORJSONResponse(config)
|
|
99
104
|
|
|
100
105
|
|
|
101
106
|
@project_router.post("/create_yaml")
|
|
@@ -126,10 +131,12 @@ async def update_backend_yaml(
|
|
|
126
131
|
)
|
|
127
132
|
|
|
128
133
|
|
|
129
|
-
@project_router.post("/{backend_name}/get_yaml")
|
|
134
|
+
@project_router.post("/{backend_name}/get_yaml", response_model=BackendInfoYAML)
|
|
130
135
|
async def get_backend_yaml(
|
|
131
136
|
backend_name: BackendType,
|
|
132
137
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
133
|
-
)
|
|
138
|
+
):
|
|
134
139
|
_, project = user_project
|
|
135
|
-
return
|
|
140
|
+
return CustomORJSONResponse(
|
|
141
|
+
await get_backend_config_yaml(project=project, backend_type=backend_name)
|
|
142
|
+
)
|
|
@@ -12,6 +12,7 @@ from dstack._internal.server.security.permissions import Authenticated
|
|
|
12
12
|
from dstack._internal.server.services import files
|
|
13
13
|
from dstack._internal.server.settings import SERVER_CODE_UPLOAD_LIMIT
|
|
14
14
|
from dstack._internal.server.utils.routers import (
|
|
15
|
+
CustomORJSONResponse,
|
|
15
16
|
get_base_api_additional_responses,
|
|
16
17
|
get_request_size,
|
|
17
18
|
)
|
|
@@ -24,12 +25,12 @@ router = APIRouter(
|
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
@router.post("/get_archive_by_hash")
|
|
28
|
+
@router.post("/get_archive_by_hash", response_model=FileArchive)
|
|
28
29
|
async def get_archive_by_hash(
|
|
29
30
|
body: GetFileArchiveByHashRequest,
|
|
30
31
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
31
32
|
user: Annotated[UserModel, Depends(Authenticated())],
|
|
32
|
-
)
|
|
33
|
+
):
|
|
33
34
|
archive = await files.get_archive_by_hash(
|
|
34
35
|
session=session,
|
|
35
36
|
user=user,
|
|
@@ -37,16 +38,16 @@ async def get_archive_by_hash(
|
|
|
37
38
|
)
|
|
38
39
|
if archive is None:
|
|
39
40
|
raise ResourceNotExistsError()
|
|
40
|
-
return archive
|
|
41
|
+
return CustomORJSONResponse(archive)
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
@router.post("/upload_archive")
|
|
44
|
+
@router.post("/upload_archive", response_model=FileArchive)
|
|
44
45
|
async def upload_archive(
|
|
45
46
|
request: Request,
|
|
46
47
|
file: UploadFile,
|
|
47
48
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
48
49
|
user: Annotated[UserModel, Depends(Authenticated())],
|
|
49
|
-
)
|
|
50
|
+
):
|
|
50
51
|
request_size = get_request_size(request)
|
|
51
52
|
if SERVER_CODE_UPLOAD_LIMIT > 0 and request_size > SERVER_CODE_UPLOAD_LIMIT:
|
|
52
53
|
diff_size_fmt = sizeof_fmt(request_size)
|
|
@@ -64,4 +65,4 @@ async def upload_archive(
|
|
|
64
65
|
user=user,
|
|
65
66
|
file=file,
|
|
66
67
|
)
|
|
67
|
-
return archive
|
|
68
|
+
return CustomORJSONResponse(archive)
|
|
@@ -18,7 +18,10 @@ from dstack._internal.server.schemas.fleets import (
|
|
|
18
18
|
ListFleetsRequest,
|
|
19
19
|
)
|
|
20
20
|
from dstack._internal.server.security.permissions import Authenticated, ProjectMember
|
|
21
|
-
from dstack._internal.server.utils.routers import
|
|
21
|
+
from dstack._internal.server.utils.routers import (
|
|
22
|
+
CustomORJSONResponse,
|
|
23
|
+
get_base_api_additional_responses,
|
|
24
|
+
)
|
|
22
25
|
|
|
23
26
|
root_router = APIRouter(
|
|
24
27
|
prefix="/api/fleets",
|
|
@@ -32,12 +35,12 @@ project_router = APIRouter(
|
|
|
32
35
|
)
|
|
33
36
|
|
|
34
37
|
|
|
35
|
-
@root_router.post("/list")
|
|
38
|
+
@root_router.post("/list", response_model=List[Fleet])
|
|
36
39
|
async def list_fleets(
|
|
37
40
|
body: ListFleetsRequest,
|
|
38
41
|
session: AsyncSession = Depends(get_session),
|
|
39
42
|
user: UserModel = Depends(Authenticated()),
|
|
40
|
-
)
|
|
43
|
+
):
|
|
41
44
|
"""
|
|
42
45
|
Returns all fleets and instances within them visible to user sorted by descending `created_at`.
|
|
43
46
|
`project_name` and `only_active` can be specified as filters.
|
|
@@ -45,36 +48,40 @@ async def list_fleets(
|
|
|
45
48
|
The results are paginated. To get the next page, pass `created_at` and `id` of
|
|
46
49
|
the last fleet from the previous page as `prev_created_at` and `prev_id`.
|
|
47
50
|
"""
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
return CustomORJSONResponse(
|
|
52
|
+
await fleets_services.list_fleets(
|
|
53
|
+
session=session,
|
|
54
|
+
user=user,
|
|
55
|
+
project_name=body.project_name,
|
|
56
|
+
only_active=body.only_active,
|
|
57
|
+
prev_created_at=body.prev_created_at,
|
|
58
|
+
prev_id=body.prev_id,
|
|
59
|
+
limit=body.limit,
|
|
60
|
+
ascending=body.ascending,
|
|
61
|
+
)
|
|
57
62
|
)
|
|
58
63
|
|
|
59
64
|
|
|
60
|
-
@project_router.post("/list")
|
|
65
|
+
@project_router.post("/list", response_model=List[Fleet])
|
|
61
66
|
async def list_project_fleets(
|
|
62
67
|
session: AsyncSession = Depends(get_session),
|
|
63
68
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
64
|
-
)
|
|
69
|
+
):
|
|
65
70
|
"""
|
|
66
71
|
Returns all fleets in the project.
|
|
67
72
|
"""
|
|
68
73
|
_, project = user_project
|
|
69
|
-
return
|
|
74
|
+
return CustomORJSONResponse(
|
|
75
|
+
await fleets_services.list_project_fleets(session=session, project=project)
|
|
76
|
+
)
|
|
70
77
|
|
|
71
78
|
|
|
72
|
-
@project_router.post("/get")
|
|
79
|
+
@project_router.post("/get", response_model=Fleet)
|
|
73
80
|
async def get_fleet(
|
|
74
81
|
body: GetFleetRequest,
|
|
75
82
|
session: AsyncSession = Depends(get_session),
|
|
76
83
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
77
|
-
)
|
|
84
|
+
):
|
|
78
85
|
"""
|
|
79
86
|
Returns a fleet given `name` or `id`.
|
|
80
87
|
If given `name`, does not return deleted fleets.
|
|
@@ -86,15 +93,15 @@ async def get_fleet(
|
|
|
86
93
|
)
|
|
87
94
|
if fleet is None:
|
|
88
95
|
raise ResourceNotExistsError()
|
|
89
|
-
return fleet
|
|
96
|
+
return CustomORJSONResponse(fleet)
|
|
90
97
|
|
|
91
98
|
|
|
92
|
-
@project_router.post("/get_plan")
|
|
99
|
+
@project_router.post("/get_plan", response_model=FleetPlan)
|
|
93
100
|
async def get_plan(
|
|
94
101
|
body: GetFleetPlanRequest,
|
|
95
102
|
session: AsyncSession = Depends(get_session),
|
|
96
103
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
97
|
-
)
|
|
104
|
+
):
|
|
98
105
|
"""
|
|
99
106
|
Returns a fleet plan for the given fleet configuration.
|
|
100
107
|
"""
|
|
@@ -105,45 +112,49 @@ async def get_plan(
|
|
|
105
112
|
user=user,
|
|
106
113
|
spec=body.spec,
|
|
107
114
|
)
|
|
108
|
-
return plan
|
|
115
|
+
return CustomORJSONResponse(plan)
|
|
109
116
|
|
|
110
117
|
|
|
111
|
-
@project_router.post("/apply")
|
|
118
|
+
@project_router.post("/apply", response_model=Fleet)
|
|
112
119
|
async def apply_plan(
|
|
113
120
|
body: ApplyFleetPlanRequest,
|
|
114
121
|
session: AsyncSession = Depends(get_session),
|
|
115
122
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
116
|
-
)
|
|
123
|
+
):
|
|
117
124
|
"""
|
|
118
125
|
Creates a new fleet or updates an existing fleet.
|
|
119
126
|
Errors if the expected current resource from the plan does not match the current resource.
|
|
120
127
|
Use `force: true` to apply even if the current resource does not match.
|
|
121
128
|
"""
|
|
122
129
|
user, project = user_project
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
return CustomORJSONResponse(
|
|
131
|
+
await fleets_services.apply_plan(
|
|
132
|
+
session=session,
|
|
133
|
+
user=user,
|
|
134
|
+
project=project,
|
|
135
|
+
plan=body.plan,
|
|
136
|
+
force=body.force,
|
|
137
|
+
)
|
|
129
138
|
)
|
|
130
139
|
|
|
131
140
|
|
|
132
|
-
@project_router.post("/create")
|
|
141
|
+
@project_router.post("/create", response_model=Fleet)
|
|
133
142
|
async def create_fleet(
|
|
134
143
|
body: CreateFleetRequest,
|
|
135
144
|
session: AsyncSession = Depends(get_session),
|
|
136
145
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
137
|
-
)
|
|
146
|
+
):
|
|
138
147
|
"""
|
|
139
148
|
Creates a fleet given a fleet configuration.
|
|
140
149
|
"""
|
|
141
150
|
user, project = user_project
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
return CustomORJSONResponse(
|
|
152
|
+
await fleets_services.create_fleet(
|
|
153
|
+
session=session,
|
|
154
|
+
project=project,
|
|
155
|
+
user=user,
|
|
156
|
+
spec=body.spec,
|
|
157
|
+
)
|
|
147
158
|
)
|
|
148
159
|
|
|
149
160
|
|
|
@@ -13,7 +13,10 @@ from dstack._internal.server.security.permissions import (
|
|
|
13
13
|
ProjectAdmin,
|
|
14
14
|
ProjectMemberOrPublicAccess,
|
|
15
15
|
)
|
|
16
|
-
from dstack._internal.server.utils.routers import
|
|
16
|
+
from dstack._internal.server.utils.routers import (
|
|
17
|
+
CustomORJSONResponse,
|
|
18
|
+
get_base_api_additional_responses,
|
|
19
|
+
)
|
|
17
20
|
|
|
18
21
|
router = APIRouter(
|
|
19
22
|
prefix="/api/project/{project_name}/gateways",
|
|
@@ -22,40 +25,44 @@ router = APIRouter(
|
|
|
22
25
|
)
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
@router.post("/list")
|
|
28
|
+
@router.post("/list", response_model=List[models.Gateway])
|
|
26
29
|
async def list_gateways(
|
|
27
30
|
session: AsyncSession = Depends(get_session),
|
|
28
31
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMemberOrPublicAccess()),
|
|
29
|
-
)
|
|
32
|
+
):
|
|
30
33
|
_, project = user_project
|
|
31
|
-
return
|
|
34
|
+
return CustomORJSONResponse(
|
|
35
|
+
await gateways.list_project_gateways(session=session, project=project)
|
|
36
|
+
)
|
|
32
37
|
|
|
33
38
|
|
|
34
|
-
@router.post("/get")
|
|
39
|
+
@router.post("/get", response_model=models.Gateway)
|
|
35
40
|
async def get_gateway(
|
|
36
41
|
body: schemas.GetGatewayRequest,
|
|
37
42
|
session: AsyncSession = Depends(get_session),
|
|
38
43
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMemberOrPublicAccess()),
|
|
39
|
-
)
|
|
44
|
+
):
|
|
40
45
|
_, project = user_project
|
|
41
46
|
gateway = await gateways.get_gateway_by_name(session=session, project=project, name=body.name)
|
|
42
47
|
if gateway is None:
|
|
43
48
|
raise ResourceNotExistsError()
|
|
44
|
-
return gateway
|
|
49
|
+
return CustomORJSONResponse(gateway)
|
|
45
50
|
|
|
46
51
|
|
|
47
|
-
@router.post("/create")
|
|
52
|
+
@router.post("/create", response_model=models.Gateway)
|
|
48
53
|
async def create_gateway(
|
|
49
54
|
body: schemas.CreateGatewayRequest,
|
|
50
55
|
session: AsyncSession = Depends(get_session),
|
|
51
56
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
52
|
-
)
|
|
57
|
+
):
|
|
53
58
|
user, project = user_project
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
return CustomORJSONResponse(
|
|
60
|
+
await gateways.create_gateway(
|
|
61
|
+
session=session,
|
|
62
|
+
user=user,
|
|
63
|
+
project=project,
|
|
64
|
+
configuration=body.configuration,
|
|
65
|
+
)
|
|
59
66
|
)
|
|
60
67
|
|
|
61
68
|
|
|
@@ -83,13 +90,15 @@ async def set_default_gateway(
|
|
|
83
90
|
await gateways.set_default_gateway(session=session, project=project, name=body.name)
|
|
84
91
|
|
|
85
92
|
|
|
86
|
-
@router.post("/set_wildcard_domain")
|
|
93
|
+
@router.post("/set_wildcard_domain", response_model=models.Gateway)
|
|
87
94
|
async def set_gateway_wildcard_domain(
|
|
88
95
|
body: schemas.SetWildcardDomainRequest,
|
|
89
96
|
session: AsyncSession = Depends(get_session),
|
|
90
97
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
91
|
-
)
|
|
98
|
+
):
|
|
92
99
|
_, project = user_project
|
|
93
|
-
return
|
|
94
|
-
|
|
100
|
+
return CustomORJSONResponse(
|
|
101
|
+
await gateways.set_gateway_wildcard_domain(
|
|
102
|
+
session=session, project=project, name=body.name, wildcard_domain=body.wildcard_domain
|
|
103
|
+
)
|
|
95
104
|
)
|
|
@@ -9,7 +9,10 @@ from dstack._internal.server.db import get_session
|
|
|
9
9
|
from dstack._internal.server.models import UserModel
|
|
10
10
|
from dstack._internal.server.schemas.instances import ListInstancesRequest
|
|
11
11
|
from dstack._internal.server.security.permissions import Authenticated
|
|
12
|
-
from dstack._internal.server.utils.routers import
|
|
12
|
+
from dstack._internal.server.utils.routers import (
|
|
13
|
+
CustomORJSONResponse,
|
|
14
|
+
get_base_api_additional_responses,
|
|
15
|
+
)
|
|
13
16
|
|
|
14
17
|
root_router = APIRouter(
|
|
15
18
|
prefix="/api/instances",
|
|
@@ -18,12 +21,12 @@ root_router = APIRouter(
|
|
|
18
21
|
)
|
|
19
22
|
|
|
20
23
|
|
|
21
|
-
@root_router.post("/list")
|
|
24
|
+
@root_router.post("/list", response_model=List[Instance])
|
|
22
25
|
async def list_instances(
|
|
23
26
|
body: ListInstancesRequest,
|
|
24
27
|
session: AsyncSession = Depends(get_session),
|
|
25
28
|
user: UserModel = Depends(Authenticated()),
|
|
26
|
-
)
|
|
29
|
+
):
|
|
27
30
|
"""
|
|
28
31
|
Returns all instances visible to user sorted by descending `created_at`.
|
|
29
32
|
`project_names` and `fleet_ids` can be specified as filters.
|
|
@@ -31,14 +34,16 @@ async def list_instances(
|
|
|
31
34
|
The results are paginated. To get the next page, pass `created_at` and `id` of
|
|
32
35
|
the last instance from the previous page as `prev_created_at` and `prev_id`.
|
|
33
36
|
"""
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
return CustomORJSONResponse(
|
|
38
|
+
await instances.list_user_instances(
|
|
39
|
+
session=session,
|
|
40
|
+
user=user,
|
|
41
|
+
project_names=body.project_names,
|
|
42
|
+
fleet_ids=body.fleet_ids,
|
|
43
|
+
only_active=body.only_active,
|
|
44
|
+
prev_created_at=body.prev_created_at,
|
|
45
|
+
prev_id=body.prev_id,
|
|
46
|
+
limit=body.limit,
|
|
47
|
+
ascending=body.ascending,
|
|
48
|
+
)
|
|
44
49
|
)
|
|
@@ -7,7 +7,10 @@ from dstack._internal.server.models import ProjectModel, UserModel
|
|
|
7
7
|
from dstack._internal.server.schemas.logs import PollLogsRequest
|
|
8
8
|
from dstack._internal.server.security.permissions import ProjectMember
|
|
9
9
|
from dstack._internal.server.services import logs
|
|
10
|
-
from dstack._internal.server.utils.routers import
|
|
10
|
+
from dstack._internal.server.utils.routers import (
|
|
11
|
+
CustomORJSONResponse,
|
|
12
|
+
get_base_api_additional_responses,
|
|
13
|
+
)
|
|
11
14
|
|
|
12
15
|
router = APIRouter(
|
|
13
16
|
prefix="/api/project/{project_name}/logs",
|
|
@@ -18,13 +21,14 @@ router = APIRouter(
|
|
|
18
21
|
|
|
19
22
|
@router.post(
|
|
20
23
|
"/poll",
|
|
24
|
+
response_model=JobSubmissionLogs,
|
|
21
25
|
)
|
|
22
26
|
async def poll_logs(
|
|
23
27
|
body: PollLogsRequest,
|
|
24
28
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
25
|
-
)
|
|
29
|
+
):
|
|
26
30
|
_, project = user_project
|
|
27
31
|
# The runner guarantees logs have different timestamps if throughput < 1k logs / sec.
|
|
28
32
|
# Otherwise, some logs with duplicated timestamps may be filtered out.
|
|
29
33
|
# This limitation is imposed by cloud log services that support up to millisecond timestamp resolution.
|
|
30
|
-
return await logs.poll_logs_async(project=project, request=body)
|
|
34
|
+
return CustomORJSONResponse(await logs.poll_logs_async(project=project, request=body))
|
|
@@ -11,7 +11,10 @@ from dstack._internal.server.models import ProjectModel, UserModel
|
|
|
11
11
|
from dstack._internal.server.security.permissions import ProjectMember
|
|
12
12
|
from dstack._internal.server.services import metrics
|
|
13
13
|
from dstack._internal.server.services.jobs import get_run_job_model
|
|
14
|
-
from dstack._internal.server.utils.routers import
|
|
14
|
+
from dstack._internal.server.utils.routers import (
|
|
15
|
+
CustomORJSONResponse,
|
|
16
|
+
get_base_api_additional_responses,
|
|
17
|
+
)
|
|
15
18
|
|
|
16
19
|
router = APIRouter(
|
|
17
20
|
prefix="/api/project/{project_name}/metrics",
|
|
@@ -22,6 +25,7 @@ router = APIRouter(
|
|
|
22
25
|
|
|
23
26
|
@router.get(
|
|
24
27
|
"/job/{run_name}",
|
|
28
|
+
response_model=JobMetrics,
|
|
25
29
|
)
|
|
26
30
|
async def get_job_metrics(
|
|
27
31
|
run_name: str,
|
|
@@ -32,7 +36,7 @@ async def get_job_metrics(
|
|
|
32
36
|
before: Optional[datetime] = None,
|
|
33
37
|
session: AsyncSession = Depends(get_session),
|
|
34
38
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectMember()),
|
|
35
|
-
)
|
|
39
|
+
):
|
|
36
40
|
"""
|
|
37
41
|
Returns job-level metrics such as hardware utilization
|
|
38
42
|
given `run_name`, `replica_num`, and `job_num`.
|
|
@@ -63,10 +67,12 @@ async def get_job_metrics(
|
|
|
63
67
|
if job_model is None:
|
|
64
68
|
raise ResourceNotExistsError("Found no job with given parameters")
|
|
65
69
|
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
return CustomORJSONResponse(
|
|
71
|
+
await metrics.get_job_metrics(
|
|
72
|
+
session=session,
|
|
73
|
+
job_model=job_model,
|
|
74
|
+
limit=limit,
|
|
75
|
+
after=after,
|
|
76
|
+
before=before,
|
|
77
|
+
)
|
|
72
78
|
)
|