anyscale 0.26.43__py3-none-any.whl → 0.26.44__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anyscale/_private/anyscale_client/common.py +1 -1
- anyscale/_private/docgen/__main__.py +2 -0
- anyscale/_private/docgen/models.md +2 -2
- anyscale/_private/workload/workload_sdk.py +6 -0
- anyscale/client/README.md +6 -0
- anyscale/client/openapi_client/__init__.py +5 -0
- anyscale/client/openapi_client/api/default_api.py +132 -0
- anyscale/client/openapi_client/models/__init__.py +5 -0
- anyscale/client/openapi_client/models/baseimagesenum.py +68 -1
- anyscale/client/openapi_client/models/describe_machine_pool_machines_filters.py +31 -3
- anyscale/client/openapi_client/models/describe_machine_pool_workloads_filters.py +150 -0
- anyscale/client/openapi_client/models/describe_machine_pool_workloads_request.py +151 -0
- anyscale/client/openapi_client/models/file_storage.py +33 -5
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +68 -1
- anyscale/client/openapi_client/models/workload_machine_info.py +210 -0
- anyscale/client/openapi_client/models/workload_state_info.py +295 -0
- anyscale/client/openapi_client/models/workloadstateinfo_list_response.py +147 -0
- anyscale/commands/cloud_commands.py +1 -0
- anyscale/commands/compute_config_commands.py +10 -3
- anyscale/compute_config/__init__.py +16 -0
- anyscale/compute_config/_private/compute_config_sdk.py +172 -60
- anyscale/compute_config/commands.py +66 -1
- anyscale/compute_config/models.py +160 -3
- anyscale/conf.py +1 -1
- anyscale/controllers/cloud_controller.py +141 -3
- anyscale/job/_private/job_sdk.py +22 -0
- anyscale/sdk/anyscale_client/models/baseimagesenum.py +68 -1
- anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +68 -1
- anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
- anyscale/version.py +1 -1
- {anyscale-0.26.43.dist-info → anyscale-0.26.44.dist-info}/METADATA +4 -3
- {anyscale-0.26.43.dist-info → anyscale-0.26.44.dist-info}/RECORD +37 -32
- {anyscale-0.26.43.dist-info → anyscale-0.26.44.dist-info}/LICENSE +0 -0
- {anyscale-0.26.43.dist-info → anyscale-0.26.44.dist-info}/NOTICE +0 -0
- {anyscale-0.26.43.dist-info → anyscale-0.26.44.dist-info}/WHEEL +0 -0
- {anyscale-0.26.43.dist-info → anyscale-0.26.44.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.43.dist-info → anyscale-0.26.44.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
176
|
-
|
|
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,
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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=
|
|
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
|
|
210
|
+
def create_multi_deployment_compute_config(
|
|
224
211
|
self,
|
|
225
|
-
|
|
212
|
+
compute_config: MultiDeploymentComputeConfig,
|
|
226
213
|
*,
|
|
227
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
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,
|
|
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]
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 = "
|
|
19
|
+
MINIMUM_RAY_VERSION = "2.7.0"
|
|
20
20
|
|
|
21
21
|
IDLE_TIMEOUT_DEFAULT_MINUTES = 120
|
|
22
22
|
|