anyscale 0.26.28__py3-none-any.whl → 0.26.30__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.
- anyscale/__init__.py +10 -0
- anyscale/_private/anyscale_client/anyscale_client.py +69 -0
- anyscale/_private/anyscale_client/common.py +38 -0
- anyscale/_private/anyscale_client/fake_anyscale_client.py +11 -0
- anyscale/_private/docgen/__main__.py +4 -18
- anyscale/_private/docgen/api.md +0 -125
- anyscale/_private/docgen/models.md +0 -111
- anyscale/client/README.md +0 -6
- anyscale/client/openapi_client/__init__.py +0 -4
- anyscale/client/openapi_client/api/default_api.py +0 -228
- anyscale/client/openapi_client/models/__init__.py +0 -4
- anyscale/client/openapi_client/models/workload_info.py +59 -3
- anyscale/commands/command_examples.py +10 -0
- anyscale/commands/job_queue_commands.py +295 -104
- anyscale/commands/list_util.py +14 -1
- anyscale/commands/machine_pool_commands.py +25 -11
- anyscale/commands/service_commands.py +10 -14
- anyscale/commands/workspace_commands_v2.py +462 -25
- anyscale/controllers/job_controller.py +5 -210
- anyscale/job_queue/__init__.py +89 -0
- anyscale/job_queue/_private/job_queue_sdk.py +158 -0
- anyscale/job_queue/commands.py +130 -0
- anyscale/job_queue/models.py +284 -0
- anyscale/scripts.py +1 -1
- anyscale/sdk/anyscale_client/__init__.py +0 -11
- anyscale/sdk/anyscale_client/api/default_api.py +140 -1433
- anyscale/sdk/anyscale_client/models/__init__.py +0 -11
- anyscale/service/__init__.py +4 -1
- anyscale/service/_private/service_sdk.py +5 -0
- anyscale/service/commands.py +4 -2
- anyscale/utils/ssh_websocket_proxy.py +178 -0
- anyscale/version.py +1 -1
- {anyscale-0.26.28.dist-info → anyscale-0.26.30.dist-info}/METADATA +3 -1
- {anyscale-0.26.28.dist-info → anyscale-0.26.30.dist-info}/RECORD +39 -49
- anyscale/client/openapi_client/models/serve_deployment_fast_api_docs_status.py +0 -123
- anyscale/client/openapi_client/models/servedeploymentfastapidocsstatus_response.py +0 -121
- anyscale/client/openapi_client/models/web_terminal.py +0 -121
- anyscale/client/openapi_client/models/webterminal_response.py +0 -121
- anyscale/sdk/anyscale_client/models/cluster_environment_build_log_response.py +0 -123
- anyscale/sdk/anyscale_client/models/clusterenvironmentbuildlogresponse_response.py +0 -121
- anyscale/sdk/anyscale_client/models/create_cloud.py +0 -518
- anyscale/sdk/anyscale_client/models/object_storage_config.py +0 -122
- anyscale/sdk/anyscale_client/models/object_storage_config_s3.py +0 -256
- anyscale/sdk/anyscale_client/models/objectstorageconfig_response.py +0 -121
- anyscale/sdk/anyscale_client/models/session_operation.py +0 -266
- anyscale/sdk/anyscale_client/models/session_operation_type.py +0 -101
- anyscale/sdk/anyscale_client/models/sessionoperation_response.py +0 -121
- anyscale/sdk/anyscale_client/models/update_cloud.py +0 -150
- anyscale/sdk/anyscale_client/models/update_project.py +0 -150
- {anyscale-0.26.28.dist-info → anyscale-0.26.30.dist-info}/LICENSE +0 -0
- {anyscale-0.26.28.dist-info → anyscale-0.26.30.dist-info}/NOTICE +0 -0
- {anyscale-0.26.28.dist-info → anyscale-0.26.30.dist-info}/WHEEL +0 -0
- {anyscale-0.26.28.dist-info → anyscale-0.26.30.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.28.dist-info → anyscale-0.26.30.dist-info}/top_level.txt +0 -0
@@ -1,172 +1,363 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from datetime import datetime
|
4
|
+
from enum import Enum
|
5
|
+
from functools import partial
|
6
|
+
from json import dumps as json_dumps
|
7
|
+
import sys
|
8
|
+
from typing import Dict, get_type_hints, List, Optional
|
2
9
|
|
3
10
|
import click
|
11
|
+
from rich.console import Console
|
12
|
+
from rich.table import Table
|
4
13
|
|
5
14
|
from anyscale.client.openapi_client.models.job_queue_sort_directive import (
|
6
15
|
JobQueueSortDirective,
|
7
16
|
)
|
8
17
|
from anyscale.client.openapi_client.models.job_queue_sort_field import JobQueueSortField
|
18
|
+
from anyscale.client.openapi_client.models.session_state import SessionState
|
9
19
|
from anyscale.client.openapi_client.models.sort_order import SortOrder
|
10
20
|
from anyscale.commands import command_examples
|
21
|
+
from anyscale.commands.list_util import (
|
22
|
+
display_list,
|
23
|
+
MAX_PAGE_SIZE,
|
24
|
+
NON_INTERACTIVE_DEFAULT_MAX_ITEMS,
|
25
|
+
validate_page_size,
|
26
|
+
)
|
11
27
|
from anyscale.commands.util import AnyscaleCommand
|
12
|
-
|
13
|
-
from anyscale.
|
28
|
+
import anyscale.job_queue
|
29
|
+
from anyscale.job_queue.models import JobQueueStatus, JobQueueStatusKeys
|
30
|
+
from anyscale.util import get_endpoint, get_user_info, validate_non_negative_arg
|
14
31
|
|
15
32
|
|
16
|
-
@click.group(
|
17
|
-
"job-queues", help="Interact with production job queues running on Anyscale."
|
18
|
-
)
|
33
|
+
@click.group("job-queue", help="Manage Anyscale Job Queues.")
|
19
34
|
def job_queue_cli() -> None:
|
20
35
|
pass
|
21
36
|
|
22
37
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
for field_str in sort_fields:
|
29
|
-
descending = field_str.startswith("-")
|
30
|
-
raw_field = field_str.lstrip("-").upper()
|
31
|
-
|
32
|
-
if raw_field not in JobQueueSortField.allowable_values:
|
33
|
-
raise click.UsageError(
|
34
|
-
f"{param} must be one of {', '.join([v.lower() for v in JobQueueSortField.allowable_values])}"
|
35
|
-
)
|
38
|
+
class ViewOption(Enum):
|
39
|
+
DEFAULT = "default"
|
40
|
+
STATS = "stats"
|
41
|
+
ALL = "all"
|
36
42
|
|
37
|
-
sort_directives.append(
|
38
|
-
JobQueueSortDirective(
|
39
|
-
sort_field=raw_field,
|
40
|
-
sort_order=SortOrder.DESC if descending else SortOrder.ASC,
|
41
|
-
)
|
42
|
-
)
|
43
43
|
|
44
|
-
|
44
|
+
VIEW_COLUMNS: Dict[ViewOption, List[JobQueueStatusKeys]] = {
|
45
|
+
ViewOption.DEFAULT: [
|
46
|
+
"name",
|
47
|
+
"id",
|
48
|
+
"state",
|
49
|
+
"creator_email",
|
50
|
+
"project_id",
|
51
|
+
"created_at",
|
52
|
+
],
|
53
|
+
ViewOption.STATS: [
|
54
|
+
"id",
|
55
|
+
"name",
|
56
|
+
"total_jobs",
|
57
|
+
"active_jobs",
|
58
|
+
"successful_jobs",
|
59
|
+
"failed_jobs",
|
60
|
+
],
|
61
|
+
ViewOption.ALL: [
|
62
|
+
"name",
|
63
|
+
"id",
|
64
|
+
"state",
|
65
|
+
"creator_email",
|
66
|
+
"project_id",
|
67
|
+
"created_at",
|
68
|
+
"max_concurrency",
|
69
|
+
"idle_timeout_s",
|
70
|
+
"cloud_id",
|
71
|
+
"user_provided_id",
|
72
|
+
"execution_mode",
|
73
|
+
"total_jobs",
|
74
|
+
"active_jobs",
|
75
|
+
"successful_jobs",
|
76
|
+
"failed_jobs",
|
77
|
+
],
|
78
|
+
}
|
45
79
|
|
46
80
|
|
47
81
|
@job_queue_cli.command(
|
48
82
|
name="list",
|
49
|
-
|
83
|
+
help="List job queues.",
|
50
84
|
cls=AnyscaleCommand,
|
51
85
|
example=command_examples.JOB_QUEUE_LIST,
|
52
86
|
)
|
87
|
+
@click.option("--id", "job_queue_id", help="ID of a job queue.")
|
88
|
+
@click.option("--name", type=str, help="Filter by name.")
|
89
|
+
@click.option("--cloud", type=str, help="Filter by cloud.")
|
90
|
+
@click.option("--project", type=str, help="Filter by project.")
|
91
|
+
@click.option("--include-all-users/--only-mine", default=False)
|
53
92
|
@click.option(
|
54
|
-
"--
|
55
|
-
|
56
|
-
|
57
|
-
help="Include job queues not created by current user.",
|
93
|
+
"--cluster-status",
|
94
|
+
type=click.Choice(SessionState.allowable_values, case_sensitive=False),
|
95
|
+
help="Filter by cluster status.",
|
58
96
|
)
|
59
97
|
@click.option(
|
60
98
|
"--view",
|
61
|
-
type=click.Choice([
|
62
|
-
default=
|
63
|
-
help="
|
64
|
-
callback=lambda
|
99
|
+
type=click.Choice([opt.value for opt in ViewOption], case_sensitive=False),
|
100
|
+
default=ViewOption.DEFAULT.value,
|
101
|
+
help="Columns view.",
|
102
|
+
callback=lambda _ctx, _param, value: ViewOption(value),
|
65
103
|
)
|
66
104
|
@click.option(
|
67
|
-
"--page",
|
68
|
-
default=
|
105
|
+
"--page-size",
|
106
|
+
default=10,
|
69
107
|
type=int,
|
70
|
-
|
71
|
-
|
108
|
+
callback=validate_page_size,
|
109
|
+
help=f"Items per page (max {MAX_PAGE_SIZE}).",
|
72
110
|
)
|
73
111
|
@click.option(
|
74
112
|
"--max-items",
|
75
|
-
required=False,
|
76
113
|
type=int,
|
77
|
-
help="Max items to show in list (only valid in interactive mode).",
|
78
114
|
callback=lambda ctx, param, value: validate_non_negative_arg(ctx, param, value)
|
79
115
|
if value
|
80
116
|
else None,
|
117
|
+
help="Non-interactive max items.",
|
81
118
|
)
|
82
119
|
@click.option(
|
83
120
|
"--sort",
|
84
|
-
"
|
121
|
+
"sort_dirs",
|
85
122
|
multiple=True,
|
86
|
-
default=[
|
87
|
-
|
88
|
-
Sort by column(s). Prefix column with - to sort in descending order.
|
89
|
-
Supported columns: {', '.join([v.lower() for v in JobQueueSortField.allowable_values])}.
|
90
|
-
""",
|
91
|
-
callback=lambda _, __, value: parse_sort_fields("sort", list(value)),
|
123
|
+
default=["-created_at"],
|
124
|
+
callback=lambda _ctx, _param, values: _parse_sort_fields("sort", list(values)),
|
92
125
|
)
|
126
|
+
@click.option("--no-interactive/--interactive", default=False)
|
93
127
|
@click.option(
|
94
|
-
"--
|
95
|
-
default=True,
|
96
|
-
help="--no-interactive disables the default interactive mode.",
|
128
|
+
"--json", "json_output", is_flag=True, default=False, help="JSON output.",
|
97
129
|
)
|
98
|
-
def list_job_queues(
|
130
|
+
def list_job_queues( # noqa: PLR0913
|
131
|
+
job_queue_id: Optional[str],
|
132
|
+
name: Optional[str],
|
133
|
+
cloud: Optional[str],
|
134
|
+
project: Optional[str],
|
135
|
+
cluster_status: Optional[str],
|
99
136
|
include_all_users: bool,
|
100
|
-
view:
|
101
|
-
|
102
|
-
max_items: int,
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
137
|
+
view: ViewOption,
|
138
|
+
page_size: int,
|
139
|
+
max_items: Optional[int],
|
140
|
+
sort_dirs: List[JobQueueSortDirective],
|
141
|
+
no_interactive: bool,
|
142
|
+
json_output: bool,
|
143
|
+
) -> None:
|
144
|
+
"""List and page job queues according to filters and view."""
|
145
|
+
if max_items and not no_interactive:
|
146
|
+
raise click.UsageError("--max-items only in non-interactive mode")
|
147
|
+
|
148
|
+
effective_max = max_items or NON_INTERACTIVE_DEFAULT_MAX_ITEMS
|
149
|
+
console = Console()
|
150
|
+
stderr = Console(stderr=True)
|
151
|
+
|
152
|
+
_print_list_diagnostics(
|
153
|
+
stderr=stderr,
|
154
|
+
job_queue_id=job_queue_id,
|
155
|
+
name=name,
|
112
156
|
include_all_users=include_all_users,
|
157
|
+
cloud=cloud,
|
158
|
+
project=project,
|
159
|
+
cluster_status=cluster_status,
|
113
160
|
view=view,
|
114
|
-
|
115
|
-
|
161
|
+
sort_dirs=sort_dirs,
|
162
|
+
no_interactive=no_interactive,
|
163
|
+
page_size=page_size,
|
164
|
+
effective_max=effective_max,
|
116
165
|
)
|
117
166
|
|
167
|
+
try:
|
168
|
+
user = get_user_info()
|
169
|
+
iterator = anyscale.job_queue.list(
|
170
|
+
job_queue_id=job_queue_id,
|
171
|
+
name=name,
|
172
|
+
creator_id=None if include_all_users else (user.id if user else None),
|
173
|
+
cloud=cloud,
|
174
|
+
project=project,
|
175
|
+
page_size=page_size,
|
176
|
+
max_items=None if not no_interactive else effective_max,
|
177
|
+
sorting_directives=sort_dirs,
|
178
|
+
)
|
179
|
+
cols = VIEW_COLUMNS[view]
|
180
|
+
table_fn = partial(_create_table, view)
|
181
|
+
|
182
|
+
def row_fn(jq: JobQueueStatus) -> Dict[str, str]:
|
183
|
+
data = _format_data(jq)
|
184
|
+
return {c: data[c] for c in cols}
|
185
|
+
|
186
|
+
total = display_list(
|
187
|
+
iterator=iter(iterator),
|
188
|
+
item_formatter=row_fn,
|
189
|
+
table_creator=table_fn,
|
190
|
+
json_output=json_output,
|
191
|
+
page_size=page_size,
|
192
|
+
interactive=not no_interactive,
|
193
|
+
max_items=effective_max,
|
194
|
+
console=console,
|
195
|
+
)
|
196
|
+
if not json_output:
|
197
|
+
stderr.print(f"Fetched {total} queues" if total else "No queues found.")
|
198
|
+
|
199
|
+
except Exception as e: # noqa: BLE001
|
200
|
+
stderr.print(f"Error: {e}", style="red")
|
201
|
+
sys.exit(1)
|
202
|
+
|
118
203
|
|
119
204
|
@job_queue_cli.command(
|
120
205
|
name="update",
|
121
|
-
|
206
|
+
help="Update job queue settings.",
|
122
207
|
cls=AnyscaleCommand,
|
123
208
|
example=command_examples.JOB_QUEUE_UPDATE,
|
124
209
|
)
|
210
|
+
@click.option("--id", "job_queue_id", required=True)
|
211
|
+
@click.option("--max-concurrency", type=int)
|
212
|
+
@click.option("--idle-timeout-s", type=int)
|
125
213
|
@click.option(
|
126
|
-
"--
|
127
|
-
)
|
128
|
-
@click.option(
|
129
|
-
"--name",
|
130
|
-
"job_queue_name",
|
131
|
-
required=False,
|
132
|
-
default=None,
|
133
|
-
help="Name of the job queue.",
|
134
|
-
)
|
135
|
-
@click.option(
|
136
|
-
"--max-concurrency",
|
137
|
-
required=False,
|
138
|
-
default=None,
|
139
|
-
help="Maximum concurrency of the job queue",
|
140
|
-
)
|
141
|
-
@click.option(
|
142
|
-
"--idle-timeout-s",
|
143
|
-
required=False,
|
144
|
-
default=None,
|
145
|
-
help="Idle timeout of the job queue",
|
214
|
+
"--json", "json_output", is_flag=True, default=False, help="JSON output.",
|
146
215
|
)
|
147
216
|
def update_job_queue(
|
148
|
-
job_queue_id: str,
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
217
|
+
job_queue_id: str,
|
218
|
+
max_concurrency: Optional[int],
|
219
|
+
idle_timeout_s: Optional[int],
|
220
|
+
json_output: bool,
|
221
|
+
) -> None:
|
222
|
+
"""Update the max_concurrency or idle_timeout_s of a job queue."""
|
223
|
+
if max_concurrency is None and idle_timeout_s is None:
|
224
|
+
raise click.ClickException("Specify --max-concurrency or --idle-timeout-s")
|
225
|
+
stderr = Console(stderr=True)
|
226
|
+
stderr.print(f"Updating job queue '{job_queue_id}'...")
|
227
|
+
try:
|
228
|
+
jq = anyscale.job_queue.update(
|
229
|
+
job_queue_id=job_queue_id,
|
230
|
+
job_queue_name=None,
|
231
|
+
max_concurrency=max_concurrency,
|
232
|
+
idle_timeout_s=idle_timeout_s,
|
233
|
+
)
|
234
|
+
if json_output:
|
235
|
+
Console().print_json(json_dumps(_format_data(jq), indent=2))
|
236
|
+
else:
|
237
|
+
_display_single(jq, stderr, ViewOption.ALL)
|
238
|
+
except Exception as e: # noqa: BLE001
|
239
|
+
stderr.print(f"Update failed: {e}", style="red")
|
240
|
+
sys.exit(1)
|
159
241
|
|
160
242
|
|
161
243
|
@job_queue_cli.command(
|
162
|
-
name="
|
163
|
-
|
244
|
+
name="status",
|
245
|
+
help="Show job queue details.",
|
164
246
|
cls=AnyscaleCommand,
|
165
247
|
example=command_examples.JOB_QUEUE_INFO,
|
166
248
|
)
|
249
|
+
@click.option("--id", "job_queue_id", required=True)
|
167
250
|
@click.option(
|
168
|
-
"--
|
251
|
+
"--view",
|
252
|
+
type=click.Choice([opt.value for opt in ViewOption], case_sensitive=False),
|
253
|
+
default=ViewOption.DEFAULT.value,
|
254
|
+
help="Columns view.",
|
255
|
+
callback=lambda _ctx, _param, value: ViewOption(value),
|
169
256
|
)
|
170
|
-
|
171
|
-
|
172
|
-
|
257
|
+
@click.option(
|
258
|
+
"--json", "json_output", is_flag=True, default=False, help="JSON output.",
|
259
|
+
)
|
260
|
+
def status(job_queue_id: str, view: ViewOption, json_output: bool,) -> None:
|
261
|
+
"""Fetch and display a single job queue's details."""
|
262
|
+
stderr = Console(stderr=True)
|
263
|
+
stderr.print(f"Fetching job queue '{job_queue_id}'...")
|
264
|
+
try:
|
265
|
+
jq = anyscale.job_queue.status(job_queue_id=job_queue_id)
|
266
|
+
if json_output:
|
267
|
+
Console().print_json(json_dumps(_format_data(jq), indent=2))
|
268
|
+
else:
|
269
|
+
_display_single(jq, stderr, view)
|
270
|
+
except Exception as e: # noqa: BLE001
|
271
|
+
stderr.print(f"Failed: {e}", style="red")
|
272
|
+
sys.exit(1)
|
273
|
+
|
274
|
+
|
275
|
+
def _parse_sort_fields(
|
276
|
+
param: str, sort_fields: List[str],
|
277
|
+
) -> List[JobQueueSortDirective]:
|
278
|
+
"""Convert a list of string fields into JobQueueSortDirective objects."""
|
279
|
+
directives: List[JobQueueSortDirective] = []
|
280
|
+
opts = ", ".join(v.lower() for v in JobQueueSortField.allowable_values)
|
281
|
+
for field_str in sort_fields:
|
282
|
+
desc = field_str.startswith("-")
|
283
|
+
raw = field_str.lstrip("-").upper()
|
284
|
+
if raw not in JobQueueSortField.allowable_values:
|
285
|
+
raise click.UsageError(f"{param} must be one of {opts}")
|
286
|
+
directives.append(
|
287
|
+
JobQueueSortDirective(
|
288
|
+
sort_field=raw, sort_order=SortOrder.DESC if desc else SortOrder.ASC,
|
289
|
+
)
|
290
|
+
)
|
291
|
+
return directives
|
292
|
+
|
293
|
+
|
294
|
+
def _create_table(view: ViewOption, show_header: bool) -> Table:
|
295
|
+
"""Create a Rich Table with columns based on the selected view."""
|
296
|
+
table = Table(show_header=show_header, expand=True)
|
297
|
+
for key in VIEW_COLUMNS[view]:
|
298
|
+
table.add_column(key.replace("_", " ").upper(), overflow="fold")
|
299
|
+
return table
|
300
|
+
|
301
|
+
|
302
|
+
def _format_data(jq: JobQueueStatus) -> Dict[str, str]:
|
303
|
+
"""Format a JobQueueStatus object into a dictionary of strings for display."""
|
304
|
+
data = {}
|
305
|
+
for key in get_type_hints(JobQueueStatus):
|
306
|
+
value = getattr(jq, key, None)
|
307
|
+
if isinstance(value, datetime):
|
308
|
+
data[key] = value.strftime("%Y-%m-%d %H:%M:%S") if value else ""
|
309
|
+
elif value is None:
|
310
|
+
data[key] = ""
|
311
|
+
else:
|
312
|
+
data[key] = str(value)
|
313
|
+
return data
|
314
|
+
|
315
|
+
|
316
|
+
def _display_single(jq: JobQueueStatus, stderr: Console, view: ViewOption,) -> None:
|
317
|
+
"""Display a single job queue's details in a table using the selected view.
|
318
|
+
|
319
|
+
Args:
|
320
|
+
jq: The JobQueueStatus object to display.
|
321
|
+
stderr: The Rich Console object to print to.
|
322
|
+
view: The ViewOption determining which columns to display.
|
323
|
+
"""
|
324
|
+
table = _create_table(view, show_header=True)
|
325
|
+
data = _format_data(jq)
|
326
|
+
table.add_row(*(data[col] for col in VIEW_COLUMNS[view]))
|
327
|
+
stderr.print(table)
|
328
|
+
|
329
|
+
|
330
|
+
def _print_list_diagnostics( # noqa: PLR0913
|
331
|
+
stderr: Console,
|
332
|
+
job_queue_id: Optional[str],
|
333
|
+
name: Optional[str],
|
334
|
+
include_all_users: bool,
|
335
|
+
cloud: Optional[str],
|
336
|
+
project: Optional[str],
|
337
|
+
cluster_status: Optional[str],
|
338
|
+
view: ViewOption,
|
339
|
+
sort_dirs: List[JobQueueSortDirective],
|
340
|
+
no_interactive: bool,
|
341
|
+
page_size: int,
|
342
|
+
effective_max: int,
|
343
|
+
) -> None:
|
344
|
+
"""Prints diagnostic information for the list_job_queues command."""
|
345
|
+
stderr.print("[bold]Listing with:[/]")
|
346
|
+
stderr.print(f"id: {job_queue_id or '<any>'}")
|
347
|
+
stderr.print(f"name: {name or '<any>'}")
|
348
|
+
stderr.print(f"creator: {'all' if include_all_users else 'mine'}")
|
349
|
+
stderr.print(f"cloud: {cloud or '<any>'}")
|
350
|
+
stderr.print(f"project: {project or '<any>'}")
|
351
|
+
stderr.print(f"cluster: {cluster_status or '<any>'}")
|
352
|
+
stderr.print(f"view: {view.value}")
|
353
|
+
|
354
|
+
formatted_sort_dirs = [
|
355
|
+
f"{'-' if d.sort_order == SortOrder.DESC else ''}{(d.sort_field or '').lower()}"
|
356
|
+
for d in sort_dirs
|
357
|
+
]
|
358
|
+
stderr.print(f"sort: {formatted_sort_dirs}")
|
359
|
+
|
360
|
+
stderr.print(f"mode: {'batch' if no_interactive else 'interactive'}")
|
361
|
+
stderr.print(f"page-size: {page_size}")
|
362
|
+
stderr.print(f"max-items: {effective_max}")
|
363
|
+
stderr.print(f"UI: {get_endpoint('/job-queues')}\n")
|
anyscale/commands/list_util.py
CHANGED
@@ -2,10 +2,23 @@ import itertools
|
|
2
2
|
from json import dumps as json_dumps
|
3
3
|
from typing import Any, Callable, Dict, Iterator, List, Optional
|
4
4
|
|
5
|
+
import click
|
5
6
|
from rich.console import Console
|
6
7
|
from rich.table import Table
|
7
8
|
|
8
|
-
from anyscale.util import AnyscaleJSONEncoder
|
9
|
+
from anyscale.util import AnyscaleJSONEncoder, validate_non_negative_arg
|
10
|
+
|
11
|
+
|
12
|
+
MAX_PAGE_SIZE = 50
|
13
|
+
NON_INTERACTIVE_DEFAULT_MAX_ITEMS = 10
|
14
|
+
|
15
|
+
|
16
|
+
def validate_page_size(ctx, param, value):
|
17
|
+
"""Click callback to validate page size argument."""
|
18
|
+
value = validate_non_negative_arg(ctx, param, value)
|
19
|
+
if value is not None and value > MAX_PAGE_SIZE:
|
20
|
+
raise click.BadParameter(f"must be less than or equal to {MAX_PAGE_SIZE}.")
|
21
|
+
return value
|
9
22
|
|
10
23
|
|
11
24
|
def _paginate(iterator: Iterator[Any], page_size: Optional[int]) -> Iterator[List[Any]]:
|
@@ -113,8 +113,9 @@ def describe(name: str, format_: str) -> None:
|
|
113
113
|
"PARTITION",
|
114
114
|
"STATE",
|
115
115
|
"WORKLOAD DETAILS",
|
116
|
-
"WORKLOAD SCORE",
|
117
116
|
"WORKLOAD START TIME",
|
117
|
+
"WORKLOAD CREATOR",
|
118
|
+
"WORKLOAD SCORE",
|
118
119
|
"CLOUD INSTANCE ID",
|
119
120
|
]
|
120
121
|
for row in scheduler_info.machines:
|
@@ -124,13 +125,16 @@ def describe(name: str, format_: str) -> None:
|
|
124
125
|
row.machine_type,
|
125
126
|
row.partition,
|
126
127
|
row.allocation_state,
|
127
|
-
f"{row.workload_info.workload_type}/{row.workload_info.workload_name}/{row.workload_info.workload_cloud}"
|
128
|
+
f"{row.workload_info.workload_type}/{row.workload_info.workload_name}/{row.workload_info.workload_project}/{row.workload_info.workload_cloud}"
|
128
129
|
if row.workload_info.workload_name
|
129
130
|
else "",
|
130
|
-
row.workload_score,
|
131
131
|
format_time(row.workload_info.workload_start_time)
|
132
132
|
if row.workload_info.workload_name
|
133
133
|
else "",
|
134
|
+
row.workload_info.workload_creator
|
135
|
+
if row.workload_info.workload_name
|
136
|
+
else "",
|
137
|
+
row.workload_score,
|
134
138
|
row.cloud_instance_id,
|
135
139
|
]
|
136
140
|
)
|
@@ -151,7 +155,7 @@ def describe(name: str, format_: str) -> None:
|
|
151
155
|
"MACHINE TYPE",
|
152
156
|
"WORKLOAD DETAILS",
|
153
157
|
"WORKLOAD START TIME",
|
154
|
-
"WORKLOAD
|
158
|
+
"WORKLOAD CREATOR",
|
155
159
|
"PARTITION SCORES",
|
156
160
|
]
|
157
161
|
for row in scheduler_info.requests:
|
@@ -159,11 +163,9 @@ def describe(name: str, format_: str) -> None:
|
|
159
163
|
[
|
160
164
|
row.size,
|
161
165
|
row.machine_type,
|
162
|
-
f"{row.workload_info.workload_type}/{row.workload_info.workload_name}",
|
163
|
-
format_time(row.workload_info.workload_start_time)
|
164
|
-
|
165
|
-
else "",
|
166
|
-
row.workload_info.workload_cloud,
|
166
|
+
f"{row.workload_info.workload_type}/{row.workload_info.workload_name}/{row.workload_info.workload_project}/{row.workload_info.workload_cloud}",
|
167
|
+
format_time(row.workload_info.workload_start_time),
|
168
|
+
row.workload_info.workload_creator,
|
167
169
|
row.partition_scores,
|
168
170
|
]
|
169
171
|
)
|
@@ -258,7 +260,13 @@ def list_machine_pools(format_: str) -> None:
|
|
258
260
|
raise click.ClickException(f"Invalid output format '{format}'.")
|
259
261
|
|
260
262
|
|
261
|
-
@machine_pool_cli.command(
|
263
|
+
@machine_pool_cli.command(
|
264
|
+
name="attach",
|
265
|
+
help="Attach a machine pool to a cloud.",
|
266
|
+
cls=AnyscaleCommand,
|
267
|
+
example=command_examples.MACHINE_POOL_ATTACH_EXAMPLE,
|
268
|
+
is_beta=True,
|
269
|
+
)
|
262
270
|
@click.option("--name", type=str, required=True, help="Provide a machine pool name.")
|
263
271
|
@click.option("--cloud", type=str, required=True, help="Provide a cloud name.")
|
264
272
|
def attach_machine_pool_to_cloud(name: str, cloud: str) -> None:
|
@@ -269,7 +277,13 @@ def attach_machine_pool_to_cloud(name: str, cloud: str) -> None:
|
|
269
277
|
print(f"Attached machine pool '{name}' to cloud '{cloud}'.")
|
270
278
|
|
271
279
|
|
272
|
-
@machine_pool_cli.command(
|
280
|
+
@machine_pool_cli.command(
|
281
|
+
name="detach",
|
282
|
+
help="Detach a machine pool from a cloud.",
|
283
|
+
cls=AnyscaleCommand,
|
284
|
+
example=command_examples.MACHINE_POOL_DETACH_EXAMPLE,
|
285
|
+
is_beta=True,
|
286
|
+
)
|
273
287
|
@click.option("--name", type=str, required=True, help="Provide a machine pool name.")
|
274
288
|
@click.option("--cloud", type=str, required=True, help="Provide a cloud name.")
|
275
289
|
def detach_machine_pool_from_cloud(name: str, cloud: str) -> None:
|
@@ -13,7 +13,12 @@ import yaml
|
|
13
13
|
from anyscale._private.models.image_uri import ImageURI
|
14
14
|
from anyscale.cli_logger import BlockLogger
|
15
15
|
from anyscale.commands import command_examples
|
16
|
-
from anyscale.commands.list_util import
|
16
|
+
from anyscale.commands.list_util import (
|
17
|
+
display_list,
|
18
|
+
MAX_PAGE_SIZE,
|
19
|
+
NON_INTERACTIVE_DEFAULT_MAX_ITEMS,
|
20
|
+
validate_page_size,
|
21
|
+
)
|
17
22
|
from anyscale.commands.util import (
|
18
23
|
AnyscaleCommand,
|
19
24
|
convert_kv_strings_to_dict,
|
@@ -159,7 +164,7 @@ def _read_name_from_config_file(path: str):
|
|
159
164
|
required=False,
|
160
165
|
default=None,
|
161
166
|
type=int,
|
162
|
-
help="The percentage of traffic to send to the canary version of the service (0-100). This can be used to manually shift traffic toward (or away from) the canary version. If not provided, traffic will be shifted incrementally toward the canary version until it reaches 100. Not supported when using --in-place.",
|
167
|
+
help="The percentage of traffic to send to the canary version of the service (0-100). This can be used to manually shift traffic toward (or away from) the canary version. If not provided, traffic will be shifted incrementally toward the canary version until it reaches 100. Not supported when using --in-place. This is ignored when restarting a service or creating a new service.",
|
163
168
|
)
|
164
169
|
@click.option(
|
165
170
|
"--max-surge-percent",
|
@@ -642,17 +647,6 @@ def rollout( # noqa: PLR0913
|
|
642
647
|
)
|
643
648
|
|
644
649
|
|
645
|
-
MAX_PAGE_SIZE = 50
|
646
|
-
NON_INTERACTIVE_DEFAULT_MAX_ITEMS = 10
|
647
|
-
|
648
|
-
|
649
|
-
def validate_page_size(ctx, param, value):
|
650
|
-
value = validate_non_negative_arg(ctx, param, value)
|
651
|
-
if value is not None and value > MAX_PAGE_SIZE:
|
652
|
-
raise click.BadParameter(f"must be less than or equal to {MAX_PAGE_SIZE}.")
|
653
|
-
return value
|
654
|
-
|
655
|
-
|
656
650
|
def validate_max_items(ctx, param, value):
|
657
651
|
if value is None:
|
658
652
|
return None
|
@@ -967,6 +961,8 @@ def terminate(
|
|
967
961
|
|
968
962
|
This applies to both v1 and v2 services.
|
969
963
|
"""
|
964
|
+
# TODO: Remove service_controller and use the sdk method. Need to update the sdk method
|
965
|
+
# so that it can resolve either service name or config file to the service id.
|
970
966
|
service_controller = ServiceController()
|
971
967
|
service_id = service_controller.get_service_id(
|
972
968
|
service_id=service_id,
|
@@ -975,7 +971,7 @@ def terminate(
|
|
975
971
|
project_id=project_id,
|
976
972
|
)
|
977
973
|
try:
|
978
|
-
anyscale.service.terminate(service_id)
|
974
|
+
anyscale.service.terminate(id=service_id)
|
979
975
|
log.info(f"Service {service_id} terminate initiated.")
|
980
976
|
log.info(
|
981
977
|
f'View the service in the UI at {get_endpoint(f"/services/{service_id}")}'
|