anyscale 0.26.43__py3-none-any.whl → 0.26.45__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 (41) hide show
  1. anyscale/_private/anyscale_client/common.py +1 -1
  2. anyscale/_private/docgen/__main__.py +2 -0
  3. anyscale/_private/docgen/models.md +2 -2
  4. anyscale/_private/workload/workload_sdk.py +6 -0
  5. anyscale/client/README.md +9 -0
  6. anyscale/client/openapi_client/__init__.py +7 -0
  7. anyscale/client/openapi_client/api/default_api.py +238 -5
  8. anyscale/client/openapi_client/models/__init__.py +7 -0
  9. anyscale/client/openapi_client/models/baseimagesenum.py +68 -1
  10. anyscale/client/openapi_client/models/describe_machine_pool_machines_filters.py +31 -3
  11. anyscale/client/openapi_client/models/describe_machine_pool_requests_filters.py +150 -0
  12. anyscale/client/openapi_client/models/describe_machine_pool_requests_request.py +19 -19
  13. anyscale/client/openapi_client/models/describe_machine_pool_workloads_filters.py +150 -0
  14. anyscale/client/openapi_client/models/describe_machine_pool_workloads_request.py +151 -0
  15. anyscale/client/openapi_client/models/file_storage.py +33 -5
  16. anyscale/client/openapi_client/models/plan_status.py +123 -0
  17. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +68 -1
  18. anyscale/client/openapi_client/models/workload_machine_info.py +210 -0
  19. anyscale/client/openapi_client/models/workload_state_info.py +295 -0
  20. anyscale/client/openapi_client/models/workloadstateinfo_list_response.py +147 -0
  21. anyscale/commands/cloud_commands.py +1 -0
  22. anyscale/commands/compute_config_commands.py +10 -3
  23. anyscale/compute_config/__init__.py +16 -0
  24. anyscale/compute_config/_private/compute_config_sdk.py +172 -60
  25. anyscale/compute_config/commands.py +66 -1
  26. anyscale/compute_config/models.py +160 -3
  27. anyscale/conf.py +1 -1
  28. anyscale/controllers/cloud_controller.py +141 -3
  29. anyscale/job/_private/job_sdk.py +22 -0
  30. anyscale/sdk/anyscale_client/models/baseimagesenum.py +68 -1
  31. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +68 -1
  32. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  33. anyscale/telemetry.py +36 -44
  34. anyscale/version.py +1 -1
  35. {anyscale-0.26.43.dist-info → anyscale-0.26.45.dist-info}/METADATA +4 -3
  36. {anyscale-0.26.43.dist-info → anyscale-0.26.45.dist-info}/RECORD +41 -34
  37. {anyscale-0.26.43.dist-info → anyscale-0.26.45.dist-info}/LICENSE +0 -0
  38. {anyscale-0.26.43.dist-info → anyscale-0.26.45.dist-info}/NOTICE +0 -0
  39. {anyscale-0.26.43.dist-info → anyscale-0.26.45.dist-info}/WHEEL +0 -0
  40. {anyscale-0.26.43.dist-info → anyscale-0.26.45.dist-info}/entry_points.txt +0 -0
  41. {anyscale-0.26.43.dist-info → anyscale-0.26.45.dist-info}/top_level.txt +0 -0
@@ -10,16 +10,20 @@ from anyscale.compute_config.commands import (
10
10
  _ARCHIVE_EXAMPLE,
11
11
  _CREATE_ARG_DOCSTRINGS,
12
12
  _CREATE_EXAMPLE,
13
+ _CREATE_MULTI_DEPLOYMENT_ARG_DOCSTRINGS,
14
+ _CREATE_MULTI_DEPLOYMENT_EXAMPLE,
13
15
  _GET_ARG_DOCSTRINGS,
14
16
  _GET_EXAMPLE,
15
17
  archive,
16
18
  create,
19
+ create_multi_deployment,
17
20
  get,
18
21
  )
19
22
  from anyscale.compute_config.models import (
20
23
  ComputeConfig,
21
24
  ComputeConfigVersion,
22
25
  HeadNodeConfig,
26
+ MultiDeploymentComputeConfig,
23
27
  WorkerNodeGroupConfig,
24
28
  )
25
29
 
@@ -49,6 +53,18 @@ class ComputeConfigSDK:
49
53
  full_name, _ = self._private_sdk.create_compute_config(config, name=name)
50
54
  return full_name
51
55
 
56
+ @sdk_docs(
57
+ doc_py_example=_CREATE_MULTI_DEPLOYMENT_EXAMPLE,
58
+ arg_docstrings=_CREATE_MULTI_DEPLOYMENT_ARG_DOCSTRINGS,
59
+ )
60
+ def create_multi_deployment( # noqa: F811
61
+ self, config: MultiDeploymentComputeConfig, *, name: Optional[str],
62
+ ) -> str:
63
+ full_name, _ = self._private_sdk.create_multi_deployment_compute_config(
64
+ config, name=name
65
+ )
66
+ return full_name
67
+
52
68
  @sdk_docs(
53
69
  doc_py_example=_GET_EXAMPLE, arg_docstrings=_GET_ARG_DOCSTRINGS,
54
70
  )
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
4
4
  from anyscale._private.sdk.base_sdk import BaseSDK
5
5
  from anyscale.client.openapi_client.models import (
6
6
  Cloud,
7
- CloudProviders,
7
+ CloudDeploymentComputeConfig,
8
8
  ComputeNodeType,
9
9
  ComputeTemplateConfig,
10
10
  DecoratedComputeTemplate,
@@ -19,6 +19,7 @@ from anyscale.compute_config.models import (
19
19
  ComputeConfigVersion,
20
20
  HeadNodeConfig,
21
21
  MarketType,
22
+ MultiDeploymentComputeConfig,
22
23
  WorkerNodeGroupConfig,
23
24
  )
24
25
  from anyscale.sdk.anyscale_client.models import ClusterComputeConfig
@@ -31,26 +32,6 @@ UNSCHEDULABLE_RESOURCES = Resources(cpu=0, gpu=0)
31
32
 
32
33
 
33
34
  class PrivateComputeConfigSDK(BaseSDK):
34
- def _populate_advanced_instance_config(
35
- self,
36
- config: Union[ComputeConfig, HeadNodeConfig, WorkerNodeGroupConfig],
37
- api_model: Union[ComputeTemplateConfig, ComputeNodeType, WorkerNodeType],
38
- *,
39
- cloud: Cloud,
40
- ):
41
- """Populates the appropriate advanced instance config field of the API model in place."""
42
- if not config.advanced_instance_config:
43
- return
44
-
45
- # Always pass the advanced configuration through `advanced_configurations_json`.
46
- # After fully migrated, we can stop setting the cloud-specific advanced configurations here.
47
- api_model.advanced_configurations_json = config.advanced_instance_config
48
-
49
- if cloud.provider == CloudProviders.AWS:
50
- api_model.aws_advanced_configurations_json = config.advanced_instance_config
51
- elif cloud.provider == CloudProviders.GCP:
52
- api_model.gcp_advanced_configurations_json = config.advanced_instance_config
53
-
54
35
  def _convert_resource_dict_to_api_model(
55
36
  self, resource_dict: Optional[Dict[str, float]]
56
37
  ) -> Optional[Resources]:
@@ -103,18 +84,13 @@ class PrivateComputeConfigSDK(BaseSDK):
103
84
  if config.resources is not None or schedulable_by_default
104
85
  else UNSCHEDULABLE_RESOURCES,
105
86
  flags=flags or None,
106
- )
107
- self._populate_advanced_instance_config(
108
- config, api_model, cloud=cloud,
87
+ advanced_configurations_json=config.advanced_instance_config or None,
109
88
  )
110
89
 
111
90
  return api_model
112
91
 
113
92
  def _convert_worker_node_group_configs_to_api_models(
114
- self,
115
- configs: Optional[List[Union[Dict, WorkerNodeGroupConfig]]],
116
- *,
117
- cloud: Cloud,
93
+ self, configs: Optional[List[Union[Dict, WorkerNodeGroupConfig]]],
118
94
  ) -> Optional[List[WorkerNodeType]]:
119
95
  if configs is None:
120
96
  return None
@@ -139,9 +115,7 @@ class PrivateComputeConfigSDK(BaseSDK):
139
115
  in {MarketType.SPOT, MarketType.PREFER_SPOT},
140
116
  fallback_to_ondemand=config.market_type == MarketType.PREFER_SPOT,
141
117
  flags=flags or None,
142
- )
143
- self._populate_advanced_instance_config(
144
- config, api_model, cloud=cloud,
118
+ advanced_configurations_json=config.advanced_instance_config or None,
145
119
  )
146
120
  api_models.append(api_model)
147
121
 
@@ -149,7 +123,7 @@ class PrivateComputeConfigSDK(BaseSDK):
149
123
 
150
124
  def _convert_compute_config_to_api_model(
151
125
  self, compute_config: ComputeConfig
152
- ) -> ComputeTemplateConfig:
126
+ ) -> CloudDeploymentComputeConfig:
153
127
  # We should only make the head node schedulable when it's the *only* node in the cluster.
154
128
  # `worker_nodes=None` uses the default serverless config, so this only happens if `worker_nodes`
155
129
  # is explicitly set to an empty list.
@@ -172,10 +146,9 @@ class PrivateComputeConfigSDK(BaseSDK):
172
146
  if compute_config.max_resources:
173
147
  flags["max_resources"] = compute_config.max_resources
174
148
 
175
- api_model = ComputeTemplateConfig(
176
- cloud_id=cloud_id,
149
+ return CloudDeploymentComputeConfig(
150
+ cloud_deployment=compute_config.cloud_deployment,
177
151
  allowed_azs=compute_config.zones,
178
- region="",
179
152
  head_node_type=self._convert_head_node_config_to_api_model(
180
153
  compute_config.head_node,
181
154
  cloud=cloud,
@@ -185,19 +158,19 @@ class PrivateComputeConfigSDK(BaseSDK):
185
158
  ),
186
159
  ),
187
160
  worker_node_types=self._convert_worker_node_group_configs_to_api_models(
188
- compute_config.worker_nodes, cloud=cloud,
161
+ compute_config.worker_nodes,
189
162
  ),
190
163
  auto_select_worker_config=compute_config.auto_select_worker_config,
191
164
  flags=flags,
165
+ advanced_configurations_json=compute_config.advanced_instance_config
166
+ or None,
192
167
  )
193
- self._populate_advanced_instance_config(
194
- compute_config, api_model, cloud=cloud,
195
- )
196
- return api_model
197
168
 
198
169
  def create_compute_config(
199
170
  self, compute_config: ComputeConfig, *, name: Optional[str] = None
200
171
  ) -> Tuple[str, str]:
172
+ """Register the provided compute config and return its internal ID."""
173
+
201
174
  if name is not None:
202
175
  _, version = parse_cluster_compute_name_version(name)
203
176
  if version is not None:
@@ -206,35 +179,101 @@ class PrivateComputeConfigSDK(BaseSDK):
206
179
  "The latest version tag will be generated and returned."
207
180
  )
208
181
 
209
- """Register the provided compute config and return its internal ID."""
210
- compute_config_api_model = self._convert_compute_config_to_api_model(
211
- compute_config
182
+ # Returns the default cloud if user-provided cloud is not specified (`None`).
183
+ cloud_id = self.client.get_cloud_id(cloud_name=compute_config.cloud) # type: ignore
184
+
185
+ deployment_config = self._convert_compute_config_to_api_model(compute_config)
186
+
187
+ compute_config_api_model = ComputeTemplateConfig(
188
+ cloud_id=cloud_id,
189
+ deployment_configs=[deployment_config],
190
+ # For compatibility, continue setting the top-level fields.
191
+ allowed_azs=deployment_config.allowed_azs,
192
+ head_node_type=deployment_config.head_node_type,
193
+ worker_node_types=deployment_config.worker_node_types,
194
+ auto_select_worker_config=deployment_config.auto_select_worker_config,
195
+ flags=deployment_config.flags,
196
+ advanced_configurations_json=deployment_config.advanced_configurations_json
197
+ or None,
212
198
  )
199
+
213
200
  full_name, compute_config_id = self.client.create_compute_config(
214
201
  compute_config_api_model, name=name
215
202
  )
216
203
  self.logger.info(f"Created compute config: '{full_name}'")
217
204
  ui_url = self.client.get_compute_config_ui_url(
218
- compute_config_id, cloud_id=compute_config_api_model.cloud_id
205
+ compute_config_id, cloud_id=cloud_id
219
206
  )
220
207
  self.logger.info(f"View the compute config in the UI: '{ui_url}'")
221
208
  return full_name, compute_config_id
222
209
 
223
- def _convert_api_model_to_advanced_instance_config(
210
+ def create_multi_deployment_compute_config(
224
211
  self,
225
- api_model: Union[DecoratedComputeTemplate, ComputeNodeType, WorkerNodeType],
212
+ compute_config: MultiDeploymentComputeConfig,
226
213
  *,
227
- cloud: Cloud,
214
+ name: Optional[str] = None,
215
+ ) -> Tuple[str, str]:
216
+ """Register the provided multi-deployment compute config and return its internal ID."""
217
+ if name is not None:
218
+ _, version = parse_cluster_compute_name_version(name)
219
+ if version is not None:
220
+ raise ValueError(
221
+ "A version tag cannot be provided when creating a compute config. "
222
+ "The latest version tag will be generated and returned."
223
+ )
224
+
225
+ # Returns the default cloud if user-provided cloud is not specified (`None`).
226
+ cloud_id = self.client.get_cloud_id(cloud_name=compute_config.cloud) # type: ignore
227
+
228
+ # Convert each compute config to the CloudDeploymentComputeConfig API model.
229
+ assert compute_config.configs
230
+ deployment_configs = []
231
+ for config in compute_config.configs:
232
+ assert isinstance(config, ComputeConfig)
233
+ deployment_configs.append(self._convert_compute_config_to_api_model(config))
234
+ default_config = deployment_configs[0]
235
+
236
+ compute_config_api_model = ComputeTemplateConfig(
237
+ cloud_id=cloud_id,
238
+ deployment_configs=deployment_configs,
239
+ # For compatibility, use the first deployment config to set the top-level fields.
240
+ allowed_azs=default_config.allowed_azs,
241
+ head_node_type=default_config.head_node_type,
242
+ worker_node_types=default_config.worker_node_types,
243
+ auto_select_worker_config=default_config.auto_select_worker_config,
244
+ flags=default_config.flags,
245
+ advanced_configurations_json=default_config.advanced_configurations_json
246
+ or None,
247
+ )
248
+ full_name, compute_config_id = self.client.create_compute_config(
249
+ compute_config_api_model, name=name
250
+ )
251
+ self.logger.info(f"Created compute config: '{full_name}'")
252
+
253
+ # TODO(janet): add this back after the UI has been updated to support multi-deployment compute configs.
254
+ # ui_url = self.client.get_compute_config_ui_url(
255
+ # compute_config_id, cloud_id=cloud_id
256
+ # )
257
+ # self.logger.info(f"View the compute config in the UI: '{ui_url}'")
258
+
259
+ return full_name, compute_config_id
260
+
261
+ def _convert_api_model_to_advanced_instance_config(
262
+ self,
263
+ api_model: Union[
264
+ DecoratedComputeTemplateConfig, ComputeNodeType, WorkerNodeType
265
+ ],
228
266
  ) -> Optional[Dict]:
229
267
  if api_model.advanced_configurations_json:
230
268
  return api_model.advanced_configurations_json
231
269
 
232
- if cloud.provider == CloudProviders.AWS:
233
- return api_model.aws_advanced_configurations_json or None
234
- elif cloud.provider == CloudProviders.GCP:
235
- return api_model.gcp_advanced_configurations_json or None
236
- else:
237
- return None
270
+ # Only one of aws_advanced_configurations_json or gcp_advanced_configurations_json will be set.
271
+ if api_model.aws_advanced_configurations_json:
272
+ return api_model.aws_advanced_configurations_json
273
+ if api_model.gcp_advanced_configurations_json:
274
+ return api_model.gcp_advanced_configurations_json
275
+
276
+ return None
238
277
 
239
278
  def _convert_api_model_to_resource_dict(
240
279
  self, resources: Optional[Resources]
@@ -256,7 +295,7 @@ class PrivateComputeConfigSDK(BaseSDK):
256
295
  }
257
296
 
258
297
  def _convert_api_model_to_head_node_config(
259
- self, api_model: ComputeNodeType, *, cloud: Cloud
298
+ self, api_model: ComputeNodeType
260
299
  ) -> HeadNodeConfig:
261
300
  flags: Dict[str, Any] = deepcopy(api_model.flags) or {}
262
301
 
@@ -271,14 +310,14 @@ class PrivateComputeConfigSDK(BaseSDK):
271
310
  instance_type=api_model.instance_type,
272
311
  resources=self._convert_api_model_to_resource_dict(api_model.resources),
273
312
  advanced_instance_config=self._convert_api_model_to_advanced_instance_config(
274
- api_model, cloud=cloud,
313
+ api_model,
275
314
  ),
276
315
  flags=flags or None,
277
316
  cloud_deployment=cloud_deployment,
278
317
  )
279
318
 
280
319
  def _convert_api_models_to_worker_node_group_configs(
281
- self, api_models: List[WorkerNodeType], *, cloud: Cloud
320
+ self, api_models: List[WorkerNodeType]
282
321
  ) -> List[WorkerNodeGroupConfig]:
283
322
  # TODO(edoakes): support advanced_instance_config.
284
323
  configs = []
@@ -318,7 +357,7 @@ class PrivateComputeConfigSDK(BaseSDK):
318
357
  api_model.resources
319
358
  ),
320
359
  advanced_instance_config=self._convert_api_model_to_advanced_instance_config(
321
- api_model, cloud=cloud,
360
+ api_model,
322
361
  ),
323
362
  min_nodes=min_nodes,
324
363
  max_nodes=max_nodes,
@@ -330,6 +369,54 @@ class PrivateComputeConfigSDK(BaseSDK):
330
369
 
331
370
  return configs
332
371
 
372
+ def _convert_cloud_deployment_compute_config_api_model_to_compute_config(
373
+ self, cloud_name: str, api_model: CloudDeploymentComputeConfig,
374
+ ) -> ComputeConfig:
375
+ worker_nodes = None
376
+ if not api_model.auto_select_worker_config:
377
+ if api_model.worker_node_types is not None:
378
+ # Convert worker node types when they are present.
379
+ worker_nodes = self._convert_api_models_to_worker_node_group_configs(
380
+ api_model.worker_node_types
381
+ )
382
+ else:
383
+ # An explicit head-node-only cluster (no worker nodes configured).
384
+ worker_nodes = []
385
+
386
+ zones = None
387
+ # NOTE(edoakes): the API returns '["any"]' if no AZs are passed in on the creation path.
388
+ if api_model.allowed_azs not in [["any"], []]:
389
+ zones = api_model.allowed_azs
390
+
391
+ enable_cross_zone_scaling = False
392
+ flags: Dict[str, Any] = deepcopy(api_model.flags) or {}
393
+ enable_cross_zone_scaling = flags.pop("allow-cross-zone-autoscaling", False)
394
+ min_resources = flags.pop("min_resources", None)
395
+ max_resources = flags.pop("max_resources", None)
396
+ if max_resources is None:
397
+ max_resources = {}
398
+ max_cpus = flags.pop("max-cpus", None)
399
+ if max_cpus:
400
+ max_resources["CPU"] = max_cpus
401
+ max_gpus = flags.pop("max-gpus", None)
402
+ if max_gpus:
403
+ max_resources["GPU"] = max_gpus
404
+
405
+ return ComputeConfig(
406
+ cloud=cloud_name,
407
+ cloud_deployment=api_model.cloud_deployment,
408
+ zones=zones,
409
+ advanced_instance_config=api_model.advanced_configurations_json or None,
410
+ enable_cross_zone_scaling=enable_cross_zone_scaling,
411
+ head_node=self._convert_api_model_to_head_node_config(
412
+ api_model.head_node_type
413
+ ),
414
+ worker_nodes=worker_nodes,
415
+ min_resources=min_resources,
416
+ max_resources=max_resources or None,
417
+ flags=flags,
418
+ )
419
+
333
420
  def _convert_api_model_to_compute_config_version(
334
421
  self, api_model: DecoratedComputeTemplate # noqa: ARG002
335
422
  ) -> ComputeConfigVersion:
@@ -341,12 +428,37 @@ class PrivateComputeConfigSDK(BaseSDK):
341
428
  "This should never happen; please reach out to Anyscale support."
342
429
  )
343
430
 
431
+ configs = None
432
+ if api_model_config.deployment_configs:
433
+ configs = [
434
+ self._convert_cloud_deployment_compute_config_api_model_to_compute_config(
435
+ cloud.name, config
436
+ )
437
+ for config in api_model_config.deployment_configs
438
+ ]
439
+ if len(configs) == 1:
440
+ # If there's only one deployment config, return it directly.
441
+ return ComputeConfigVersion(
442
+ name=f"{api_model.name}:{api_model.version}",
443
+ id=api_model.id,
444
+ config=configs[0],
445
+ )
446
+ return ComputeConfigVersion(
447
+ name=f"{api_model.name}:{api_model.version}",
448
+ id=api_model.id,
449
+ multi_deployment_config=MultiDeploymentComputeConfig(
450
+ cloud=cloud.name, configs=configs,
451
+ ),
452
+ )
453
+
454
+ # If there are no deployment configs, this is a compute config for a single cloud deployment - parse the top-level fields.
455
+
344
456
  worker_nodes = None
345
457
  if not api_model_config.auto_select_worker_config:
346
458
  if api_model_config.worker_node_types is not None:
347
459
  # Convert worker node types when they are present.
348
460
  worker_nodes = self._convert_api_models_to_worker_node_group_configs(
349
- api_model_config.worker_node_types, cloud=cloud,
461
+ api_model_config.worker_node_types
350
462
  )
351
463
  else:
352
464
  # An explicit head-node-only cluster (no worker nodes configured).
@@ -378,11 +490,11 @@ class PrivateComputeConfigSDK(BaseSDK):
378
490
  cloud=cloud.name,
379
491
  zones=zones,
380
492
  advanced_instance_config=self._convert_api_model_to_advanced_instance_config(
381
- api_model_config, cloud=cloud,
493
+ api_model_config
382
494
  ),
383
495
  enable_cross_zone_scaling=enable_cross_zone_scaling,
384
496
  head_node=self._convert_api_model_to_head_node_config(
385
- api_model_config.head_node_type, cloud=cloud
497
+ api_model_config.head_node_type
386
498
  ),
387
499
  worker_nodes=worker_nodes, # type: ignore
388
500
  min_resources=min_resources,
@@ -2,7 +2,11 @@ from typing import Optional
2
2
 
3
3
  from anyscale._private.sdk import sdk_command
4
4
  from anyscale.compute_config._private.compute_config_sdk import PrivateComputeConfigSDK
5
- from anyscale.compute_config.models import ComputeConfig, ComputeConfigVersion
5
+ from anyscale.compute_config.models import (
6
+ ComputeConfig,
7
+ ComputeConfigVersion,
8
+ MultiDeploymentComputeConfig,
9
+ )
6
10
 
7
11
 
8
12
  _COMPUTE_CONFIG_SDK_SINGLETON_KEY = "compute_config_sdk"
@@ -58,6 +62,67 @@ def create(
58
62
  return full_name
59
63
 
60
64
 
65
+ _CREATE_MULTI_DEPLOYMENT_EXAMPLE = """
66
+ import anyscale
67
+ from anyscale.compute_config.models import MultiDeploymentComputeConfig, ComputeConfig, HeadNodeConfig, WorkerNodeGroupConfig
68
+ config = MultiDeploymentComputeConfig(
69
+ configs=[
70
+ ComputeConfig(
71
+ cloud_deployment="vm-aws-us-west-1",
72
+ head_node=HeadNodeConfig(
73
+ instance_type="m5.2xlarge",
74
+ ),
75
+ worker_nodes=[
76
+ WorkerNodeGroupConfig(
77
+ instance_type="m5.4xlarge",
78
+ min_nodes=1,
79
+ max_nodes=10,
80
+ ),
81
+ ],
82
+ ),
83
+ ComputeConfig(
84
+ cloud_deployment="vm-aws-us-west-2",
85
+ head_node=HeadNodeConfig(
86
+ instance_type="m5.2xlarge",
87
+ ),
88
+ worker_nodes=[
89
+ WorkerNodeGroupConfig(
90
+ instance_type="m5.4xlarge",
91
+ min_nodes=1,
92
+ max_nodes=10,
93
+ ),
94
+ ],
95
+ )
96
+ ]
97
+ )
98
+ full_name: str = anyscale.compute_config.create_multi_deployment(config, name="my-compute-config")
99
+ """
100
+
101
+ _CREATE_MULTI_DEPLOYMENT_ARG_DOCSTRINGS = {
102
+ "config": "The config options defining the multi-deployment compute config.",
103
+ "name": "The name of the compute config. This should *not* include a version tag. If a name is not provided, one will be automatically generated.",
104
+ }
105
+
106
+
107
+ @sdk_command(
108
+ _COMPUTE_CONFIG_SDK_SINGLETON_KEY,
109
+ PrivateComputeConfigSDK,
110
+ doc_py_example=_CREATE_MULTI_DEPLOYMENT_EXAMPLE,
111
+ arg_docstrings=_CREATE_MULTI_DEPLOYMENT_ARG_DOCSTRINGS,
112
+ )
113
+ def create_multi_deployment(
114
+ config: MultiDeploymentComputeConfig,
115
+ *,
116
+ name: Optional[str],
117
+ _private_sdk: Optional[PrivateComputeConfigSDK] = None,
118
+ ) -> str:
119
+ """EXPERIMENTAL. Create a new version of a compute config with multiple possible cloud deployments.
120
+ Returns the full name of the registered compute config, including the version.
121
+ """
122
+ full_name, _ = _private_sdk.create_multi_deployment_compute_config(config, name=name) # type: ignore
123
+ return full_name
124
+
125
+
61
126
  _GET_EXAMPLE = """
62
127
  import anyscale
63
128
  from anyscale.compute_config.models import ComputeConfig
@@ -417,6 +417,19 @@ advanced_instance_config: # (Optional) Defaults to no advanced configurations.
417
417
  if cloud is not None and not isinstance(cloud, str):
418
418
  raise TypeError("'cloud' must be a string")
419
419
 
420
+ cloud_deployment: Optional[str] = field(
421
+ default=None,
422
+ repr=False,
423
+ metadata={
424
+ "docstring": "The cloud deployment to use for this workload. Defaults to the primary deployment of the Cloud.",
425
+ "customer_hosted_only": True,
426
+ },
427
+ )
428
+
429
+ def _validate_cloud_deployment(self, cloud_deployment: Optional[str]):
430
+ if cloud_deployment is not None and not isinstance(cloud_deployment, str):
431
+ raise TypeError("'cloud_deployment' must be a string")
432
+
420
433
  head_node: Union[HeadNodeConfig, Dict, None] = field(
421
434
  default=None,
422
435
  repr=False,
@@ -586,6 +599,129 @@ advanced_instance_config: # (Optional) Defaults to no advanced configurations.
586
599
  )
587
600
 
588
601
 
602
+ @dataclass(frozen=True)
603
+ class MultiDeploymentComputeConfig(ModelBase):
604
+ """EXPERIMENTAL. Compute configuration for a cluster with multiple possible cloud deployments."""
605
+
606
+ __doc_py_example__ = """
607
+ from anyscale.compute_config.models import (
608
+ MultiDeploymentComputeConfig, ComputeConfig, HeadNodeConfig, WorkerNodeGroupConfig
609
+ )
610
+ config = MultiDeploymentComputeConfig(
611
+ configs=[
612
+ ComputeConfig(
613
+ cloud_deployment="vm-aws-us-west-1",
614
+ head_node=HeadNodeConfig(
615
+ instance_type="m5.2xlarge",
616
+ ),
617
+ worker_nodes=[
618
+ WorkerNodeGroupConfig(
619
+ instance_type="m5.4xlarge",
620
+ min_nodes=1,
621
+ max_nodes=10,
622
+ ),
623
+ ],
624
+ ),
625
+ ComputeConfig(
626
+ cloud_deployment="vm-aws-us-west-2",
627
+ head_node=HeadNodeConfig(
628
+ instance_type="m5.2xlarge",
629
+ ),
630
+ worker_nodes=[
631
+ WorkerNodeGroupConfig(
632
+ instance_type="m5.4xlarge",
633
+ min_nodes=1,
634
+ max_nodes=10,
635
+ ),
636
+ ],
637
+ )
638
+ ]
639
+ )
640
+ """
641
+
642
+ __doc_yaml_example__ = """
643
+ cloud: my-cloud
644
+ configs:
645
+ - cloud_deployment: vm-aws-us-west-1
646
+ head_node:
647
+ instance_type: m5.2xlarge
648
+ worker_nodes:
649
+ - instance_type: m5.4xlarge
650
+ min_nodes: 1
651
+ max_nodes: 10
652
+ - cloud_deployment: vm-aws-us-west-2
653
+ head_node:
654
+ instance_type: m5.2xlarge
655
+ worker_nodes:
656
+ - instance_type: m5.4xlarge
657
+ min_nodes: 1
658
+ max_nodes: 10
659
+ """
660
+
661
+ cloud: Optional[str] = field(
662
+ default=None,
663
+ metadata={
664
+ "docstring": "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)."
665
+ },
666
+ )
667
+
668
+ def _validate_cloud(self, cloud: Optional[str]):
669
+ if cloud is not None and not isinstance(cloud, str):
670
+ raise TypeError("'cloud' must be a string")
671
+
672
+ configs: List[Union[ComputeConfig, Dict]] = field(
673
+ default_factory=list,
674
+ repr=False,
675
+ metadata={
676
+ "docstring": "List of compute configurations, one for each cloud deployment.",
677
+ "customer_hosted_only": True,
678
+ },
679
+ )
680
+
681
+ def _validate_configs(
682
+ self, configs: List[Union[ComputeConfig, Dict]]
683
+ ) -> List[ComputeConfig]:
684
+ if not isinstance(configs, list) or not all(
685
+ isinstance(c, (dict, ComputeConfig)) for c in configs
686
+ ):
687
+ raise TypeError(
688
+ "'configs' must be a list of ComputeConfigs or corresponding dicts"
689
+ )
690
+
691
+ config_models: List[ComputeConfig] = []
692
+ unique_clouds = set()
693
+ unique_deployments = set()
694
+ for config in configs:
695
+ if isinstance(config, dict):
696
+ config = ComputeConfig.from_dict(config)
697
+
698
+ assert isinstance(config, ComputeConfig)
699
+ config_models.append(config)
700
+
701
+ if not config.cloud_deployment:
702
+ raise ValueError("'cloud_deployment' is required for each config.")
703
+
704
+ if config.cloud:
705
+ unique_clouds.add(config.cloud)
706
+
707
+ unique_deployments.add(config.cloud_deployment)
708
+
709
+ if len(unique_clouds) > 1:
710
+ raise ValueError("'cloud' must be the same for all configs.")
711
+
712
+ if len(unique_deployments) != len(configs):
713
+ raise ValueError(
714
+ "'cloud_deployment' must be unique for each compute configuration."
715
+ )
716
+
717
+ if len(configs) == 0:
718
+ raise ValueError(
719
+ "'configs' must include at least one compute configuration."
720
+ )
721
+
722
+ return config_models
723
+
724
+
589
725
  @dataclass(frozen=True)
590
726
  class ComputeConfigVersion(ModelBase):
591
727
  """Details of a created version of a compute config.
@@ -639,8 +775,29 @@ config:
639
775
  if not isinstance(id, str):
640
776
  raise TypeError("'id' must be a string.")
641
777
 
642
- config: ComputeConfig = field(metadata={"docstring": "The compute configuration."},)
778
+ config: Optional[ComputeConfig] = field(
779
+ default=None, metadata={"docstring": "The compute configuration."},
780
+ )
643
781
 
644
- def _validate_config(self, config: ComputeConfig):
645
- if not isinstance(config, ComputeConfig):
782
+ def _validate_config(self, config: Optional[ComputeConfig]):
783
+ if config is not None and not isinstance(config, ComputeConfig):
646
784
  raise TypeError("'config' must be a ComputeConfig")
785
+
786
+ multi_deployment_config: Optional[MultiDeploymentComputeConfig] = field(
787
+ default=None,
788
+ repr=False,
789
+ metadata={
790
+ "docstring": "Compute configuration for a cluster with multiple possible cloud deployments.",
791
+ "customer_hosted_only": True,
792
+ },
793
+ )
794
+
795
+ def _validate_multi_deployment_config(
796
+ self, multi_deployment_config: Optional[MultiDeploymentComputeConfig]
797
+ ):
798
+ if multi_deployment_config is not None and not isinstance(
799
+ multi_deployment_config, MultiDeploymentComputeConfig
800
+ ):
801
+ raise TypeError(
802
+ "'multi_deployment_config' must be a MultiDeploymentComputeConfig"
803
+ )
anyscale/conf.py CHANGED
@@ -16,7 +16,7 @@ ANYSCALE_IAM_ROLE_NAME = "anyscale-iam-role"
16
16
  # Minimum default Ray version to return when a user either asks for `anyscale.connect required_ray_version`
17
17
  # or when a Default Cluster Env is used
18
18
  # TODO(ilr/nikita) Convert this to a backend call for the most recent Ray Version in the Dataplane!
19
- MINIMUM_RAY_VERSION = "1.7.0"
19
+ MINIMUM_RAY_VERSION = "2.7.0"
20
20
 
21
21
  IDLE_TIMEOUT_DEFAULT_MINUTES = 120
22
22