anyscale 0.26.17__py3-none-any.whl → 0.26.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.
- anyscale/_private/docgen/models.md +3 -3
- anyscale/anyscale-cloud-setup.yaml +0 -4
- anyscale/client/README.md +18 -38
- anyscale/client/openapi_client/__init__.py +14 -20
- anyscale/client/openapi_client/api/default_api.py +460 -2097
- anyscale/client/openapi_client/models/__init__.py +14 -20
- anyscale/client/openapi_client/models/aws_config.py +402 -0
- anyscale/client/openapi_client/models/baseimagesenum.py +68 -1
- anyscale/client/openapi_client/models/cloud_deployment.py +397 -0
- anyscale/client/openapi_client/models/{webterminal_list_response.py → clouddeployment_list_response.py} +15 -15
- anyscale/client/openapi_client/models/decorated_production_job_state_transition.py +2 -2
- anyscale/client/openapi_client/models/file_storage.py +206 -0
- anyscale/client/openapi_client/models/gcp_config.py +402 -0
- anyscale/client/openapi_client/models/{session_details.py → job_queue_sort_directive.py} +39 -39
- anyscale/client/openapi_client/models/{sessiondescribe_response.py → job_queue_sort_field.py} +20 -34
- anyscale/client/openapi_client/models/job_queues_query.py +31 -3
- anyscale/client/openapi_client/models/kubernetes_config.py +150 -0
- anyscale/client/openapi_client/models/{monitor_logs_extension.py → networking_mode.py} +7 -7
- anyscale/client/openapi_client/models/object_storage.py +178 -0
- anyscale/client/openapi_client/models/{sessiondetails_response.py → pcp_config.py} +23 -22
- anyscale/client/openapi_client/models/production_job_state_transition.py +2 -2
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +68 -1
- anyscale/client/openapi_client/models/{external_service_status.py → update_job_queue_request.py} +39 -36
- anyscale/client/openapi_client/models/workspace_template_readme.py +181 -0
- anyscale/client/openapi_client/models/{archivedlogsinfo_response.py → workspacetemplatereadme_response.py} +11 -11
- anyscale/commands/cloud_commands.py +55 -7
- anyscale/commands/command_examples.py +58 -0
- anyscale/commands/job_commands.py +2 -2
- anyscale/commands/job_queue_commands.py +172 -0
- anyscale/connect_utils/prepare_cluster.py +19 -14
- anyscale/controllers/cloud_controller.py +60 -3
- anyscale/controllers/job_controller.py +215 -3
- anyscale/scripts.py +3 -0
- anyscale/sdk/anyscale_client/models/baseimagesenum.py +68 -1
- anyscale/sdk/anyscale_client/models/production_job_state_transition.py +2 -2
- anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +68 -1
- anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
- anyscale/util.py +3 -1
- anyscale/utils/connect_helpers.py +34 -0
- anyscale/version.py +1 -1
- anyscale/workspace/_private/workspace_sdk.py +19 -6
- {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/METADATA +1 -1
- {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/RECORD +48 -53
- anyscale/client/openapi_client/models/archived_logs_info.py +0 -164
- anyscale/client/openapi_client/models/create_experimental_workspace_from_job.py +0 -123
- anyscale/client/openapi_client/models/create_session_from_snapshot_options.py +0 -538
- anyscale/client/openapi_client/models/create_session_in_db.py +0 -434
- anyscale/client/openapi_client/models/create_session_response.py +0 -174
- anyscale/client/openapi_client/models/createsessionresponse_response.py +0 -121
- anyscale/client/openapi_client/models/external_service_status_response.py +0 -250
- anyscale/client/openapi_client/models/externalservicestatusresponse_response.py +0 -121
- anyscale/client/openapi_client/models/session_describe.py +0 -175
- anyscale/client/openapi_client/models/session_history_item.py +0 -146
- anyscale/client/openapi_client/models/sessionhistoryitem_list_response.py +0 -147
- anyscale/client/openapi_client/models/update_compute_template.py +0 -146
- anyscale/client/openapi_client/models/update_compute_template_config.py +0 -464
- {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/LICENSE +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/NOTICE +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/WHEEL +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/top_level.txt +0 -0
@@ -1388,6 +1388,43 @@ class CloudController(BaseController):
|
|
1388
1388
|
cloud_id, CloudProviders.AWS, functions_to_verify, yes,
|
1389
1389
|
)
|
1390
1390
|
|
1391
|
+
def get_cloud_deployments(self, cloud_id: str, cloud_name: str) -> Dict[str, Any]:
|
1392
|
+
cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(
|
1393
|
+
cloud_id=cloud_id,
|
1394
|
+
).result
|
1395
|
+
|
1396
|
+
if cloud.is_aioa:
|
1397
|
+
raise ValueError(
|
1398
|
+
"Listing cloud deployments is only supported for customer-hosted clouds."
|
1399
|
+
)
|
1400
|
+
|
1401
|
+
try:
|
1402
|
+
deployments = self.api_client.get_cloud_deployments_api_v2_clouds_cloud_id_deployments_get(
|
1403
|
+
cloud_id=cloud_id,
|
1404
|
+
).results
|
1405
|
+
except Exception as e: # noqa: BLE001
|
1406
|
+
raise ClickException(
|
1407
|
+
f"Failed to get cloud deployments for cloud {cloud_name} ({cloud_id}). Error: {e}"
|
1408
|
+
)
|
1409
|
+
|
1410
|
+
# Avoid displaying fields with empty values (since the values for optional fields default to None).
|
1411
|
+
def remove_empty_values(d):
|
1412
|
+
if isinstance(d, dict):
|
1413
|
+
return {
|
1414
|
+
k: remove_empty_values(v)
|
1415
|
+
for k, v in d.items()
|
1416
|
+
if remove_empty_values(v)
|
1417
|
+
}
|
1418
|
+
return d
|
1419
|
+
|
1420
|
+
return {
|
1421
|
+
"id": cloud_id,
|
1422
|
+
"name": cloud_name,
|
1423
|
+
"deployments": [
|
1424
|
+
remove_empty_values(deployment.to_dict()) for deployment in deployments
|
1425
|
+
],
|
1426
|
+
}
|
1427
|
+
|
1391
1428
|
def get_cloud_config(
|
1392
1429
|
self, cloud_name: Optional[str] = None, cloud_id: Optional[str] = None,
|
1393
1430
|
) -> CloudDeploymentConfig:
|
@@ -3650,7 +3687,9 @@ class CloudController(BaseController):
|
|
3650
3687
|
|
3651
3688
|
### End of edit cloud ###
|
3652
3689
|
|
3653
|
-
def generate_jobs_report(
|
3690
|
+
def generate_jobs_report(
|
3691
|
+
self, cloud_id: str, csv: bool, out_path: str, sort: str, sort_order_asc: bool
|
3692
|
+
) -> None:
|
3654
3693
|
end_time = datetime.now()
|
3655
3694
|
start_time = end_time - timedelta(days=7)
|
3656
3695
|
|
@@ -3690,7 +3729,25 @@ class CloudController(BaseController):
|
|
3690
3729
|
for job in full_results
|
3691
3730
|
if job.job_state in TERMINAL_HA_JOB_STATES and job.job_report is not None
|
3692
3731
|
]
|
3693
|
-
|
3732
|
+
if sort == "created_at":
|
3733
|
+
filtered_results.sort(
|
3734
|
+
key=lambda x: x.created_at, reverse=not sort_order_asc
|
3735
|
+
)
|
3736
|
+
elif sort == "gpu":
|
3737
|
+
filtered_results.sort(
|
3738
|
+
key=lambda x: x.job_report.unused_gpu_hours or 0,
|
3739
|
+
reverse=not sort_order_asc,
|
3740
|
+
)
|
3741
|
+
elif sort == "cpu":
|
3742
|
+
filtered_results.sort(
|
3743
|
+
key=lambda x: x.job_report.unused_cpu_hours or 0,
|
3744
|
+
reverse=not sort_order_asc,
|
3745
|
+
)
|
3746
|
+
elif sort == "instances":
|
3747
|
+
filtered_results.sort(
|
3748
|
+
key=lambda x: x.job_report.max_instances_launched or 0,
|
3749
|
+
reverse=not sort_order_asc,
|
3750
|
+
)
|
3694
3751
|
|
3695
3752
|
with open(out_path, "w") as out_file:
|
3696
3753
|
if csv:
|
@@ -3710,7 +3767,7 @@ class CloudController(BaseController):
|
|
3710
3767
|
max_instances_launched = job.job_report.max_instances_launched or ""
|
3711
3768
|
|
3712
3769
|
out_file.write(
|
3713
|
-
f"{job.job_id},{job.job_name},{job_state},{str(job.created_at)},{finished_at},{duration},{unused_cpu_hours},{unused_gpu_hours},{max_instances_launched}\n
|
3770
|
+
f'"{job.job_id}","{job.job_name}","{job_state}","{str(job.created_at)}","{finished_at}","{duration}","{unused_cpu_hours}","{unused_gpu_hours}","{max_instances_launched}"\n'
|
3714
3771
|
)
|
3715
3772
|
else:
|
3716
3773
|
out_file.write(
|
@@ -1,9 +1,13 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
4
|
+
from collections import defaultdict
|
5
|
+
from enum import Enum
|
2
6
|
import os
|
3
7
|
import random
|
4
8
|
import string
|
5
9
|
import time
|
6
|
-
from typing import Any, cast, Dict, List, Optional
|
10
|
+
from typing import Any, Callable, cast, Dict, Iterable, List, Optional
|
7
11
|
|
8
12
|
import click
|
9
13
|
import tabulate
|
@@ -23,10 +27,21 @@ from anyscale.client.openapi_client.models.create_internal_production_job import
|
|
23
27
|
from anyscale.client.openapi_client.models.create_job_queue_config import (
|
24
28
|
CreateJobQueueConfig,
|
25
29
|
)
|
30
|
+
from anyscale.client.openapi_client.models.decorated_job_queue import DecoratedJobQueue
|
26
31
|
from anyscale.client.openapi_client.models.decorated_production_job import (
|
27
32
|
DecoratedProductionJob,
|
28
33
|
)
|
34
|
+
from anyscale.client.openapi_client.models.decoratedjobqueue_response import (
|
35
|
+
DecoratedjobqueueResponse,
|
36
|
+
)
|
29
37
|
from anyscale.client.openapi_client.models.ha_job_states import HaJobStates
|
38
|
+
from anyscale.client.openapi_client.models.job_queue_sort_directive import (
|
39
|
+
JobQueueSortDirective,
|
40
|
+
)
|
41
|
+
from anyscale.client.openapi_client.models.job_queues_query import JobQueuesQuery
|
42
|
+
from anyscale.client.openapi_client.models.update_job_queue_request import (
|
43
|
+
UpdateJobQueueRequest,
|
44
|
+
)
|
30
45
|
from anyscale.controllers.base_controller import BaseController
|
31
46
|
from anyscale.models.job_model import JobConfig
|
32
47
|
from anyscale.project_utils import infer_project_id
|
@@ -45,7 +60,7 @@ from anyscale.util import (
|
|
45
60
|
populate_unspecified_cluster_configs_from_current_workspace,
|
46
61
|
validate_job_config_dict,
|
47
62
|
)
|
48
|
-
from anyscale.utils.connect_helpers import search_entities
|
63
|
+
from anyscale.utils.connect_helpers import paginate, search_entities
|
49
64
|
from anyscale.utils.runtime_env import override_runtime_env_config
|
50
65
|
from anyscale.utils.workload_types import Workload
|
51
66
|
|
@@ -294,7 +309,7 @@ class JobController(BaseController):
|
|
294
309
|
project_id: Optional[str],
|
295
310
|
include_archived: bool,
|
296
311
|
max_items: int,
|
297
|
-
states: List[
|
312
|
+
states: List[HaJobStates],
|
298
313
|
) -> None:
|
299
314
|
"""
|
300
315
|
This function will list jobs.
|
@@ -338,6 +353,7 @@ class JobController(BaseController):
|
|
338
353
|
)
|
339
354
|
else:
|
340
355
|
creator_id = None
|
356
|
+
|
341
357
|
resp = self.api_client.list_decorated_jobs_api_v2_decorated_ha_jobs_get(
|
342
358
|
project_id=project_id,
|
343
359
|
name=name,
|
@@ -647,3 +663,199 @@ class JobController(BaseController):
|
|
647
663
|
),
|
648
664
|
)
|
649
665
|
return job_runs
|
666
|
+
|
667
|
+
def update_job_queue(
|
668
|
+
self,
|
669
|
+
job_queue_id: str,
|
670
|
+
job_queue_name: str,
|
671
|
+
max_concurrency: Optional[int] = None,
|
672
|
+
idle_timeout_s: Optional[int] = None,
|
673
|
+
):
|
674
|
+
job_queue: DecoratedJobQueue = _resolve_object(
|
675
|
+
fetch_by_id=cast(
|
676
|
+
Callable[[str], DecoratedjobqueueResponse],
|
677
|
+
self.api_client.get_job_queue_api_v2_job_queues_job_queue_id_get,
|
678
|
+
),
|
679
|
+
fetch_by_id_param=job_queue_id,
|
680
|
+
fetch_by_name=cast(
|
681
|
+
Callable[[str], DecoratedjobqueueResponse],
|
682
|
+
self.api_client.list_job_queues_api_v2_job_queues_post,
|
683
|
+
),
|
684
|
+
fetch_by_name_query={
|
685
|
+
"job_queues_query": {"name": {"equals": job_queue_name,}}
|
686
|
+
},
|
687
|
+
object_type_description="job queue",
|
688
|
+
)
|
689
|
+
|
690
|
+
queue: DecoratedJobQueue = self.api_client.update_job_queue_api_v2_job_queues_job_queue_id_put(
|
691
|
+
job_queue_id=job_queue.id,
|
692
|
+
update_job_queue_request=UpdateJobQueueRequest(
|
693
|
+
max_concurrency=max_concurrency, idle_timeout_sec=idle_timeout_s,
|
694
|
+
),
|
695
|
+
).result
|
696
|
+
|
697
|
+
_print_job_queue_vertical(queue, JobQueueView.ALL)
|
698
|
+
|
699
|
+
def get_job_queue(self, job_queue_id: str):
|
700
|
+
queue: DecoratedJobQueue = self.api_client.get_job_queue_api_v2_job_queues_job_queue_id_get(
|
701
|
+
job_queue_id=job_queue_id
|
702
|
+
).result
|
703
|
+
|
704
|
+
_print_job_queue_vertical(queue, JobQueueView.ALL)
|
705
|
+
|
706
|
+
def list_job_queues(
|
707
|
+
self,
|
708
|
+
include_all_users: bool,
|
709
|
+
view: JobQueueView,
|
710
|
+
page_size: int,
|
711
|
+
max_items: Optional[int],
|
712
|
+
sorting_directives: List[JobQueueSortDirective],
|
713
|
+
interactive: bool,
|
714
|
+
):
|
715
|
+
creator_id = (
|
716
|
+
None
|
717
|
+
if include_all_users
|
718
|
+
else self.api_client.get_user_info_api_v2_userinfo_get().result.id
|
719
|
+
)
|
720
|
+
|
721
|
+
def build_query(paging_token: Optional[str], count: int) -> Dict:
|
722
|
+
return {
|
723
|
+
"job_queues_query": JobQueuesQuery(
|
724
|
+
creator_id=creator_id,
|
725
|
+
paging=PageQuery(paging_token=paging_token, count=count),
|
726
|
+
sorting_directives=sorting_directives,
|
727
|
+
)
|
728
|
+
}
|
729
|
+
|
730
|
+
for batch in paginate(
|
731
|
+
search_function=self.api_client.list_job_queues_api_v2_job_queues_post,
|
732
|
+
query_builder=build_query,
|
733
|
+
interactive=interactive,
|
734
|
+
page_size=page_size,
|
735
|
+
max_items=max_items,
|
736
|
+
):
|
737
|
+
_render_job_queues(batch, view)
|
738
|
+
|
739
|
+
|
740
|
+
def _render_jobs(jobs):
|
741
|
+
jobs_table = [
|
742
|
+
[
|
743
|
+
job.name,
|
744
|
+
job.id,
|
745
|
+
job.project.name,
|
746
|
+
job.last_job_run.cluster.name
|
747
|
+
if job.last_job_run and job.last_job_run.cluster
|
748
|
+
else None,
|
749
|
+
job.state.current_state,
|
750
|
+
job.creator.email,
|
751
|
+
job.config.entrypoint
|
752
|
+
if len(job.config.entrypoint) < 50
|
753
|
+
else job.config.entrypoint[:50] + " ...",
|
754
|
+
]
|
755
|
+
for job in jobs
|
756
|
+
]
|
757
|
+
|
758
|
+
table = tabulate.tabulate(
|
759
|
+
jobs_table,
|
760
|
+
headers=[
|
761
|
+
"NAME",
|
762
|
+
"ID",
|
763
|
+
"PROJECT NAME",
|
764
|
+
"CLUSTER NAME",
|
765
|
+
"CURRENT STATE",
|
766
|
+
"CREATOR",
|
767
|
+
"ENTRYPOINT",
|
768
|
+
],
|
769
|
+
tablefmt="plain",
|
770
|
+
)
|
771
|
+
click.echo(f"{table}")
|
772
|
+
|
773
|
+
|
774
|
+
class JobQueueView(Enum):
|
775
|
+
ALL = DecoratedJobQueue.attribute_map.keys()
|
776
|
+
STATS = [
|
777
|
+
"id",
|
778
|
+
"name",
|
779
|
+
"total_jobs",
|
780
|
+
"active_jobs",
|
781
|
+
"successful_jobs",
|
782
|
+
"failed_jobs",
|
783
|
+
]
|
784
|
+
DEFAULT = [
|
785
|
+
"id",
|
786
|
+
"name",
|
787
|
+
"cluster_id",
|
788
|
+
"creator_id",
|
789
|
+
"max_concurrency",
|
790
|
+
"idle_timeout_sec",
|
791
|
+
"current_cluster_state",
|
792
|
+
"created_at",
|
793
|
+
]
|
794
|
+
|
795
|
+
|
796
|
+
def _format_job_queue(queue: DecoratedJobQueue, view: JobQueueView) -> List[str]:
|
797
|
+
formatters: Dict[str, Callable[[Any], Any]] = defaultdict(lambda: (lambda v: v))
|
798
|
+
formatters["created_at"] = lambda k: k.strftime("%Y-%m-%d %H:%M:%S")
|
799
|
+
|
800
|
+
return [formatters[field](getattr(queue, field, "")) or "" for field in view.value]
|
801
|
+
|
802
|
+
|
803
|
+
def _render_job_queues(queues: Iterable[DecoratedJobQueue], view: JobQueueView):
|
804
|
+
if not queues:
|
805
|
+
click.echo("No job queues found!")
|
806
|
+
return
|
807
|
+
table = tabulate.tabulate(
|
808
|
+
[
|
809
|
+
_format_job_queue(queue, view)
|
810
|
+
for queue in cast(Iterable[DecoratedJobQueue], queues)
|
811
|
+
],
|
812
|
+
headers=[field.replace("_", " ").upper() for field in view.value],
|
813
|
+
tablefmt="plain",
|
814
|
+
maxcolwidths=30, # type: ignore
|
815
|
+
numalign="center",
|
816
|
+
stralign="center",
|
817
|
+
)
|
818
|
+
click.echo(table)
|
819
|
+
|
820
|
+
|
821
|
+
def _print_job_queue_vertical(queue: DecoratedJobQueue, job_queue_view: JobQueueView):
|
822
|
+
"""
|
823
|
+
Print single job queue with headers as a vertical table
|
824
|
+
"""
|
825
|
+
for header, value in zip(
|
826
|
+
[field.replace("_", " ").upper() for field in job_queue_view.value],
|
827
|
+
_format_job_queue(queue, job_queue_view),
|
828
|
+
):
|
829
|
+
print(f"{header:<{30}}: {value}")
|
830
|
+
|
831
|
+
|
832
|
+
def _resolve_object(
|
833
|
+
fetch_by_id: Optional[Callable[[str], object]],
|
834
|
+
fetch_by_id_param: Optional[str],
|
835
|
+
fetch_by_name,
|
836
|
+
fetch_by_name_query,
|
837
|
+
object_type_description: str,
|
838
|
+
) -> Any:
|
839
|
+
"""Given job_id or job_name, retrieve decorated ha job spec"""
|
840
|
+
if fetch_by_id_param is None and fetch_by_name_query is None:
|
841
|
+
raise click.ClickException(
|
842
|
+
"Either `--id` or `--name` must be passed in for object."
|
843
|
+
)
|
844
|
+
if fetch_by_id_param:
|
845
|
+
try:
|
846
|
+
return fetch_by_id(fetch_by_id_param).result # type: ignore
|
847
|
+
except Exception as e: # noqa: BLE001
|
848
|
+
raise click.ClickException(
|
849
|
+
f"Could not fetch {object_type_description} by id: {e}"
|
850
|
+
)
|
851
|
+
|
852
|
+
object_list_resp: List[Any] = fetch_by_name(**fetch_by_name_query).results
|
853
|
+
if len(object_list_resp) == 0:
|
854
|
+
raise click.ClickException(
|
855
|
+
f"No {object_type_description} found with the provided name"
|
856
|
+
)
|
857
|
+
if len(object_list_resp) > 1:
|
858
|
+
raise click.ClickException(
|
859
|
+
f"Multiple {object_type_description}s found with the provided name"
|
860
|
+
)
|
861
|
+
return object_list_resp[0]
|
anyscale/scripts.py
CHANGED
@@ -22,6 +22,7 @@ from anyscale.commands.experimental_integrations_commands import (
|
|
22
22
|
)
|
23
23
|
from anyscale.commands.image_commands import image_cli
|
24
24
|
from anyscale.commands.job_commands import job_cli
|
25
|
+
from anyscale.commands.job_queue_commands import job_queue_cli
|
25
26
|
from anyscale.commands.list_commands import list_cli
|
26
27
|
from anyscale.commands.llm.group import llm_cli
|
27
28
|
from anyscale.commands.login_commands import anyscale_login, anyscale_logout
|
@@ -120,6 +121,7 @@ cli.add_command(version_cli)
|
|
120
121
|
cli.add_command(list_cli)
|
121
122
|
cli.add_command(cluster_env_cli)
|
122
123
|
cli.add_command(job_cli)
|
124
|
+
# cli.add_command(job_queue_cli) # TODO will be enabled later
|
123
125
|
cli.add_command(schedule_cli)
|
124
126
|
cli.add_command(service_cli)
|
125
127
|
cli.add_command(cluster_cli)
|
@@ -153,6 +155,7 @@ ALIASES = {
|
|
153
155
|
"h": anyscale_help,
|
154
156
|
"schedules": schedule_cli,
|
155
157
|
"jobs": job_cli,
|
158
|
+
"jq": job_queue_cli,
|
156
159
|
"services": service_cli,
|
157
160
|
"cluster-compute": compute_config_cli,
|
158
161
|
"images": image_cli,
|