anyscale 0.26.69__py3-none-any.whl → 0.26.71__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 (113) hide show
  1. anyscale/_private/anyscale_client/anyscale_client.py +126 -3
  2. anyscale/_private/anyscale_client/common.py +51 -2
  3. anyscale/_private/anyscale_client/fake_anyscale_client.py +103 -11
  4. anyscale/client/README.md +43 -4
  5. anyscale/client/openapi_client/__init__.py +30 -4
  6. anyscale/client/openapi_client/api/default_api.py +1769 -27
  7. anyscale/client/openapi_client/models/__init__.py +30 -4
  8. anyscale/client/openapi_client/models/api_key_info.py +29 -3
  9. anyscale/client/openapi_client/models/apply_autoscaling_config_update_model.py +350 -0
  10. anyscale/client/openapi_client/models/apply_multi_version_update_weights_update_model.py +152 -0
  11. anyscale/client/openapi_client/models/apply_production_service_multi_version_v2_model.py +207 -0
  12. anyscale/client/openapi_client/models/apply_production_service_v2_model.py +31 -3
  13. anyscale/client/openapi_client/models/apply_version_weight_update_model.py +181 -0
  14. anyscale/client/openapi_client/models/backend_server_api_product_models_catalog_client_models_table_metadata.py +546 -0
  15. anyscale/client/openapi_client/models/backend_server_api_product_models_data_catalogs_table_metadata.py +178 -0
  16. anyscale/client/openapi_client/models/baseimagesenum.py +139 -1
  17. anyscale/client/openapi_client/models/catalog_metadata.py +150 -0
  18. anyscale/client/openapi_client/models/cloud_data_bucket_file_type.py +2 -1
  19. anyscale/client/openapi_client/models/{oauthconnectionresponse_response.py → clouddeployment_response.py} +11 -11
  20. anyscale/client/openapi_client/models/column_info.py +265 -0
  21. anyscale/client/openapi_client/models/compute_node_type.py +29 -1
  22. anyscale/client/openapi_client/models/connection_metadata.py +206 -0
  23. anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
  24. anyscale/client/openapi_client/models/create_workspace_from_template.py +29 -1
  25. anyscale/client/openapi_client/models/create_workspace_template_version.py +59 -3
  26. anyscale/client/openapi_client/models/data_catalog.py +45 -31
  27. anyscale/client/openapi_client/models/data_catalog_connection.py +74 -58
  28. anyscale/client/openapi_client/models/{ha_job_event_level.py → data_catalog_object_type.py} +7 -8
  29. anyscale/client/openapi_client/models/data_catalog_schema.py +324 -0
  30. anyscale/client/openapi_client/models/data_catalog_table.py +437 -0
  31. anyscale/client/openapi_client/models/data_catalog_volume.py +437 -0
  32. anyscale/client/openapi_client/models/datacatalogschema_list_response.py +147 -0
  33. anyscale/client/openapi_client/models/datacatalogtable_list_response.py +147 -0
  34. anyscale/client/openapi_client/models/datacatalogvolume_list_response.py +147 -0
  35. anyscale/client/openapi_client/models/decorated_list_service_api_model.py +58 -1
  36. anyscale/client/openapi_client/models/decorated_production_service_v2_api_model.py +60 -3
  37. anyscale/client/openapi_client/models/decorated_serve_deployment.py +27 -1
  38. anyscale/client/openapi_client/models/decorated_service_event_api_model.py +3 -3
  39. anyscale/client/openapi_client/models/decoratedproductionservicev2_versionapimodel_response.py +121 -0
  40. anyscale/client/openapi_client/models/describe_machine_pool_machines_filters.py +33 -5
  41. anyscale/client/openapi_client/models/describe_machine_pool_requests_filters.py +33 -5
  42. anyscale/client/openapi_client/models/describe_machine_pool_workloads_filters.py +33 -5
  43. anyscale/client/openapi_client/models/{service_event_level.py → entity_type.py} +9 -9
  44. anyscale/client/openapi_client/models/event_level.py +2 -1
  45. anyscale/client/openapi_client/models/job_event_fields.py +206 -0
  46. anyscale/client/openapi_client/models/machine_type_partition_filter.py +152 -0
  47. anyscale/client/openapi_client/models/partition_info.py +30 -1
  48. anyscale/client/openapi_client/models/physical_resources.py +178 -0
  49. anyscale/client/openapi_client/models/production_job_event.py +3 -3
  50. anyscale/client/openapi_client/models/rollout_strategy.py +2 -1
  51. anyscale/client/openapi_client/models/schema_metadata.py +150 -0
  52. anyscale/client/openapi_client/models/service_event_fields.py +318 -0
  53. anyscale/client/openapi_client/models/sso_config.py +18 -18
  54. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +139 -1
  55. anyscale/client/openapi_client/models/table_data_preview.py +209 -0
  56. anyscale/client/openapi_client/models/task_summary_config.py +29 -3
  57. anyscale/client/openapi_client/models/task_table_config.py +29 -3
  58. anyscale/client/openapi_client/models/unified_event.py +377 -0
  59. anyscale/client/openapi_client/models/unified_origin_filter.py +113 -0
  60. anyscale/client/openapi_client/models/unifiedevent_list_response.py +147 -0
  61. anyscale/client/openapi_client/models/volume_metadata.py +150 -0
  62. anyscale/client/openapi_client/models/worker_node_type.py +29 -1
  63. anyscale/client/openapi_client/models/workspace_event_fields.py +122 -0
  64. anyscale/client/openapi_client/models/workspace_template_version.py +58 -1
  65. anyscale/client/openapi_client/models/workspace_template_version_data_object.py +58 -1
  66. anyscale/cloud/models.py +2 -2
  67. anyscale/commands/cloud_commands.py +133 -2
  68. anyscale/commands/job_commands.py +121 -1
  69. anyscale/commands/job_queue_commands.py +99 -2
  70. anyscale/commands/service_commands.py +267 -67
  71. anyscale/commands/setup_k8s.py +546 -31
  72. anyscale/commands/util.py +104 -1
  73. anyscale/commands/workspace_commands.py +123 -5
  74. anyscale/commands/workspace_commands_v2.py +17 -1
  75. anyscale/compute_config/_private/compute_config_sdk.py +25 -12
  76. anyscale/compute_config/models.py +15 -0
  77. anyscale/controllers/cloud_controller.py +15 -2
  78. anyscale/controllers/job_controller.py +12 -0
  79. anyscale/controllers/kubernetes_verifier.py +80 -66
  80. anyscale/controllers/workspace_controller.py +67 -5
  81. anyscale/job/_private/job_sdk.py +50 -2
  82. anyscale/job/commands.py +3 -0
  83. anyscale/job/models.py +16 -0
  84. anyscale/job_queue/__init__.py +37 -1
  85. anyscale/job_queue/_private/job_queue_sdk.py +28 -1
  86. anyscale/job_queue/commands.py +61 -1
  87. anyscale/sdk/anyscale_client/__init__.py +1 -0
  88. anyscale/sdk/anyscale_client/api/default_api.py +12 -2
  89. anyscale/sdk/anyscale_client/models/__init__.py +1 -0
  90. anyscale/sdk/anyscale_client/models/apply_production_service_v2_model.py +31 -3
  91. anyscale/sdk/anyscale_client/models/apply_service_model.py +31 -3
  92. anyscale/sdk/anyscale_client/models/baseimagesenum.py +139 -1
  93. anyscale/sdk/anyscale_client/models/compute_node_type.py +29 -1
  94. anyscale/sdk/anyscale_client/models/physical_resources.py +178 -0
  95. anyscale/sdk/anyscale_client/models/rollout_strategy.py +2 -1
  96. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +139 -1
  97. anyscale/sdk/anyscale_client/models/worker_node_type.py +29 -1
  98. anyscale/service/__init__.py +51 -3
  99. anyscale/service/_private/service_sdk.py +481 -58
  100. anyscale/service/commands.py +90 -4
  101. anyscale/service/models.py +56 -0
  102. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  103. anyscale/version.py +1 -1
  104. anyscale/workspace/_private/workspace_sdk.py +1 -0
  105. anyscale/workspace/models.py +19 -0
  106. {anyscale-0.26.69.dist-info → anyscale-0.26.71.dist-info}/METADATA +1 -1
  107. {anyscale-0.26.69.dist-info → anyscale-0.26.71.dist-info}/RECORD +112 -85
  108. anyscale/client/openapi_client/models/o_auth_connection_response.py +0 -229
  109. {anyscale-0.26.69.dist-info → anyscale-0.26.71.dist-info}/WHEEL +0 -0
  110. {anyscale-0.26.69.dist-info → anyscale-0.26.71.dist-info}/entry_points.txt +0 -0
  111. {anyscale-0.26.69.dist-info → anyscale-0.26.71.dist-info}/licenses/LICENSE +0 -0
  112. {anyscale-0.26.69.dist-info → anyscale-0.26.71.dist-info}/licenses/NOTICE +0 -0
  113. {anyscale-0.26.69.dist-info → anyscale-0.26.71.dist-info}/top_level.txt +0 -0
@@ -34,6 +34,7 @@ from anyscale.client.openapi_client.models import (
34
34
  AdminCreateUser,
35
35
  AnyscaleServiceAccount,
36
36
  ApiKeyParameters,
37
+ ApplyProductionServiceMultiVersionV2Model,
37
38
  ArchiveStatus,
38
39
  Cloud,
39
40
  CloudDataBucketAccessMode,
@@ -54,11 +55,14 @@ from anyscale.client.openapi_client.models import (
54
55
  CreateOrganizationInvitation,
55
56
  CreateResourceQuota,
56
57
  CreateUserProjectCollaborator,
58
+ DecoratedCloudResource,
57
59
  DecoratedComputeTemplate,
58
60
  DecoratedjobqueueListResponse,
59
61
  DecoratedlistserviceapimodelListResponse,
60
62
  DecoratedProductionServiceV2APIModel,
63
+ DecoratedProductionServiceV2VersionAPIModel,
61
64
  DecoratedSession,
65
+ DeleteResourceTagsRequest,
62
66
  ExperimentalWorkspace,
63
67
  GetOrCreateBuildFromImageUriRequest,
64
68
  InternalProductionJob,
@@ -72,12 +76,14 @@ from anyscale.client.openapi_client.models import (
72
76
  ProjectListResponse,
73
77
  ResourceQuota,
74
78
  ResourceQuotaStatus,
79
+ ResourceTagResourceType,
75
80
  ServerSessionToken,
76
81
  SessionSshKey,
77
82
  SessionState,
78
83
  StartSessionOptions,
79
84
  StopSessionOptions,
80
85
  SystemWorkloadName,
86
+ UpsertResourceTagsRequest,
81
87
  WorkspaceDataplaneProxiedArtifacts,
82
88
  WriteProject,
83
89
  )
@@ -575,6 +581,14 @@ class AnyscaleClient(AnyscaleClientInterface):
575
581
  cloud_id = self.get_cloud_id(cloud_name=name)
576
582
  return self.get_cloud(cloud_id=cloud_id)
577
583
 
584
+ @handle_api_exceptions
585
+ def get_cloud_resource_by_name(
586
+ self, cloud_id: str, cloud_resource_name: str
587
+ ) -> Optional[DecoratedCloudResource]:
588
+ return self._internal_api_client.find_cloud_resource_by_name_api_v2_clouds_cloud_id_find_cloud_resource_by_name_post(
589
+ cloud_id=cloud_id, cloud_resource_name=cloud_resource_name,
590
+ ).result
591
+
578
592
  @handle_api_exceptions
579
593
  def get_default_cloud(self) -> Optional[Cloud]:
580
594
  try:
@@ -702,9 +716,11 @@ class AnyscaleClient(AnyscaleClientInterface):
702
716
  )
703
717
 
704
718
  @handle_api_exceptions
705
- def get_default_compute_config(self, *, cloud_id: str) -> ClusterCompute:
719
+ def get_default_compute_config(
720
+ self, *, cloud_id: str, cloud_resource_id: Optional[str] = None
721
+ ) -> ClusterCompute:
706
722
  return self._external_api_client.get_default_cluster_compute(
707
- cloud_id=cloud_id,
723
+ cloud_id=cloud_id, cloud_resource_id=cloud_resource_id
708
724
  ).result
709
725
 
710
726
  def _build_standard_compute_template_from_existing_auto_config(
@@ -1022,6 +1038,7 @@ class AnyscaleClient(AnyscaleClientInterface):
1022
1038
  *,
1023
1039
  name: Optional[str] = None,
1024
1040
  state_filter: Optional[List[str]] = None,
1041
+ tag_filter: Optional[List[str]] = None,
1025
1042
  creator_id: Optional[str] = None,
1026
1043
  cloud: Optional[str] = None,
1027
1044
  project: Optional[str] = None,
@@ -1047,12 +1064,38 @@ class AnyscaleClient(AnyscaleClientInterface):
1047
1064
  archive_status=ArchiveStatus.ALL
1048
1065
  if include_archived
1049
1066
  else ArchiveStatus.NOT_ARCHIVED,
1067
+ tag_filter=tag_filter,
1050
1068
  count=count if count else self.LIST_ENDPOINT_COUNT,
1051
1069
  paging_token=paging_token,
1052
1070
  sort_field=sort_field,
1053
1071
  sort_order=sort_order,
1054
1072
  )
1055
1073
 
1074
+ def get_service_versions(
1075
+ self, service_id: str, read_all_versions: bool = False,
1076
+ ) -> List[DecoratedProductionServiceV2VersionAPIModel]:
1077
+ resp = self._internal_api_client.get_service_versions_api_v2_services_v2_service_id_versions_get(
1078
+ service_id=service_id,
1079
+ )
1080
+
1081
+ if not read_all_versions:
1082
+ return resp.results
1083
+
1084
+ all_versions: List[DecoratedProductionServiceV2VersionAPIModel] = []
1085
+ all_versions.extend(resp.results)
1086
+ paging_token = resp.metadata.next_paging_token
1087
+
1088
+ while paging_token is not None:
1089
+ resp = self._internal_api_client.get_service_versions_api_v2_services_v2_service_id_versions_get(
1090
+ service_id=service_id,
1091
+ count=self.LIST_ENDPOINT_COUNT,
1092
+ paging_token=paging_token,
1093
+ )
1094
+ all_versions.extend(resp.results)
1095
+ paging_token = resp.metadata.next_paging_token
1096
+
1097
+ return all_versions
1098
+
1056
1099
  @handle_api_exceptions
1057
1100
  def get_project(self, project_id: str) -> Project:
1058
1101
  return self._internal_api_client.get_project_api_v2_projects_project_id_get(
@@ -1206,6 +1249,7 @@ class AnyscaleClient(AnyscaleClientInterface):
1206
1249
  cluster_status: Optional[SessionState] = None,
1207
1250
  project: Optional[str] = None,
1208
1251
  cloud: Optional[str] = None,
1252
+ tags_filter: Optional[Dict[str, List[str]]] = None,
1209
1253
  count: Optional[int] = None,
1210
1254
  paging_token: Optional[str] = None,
1211
1255
  sorting_directives: Optional[List[JobQueueSortDirective]] = None,
@@ -1224,6 +1268,7 @@ class AnyscaleClient(AnyscaleClientInterface):
1224
1268
  cluster_status=cluster_status,
1225
1269
  project_id=project_id,
1226
1270
  cloud_id=cloud_id,
1271
+ tags_filter=tags_filter,
1227
1272
  paging=PageQuery(count=count, paging_token=paging_token),
1228
1273
  sorting_directives=sorting_directives,
1229
1274
  ),
@@ -1238,6 +1283,15 @@ class AnyscaleClient(AnyscaleClientInterface):
1238
1283
  ).result
1239
1284
  return result
1240
1285
 
1286
+ @handle_api_exceptions
1287
+ def rollout_service_multi_version(
1288
+ self, model: ApplyProductionServiceMultiVersionV2Model
1289
+ ) -> DecoratedProductionServiceV2APIModel:
1290
+ result = self._internal_api_client.apply_service_multi_version_api_v2_services_v2_apply_multi_version_put(
1291
+ model
1292
+ ).result
1293
+ return result
1294
+
1241
1295
  @handle_api_exceptions
1242
1296
  def rollback_service(
1243
1297
  self, service_id: str, *, max_surge_percent: Optional[int] = None
@@ -1493,7 +1547,7 @@ class AnyscaleClient(AnyscaleClientInterface):
1493
1547
 
1494
1548
  return all_log_chunk_urls, bearer_token
1495
1549
 
1496
- def _read_log_lines(
1550
+ def _read_log_lines( # noqa: PLR0912
1497
1551
  self,
1498
1552
  log_chunk_urls: List[str],
1499
1553
  head: bool,
@@ -1558,6 +1612,8 @@ class AnyscaleClient(AnyscaleClientInterface):
1558
1612
  if line_count == max_lines:
1559
1613
  break
1560
1614
 
1615
+ if not result_lines:
1616
+ return ""
1561
1617
  return "\n".join(result_lines) + "\n"
1562
1618
 
1563
1619
  @handle_api_exceptions
@@ -1584,6 +1640,52 @@ class AnyscaleClient(AnyscaleClientInterface):
1584
1640
  )
1585
1641
  return logs
1586
1642
 
1643
+ @handle_api_exceptions
1644
+ def stream_logs_for_job_run(
1645
+ self, job_run_id: str, next_page_token: Optional[str] = None,
1646
+ ) -> Tuple[str, Optional[str]]:
1647
+ """Stream logs incrementally for a job run with pagination support.
1648
+
1649
+ Args:
1650
+ job_run_id: The ID of the job run to fetch logs for
1651
+ next_page_token: Token for fetching the next page of logs (for incremental streaming)
1652
+
1653
+ Returns:
1654
+ Tuple of (logs, next_page_token) where next_page_token can be used for the next call
1655
+ """
1656
+ # Fetch only the new log chunks since the last call
1657
+ if next_page_token:
1658
+ # Incremental fetch - get only new chunks
1659
+ log_download_result = self._internal_api_client.get_job_logs_download_v2_api_v2_logs_job_logs_download_v2_job_id_get(
1660
+ job_id=job_run_id, next_page_token=next_page_token,
1661
+ ).result
1662
+ else:
1663
+ # First fetch - get all available chunks
1664
+ log_download_result = self._internal_api_client.get_job_logs_download_v2_api_v2_logs_job_logs_download_v2_job_id_get(
1665
+ job_id=job_run_id,
1666
+ ).result
1667
+
1668
+ # Download and concatenate log chunks
1669
+ log_chunk_urls = [chunk.chunk_url for chunk in log_download_result.log_chunks]
1670
+ bearer_token = log_download_result.bearer_token
1671
+
1672
+ logs = self._read_log_lines(
1673
+ log_chunk_urls,
1674
+ head=False,
1675
+ bearer_token=bearer_token,
1676
+ max_lines=None,
1677
+ parse_json=False,
1678
+ )
1679
+
1680
+ # Return logs and the token for the next page
1681
+ new_next_page_token = (
1682
+ log_download_result.next_page_token
1683
+ if len(log_download_result.log_chunks) > 0
1684
+ else next_page_token
1685
+ )
1686
+
1687
+ return logs, new_next_page_token
1688
+
1587
1689
  @handle_api_exceptions
1588
1690
  def controller_logs_for_service_version(
1589
1691
  self,
@@ -2079,3 +2181,24 @@ class AnyscaleClient(AnyscaleClientInterface):
2079
2181
  self._internal_api_client.set_resource_quota_status_api_v2_resource_quotas_resource_quota_id_status_patch(
2080
2182
  resource_quota_id, ResourceQuotaStatus(is_enabled=is_enabled)
2081
2183
  ).result
2184
+
2185
+ @handle_api_exceptions
2186
+ def upsert_resource_tags(
2187
+ self,
2188
+ resource_type: ResourceTagResourceType,
2189
+ resource_id: str,
2190
+ tags: Dict[str, str],
2191
+ ) -> None:
2192
+ req = UpsertResourceTagsRequest(
2193
+ resource_type=resource_type, resource_id=resource_id, tags=tags
2194
+ )
2195
+ self._internal_api_client.upsert_resource_tags_api_v2_tags_resource_put(req)
2196
+
2197
+ @handle_api_exceptions
2198
+ def delete_resource_tags(
2199
+ self, resource_type: ResourceTagResourceType, resource_id: str, keys: List[str],
2200
+ ) -> None:
2201
+ req = DeleteResourceTagsRequest(
2202
+ resource_type=resource_type, resource_id=resource_id, keys=keys
2203
+ )
2204
+ self._internal_api_client.delete_resource_tags_api_v2_tags_resource_delete(req)
@@ -7,6 +7,7 @@ from anyscale.client.openapi_client.models import (
7
7
  AdminCreatedUser,
8
8
  AdminCreateUser,
9
9
  AnyscaleServiceAccount,
10
+ ApplyProductionServiceMultiVersionV2Model,
10
11
  Cloud,
11
12
  ClusteroperationResponse,
12
13
  CollaboratorType,
@@ -16,10 +17,12 @@ from anyscale.client.openapi_client.models import (
16
17
  CreateInternalProductionJob,
17
18
  CreateResourceQuota,
18
19
  CreateUserProjectCollaborator,
20
+ DecoratedCloudResource,
19
21
  DecoratedComputeTemplate,
20
22
  DecoratedjobqueueListResponse,
21
23
  DecoratedlistserviceapimodelListResponse,
22
24
  DecoratedProductionServiceV2APIModel,
25
+ DecoratedProductionServiceV2VersionAPIModel,
23
26
  InternalProductionJob,
24
27
  JobQueueSortDirective,
25
28
  OrganizationCollaborator,
@@ -28,6 +31,7 @@ from anyscale.client.openapi_client.models import (
28
31
  ProjectBase,
29
32
  ProjectListResponse,
30
33
  ResourceQuota,
34
+ ResourceTagResourceType,
31
35
  ServerSessionToken,
32
36
  SessionState,
33
37
  WorkspaceDataplaneProxiedArtifacts,
@@ -57,7 +61,7 @@ from anyscale.utils.workspace_notification import WorkspaceNotification
57
61
  # Maybe just make it part of the release process to update it, or fetch the
58
62
  # default builds and get the latest one. The best thing to do is probably
59
63
  # to populate this in the backend.
60
- DEFAULT_RAY_VERSION = "2.50.0" # RAY_RELEASE_UPDATE: update to latest version.
64
+ DEFAULT_RAY_VERSION = "2.51.0" # RAY_RELEASE_UPDATE: update to latest version
61
65
  DEFAULT_PYTHON_VERSION = "py311"
62
66
  RUNTIME_ENV_PACKAGE_FORMAT = "pkg_{content_hash}.zip"
63
67
 
@@ -166,6 +170,13 @@ class AnyscaleClientInterface(ABC):
166
170
  """
167
171
  raise NotImplementedError
168
172
 
173
+ @abstractmethod
174
+ def get_cloud_resource_by_name(
175
+ self, cloud_id: str, cloud_resource_name: str
176
+ ) -> Optional[DecoratedCloudResource]:
177
+ """Get a cloud resource by name."""
178
+ raise NotImplementedError
179
+
169
180
  @abstractmethod
170
181
  def get_default_cloud(self) -> Optional[Cloud]:
171
182
  """Get the user's default cloud."""
@@ -234,7 +245,9 @@ class AnyscaleClientInterface(ABC):
234
245
  raise NotImplementedError
235
246
 
236
247
  @abstractmethod
237
- def get_default_compute_config(self, *, cloud_id: str) -> ClusterCompute:
248
+ def get_default_compute_config(
249
+ self, *, cloud_id: str, cloud_resource_id: Optional[str] = None
250
+ ) -> ClusterCompute:
238
251
  """Get the default compute config for the provided cloud ID."""
239
252
  raise NotImplementedError
240
253
 
@@ -345,6 +358,7 @@ class AnyscaleClientInterface(ABC):
345
358
  *,
346
359
  name: Optional[str],
347
360
  state_filter: Optional[List[str]],
361
+ tag_filter: Optional[List[str]],
348
362
  creator_id: Optional[str],
349
363
  cloud: Optional[str],
350
364
  project: Optional[str],
@@ -381,6 +395,13 @@ class AnyscaleClientInterface(ABC):
381
395
  """List projects."""
382
396
  raise NotImplementedError
383
397
 
398
+ @abstractmethod
399
+ def get_service_versions(
400
+ self, service_id: str, read_all_versions: bool = False
401
+ ) -> List[DecoratedProductionServiceV2VersionAPIModel]:
402
+ """Get the versions of a service."""
403
+ raise NotImplementedError
404
+
384
405
  @abstractmethod
385
406
  def create_project(self, project: WriteProject) -> ProjectBase:
386
407
  """Create a project."""
@@ -453,6 +474,7 @@ class AnyscaleClientInterface(ABC):
453
474
  cluster_status: Optional[SessionState] = None,
454
475
  project: Optional[str] = None,
455
476
  cloud: Optional[str] = None,
477
+ tags_filter: Optional[Dict[str, List[str]]] = None,
456
478
  count: Optional[int] = None,
457
479
  paging_token: Optional[str] = None,
458
480
  sorting_directives: Optional[List[JobQueueSortDirective]] = None,
@@ -470,6 +492,16 @@ class AnyscaleClientInterface(ABC):
470
492
  """
471
493
  raise NotImplementedError
472
494
 
495
+ @abstractmethod
496
+ def rollout_service_multi_version(
497
+ self, model: ApplyProductionServiceMultiVersionV2Model
498
+ ) -> DecoratedProductionServiceV2APIModel:
499
+ """Deploy or update the service to use the provided multi-version configs.
500
+
501
+ Returns the service ID.
502
+ """
503
+ raise NotImplementedError
504
+
473
505
  @abstractmethod
474
506
  def rollback_service(
475
507
  self, service_id: str, *, max_surge_percent: Optional[int] = None
@@ -821,3 +853,20 @@ class AnyscaleClientInterface(ABC):
821
853
  ) -> None:
822
854
  """Set the status of a resource quota."""
823
855
  raise NotImplementedError
856
+
857
+ @abstractmethod
858
+ def upsert_resource_tags(
859
+ self,
860
+ resource_type: ResourceTagResourceType,
861
+ resource_id: str,
862
+ tags: Dict[str, str],
863
+ ) -> None:
864
+ """Upsert tags (add/update) for a resource."""
865
+ raise NotImplementedError
866
+
867
+ @abstractmethod
868
+ def delete_resource_tags(
869
+ self, resource_type: ResourceTagResourceType, resource_id: str, keys: List[str],
870
+ ) -> None:
871
+ """Delete tags for the provided keys from a resource."""
872
+ raise NotImplementedError
@@ -2,7 +2,7 @@ from collections import defaultdict
2
2
  from datetime import date, datetime
3
3
  import logging
4
4
  import os
5
- from typing import DefaultDict, Dict, Generator, List, Optional, Tuple
5
+ from typing import DefaultDict, Dict, Generator, List, Optional, Tuple, Union
6
6
  from unittest.mock import Mock
7
7
  import uuid
8
8
 
@@ -16,6 +16,7 @@ from anyscale.client.openapi_client.models import (
16
16
  AdminCreatedUser,
17
17
  AdminCreateUser,
18
18
  AnyscaleServiceAccount,
19
+ ApplyProductionServiceMultiVersionV2Model,
19
20
  Cloud,
20
21
  CloudProviders,
21
22
  ClusterOperation,
@@ -28,9 +29,11 @@ from anyscale.client.openapi_client.models import (
28
29
  CreateInternalProductionJob,
29
30
  CreateResourceQuota,
30
31
  CreateUserProjectCollaborator,
32
+ DecoratedCloudResource,
31
33
  DecoratedComputeTemplate,
32
34
  DecoratedlistserviceapimodelListResponse,
33
35
  DecoratedProductionServiceV2APIModel,
36
+ DecoratedProductionServiceV2VersionAPIModel,
34
37
  DeletedPlatformFineTunedModel,
35
38
  ExperimentalWorkspace,
36
39
  FineTunedModel,
@@ -50,6 +53,7 @@ from anyscale.client.openapi_client.models import (
50
53
  ProjectBase,
51
54
  ProjectListResponse,
52
55
  ResourceQuota,
56
+ ResourceTagResourceType,
53
57
  ServerSessionToken,
54
58
  WorkspaceDataplaneProxiedArtifacts,
55
59
  WriteProject,
@@ -153,6 +157,9 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
153
157
  self._workspace_cluster: Optional[Cluster] = None
154
158
  self._workspace_dependency_tracking_enabled: bool = False
155
159
  self._services: Dict[str, DecoratedProductionServiceV2APIModel] = {}
160
+ self._versions: Dict[
161
+ str, Dict[str, ProductionServiceV2VersionModel]
162
+ ] = defaultdict(dict)
156
163
  self._archived_services: Dict[str, DecoratedProductionServiceV2APIModel] = {}
157
164
  self._deleted_services: Dict[str, DecoratedProductionServiceV2APIModel] = {}
158
165
  self._jobs: Dict[str, ProductionJob] = {}
@@ -409,6 +416,11 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
409
416
  return c
410
417
  return None
411
418
 
419
+ def get_cloud_resource_by_name(
420
+ self, cloud_id: str, cloud_resource_name: str # noqa: ARG002
421
+ ) -> Optional[DecoratedCloudResource]:
422
+ return None
423
+
412
424
  def get_default_cloud(self) -> Optional[Cloud]:
413
425
  return self._clouds.get(self.DEFAULT_CLOUD_ID, None)
414
426
 
@@ -536,7 +548,9 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
536
548
  ):
537
549
  self._default_compute_configs[cloud_id] = compute_config
538
550
 
539
- def get_default_compute_config(self, *, cloud_id: str) -> ClusterCompute:
551
+ def get_default_compute_config(
552
+ self, *, cloud_id: str, cloud_resource_id: Optional[str] = None # noqa: ARG002
553
+ ) -> ClusterCompute:
540
554
  return self._default_compute_configs[cloud_id]
541
555
 
542
556
  def list_cluster_env_builds(
@@ -658,6 +672,9 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
658
672
 
659
673
  def update_service(self, model: DecoratedProductionServiceV2APIModel):
660
674
  self._services[model.id] = model
675
+ if model.versions is not None:
676
+ for version in model.versions:
677
+ self._versions[model.id][version.id] = version
661
678
 
662
679
  def get_service(
663
680
  self,
@@ -897,7 +914,11 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
897
914
  return project_id
898
915
 
899
916
  @property
900
- def rolled_out_model(self) -> Optional[ApplyProductionServiceV2Model]:
917
+ def rolled_out_model(
918
+ self,
919
+ ) -> Optional[
920
+ Union[ApplyProductionServiceV2Model, ApplyProductionServiceMultiVersionV2Model]
921
+ ]:
901
922
  return self._rolled_out_model
902
923
 
903
924
  def rollout_service(
@@ -917,6 +938,18 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
917
938
  else:
918
939
  service_id = f"service-id-{uuid.uuid4()!s}"
919
940
 
941
+ primary_version = ProductionServiceV2VersionModel(
942
+ id=str(uuid.uuid4()),
943
+ created_at=datetime.now(),
944
+ version=model.version,
945
+ current_state=ServiceVersionState.RUNNING,
946
+ weight=100,
947
+ build_id=model.build_id,
948
+ compute_config_id=model.compute_config_id,
949
+ ray_serve_config=model.ray_serve_config,
950
+ ray_gcs_external_storage_config=model.ray_gcs_external_storage_config,
951
+ local_vars_configuration=OPENAPI_NO_VALIDATION,
952
+ )
920
953
  service = DecoratedProductionServiceV2APIModel(
921
954
  id=service_id,
922
955
  name=model.name,
@@ -925,18 +958,56 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
925
958
  current_state=ServiceEventCurrentState.STARTING,
926
959
  base_url=f"http://{model.name}.fake.url",
927
960
  auth_token="fake-auth-token",
928
- primary_version=ProductionServiceV2VersionModel(
961
+ primary_version=primary_version,
962
+ versions=[primary_version],
963
+ local_vars_configuration=OPENAPI_NO_VALIDATION,
964
+ )
965
+ self.update_service(service)
966
+ return service
967
+
968
+ def rollout_service_multi_version(
969
+ self, model: ApplyProductionServiceMultiVersionV2Model
970
+ ) -> DecoratedProductionServiceV2APIModel:
971
+ self._rolled_out_model = model
972
+ version = model.service_versions[0]
973
+ project_model = self.get_project(version.project_id)
974
+ project = project_model.name if project_model else None
975
+ compute_config = self.get_compute_config(version.compute_config_id)
976
+ cloud_id = compute_config.config.cloud_id if compute_config else None
977
+ cloud_model = self.get_cloud(cloud_id=cloud_id)
978
+ cloud = cloud_model.name if cloud_model else None
979
+ existing_service = self.get_service(version.name, project=project, cloud=cloud)
980
+ if existing_service is not None:
981
+ service_id = existing_service.id
982
+ else:
983
+ service_id = f"service-id-{uuid.uuid4()!s}"
984
+
985
+ service_versions = []
986
+ for version in model.service_versions:
987
+ service_version = ProductionServiceV2VersionModel(
929
988
  id=str(uuid.uuid4()),
930
989
  created_at=datetime.now(),
931
- version="primary",
990
+ version=version.version,
932
991
  current_state=ServiceVersionState.RUNNING,
933
- weight=100,
934
- build_id=model.build_id,
935
- compute_config_id=model.compute_config_id,
936
- ray_serve_config=model.ray_serve_config,
937
- ray_gcs_external_storage_config=model.ray_gcs_external_storage_config,
992
+ weight=version.traffic_percent,
993
+ build_id=version.build_id,
994
+ compute_config_id=version.compute_config_id,
995
+ ray_serve_config=version.ray_serve_config,
996
+ ray_gcs_external_storage_config=version.ray_gcs_external_storage_config,
938
997
  local_vars_configuration=OPENAPI_NO_VALIDATION,
939
- ),
998
+ )
999
+ service_versions.append(service_version)
1000
+
1001
+ service = DecoratedProductionServiceV2APIModel(
1002
+ id=service_id,
1003
+ name=version.name,
1004
+ project_id=version.project_id,
1005
+ cloud_id=self.get_cloud_id(compute_config_id=version.compute_config_id),
1006
+ current_state=ServiceEventCurrentState.STARTING,
1007
+ base_url=f"http://{version.name}.fake.url",
1008
+ auth_token="fake-auth-token",
1009
+ primary_version="",
1010
+ versions=service_versions,
940
1011
  local_vars_configuration=OPENAPI_NO_VALIDATION,
941
1012
  )
942
1013
  self.update_service(service)
@@ -1551,6 +1622,7 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
1551
1622
  *,
1552
1623
  name: Optional[str] = None,
1553
1624
  state_filter: Optional[List[str]] = None,
1625
+ tag_filter: Optional[List[str]] = None, # noqa: ARG002
1554
1626
  creator_id: Optional[str] = None, # noqa: ARG002
1555
1627
  cloud: Optional[str] = None,
1556
1628
  project: Optional[str] = None,
@@ -1603,3 +1675,23 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
1603
1675
  local_vars_configuration=OPENAPI_NO_VALIDATION,
1604
1676
  )
1605
1677
  return response
1678
+
1679
+ def get_service_versions(
1680
+ self, service_id: str, read_all_versions: bool = False # noqa: ARG002
1681
+ ) -> List[DecoratedProductionServiceV2VersionAPIModel]:
1682
+ return list(self._versions[service_id].values())
1683
+
1684
+ def upsert_resource_tags(
1685
+ self,
1686
+ resource_type: ResourceTagResourceType,
1687
+ resource_id: str,
1688
+ tags: Dict[str, str],
1689
+ ) -> None:
1690
+ _ = resource_type, resource_id, tags
1691
+ return None
1692
+
1693
+ def delete_resource_tags(
1694
+ self, resource_type: ResourceTagResourceType, resource_id: str, keys: List[str],
1695
+ ) -> None:
1696
+ _ = resource_type, resource_id, keys
1697
+ return None