anyscale 0.24.88__py3-none-any.whl → 0.25.5__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 +56 -0
- anyscale/_private/anyscale_client/anyscale_client.py +179 -28
- anyscale/_private/anyscale_client/common.py +109 -2
- anyscale/_private/anyscale_client/fake_anyscale_client.py +239 -1
- anyscale/_private/docgen/README.md +1 -1
- anyscale/_private/docgen/__main__.py +71 -21
- anyscale/_private/docgen/api.md +13 -20
- anyscale/_private/docgen/generator.py +3 -2
- anyscale/_private/docgen/models.md +4 -49
- anyscale/_private/workload/workload_config.py +21 -7
- anyscale/aggregated_instance_usage/__init__.py +1 -1
- anyscale/aggregated_instance_usage/commands.py +2 -4
- anyscale/aggregated_instance_usage/models.py +8 -8
- anyscale/client/README.md +25 -22
- anyscale/client/openapi_client/__init__.py +16 -14
- anyscale/client/openapi_client/api/default_api.py +1139 -959
- anyscale/client/openapi_client/models/__init__.py +16 -14
- anyscale/client/openapi_client/models/baseimagesenum.py +43 -1
- anyscale/client/openapi_client/models/{session_event_types.py → cloud_deployment_config.py} +35 -24
- anyscale/client/openapi_client/models/{platformfinetuningjob_response.py → clouddeploymentconfig_response.py} +11 -11
- anyscale/client/openapi_client/models/{log_level_types.py → cluster_event_source.py} +12 -7
- anyscale/client/openapi_client/models/{company_size.py → cluster_size.py} +10 -10
- anyscale/client/openapi_client/models/cluster_status_details.py +2 -1
- anyscale/client/openapi_client/models/{sessionevent_list_response.py → clusterevent_list_response.py} +15 -15
- anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
- anyscale/client/openapi_client/models/create_notification_channel_record.py +29 -3
- anyscale/client/openapi_client/models/decorated_interactive_session.py +1 -57
- anyscale/client/openapi_client/models/decorated_job.py +1 -57
- anyscale/client/openapi_client/models/decorated_job_submission.py +1 -29
- anyscale/client/openapi_client/models/decorated_production_job.py +1 -29
- anyscale/client/openapi_client/models/decorated_session.py +1 -57
- anyscale/client/openapi_client/models/decorated_unified_job.py +1 -30
- anyscale/client/openapi_client/models/{resubmit_ft_job_request.py → describe_machine_pool_request.py} +21 -20
- anyscale/client/openapi_client/models/describe_machine_pool_response.py +123 -0
- anyscale/client/openapi_client/models/describemachinepoolresponse_response.py +121 -0
- anyscale/client/openapi_client/models/ha_jobs_sort_field.py +1 -2
- anyscale/client/openapi_client/models/internal_production_job.py +1 -29
- anyscale/client/openapi_client/models/jobs_sort_field.py +1 -2
- anyscale/client/openapi_client/models/machine_allocation_state.py +3 -1
- anyscale/client/openapi_client/models/machine_state_info.py +326 -0
- anyscale/client/openapi_client/models/{fine_tuning_job_status.py → notification_channel_slack_config.py} +34 -16
- anyscale/client/openapi_client/models/organization_marketing_questions.py +80 -54
- anyscale/client/openapi_client/models/request_state_info.py +210 -0
- anyscale/client/openapi_client/models/{platformfinetuningjob_list_response.py → scheduler_info.py} +43 -38
- anyscale/client/openapi_client/models/serve_deployment_fast_api_docs_status.py +123 -0
- anyscale/client/openapi_client/models/serve_deployment_state.py +2 -1
- anyscale/client/openapi_client/models/servedeploymentfastapidocsstatus_response.py +121 -0
- anyscale/client/openapi_client/models/sessions_sort_field.py +1 -2
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +43 -1
- anyscale/client/openapi_client/models/unified_job_sort_field.py +1 -2
- anyscale/client/openapi_client/models/update_cloud_collaborator.py +121 -0
- anyscale/client/openapi_client/models/usage_by_cluster.py +28 -1
- anyscale/client/openapi_client/models/usage_by_user.py +30 -3
- anyscale/client/openapi_client/models/workload_info.py +210 -0
- anyscale/cloud/__init__.py +83 -0
- anyscale/cloud/_private/cloud_sdk.py +25 -0
- anyscale/cloud/commands.py +45 -0
- anyscale/cloud/models.py +91 -0
- anyscale/cluster_compute.py +1 -1
- anyscale/commands/aggregated_instance_usage_commands.py +4 -4
- anyscale/commands/cloud_commands.py +87 -14
- anyscale/commands/command_examples.py +65 -0
- anyscale/commands/job_commands.py +15 -3
- anyscale/commands/machine_pool_commands.py +113 -1
- anyscale/commands/organization_invitation_commands.py +98 -0
- anyscale/commands/project_commands.py +52 -2
- anyscale/commands/resource_quota_commands.py +98 -11
- anyscale/commands/service_account_commands.py +65 -8
- anyscale/commands/service_commands.py +61 -1
- anyscale/commands/session_commands_hidden.py +5 -1
- anyscale/commands/user_commands.py +1 -1
- anyscale/commands/util.py +2 -2
- anyscale/commands/workspace_commands.py +1 -1
- anyscale/connect.py +1 -1
- anyscale/connect_utils/project.py +7 -4
- anyscale/controllers/cloud_controller.py +63 -30
- anyscale/controllers/cloud_functional_verification_controller.py +1 -1
- anyscale/controllers/cluster_controller.py +3 -11
- anyscale/controllers/compute_config_controller.py +1 -1
- anyscale/controllers/experimental_integrations_controller.py +1 -1
- anyscale/controllers/job_controller.py +8 -6
- anyscale/controllers/list_controller.py +2 -2
- anyscale/controllers/machine_pool_controller.py +12 -1
- anyscale/controllers/project_controller.py +4 -3
- anyscale/controllers/schedule_controller.py +1 -1
- anyscale/controllers/service_controller.py +1 -1
- anyscale/controllers/workspace_controller.py +1 -1
- anyscale/models/job_model.py +1 -1
- anyscale/organization_invitation/__init__.py +61 -0
- anyscale/organization_invitation/_private/organization_invitation_sdk.py +24 -0
- anyscale/organization_invitation/commands.py +84 -0
- anyscale/organization_invitation/models.py +45 -0
- anyscale/project/__init__.py +35 -0
- anyscale/project/_private/project_sdk.py +27 -0
- anyscale/project/commands.py +56 -0
- anyscale/project/models.py +91 -0
- anyscale/{project.py → project_utils.py} +3 -4
- anyscale/resource_quota/__init__.py +99 -0
- anyscale/resource_quota/_private/resource_quota_sdk.py +120 -0
- anyscale/resource_quota/commands.py +150 -0
- anyscale/resource_quota/models.py +303 -0
- anyscale/scripts.py +4 -0
- anyscale/sdk/anyscale_client/__init__.py +0 -5
- anyscale/sdk/anyscale_client/api/default_api.py +119 -150
- anyscale/sdk/anyscale_client/models/__init__.py +0 -5
- anyscale/sdk/anyscale_client/models/baseimagesenum.py +43 -1
- anyscale/sdk/anyscale_client/models/cluster_status_details.py +2 -1
- anyscale/sdk/anyscale_client/models/jobs_sort_field.py +1 -2
- anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +43 -1
- anyscale/sdk/anyscale_client/sdk.py +1 -1
- anyscale/service/__init__.py +21 -0
- anyscale/service/_private/service_sdk.py +13 -0
- anyscale/service/commands.py +35 -0
- anyscale/service_account/__init__.py +88 -0
- anyscale/service_account/_private/service_account_sdk.py +101 -0
- anyscale/service_account/commands.py +147 -0
- anyscale/service_account/models.py +66 -0
- anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
- anyscale/shared_anyscale_utils/utils/id_gen.py +2 -0
- anyscale/user/__init__.py +1 -1
- anyscale/user/commands.py +1 -1
- anyscale/user/models.py +25 -15
- anyscale/util.py +23 -0
- anyscale/utils/cloud_utils.py +1 -1
- anyscale/version.py +1 -1
- anyscale/workspace_utils.py +1 -1
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/METADATA +1 -5
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/RECORD +134 -119
- anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +0 -156
- anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +0 -353
- anyscale/client/openapi_client/models/finish_ft_job_request.py +0 -204
- anyscale/client/openapi_client/models/platform_fine_tuning_job.py +0 -577
- anyscale/client/openapi_client/models/session_event.py +0 -267
- anyscale/client/openapi_client/models/session_event_cause.py +0 -150
- anyscale/controllers/resource_quota_controller.py +0 -183
- anyscale/controllers/service_account_controller.py +0 -168
- anyscale/sdk/anyscale_client/models/log_level_types.py +0 -100
- anyscale/sdk/anyscale_client/models/session_event.py +0 -267
- anyscale/sdk/anyscale_client/models/session_event_cause.py +0 -150
- anyscale/sdk/anyscale_client/models/session_event_types.py +0 -111
- anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +0 -147
- anyscale/utils/imports/azure.py +0 -14
- /anyscale/{cloud.py → cloud_utils.py} +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/LICENSE +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/NOTICE +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/WHEEL +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/entry_points.txt +0 -0
- {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,19 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import List, Optional
|
2
2
|
|
3
3
|
import click
|
4
|
+
from rich import print as rprint
|
5
|
+
import tabulate
|
4
6
|
|
7
|
+
import anyscale
|
5
8
|
from anyscale.cli_logger import BlockLogger
|
6
|
-
from anyscale.
|
9
|
+
from anyscale.service_account.models import ServiceAccount
|
7
10
|
from anyscale.util import validate_non_negative_arg
|
8
11
|
|
9
12
|
|
13
|
+
DEFAULT_OVERFLOW = "fold"
|
14
|
+
DEFAULT_COL_WIDTH = 36
|
15
|
+
|
16
|
+
|
10
17
|
log = BlockLogger() # CLI Logger
|
11
18
|
|
12
19
|
|
@@ -18,12 +25,45 @@ def service_account_cli() -> None:
|
|
18
25
|
pass
|
19
26
|
|
20
27
|
|
28
|
+
def _print_new_api_key(api_key: str):
|
29
|
+
log.warning(
|
30
|
+
"The following API token for the service account will only appear once:",
|
31
|
+
)
|
32
|
+
log.info(api_key)
|
33
|
+
|
34
|
+
|
35
|
+
def _print_service_account_table(service_accounts: List[ServiceAccount]):
|
36
|
+
table_rows = []
|
37
|
+
for service_account in service_accounts:
|
38
|
+
table_rows.append(
|
39
|
+
[
|
40
|
+
service_account.name,
|
41
|
+
service_account.created_at.strftime("%m/%d/%Y"),
|
42
|
+
service_account.permission_level,
|
43
|
+
service_account.email,
|
44
|
+
]
|
45
|
+
)
|
46
|
+
table = tabulate.tabulate(
|
47
|
+
table_rows,
|
48
|
+
headers=["NAME", "CREATED AT", "ORGANIZATION PERMISSION LEVEL", "EMAIL",],
|
49
|
+
tablefmt="plain",
|
50
|
+
)
|
51
|
+
|
52
|
+
rprint(f"Service accounts:\n{table}")
|
53
|
+
|
54
|
+
|
21
55
|
@service_account_cli.command(name="create", help="Create a service account.")
|
22
56
|
@click.option(
|
23
57
|
"--name", "-n", help="Name for the service account.", type=str, required=True
|
24
58
|
)
|
25
59
|
def create(name: str) -> None:
|
26
|
-
|
60
|
+
try:
|
61
|
+
api_key = anyscale.service_account.create(name)
|
62
|
+
|
63
|
+
log.info(f"Service account {name} created successfully.")
|
64
|
+
_print_new_api_key(api_key)
|
65
|
+
except ValueError as e:
|
66
|
+
log.error(f"Error creating service account: {e}")
|
27
67
|
|
28
68
|
|
29
69
|
@service_account_cli.command(
|
@@ -36,10 +76,14 @@ def create(name: str) -> None:
|
|
36
76
|
"--name", help="Name of the service account to create the new key for.", type=str
|
37
77
|
)
|
38
78
|
def create_api_key(email: Optional[str], name: Optional[str]) -> None:
|
39
|
-
|
79
|
+
try:
|
80
|
+
api_key = anyscale.service_account.create_api_key(email, name)
|
81
|
+
_print_new_api_key(api_key)
|
82
|
+
except ValueError as e:
|
83
|
+
log.error(f"Error creating API key: {e}")
|
40
84
|
|
41
85
|
|
42
|
-
@service_account_cli.command(name="list", help="List
|
86
|
+
@service_account_cli.command(name="list", help="List service accounts.")
|
43
87
|
@click.option(
|
44
88
|
"--max-items",
|
45
89
|
required=False,
|
@@ -49,14 +93,19 @@ def create_api_key(email: Optional[str], name: Optional[str]) -> None:
|
|
49
93
|
callback=validate_non_negative_arg,
|
50
94
|
)
|
51
95
|
def list_service_accounts(max_items: int) -> None:
|
52
|
-
|
96
|
+
service_accounts = anyscale.service_account.list(max_items)
|
97
|
+
_print_service_account_table(service_accounts)
|
53
98
|
|
54
99
|
|
55
100
|
@service_account_cli.command(name="delete", help="Delete a service account.")
|
56
101
|
@click.option("--email", help="Email of the service account to delete.", type=str)
|
57
102
|
@click.option("--name", help="Name of the service account to delete.", type=str)
|
58
103
|
def delete(email: Optional[str], name: Optional[str]) -> None:
|
59
|
-
|
104
|
+
try:
|
105
|
+
anyscale.service_account.delete(email, name)
|
106
|
+
log.info(f"Service account {email or name} deleted successfully.")
|
107
|
+
except ValueError as e:
|
108
|
+
log.error(f"Error deleting service account: {e}")
|
60
109
|
|
61
110
|
|
62
111
|
@service_account_cli.command(
|
@@ -69,4 +118,12 @@ def delete(email: Optional[str], name: Optional[str]) -> None:
|
|
69
118
|
"--name", help="Rotate API keys for the service account with this name.", type=str
|
70
119
|
)
|
71
120
|
def rotate_api_keys(email: Optional[str], name: Optional[str]) -> None:
|
72
|
-
|
121
|
+
try:
|
122
|
+
api_key = anyscale.service_account.rotate_api_keys(email, name)
|
123
|
+
|
124
|
+
log.info(
|
125
|
+
f"\nAll API keys for service account {email or name} rotated successfully."
|
126
|
+
)
|
127
|
+
_print_new_api_key(api_key)
|
128
|
+
except ValueError as e:
|
129
|
+
log.error(f"Error rotating API keys: {e}")
|
@@ -9,7 +9,9 @@ import yaml
|
|
9
9
|
|
10
10
|
from anyscale._private.models.image_uri import ImageURI
|
11
11
|
from anyscale.cli_logger import BlockLogger
|
12
|
+
from anyscale.commands import command_examples
|
12
13
|
from anyscale.commands.util import (
|
14
|
+
AnyscaleCommand,
|
13
15
|
convert_kv_strings_to_dict,
|
14
16
|
LegacyAnyscaleCommand,
|
15
17
|
override_env_vars,
|
@@ -125,7 +127,7 @@ def _read_name_from_config_file(path: str):
|
|
125
127
|
required=False,
|
126
128
|
default=None,
|
127
129
|
type=str,
|
128
|
-
help="Path to a requirements.txt file containing dependencies for the service.
|
130
|
+
help="Path to a requirements.txt file containing dependencies for the service. Anyscale installs these dependencies on top of the image. If you deploy a service from a workspace, the default is to use the workspace dependencies, but specifying this option overrides them.",
|
129
131
|
)
|
130
132
|
@click.option(
|
131
133
|
"-i",
|
@@ -736,3 +738,61 @@ def terminate(
|
|
736
738
|
project_id=project_id,
|
737
739
|
)
|
738
740
|
service_controller.terminate(service_id)
|
741
|
+
|
742
|
+
|
743
|
+
@service_cli.command(
|
744
|
+
name="archive",
|
745
|
+
help="Archive a service.",
|
746
|
+
cls=AnyscaleCommand,
|
747
|
+
example=command_examples.SERVICE_ARCHIVE_EXAMPLE,
|
748
|
+
)
|
749
|
+
@click.option(
|
750
|
+
"-n", "--name", required=False, default=None, type=str, help="Name of the service.",
|
751
|
+
)
|
752
|
+
@click.option(
|
753
|
+
"-f",
|
754
|
+
"--config-file",
|
755
|
+
required=False,
|
756
|
+
default=None,
|
757
|
+
type=str,
|
758
|
+
help="Path to a YAML config file to read the name from.",
|
759
|
+
)
|
760
|
+
@click.option(
|
761
|
+
"--cloud",
|
762
|
+
required=False,
|
763
|
+
default=None,
|
764
|
+
type=str,
|
765
|
+
help="The Anyscale Cloud to run this workload on. If not provided, the organization default will be used (or, if running in a workspace, the cloud of the workspace).",
|
766
|
+
)
|
767
|
+
@click.option(
|
768
|
+
"--project",
|
769
|
+
required=False,
|
770
|
+
default=None,
|
771
|
+
type=str,
|
772
|
+
help="Named project to use for the service. If not provided, the default project for the cloud will be used (or, if running in a workspace, the project of the workspace).",
|
773
|
+
)
|
774
|
+
def archive(
|
775
|
+
name: Optional[str],
|
776
|
+
config_file: Optional[str],
|
777
|
+
cloud: Optional[str],
|
778
|
+
project: Optional[str],
|
779
|
+
) -> None:
|
780
|
+
"""Archive a service.
|
781
|
+
|
782
|
+
To specify the service by name, use the --name flag. To specify the service by id, use the --id flag. Either name or
|
783
|
+
id should be used, specifying both will result in an error.
|
784
|
+
"""
|
785
|
+
if name is not None and config_file is not None:
|
786
|
+
raise click.ClickException(
|
787
|
+
"Only one of '--name' and '--config-file' can be provided."
|
788
|
+
)
|
789
|
+
|
790
|
+
if config_file is not None:
|
791
|
+
name = _read_name_from_config_file(config_file)
|
792
|
+
|
793
|
+
if name is None:
|
794
|
+
raise click.ClickException(
|
795
|
+
"Service name must be provided using '--name' or in a config file using '-f'."
|
796
|
+
)
|
797
|
+
|
798
|
+
anyscale.service.archive(name=name, cloud=cloud, project=project)
|
@@ -4,7 +4,11 @@ from typing import Optional
|
|
4
4
|
|
5
5
|
import click
|
6
6
|
|
7
|
-
from anyscale.
|
7
|
+
from anyscale.project_utils import (
|
8
|
+
get_project_id,
|
9
|
+
get_project_session,
|
10
|
+
load_project_or_throw,
|
11
|
+
)
|
8
12
|
from anyscale.shared_anyscale_utils.util import execution_log_name
|
9
13
|
from anyscale.snapshot import copy_file
|
10
14
|
from anyscale.util import deserialize_datetime, send_json_request
|
anyscale/commands/util.py
CHANGED
@@ -125,8 +125,8 @@ def convert_kv_strings_to_dict(strings: Tuple[str]) -> Dict[str, str]:
|
|
125
125
|
"""
|
126
126
|
ret_dict = {}
|
127
127
|
for s in strings:
|
128
|
-
split = s.split("=")
|
129
|
-
if len(split) != 2:
|
128
|
+
split = s.split("=", maxsplit=1)
|
129
|
+
if len(split) != 2 or len(split[1]) == 0:
|
130
130
|
raise click.ClickException(
|
131
131
|
f"Invalid key-value string '{s}'. Must be of the form 'key=value'."
|
132
132
|
)
|
@@ -15,7 +15,7 @@ from anyscale.commands import workspace_commands_v2
|
|
15
15
|
from anyscale.commands.util import LegacyAnyscaleCommand
|
16
16
|
from anyscale.controllers.cluster_controller import ClusterController
|
17
17
|
from anyscale.controllers.workspace_controller import WorkspaceController
|
18
|
-
from anyscale.
|
18
|
+
from anyscale.project_utils import find_project_root
|
19
19
|
from anyscale.shared_anyscale_utils.utils.byod import BYODInfo
|
20
20
|
from anyscale.util import get_endpoint
|
21
21
|
from anyscale.workspace_utils import (
|
anyscale/connect.py
CHANGED
@@ -48,7 +48,7 @@ from anyscale.connect_utils.start_interactive_session import ( # pylint:disable
|
|
48
48
|
_get_interactive_shell_frame,
|
49
49
|
start_interactive_session_block,
|
50
50
|
)
|
51
|
-
import anyscale.
|
51
|
+
import anyscale.project_utils
|
52
52
|
from anyscale.sdk.anyscale_client.sdk import AnyscaleSDK
|
53
53
|
from anyscale.shared_anyscale_utils.util import slugify
|
54
54
|
from anyscale.util import PROJECT_NAME_ENV_VAR
|
@@ -7,13 +7,14 @@ import yaml
|
|
7
7
|
import anyscale
|
8
8
|
from anyscale.authenticate import get_auth_api_client
|
9
9
|
from anyscale.cli_logger import BlockLogger
|
10
|
-
from anyscale.
|
10
|
+
from anyscale.cloud_utils import (
|
11
11
|
get_cloud_id_and_name,
|
12
12
|
get_last_used_cloud,
|
13
13
|
get_organization_default_cloud,
|
14
14
|
)
|
15
15
|
from anyscale.cluster_compute import get_cluster_compute_from_name
|
16
16
|
import anyscale.conf
|
17
|
+
import anyscale.project_utils
|
17
18
|
from anyscale.sdk.anyscale_client import ComputeTemplateConfig
|
18
19
|
from anyscale.utils.connect_helpers import find_project_id
|
19
20
|
|
@@ -70,7 +71,7 @@ class ProjectBlock:
|
|
70
71
|
self.log.open_block(self.block_label, block_title="Choosing a project")
|
71
72
|
|
72
73
|
if self.project_dir is None and project_name is None:
|
73
|
-
self.project_dir = anyscale.
|
74
|
+
self.project_dir = anyscale.project_utils.find_project_root(os.getcwd())
|
74
75
|
|
75
76
|
parent_cloud_id = self._get_parent_cloud_id(
|
76
77
|
self.cloud_name, self.cluster_compute_name, self.cluster_compute_dict,
|
@@ -186,8 +187,10 @@ class ProjectBlock:
|
|
186
187
|
project_yaml = os.path.join(project_dir, ".anyscale.yaml")
|
187
188
|
if os.path.exists(project_yaml):
|
188
189
|
# Validate format of project yaml and get project id
|
189
|
-
proj_def = anyscale.
|
190
|
-
project_id: Optional[str] = anyscale.
|
190
|
+
proj_def = anyscale.project_utils.ProjectDefinition(project_dir)
|
191
|
+
project_id: Optional[str] = anyscale.project_utils.get_project_id(
|
192
|
+
proj_def.root
|
193
|
+
)
|
191
194
|
if not project_id:
|
192
195
|
raise click.ClickException(
|
193
196
|
f"{project_yaml} is not correctly formatted. Please attach to a different "
|
@@ -5,6 +5,7 @@ Fetches data required and formats output for `anyscale cloud` commands.
|
|
5
5
|
import copy
|
6
6
|
import json
|
7
7
|
from os import getenv
|
8
|
+
import pathlib
|
8
9
|
import re
|
9
10
|
import secrets
|
10
11
|
import time
|
@@ -15,6 +16,7 @@ import boto3
|
|
15
16
|
from botocore.exceptions import ClientError, NoCredentialsError
|
16
17
|
import click
|
17
18
|
from click import Abort, ClickException
|
19
|
+
import yaml
|
18
20
|
|
19
21
|
from anyscale import __version__ as anyscale_version
|
20
22
|
from anyscale.aws_iam_policies import get_anyscale_iam_permissions_ec2_restricted
|
@@ -24,6 +26,7 @@ from anyscale.client.openapi_client.models import (
|
|
24
26
|
CloudAnalyticsEventCloudResource,
|
25
27
|
CloudAnalyticsEventCommandName,
|
26
28
|
CloudAnalyticsEventName,
|
29
|
+
CloudDeploymentConfig,
|
27
30
|
CloudProviders,
|
28
31
|
CloudState,
|
29
32
|
CloudWithCloudResource,
|
@@ -43,12 +46,6 @@ from anyscale.client.openapi_client.models import (
|
|
43
46
|
from anyscale.client.openapi_client.models.gcp_file_store_config import (
|
44
47
|
GCPFileStoreConfig,
|
45
48
|
)
|
46
|
-
from anyscale.cloud import (
|
47
|
-
get_cloud_id_and_name,
|
48
|
-
get_cloud_json_from_id,
|
49
|
-
get_cloud_resource_by_cloud_id,
|
50
|
-
get_organization_id,
|
51
|
-
)
|
52
49
|
from anyscale.cloud_resource import (
|
53
50
|
associate_aws_subnets_with_azs,
|
54
51
|
GCS_STORAGE_PREFIX,
|
@@ -63,6 +60,11 @@ from anyscale.cloud_resource import (
|
|
63
60
|
verify_aws_subnets,
|
64
61
|
verify_aws_vpc,
|
65
62
|
)
|
63
|
+
from anyscale.cloud_utils import (
|
64
|
+
get_cloud_id_and_name,
|
65
|
+
get_cloud_resource_by_cloud_id,
|
66
|
+
get_organization_id,
|
67
|
+
)
|
66
68
|
from anyscale.conf import ANYSCALE_IAM_ROLE_NAME
|
67
69
|
from anyscale.controllers.base_controller import BaseController
|
68
70
|
from anyscale.controllers.cloud_functional_verification_controller import (
|
@@ -1382,23 +1384,28 @@ class CloudController(BaseController):
|
|
1382
1384
|
|
1383
1385
|
def get_cloud_config(
|
1384
1386
|
self, cloud_name: Optional[str] = None, cloud_id: Optional[str] = None,
|
1385
|
-
) ->
|
1387
|
+
) -> CloudDeploymentConfig:
|
1386
1388
|
"""Get a cloud's current JSON configuration."""
|
1387
1389
|
|
1388
1390
|
cloud_id, cloud_name = get_cloud_id_and_name(
|
1389
1391
|
self.api_client, cloud_id, cloud_name
|
1390
1392
|
)
|
1391
1393
|
|
1392
|
-
|
1394
|
+
# In the future we will expose cloud_deployment id as a parameter, for now, it's just a placeholder.
|
1395
|
+
config: CloudDeploymentConfig = self.api_client.get_cloud_deployment_config_api_v2_clouds_cloud_id_deployment_cloud_deployment_id_config_get(
|
1396
|
+
cloud_id=cloud_id, cloud_deployment_id="default"
|
1397
|
+
).result
|
1398
|
+
|
1399
|
+
return config
|
1393
1400
|
|
1394
1401
|
def update_cloud_config(
|
1395
1402
|
self,
|
1396
1403
|
cloud_name: Optional[str] = None,
|
1397
1404
|
cloud_id: Optional[str] = None,
|
1398
1405
|
enable_log_ingestion: Optional[bool] = None,
|
1406
|
+
spec_file: Optional[str] = None,
|
1399
1407
|
):
|
1400
1408
|
"""Update a cloud's configuration."""
|
1401
|
-
|
1402
1409
|
cloud_id, cloud_name = get_cloud_id_and_name(
|
1403
1410
|
self.api_client, cloud_id, cloud_name
|
1404
1411
|
)
|
@@ -1410,6 +1417,24 @@ class CloudController(BaseController):
|
|
1410
1417
|
f"Successfully updated log ingestion configuration for cloud, "
|
1411
1418
|
f"{cloud_id} to {enable_log_ingestion}"
|
1412
1419
|
)
|
1420
|
+
elif spec_file is not None:
|
1421
|
+
path = pathlib.Path(spec_file)
|
1422
|
+
if not path.exists():
|
1423
|
+
raise FileNotFoundError(f"File {spec_file} does not exist.")
|
1424
|
+
|
1425
|
+
if not path.is_file():
|
1426
|
+
raise ValueError(f"File {spec_file} is not a file.")
|
1427
|
+
|
1428
|
+
spec = yaml.safe_load(path.read_text())
|
1429
|
+
config = CloudDeploymentConfig(spec=spec)
|
1430
|
+
self.api_client.update_cloud_deployment_config_api_v2_clouds_cloud_id_deployment_cloud_deployment_id_config_put(
|
1431
|
+
cloud_id=cloud_id,
|
1432
|
+
cloud_deployment_id="default",
|
1433
|
+
cloud_deployment_config=config,
|
1434
|
+
)
|
1435
|
+
self.log.info(
|
1436
|
+
f"Successfully updated cloud configuration for cloud {cloud_name}"
|
1437
|
+
)
|
1413
1438
|
|
1414
1439
|
def set_default_cloud(
|
1415
1440
|
self, cloud_name: Optional[str], cloud_id: Optional[str],
|
@@ -1641,12 +1666,14 @@ class CloudController(BaseController):
|
|
1641
1666
|
logger=logger,
|
1642
1667
|
strict=strict,
|
1643
1668
|
)
|
1644
|
-
verify_aws_efs_result =
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1669
|
+
verify_aws_efs_result = True
|
1670
|
+
if cloud_resource.aws_efs_id:
|
1671
|
+
verify_aws_efs_result = verify_aws_efs(
|
1672
|
+
cloud_resource=cloud_resource,
|
1673
|
+
boto3_session=boto3_session,
|
1674
|
+
logger=logger,
|
1675
|
+
strict=strict,
|
1676
|
+
)
|
1650
1677
|
# Cloudformation is only used in managed cloud setup. Set to True in BYOR case because it's not used.
|
1651
1678
|
verify_aws_cloudformation_stack_result = True
|
1652
1679
|
if not is_bring_your_own_resource:
|
@@ -1676,9 +1703,12 @@ class CloudController(BaseController):
|
|
1676
1703
|
f"iam roles: {self._passed_or_failed_str_from_bool(verify_aws_iam_roles_result)}",
|
1677
1704
|
f"security groups: {self._passed_or_failed_str_from_bool(verify_aws_security_groups_result)}",
|
1678
1705
|
f"s3: {self._passed_or_failed_str_from_bool(verify_aws_s3_result)}",
|
1679
|
-
f"efs: {self._passed_or_failed_str_from_bool(verify_aws_efs_result)}",
|
1680
1706
|
f"cloudformation stack: {self._passed_or_failed_str_from_bool(verify_aws_cloudformation_stack_result) if not is_bring_your_own_resource else 'N/A'}",
|
1681
1707
|
]
|
1708
|
+
if cloud_resource.aws_efs_id:
|
1709
|
+
verification_result_summary.append(
|
1710
|
+
f"efs: {self._passed_or_failed_str_from_bool(verify_aws_efs_result)}"
|
1711
|
+
)
|
1682
1712
|
if cloud_resource.memorydb_cluster_config is not None:
|
1683
1713
|
verification_result_summary.append(
|
1684
1714
|
f"memorydb cluster: {self._passed_or_failed_str_from_bool(verify_aws_memorydb_cluster_result)}"
|
@@ -1875,7 +1905,7 @@ class CloudController(BaseController):
|
|
1875
1905
|
name: str,
|
1876
1906
|
vpc_id: str,
|
1877
1907
|
subnet_ids: List[str],
|
1878
|
-
efs_id: str,
|
1908
|
+
efs_id: Optional[str],
|
1879
1909
|
anyscale_iam_role_id: str,
|
1880
1910
|
instance_iam_role_id: str,
|
1881
1911
|
security_group_ids: List[str],
|
@@ -2012,10 +2042,7 @@ class CloudController(BaseController):
|
|
2012
2042
|
# When running on the VM compute stack, validate and retrieve the EFS mount target IP.
|
2013
2043
|
# When running on the K8S compute stack, EFS is optional; if efs_id is provided, then
|
2014
2044
|
# validate and retrieve the EFS mount target IP.
|
2015
|
-
|
2016
|
-
compute_stack == ComputeStack.K8S and efs_id
|
2017
|
-
)
|
2018
|
-
if enable_efs:
|
2045
|
+
if efs_id:
|
2019
2046
|
try:
|
2020
2047
|
boto3_session = boto3.Session(region_name=region)
|
2021
2048
|
aws_efs_mount_target_ip = _get_aws_efs_mount_target_ip(
|
@@ -2106,7 +2133,8 @@ class CloudController(BaseController):
|
|
2106
2133
|
# TODO (shomilj): Add verification to the K8S compute stack as well.
|
2107
2134
|
if compute_stack == ComputeStack.VM:
|
2108
2135
|
with self.log.spinner("Verifying cloud resources...") as spinner:
|
2109
|
-
|
2136
|
+
if boto3_session is None:
|
2137
|
+
boto3_session = boto3.Session(region_name=region)
|
2110
2138
|
if not skip_verifications and not self.verify_aws_cloud_resources(
|
2111
2139
|
cloud_resource=create_cloud_resource,
|
2112
2140
|
boto3_session=boto3_session,
|
@@ -2264,15 +2292,17 @@ class CloudController(BaseController):
|
|
2264
2292
|
gcp_logger,
|
2265
2293
|
strict=strict,
|
2266
2294
|
)
|
2267
|
-
verify_filestore_result = verify_lib.verify_filestore(
|
2268
|
-
factory, cloud_resource, region, gcp_logger, strict=strict
|
2269
|
-
)
|
2270
2295
|
verify_cloud_storage_result = verify_lib.verify_cloud_storage(
|
2271
2296
|
factory, cloud_resource, project_id, region, gcp_logger, strict=strict,
|
2272
2297
|
)
|
2273
2298
|
verify_anyscale_access_result = verify_anyscale_access(
|
2274
2299
|
self.api_client, cloud_id, CloudProviders.GCP, self.log
|
2275
2300
|
)
|
2301
|
+
verify_filestore_result = True
|
2302
|
+
if cloud_resource.gcp_filestore_config.instance_name:
|
2303
|
+
verify_filestore_result = verify_lib.verify_filestore(
|
2304
|
+
factory, cloud_resource, region, gcp_logger, strict=strict
|
2305
|
+
)
|
2276
2306
|
verify_memorystore_result = True
|
2277
2307
|
if cloud_resource.memorystore_instance_config is not None:
|
2278
2308
|
verify_memorystore_result = verify_lib.verify_memorystore(
|
@@ -2287,10 +2317,13 @@ class CloudController(BaseController):
|
|
2287
2317
|
f"anyscale iam service account: {self._passed_or_failed_str_from_bool(verify_gcp_access_service_account_result)}",
|
2288
2318
|
f"cluster node service account: {self._passed_or_failed_str_from_bool(verify_gcp_dataplane_service_account_result)}",
|
2289
2319
|
f"firewall policy: {self._passed_or_failed_str_from_bool(verify_firewall_policy_result)}",
|
2290
|
-
f"filestore: {self._passed_or_failed_str_from_bool(verify_filestore_result)}",
|
2291
2320
|
f"cloud storage: {self._passed_or_failed_str_from_bool(verify_cloud_storage_result)}",
|
2292
2321
|
]
|
2293
2322
|
|
2323
|
+
if cloud_resource.gcp_filestore_config.instance_name:
|
2324
|
+
verification_results.append(
|
2325
|
+
f"filestore: {self._passed_or_failed_str_from_bool(verify_filestore_result)}"
|
2326
|
+
)
|
2294
2327
|
if cloud_resource.memorystore_instance_config is not None:
|
2295
2328
|
verification_results.append(
|
2296
2329
|
f"memorystore: {self._passed_or_failed_str_from_bool(verify_memorystore_result)}"
|
@@ -2425,9 +2458,7 @@ class CloudController(BaseController):
|
|
2425
2458
|
instance_service_account_email = ""
|
2426
2459
|
subnet_names = []
|
2427
2460
|
|
2428
|
-
enable_filestore =
|
2429
|
-
filestore_location and filestore_instance_id
|
2430
|
-
)
|
2461
|
+
enable_filestore = filestore_location and filestore_instance_id
|
2431
2462
|
|
2432
2463
|
# Normally, for Kubernetes clouds, we don't need a VPC name, since networking is managed by Kubernetes.
|
2433
2464
|
# For Kubernetes clouds on GCP where Filestore is enabled, we require the VPC name, since it is needed
|
@@ -2458,7 +2489,9 @@ class CloudController(BaseController):
|
|
2458
2489
|
filestore_config = GCPFileStoreConfig(
|
2459
2490
|
instance_name="", mount_target_ip="", root_dir=""
|
2460
2491
|
)
|
2461
|
-
|
2492
|
+
if compute_stack == ComputeStack.K8S:
|
2493
|
+
# Set vpc_name to empty string for Kubernetes clouds
|
2494
|
+
vpc_name = ""
|
2462
2495
|
|
2463
2496
|
if memorystore_instance_name:
|
2464
2497
|
memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
|
@@ -30,7 +30,7 @@ from anyscale.client.openapi_client.models import (
|
|
30
30
|
SessionState,
|
31
31
|
)
|
32
32
|
from anyscale.controllers.base_controller import BaseController
|
33
|
-
from anyscale.
|
33
|
+
from anyscale.project_utils import get_default_project
|
34
34
|
from anyscale.sdk.anyscale_client.models import (
|
35
35
|
ComputeNodeType,
|
36
36
|
ComputeTemplateQuery,
|
@@ -7,7 +7,7 @@ import tabulate
|
|
7
7
|
import yaml
|
8
8
|
|
9
9
|
from anyscale.cli_logger import BlockLogger
|
10
|
-
from anyscale.
|
10
|
+
from anyscale.cloud_utils import get_cloud_id_and_name, get_last_used_cloud
|
11
11
|
from anyscale.cluster_compute import (
|
12
12
|
get_cluster_compute_from_name,
|
13
13
|
get_default_cluster_compute,
|
@@ -18,7 +18,7 @@ from anyscale.cluster_env import (
|
|
18
18
|
get_default_cluster_env_build,
|
19
19
|
)
|
20
20
|
from anyscale.controllers.base_controller import BaseController
|
21
|
-
from anyscale.
|
21
|
+
from anyscale.project_utils import (
|
22
22
|
get_and_validate_project_id,
|
23
23
|
get_proj_id_from_name,
|
24
24
|
get_project_id,
|
@@ -411,7 +411,6 @@ class ClusterController(BaseController):
|
|
411
411
|
cluster.id,
|
412
412
|
cluster.state,
|
413
413
|
cluster.cloud_id,
|
414
|
-
cluster.cost_since_restarted_dollars,
|
415
414
|
get_endpoint(f"/projects/{cluster.project_id}/clusters/{cluster.id}"),
|
416
415
|
]
|
417
416
|
for cluster in cluster_list
|
@@ -419,14 +418,7 @@ class ClusterController(BaseController):
|
|
419
418
|
|
420
419
|
table = tabulate.tabulate(
|
421
420
|
clusters_table,
|
422
|
-
headers=[
|
423
|
-
"NAME",
|
424
|
-
"ID",
|
425
|
-
"STATE",
|
426
|
-
"CLOUD ID",
|
427
|
-
"COST SINCE LAST START",
|
428
|
-
"URL",
|
429
|
-
],
|
421
|
+
headers=["NAME", "ID", "STATE", "CLOUD ID", "URL",],
|
430
422
|
tablefmt="plain",
|
431
423
|
)
|
432
424
|
print(f"Clusters:\n{table}")
|
@@ -14,7 +14,7 @@ from anyscale.anyscale_pydantic import (
|
|
14
14
|
validator,
|
15
15
|
)
|
16
16
|
from anyscale.cli_logger import BlockLogger
|
17
|
-
from anyscale.
|
17
|
+
from anyscale.cloud_utils import get_cloud_id_and_name
|
18
18
|
from anyscale.cluster_compute import get_cluster_compute_from_name
|
19
19
|
from anyscale.conf import IDLE_TIMEOUT_DEFAULT_MINUTES
|
20
20
|
from anyscale.controllers.base_controller import BaseController
|
@@ -3,7 +3,7 @@ from typing import Optional
|
|
3
3
|
import click
|
4
4
|
|
5
5
|
from anyscale.cli_logger import BlockLogger
|
6
|
-
from anyscale.
|
6
|
+
from anyscale.cloud_utils import get_cloud_id_and_name
|
7
7
|
from anyscale.controllers.base_controller import BaseController
|
8
8
|
|
9
9
|
|
@@ -29,7 +29,7 @@ from anyscale.client.openapi_client.models.decorated_production_job import (
|
|
29
29
|
from anyscale.client.openapi_client.models.ha_job_states import HaJobStates
|
30
30
|
from anyscale.controllers.base_controller import BaseController
|
31
31
|
from anyscale.models.job_model import JobConfig
|
32
|
-
from anyscale.
|
32
|
+
from anyscale.project_utils import infer_project_id
|
33
33
|
from anyscale.sdk.anyscale_client.models.job import Job
|
34
34
|
from anyscale.sdk.anyscale_client.models.jobs_query import JobsQuery
|
35
35
|
from anyscale.sdk.anyscale_client.models.jobs_sort_field import JobsSortField
|
@@ -65,6 +65,8 @@ _PENDING_STATES = {
|
|
65
65
|
HaJobStates.RESTARTING,
|
66
66
|
}
|
67
67
|
|
68
|
+
DEFAULT_PAGE_LIMIT = 500
|
69
|
+
|
68
70
|
|
69
71
|
class MiniJobRun(BaseModel):
|
70
72
|
last_job_run_id: str
|
@@ -292,6 +294,7 @@ class JobController(BaseController):
|
|
292
294
|
project_id: Optional[str],
|
293
295
|
include_archived: bool,
|
294
296
|
max_items: int,
|
297
|
+
states: List[str],
|
295
298
|
) -> None:
|
296
299
|
"""
|
297
300
|
This function will list jobs.
|
@@ -313,7 +316,6 @@ class JobController(BaseController):
|
|
313
316
|
output_map = {
|
314
317
|
"Name": job.name,
|
315
318
|
"Id": job.id,
|
316
|
-
"Cost (dollars)": job.cost_dollars,
|
317
319
|
"Project name": job.project.name,
|
318
320
|
"Cluster name": job.last_job_run.cluster.name
|
319
321
|
if job.last_job_run and job.last_job_run.cluster
|
@@ -342,7 +344,8 @@ class JobController(BaseController):
|
|
342
344
|
creator_id=creator_id,
|
343
345
|
type_filter="BATCH_JOB",
|
344
346
|
archive_status="ALL" if include_archived else "NOT_ARCHIVED",
|
345
|
-
count=
|
347
|
+
count=DEFAULT_PAGE_LIMIT,
|
348
|
+
state_filter=states,
|
346
349
|
)
|
347
350
|
jobs_list.extend(resp.results)
|
348
351
|
paging_token = resp.metadata.next_paging_token
|
@@ -354,8 +357,9 @@ class JobController(BaseController):
|
|
354
357
|
creator_id=creator_id,
|
355
358
|
type_filter="BATCH_JOB",
|
356
359
|
archive_status="ALL" if include_archived else "NOT_ARCHIVED",
|
357
|
-
count=
|
360
|
+
count=DEFAULT_PAGE_LIMIT,
|
358
361
|
paging_token=paging_token,
|
362
|
+
state_filter=states,
|
359
363
|
)
|
360
364
|
jobs_list.extend(resp.results)
|
361
365
|
paging_token = resp.metadata.next_paging_token
|
@@ -366,7 +370,6 @@ class JobController(BaseController):
|
|
366
370
|
[
|
367
371
|
job.name,
|
368
372
|
job.id,
|
369
|
-
job.cost_dollars,
|
370
373
|
job.project.name,
|
371
374
|
job.last_job_run.cluster.name
|
372
375
|
if job.last_job_run and job.last_job_run.cluster
|
@@ -385,7 +388,6 @@ class JobController(BaseController):
|
|
385
388
|
headers=[
|
386
389
|
"NAME",
|
387
390
|
"ID",
|
388
|
-
"COST",
|
389
391
|
"PROJECT NAME",
|
390
392
|
"CLUSTER NAME",
|
391
393
|
"CURRENT STATE",
|