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.
Files changed (148) hide show
  1. anyscale/__init__.py +56 -0
  2. anyscale/_private/anyscale_client/anyscale_client.py +179 -28
  3. anyscale/_private/anyscale_client/common.py +109 -2
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +239 -1
  5. anyscale/_private/docgen/README.md +1 -1
  6. anyscale/_private/docgen/__main__.py +71 -21
  7. anyscale/_private/docgen/api.md +13 -20
  8. anyscale/_private/docgen/generator.py +3 -2
  9. anyscale/_private/docgen/models.md +4 -49
  10. anyscale/_private/workload/workload_config.py +21 -7
  11. anyscale/aggregated_instance_usage/__init__.py +1 -1
  12. anyscale/aggregated_instance_usage/commands.py +2 -4
  13. anyscale/aggregated_instance_usage/models.py +8 -8
  14. anyscale/client/README.md +25 -22
  15. anyscale/client/openapi_client/__init__.py +16 -14
  16. anyscale/client/openapi_client/api/default_api.py +1139 -959
  17. anyscale/client/openapi_client/models/__init__.py +16 -14
  18. anyscale/client/openapi_client/models/baseimagesenum.py +43 -1
  19. anyscale/client/openapi_client/models/{session_event_types.py → cloud_deployment_config.py} +35 -24
  20. anyscale/client/openapi_client/models/{platformfinetuningjob_response.py → clouddeploymentconfig_response.py} +11 -11
  21. anyscale/client/openapi_client/models/{log_level_types.py → cluster_event_source.py} +12 -7
  22. anyscale/client/openapi_client/models/{company_size.py → cluster_size.py} +10 -10
  23. anyscale/client/openapi_client/models/cluster_status_details.py +2 -1
  24. anyscale/client/openapi_client/models/{sessionevent_list_response.py → clusterevent_list_response.py} +15 -15
  25. anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
  26. anyscale/client/openapi_client/models/create_notification_channel_record.py +29 -3
  27. anyscale/client/openapi_client/models/decorated_interactive_session.py +1 -57
  28. anyscale/client/openapi_client/models/decorated_job.py +1 -57
  29. anyscale/client/openapi_client/models/decorated_job_submission.py +1 -29
  30. anyscale/client/openapi_client/models/decorated_production_job.py +1 -29
  31. anyscale/client/openapi_client/models/decorated_session.py +1 -57
  32. anyscale/client/openapi_client/models/decorated_unified_job.py +1 -30
  33. anyscale/client/openapi_client/models/{resubmit_ft_job_request.py → describe_machine_pool_request.py} +21 -20
  34. anyscale/client/openapi_client/models/describe_machine_pool_response.py +123 -0
  35. anyscale/client/openapi_client/models/describemachinepoolresponse_response.py +121 -0
  36. anyscale/client/openapi_client/models/ha_jobs_sort_field.py +1 -2
  37. anyscale/client/openapi_client/models/internal_production_job.py +1 -29
  38. anyscale/client/openapi_client/models/jobs_sort_field.py +1 -2
  39. anyscale/client/openapi_client/models/machine_allocation_state.py +3 -1
  40. anyscale/client/openapi_client/models/machine_state_info.py +326 -0
  41. anyscale/client/openapi_client/models/{fine_tuning_job_status.py → notification_channel_slack_config.py} +34 -16
  42. anyscale/client/openapi_client/models/organization_marketing_questions.py +80 -54
  43. anyscale/client/openapi_client/models/request_state_info.py +210 -0
  44. anyscale/client/openapi_client/models/{platformfinetuningjob_list_response.py → scheduler_info.py} +43 -38
  45. anyscale/client/openapi_client/models/serve_deployment_fast_api_docs_status.py +123 -0
  46. anyscale/client/openapi_client/models/serve_deployment_state.py +2 -1
  47. anyscale/client/openapi_client/models/servedeploymentfastapidocsstatus_response.py +121 -0
  48. anyscale/client/openapi_client/models/sessions_sort_field.py +1 -2
  49. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +43 -1
  50. anyscale/client/openapi_client/models/unified_job_sort_field.py +1 -2
  51. anyscale/client/openapi_client/models/update_cloud_collaborator.py +121 -0
  52. anyscale/client/openapi_client/models/usage_by_cluster.py +28 -1
  53. anyscale/client/openapi_client/models/usage_by_user.py +30 -3
  54. anyscale/client/openapi_client/models/workload_info.py +210 -0
  55. anyscale/cloud/__init__.py +83 -0
  56. anyscale/cloud/_private/cloud_sdk.py +25 -0
  57. anyscale/cloud/commands.py +45 -0
  58. anyscale/cloud/models.py +91 -0
  59. anyscale/cluster_compute.py +1 -1
  60. anyscale/commands/aggregated_instance_usage_commands.py +4 -4
  61. anyscale/commands/cloud_commands.py +87 -14
  62. anyscale/commands/command_examples.py +65 -0
  63. anyscale/commands/job_commands.py +15 -3
  64. anyscale/commands/machine_pool_commands.py +113 -1
  65. anyscale/commands/organization_invitation_commands.py +98 -0
  66. anyscale/commands/project_commands.py +52 -2
  67. anyscale/commands/resource_quota_commands.py +98 -11
  68. anyscale/commands/service_account_commands.py +65 -8
  69. anyscale/commands/service_commands.py +61 -1
  70. anyscale/commands/session_commands_hidden.py +5 -1
  71. anyscale/commands/user_commands.py +1 -1
  72. anyscale/commands/util.py +2 -2
  73. anyscale/commands/workspace_commands.py +1 -1
  74. anyscale/connect.py +1 -1
  75. anyscale/connect_utils/project.py +7 -4
  76. anyscale/controllers/cloud_controller.py +63 -30
  77. anyscale/controllers/cloud_functional_verification_controller.py +1 -1
  78. anyscale/controllers/cluster_controller.py +3 -11
  79. anyscale/controllers/compute_config_controller.py +1 -1
  80. anyscale/controllers/experimental_integrations_controller.py +1 -1
  81. anyscale/controllers/job_controller.py +8 -6
  82. anyscale/controllers/list_controller.py +2 -2
  83. anyscale/controllers/machine_pool_controller.py +12 -1
  84. anyscale/controllers/project_controller.py +4 -3
  85. anyscale/controllers/schedule_controller.py +1 -1
  86. anyscale/controllers/service_controller.py +1 -1
  87. anyscale/controllers/workspace_controller.py +1 -1
  88. anyscale/models/job_model.py +1 -1
  89. anyscale/organization_invitation/__init__.py +61 -0
  90. anyscale/organization_invitation/_private/organization_invitation_sdk.py +24 -0
  91. anyscale/organization_invitation/commands.py +84 -0
  92. anyscale/organization_invitation/models.py +45 -0
  93. anyscale/project/__init__.py +35 -0
  94. anyscale/project/_private/project_sdk.py +27 -0
  95. anyscale/project/commands.py +56 -0
  96. anyscale/project/models.py +91 -0
  97. anyscale/{project.py → project_utils.py} +3 -4
  98. anyscale/resource_quota/__init__.py +99 -0
  99. anyscale/resource_quota/_private/resource_quota_sdk.py +120 -0
  100. anyscale/resource_quota/commands.py +150 -0
  101. anyscale/resource_quota/models.py +303 -0
  102. anyscale/scripts.py +4 -0
  103. anyscale/sdk/anyscale_client/__init__.py +0 -5
  104. anyscale/sdk/anyscale_client/api/default_api.py +119 -150
  105. anyscale/sdk/anyscale_client/models/__init__.py +0 -5
  106. anyscale/sdk/anyscale_client/models/baseimagesenum.py +43 -1
  107. anyscale/sdk/anyscale_client/models/cluster_status_details.py +2 -1
  108. anyscale/sdk/anyscale_client/models/jobs_sort_field.py +1 -2
  109. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +43 -1
  110. anyscale/sdk/anyscale_client/sdk.py +1 -1
  111. anyscale/service/__init__.py +21 -0
  112. anyscale/service/_private/service_sdk.py +13 -0
  113. anyscale/service/commands.py +35 -0
  114. anyscale/service_account/__init__.py +88 -0
  115. anyscale/service_account/_private/service_account_sdk.py +101 -0
  116. anyscale/service_account/commands.py +147 -0
  117. anyscale/service_account/models.py +66 -0
  118. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  119. anyscale/shared_anyscale_utils/utils/id_gen.py +2 -0
  120. anyscale/user/__init__.py +1 -1
  121. anyscale/user/commands.py +1 -1
  122. anyscale/user/models.py +25 -15
  123. anyscale/util.py +23 -0
  124. anyscale/utils/cloud_utils.py +1 -1
  125. anyscale/version.py +1 -1
  126. anyscale/workspace_utils.py +1 -1
  127. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/METADATA +1 -5
  128. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/RECORD +134 -119
  129. anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +0 -156
  130. anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +0 -353
  131. anyscale/client/openapi_client/models/finish_ft_job_request.py +0 -204
  132. anyscale/client/openapi_client/models/platform_fine_tuning_job.py +0 -577
  133. anyscale/client/openapi_client/models/session_event.py +0 -267
  134. anyscale/client/openapi_client/models/session_event_cause.py +0 -150
  135. anyscale/controllers/resource_quota_controller.py +0 -183
  136. anyscale/controllers/service_account_controller.py +0 -168
  137. anyscale/sdk/anyscale_client/models/log_level_types.py +0 -100
  138. anyscale/sdk/anyscale_client/models/session_event.py +0 -267
  139. anyscale/sdk/anyscale_client/models/session_event_cause.py +0 -150
  140. anyscale/sdk/anyscale_client/models/session_event_types.py +0 -111
  141. anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +0 -147
  142. anyscale/utils/imports/azure.py +0 -14
  143. /anyscale/{cloud.py → cloud_utils.py} +0 -0
  144. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/LICENSE +0 -0
  145. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/NOTICE +0 -0
  146. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/WHEEL +0 -0
  147. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/entry_points.txt +0 -0
  148. {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.controllers.service_account_controller import ServiceAccountController
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
- ServiceAccountController().create_service_account(name)
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
- ServiceAccountController().create_new_service_account_api_key(email, name)
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 all service accounts.")
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
- ServiceAccountController().list_service_accounts(max_items)
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
- ServiceAccountController().delete_service_account(email, name)
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
- ServiceAccountController().rotate_service_account_api_keys(email, name)
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. These will be installed on top of the image. When running in a workspace, this defaults to the workspace dependencies.",
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.project import get_project_id, get_project_session, load_project_or_throw
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
@@ -29,7 +29,7 @@ def user_cli() -> None:
29
29
  )
30
30
  def admin_batch_create(users_file: str,) -> None:
31
31
  """
32
- Batch create users without email verification as an admin.
32
+ Batch create, as an admin, users without email verification.
33
33
  """
34
34
  log.info("Creating users...")
35
35
 
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.project import find_project_root
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.project
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.cloud import (
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.project.find_project_root(os.getcwd())
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.project.ProjectDefinition(project_dir)
190
- project_id: Optional[str] = anyscale.project.get_project_id(proj_def.root)
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
- ) -> str:
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
- return str(get_cloud_json_from_id(cloud_id, self.api_client)["config"])
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 = verify_aws_efs(
1645
- cloud_resource=cloud_resource,
1646
- boto3_session=boto3_session,
1647
- logger=logger,
1648
- strict=strict,
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
- enable_efs = compute_stack == ComputeStack.VM or (
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
- assert boto3_session is not None
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 = compute_stack == ComputeStack.VM or (
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
- vpc_name = ""
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.project import get_default_project
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.cloud import get_cloud_id_and_name, get_last_used_cloud
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.project import (
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.cloud import get_cloud_id_and_name
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.cloud import get_cloud_id_and_name
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.project import infer_project_id
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=10,
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=10,
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",