anyscale 0.26.23__py3-none-any.whl → 0.26.25__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/anyscale_client.py +1 -1
- anyscale/_private/docgen/__main__.py +0 -8
- anyscale/_private/docgen/models.md +39 -182
- anyscale/_private/workload/workload_config.py +1 -1
- anyscale/client/README.md +2 -35
- anyscale/client/openapi_client/__init__.py +2 -21
- anyscale/client/openapi_client/api/default_api.py +275 -1868
- anyscale/client/openapi_client/models/__init__.py +2 -21
- anyscale/client/openapi_client/models/cloud_data_bucket_file_type.py +2 -1
- anyscale/client/openapi_client/models/cloud_deployment.py +2 -2
- anyscale/client/openapi_client/models/cloud_deployment_compute_config.py +291 -0
- anyscale/client/openapi_client/models/cloud_deployment_selector.py +206 -0
- anyscale/client/openapi_client/models/cloud_providers.py +2 -1
- anyscale/client/openapi_client/models/compute_template_config.py +61 -34
- anyscale/client/openapi_client/models/create_cluster_compute_config.py +59 -31
- anyscale/client/openapi_client/models/create_compute_template_config.py +61 -34
- anyscale/client/openapi_client/models/create_workspace_from_template.py +3 -2
- anyscale/client/openapi_client/models/decorated_compute_template_config.py +61 -34
- anyscale/client/openapi_client/models/decorated_session.py +31 -3
- anyscale/client/openapi_client/models/workspace_dataplane_proxied_artifacts.py +31 -3
- anyscale/cluster_compute.py +4 -4
- anyscale/commands/anyscale_api/sessions_commands.py +1 -70
- anyscale/commands/cloud_commands.py +5 -4
- anyscale/commands/image_commands.py +2 -2
- anyscale/commands/job_commands.py +1 -1
- anyscale/commands/service_commands.py +1 -1
- anyscale/commands/workspace_commands_v2.py +1 -1
- anyscale/connect_utils/prepare_cluster.py +1 -3
- anyscale/controllers/cloud_controller.py +12 -6
- anyscale/controllers/cloud_functional_verification_controller.py +2 -2
- anyscale/image/commands.py +1 -1
- anyscale/sdk/anyscale_client/__init__.py +2 -15
- anyscale/sdk/anyscale_client/api/default_api.py +1270 -2871
- anyscale/sdk/anyscale_client/models/__init__.py +2 -15
- anyscale/sdk/anyscale_client/models/cloud_deployment_compute_config.py +291 -0
- anyscale/sdk/anyscale_client/models/cloud_deployment_selector.py +206 -0
- anyscale/sdk/anyscale_client/models/cloud_providers.py +2 -1
- anyscale/sdk/anyscale_client/models/cluster_compute_config.py +61 -34
- anyscale/sdk/anyscale_client/models/cluster_services_urls.py +29 -1
- anyscale/sdk/anyscale_client/models/compute_template_config.py +61 -34
- anyscale/sdk/anyscale_client/models/create_cluster_compute_config.py +59 -31
- anyscale/sdk/anyscale_client/models/session.py +31 -3
- anyscale/shared_anyscale_utils/utils/id_gen.py +3 -1
- anyscale/version.py +1 -1
- anyscale/workspace/models.py +1 -1
- {anyscale-0.26.23.dist-info → anyscale-0.26.25.dist-info}/METADATA +1 -1
- {anyscale-0.26.23.dist-info → anyscale-0.26.25.dist-info}/RECORD +52 -84
- anyscale/client/openapi_client/models/bank_account_information.py +0 -239
- anyscale/client/openapi_client/models/billing_information.py +0 -181
- anyscale/client/openapi_client/models/credit_card_information.py +0 -268
- anyscale/client/openapi_client/models/decoratedunifiedjob_response.py +0 -121
- anyscale/client/openapi_client/models/invoice.py +0 -413
- anyscale/client/openapi_client/models/invoice_list_response.py +0 -147
- anyscale/client/openapi_client/models/invoice_status.py +0 -102
- anyscale/client/openapi_client/models/invoices_query.py +0 -150
- anyscale/client/openapi_client/models/log_stream.py +0 -151
- anyscale/client/openapi_client/models/logstream_response.py +0 -121
- anyscale/client/openapi_client/models/miniproject_list_response.py +0 -147
- anyscale/client/openapi_client/models/organization_project_collaborator.py +0 -175
- anyscale/client/openapi_client/models/organization_project_collaborator_value.py +0 -148
- anyscale/client/openapi_client/models/organizationprojectcollaborator_list_response.py +0 -147
- anyscale/client/openapi_client/models/product_type.py +0 -100
- anyscale/client/openapi_client/models/service_usage.py +0 -353
- anyscale/client/openapi_client/models/templatized_compute_configs.py +0 -202
- anyscale/client/openapi_client/models/templatized_decorated_application_templates.py +0 -202
- anyscale/client/openapi_client/models/templatizedcomputeconfigs_response.py +0 -121
- anyscale/client/openapi_client/models/templatizeddecoratedapplicationtemplates_response.py +0 -121
- anyscale/client/openapi_client/models/write_session.py +0 -147
- anyscale/sdk/anyscale_client/models/build_list_response.py +0 -147
- anyscale/sdk/anyscale_client/models/build_log_response.py +0 -123
- anyscale/sdk/anyscale_client/models/buildlogresponse_response.py +0 -121
- anyscale/sdk/anyscale_client/models/compute_template_query.py +0 -316
- anyscale/sdk/anyscale_client/models/computetemplate_list_response.py +0 -147
- anyscale/sdk/anyscale_client/models/create_build.py +0 -263
- anyscale/sdk/anyscale_client/models/create_compute_template.py +0 -229
- anyscale/sdk/anyscale_client/models/create_compute_template_config.py +0 -464
- anyscale/sdk/anyscale_client/models/session_list_response.py +0 -147
- anyscale/sdk/anyscale_client/models/sessions_query.py +0 -206
- anyscale/sdk/anyscale_client/models/start_session_options.py +0 -206
- anyscale/sdk/anyscale_client/models/terminate_session_options.py +0 -206
- anyscale/sdk/anyscale_client/models/update_compute_template.py +0 -146
- anyscale/sdk/anyscale_client/models/update_compute_template_config.py +0 -464
- anyscale/sdk/anyscale_client/models/update_session.py +0 -150
- {anyscale-0.26.23.dist-info → anyscale-0.26.25.dist-info}/LICENSE +0 -0
- {anyscale-0.26.23.dist-info → anyscale-0.26.25.dist-info}/NOTICE +0 -0
- {anyscale-0.26.23.dist-info → anyscale-0.26.25.dist-info}/WHEEL +0 -0
- {anyscale-0.26.23.dist-info → anyscale-0.26.25.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.23.dist-info → anyscale-0.26.25.dist-info}/top_level.txt +0 -0
@@ -34,6 +34,8 @@ class DecoratedComputeTemplateConfig(object):
|
|
34
34
|
"""
|
35
35
|
openapi_types = {
|
36
36
|
'cloud_id': 'str',
|
37
|
+
'maximum_uptime_minutes': 'int',
|
38
|
+
'deployment_configs': 'list[CloudDeploymentComputeConfig]',
|
37
39
|
'max_workers': 'int',
|
38
40
|
'region': 'str',
|
39
41
|
'allowed_azs': 'list[str]',
|
@@ -42,7 +44,6 @@ class DecoratedComputeTemplateConfig(object):
|
|
42
44
|
'aws_advanced_configurations_json': 'object',
|
43
45
|
'gcp_advanced_configurations_json': 'object',
|
44
46
|
'advanced_configurations_json': 'object',
|
45
|
-
'maximum_uptime_minutes': 'int',
|
46
47
|
'auto_select_worker_config': 'bool',
|
47
48
|
'flags': 'object',
|
48
49
|
'idle_termination_minutes': 'int',
|
@@ -51,6 +52,8 @@ class DecoratedComputeTemplateConfig(object):
|
|
51
52
|
|
52
53
|
attribute_map = {
|
53
54
|
'cloud_id': 'cloud_id',
|
55
|
+
'maximum_uptime_minutes': 'maximum_uptime_minutes',
|
56
|
+
'deployment_configs': 'deployment_configs',
|
54
57
|
'max_workers': 'max_workers',
|
55
58
|
'region': 'region',
|
56
59
|
'allowed_azs': 'allowed_azs',
|
@@ -59,20 +62,21 @@ class DecoratedComputeTemplateConfig(object):
|
|
59
62
|
'aws_advanced_configurations_json': 'aws_advanced_configurations_json',
|
60
63
|
'gcp_advanced_configurations_json': 'gcp_advanced_configurations_json',
|
61
64
|
'advanced_configurations_json': 'advanced_configurations_json',
|
62
|
-
'maximum_uptime_minutes': 'maximum_uptime_minutes',
|
63
65
|
'auto_select_worker_config': 'auto_select_worker_config',
|
64
66
|
'flags': 'flags',
|
65
67
|
'idle_termination_minutes': 'idle_termination_minutes',
|
66
68
|
'cloud': 'cloud'
|
67
69
|
}
|
68
70
|
|
69
|
-
def __init__(self, cloud_id=None, max_workers=None, region=None, allowed_azs=None, head_node_type=None, worker_node_types=None, aws_advanced_configurations_json=None, gcp_advanced_configurations_json=None, advanced_configurations_json=None,
|
71
|
+
def __init__(self, cloud_id=None, maximum_uptime_minutes=None, deployment_configs=None, max_workers=None, region=None, allowed_azs=None, head_node_type=None, worker_node_types=None, aws_advanced_configurations_json=None, gcp_advanced_configurations_json=None, advanced_configurations_json=None, auto_select_worker_config=False, flags=None, idle_termination_minutes=None, cloud=None, local_vars_configuration=None): # noqa: E501
|
70
72
|
"""DecoratedComputeTemplateConfig - a model defined in OpenAPI""" # noqa: E501
|
71
73
|
if local_vars_configuration is None:
|
72
74
|
local_vars_configuration = Configuration()
|
73
75
|
self.local_vars_configuration = local_vars_configuration
|
74
76
|
|
75
77
|
self._cloud_id = None
|
78
|
+
self._maximum_uptime_minutes = None
|
79
|
+
self._deployment_configs = None
|
76
80
|
self._max_workers = None
|
77
81
|
self._region = None
|
78
82
|
self._allowed_azs = None
|
@@ -81,7 +85,6 @@ class DecoratedComputeTemplateConfig(object):
|
|
81
85
|
self._aws_advanced_configurations_json = None
|
82
86
|
self._gcp_advanced_configurations_json = None
|
83
87
|
self._advanced_configurations_json = None
|
84
|
-
self._maximum_uptime_minutes = None
|
85
88
|
self._auto_select_worker_config = None
|
86
89
|
self._flags = None
|
87
90
|
self._idle_termination_minutes = None
|
@@ -89,9 +92,14 @@ class DecoratedComputeTemplateConfig(object):
|
|
89
92
|
self.discriminator = None
|
90
93
|
|
91
94
|
self.cloud_id = cloud_id
|
95
|
+
if maximum_uptime_minutes is not None:
|
96
|
+
self.maximum_uptime_minutes = maximum_uptime_minutes
|
97
|
+
if deployment_configs is not None:
|
98
|
+
self.deployment_configs = deployment_configs
|
92
99
|
if max_workers is not None:
|
93
100
|
self.max_workers = max_workers
|
94
|
-
|
101
|
+
if region is not None:
|
102
|
+
self.region = region
|
95
103
|
if allowed_azs is not None:
|
96
104
|
self.allowed_azs = allowed_azs
|
97
105
|
self.head_node_type = head_node_type
|
@@ -103,8 +111,6 @@ class DecoratedComputeTemplateConfig(object):
|
|
103
111
|
self.gcp_advanced_configurations_json = gcp_advanced_configurations_json
|
104
112
|
if advanced_configurations_json is not None:
|
105
113
|
self.advanced_configurations_json = advanced_configurations_json
|
106
|
-
if maximum_uptime_minutes is not None:
|
107
|
-
self.maximum_uptime_minutes = maximum_uptime_minutes
|
108
114
|
if auto_select_worker_config is not None:
|
109
115
|
self.auto_select_worker_config = auto_select_worker_config
|
110
116
|
if flags is not None:
|
@@ -138,11 +144,57 @@ class DecoratedComputeTemplateConfig(object):
|
|
138
144
|
|
139
145
|
self._cloud_id = cloud_id
|
140
146
|
|
147
|
+
@property
|
148
|
+
def maximum_uptime_minutes(self):
|
149
|
+
"""Gets the maximum_uptime_minutes of this DecoratedComputeTemplateConfig. # noqa: E501
|
150
|
+
|
151
|
+
If set to a positive number, Anyscale will terminate the cluster this many minutes after cluster start. # noqa: E501
|
152
|
+
|
153
|
+
:return: The maximum_uptime_minutes of this DecoratedComputeTemplateConfig. # noqa: E501
|
154
|
+
:rtype: int
|
155
|
+
"""
|
156
|
+
return self._maximum_uptime_minutes
|
157
|
+
|
158
|
+
@maximum_uptime_minutes.setter
|
159
|
+
def maximum_uptime_minutes(self, maximum_uptime_minutes):
|
160
|
+
"""Sets the maximum_uptime_minutes of this DecoratedComputeTemplateConfig.
|
161
|
+
|
162
|
+
If set to a positive number, Anyscale will terminate the cluster this many minutes after cluster start. # noqa: E501
|
163
|
+
|
164
|
+
:param maximum_uptime_minutes: The maximum_uptime_minutes of this DecoratedComputeTemplateConfig. # noqa: E501
|
165
|
+
:type: int
|
166
|
+
"""
|
167
|
+
|
168
|
+
self._maximum_uptime_minutes = maximum_uptime_minutes
|
169
|
+
|
170
|
+
@property
|
171
|
+
def deployment_configs(self):
|
172
|
+
"""Gets the deployment_configs of this DecoratedComputeTemplateConfig. # noqa: E501
|
173
|
+
|
174
|
+
A list of cloud deployment-specific configs to use. # noqa: E501
|
175
|
+
|
176
|
+
:return: The deployment_configs of this DecoratedComputeTemplateConfig. # noqa: E501
|
177
|
+
:rtype: list[CloudDeploymentComputeConfig]
|
178
|
+
"""
|
179
|
+
return self._deployment_configs
|
180
|
+
|
181
|
+
@deployment_configs.setter
|
182
|
+
def deployment_configs(self, deployment_configs):
|
183
|
+
"""Sets the deployment_configs of this DecoratedComputeTemplateConfig.
|
184
|
+
|
185
|
+
A list of cloud deployment-specific configs to use. # noqa: E501
|
186
|
+
|
187
|
+
:param deployment_configs: The deployment_configs of this DecoratedComputeTemplateConfig. # noqa: E501
|
188
|
+
:type: list[CloudDeploymentComputeConfig]
|
189
|
+
"""
|
190
|
+
|
191
|
+
self._deployment_configs = deployment_configs
|
192
|
+
|
141
193
|
@property
|
142
194
|
def max_workers(self):
|
143
195
|
"""Gets the max_workers of this DecoratedComputeTemplateConfig. # noqa: E501
|
144
196
|
|
145
|
-
|
197
|
+
DEPRECATED. This attribute will be ignored - please use the \"max_resources\" flag to configure resource limits. # noqa: E501
|
146
198
|
|
147
199
|
:return: The max_workers of this DecoratedComputeTemplateConfig. # noqa: E501
|
148
200
|
:rtype: int
|
@@ -153,7 +205,7 @@ class DecoratedComputeTemplateConfig(object):
|
|
153
205
|
def max_workers(self, max_workers):
|
154
206
|
"""Sets the max_workers of this DecoratedComputeTemplateConfig.
|
155
207
|
|
156
|
-
|
208
|
+
DEPRECATED. This attribute will be ignored - please use the \"max_resources\" flag to configure resource limits. # noqa: E501
|
157
209
|
|
158
210
|
:param max_workers: The max_workers of this DecoratedComputeTemplateConfig. # noqa: E501
|
159
211
|
:type: int
|
@@ -181,8 +233,6 @@ class DecoratedComputeTemplateConfig(object):
|
|
181
233
|
:param region: The region of this DecoratedComputeTemplateConfig. # noqa: E501
|
182
234
|
:type: str
|
183
235
|
"""
|
184
|
-
if self.local_vars_configuration.client_side_validation and region is None: # noqa: E501
|
185
|
-
raise ValueError("Invalid value for `region`, must not be `None`") # noqa: E501
|
186
236
|
|
187
237
|
self._region = region
|
188
238
|
|
@@ -326,29 +376,6 @@ class DecoratedComputeTemplateConfig(object):
|
|
326
376
|
|
327
377
|
self._advanced_configurations_json = advanced_configurations_json
|
328
378
|
|
329
|
-
@property
|
330
|
-
def maximum_uptime_minutes(self):
|
331
|
-
"""Gets the maximum_uptime_minutes of this DecoratedComputeTemplateConfig. # noqa: E501
|
332
|
-
|
333
|
-
If set to a positive number, Anyscale will terminate the cluster this many minutes after cluster start. # noqa: E501
|
334
|
-
|
335
|
-
:return: The maximum_uptime_minutes of this DecoratedComputeTemplateConfig. # noqa: E501
|
336
|
-
:rtype: int
|
337
|
-
"""
|
338
|
-
return self._maximum_uptime_minutes
|
339
|
-
|
340
|
-
@maximum_uptime_minutes.setter
|
341
|
-
def maximum_uptime_minutes(self, maximum_uptime_minutes):
|
342
|
-
"""Sets the maximum_uptime_minutes of this DecoratedComputeTemplateConfig.
|
343
|
-
|
344
|
-
If set to a positive number, Anyscale will terminate the cluster this many minutes after cluster start. # noqa: E501
|
345
|
-
|
346
|
-
:param maximum_uptime_minutes: The maximum_uptime_minutes of this DecoratedComputeTemplateConfig. # noqa: E501
|
347
|
-
:type: int
|
348
|
-
"""
|
349
|
-
|
350
|
-
self._maximum_uptime_minutes = maximum_uptime_minutes
|
351
|
-
|
352
379
|
@property
|
353
380
|
def auto_select_worker_config(self):
|
354
381
|
"""Gets the auto_select_worker_config of this DecoratedComputeTemplateConfig. # noqa: E501
|
@@ -60,6 +60,7 @@ class DecoratedSession(object):
|
|
60
60
|
'train_metrics_dashboard_url': 'str',
|
61
61
|
'serve_metrics_dashboard_url': 'str',
|
62
62
|
'serve_deployment_metrics_dashboard_url': 'str',
|
63
|
+
'serve_llm_metrics_dashboard_url': 'str',
|
63
64
|
'persistent_metrics_url': 'str',
|
64
65
|
'connect_url': 'str',
|
65
66
|
'jupyter_notebook_url': 'str',
|
@@ -121,6 +122,7 @@ class DecoratedSession(object):
|
|
121
122
|
'train_metrics_dashboard_url': 'train_metrics_dashboard_url',
|
122
123
|
'serve_metrics_dashboard_url': 'serve_metrics_dashboard_url',
|
123
124
|
'serve_deployment_metrics_dashboard_url': 'serve_deployment_metrics_dashboard_url',
|
125
|
+
'serve_llm_metrics_dashboard_url': 'serve_llm_metrics_dashboard_url',
|
124
126
|
'persistent_metrics_url': 'persistent_metrics_url',
|
125
127
|
'connect_url': 'connect_url',
|
126
128
|
'jupyter_notebook_url': 'jupyter_notebook_url',
|
@@ -154,7 +156,7 @@ class DecoratedSession(object):
|
|
154
156
|
'project': 'project'
|
155
157
|
}
|
156
158
|
|
157
|
-
def __init__(self, name=None, project_id=None, cloud_id=None, cluster_config=None, build_id=None, compute_template_id=None, idle_timeout=120, uses_app_config=False, allow_public_internet_traffic=False, user_service_access=None, user_service_token=None, ha_job_id=None, id=None, state=None, pending_state=None, state_data=None, status=None, status_details=None, creator_id=None, created_at=None, archived_at=None, webterminal_auth_url=None, metrics_dashboard_url=None, data_metrics_dashboard_url=None, train_metrics_dashboard_url=None, serve_metrics_dashboard_url=None, serve_deployment_metrics_dashboard_url=None, persistent_metrics_url=None, connect_url=None, jupyter_notebook_url=None, ray_dashboard_url=None, access_token=None, service_proxy_url=None, tensorboard_available=None, cluster_config_last_modified_at=None, host_name=None, head_node_ip=None, ssh_authorized_keys=None, ssh_private_key=None, anyscaled_config=None, anyscaled_config_generated_at=None, default_build_id=None, idle_timeout_last_activity_at=None, ray_version=None, ray_version_last_updated_at=None, user_service_url=None, ray_component_activities_last_reported_at=None, activity_details=None, maximum_uptime_will_terminate_cluster_at=None, idle_termination_status=None, ray_dashboard_snapshot_last_reported_at=None, build=None, cloud=None, creator=None, compute_template=None, idle_time_remaining_seconds=None, access=None, project=None, local_vars_configuration=None): # noqa: E501
|
159
|
+
def __init__(self, name=None, project_id=None, cloud_id=None, cluster_config=None, build_id=None, compute_template_id=None, idle_timeout=120, uses_app_config=False, allow_public_internet_traffic=False, user_service_access=None, user_service_token=None, ha_job_id=None, id=None, state=None, pending_state=None, state_data=None, status=None, status_details=None, creator_id=None, created_at=None, archived_at=None, webterminal_auth_url=None, metrics_dashboard_url=None, data_metrics_dashboard_url=None, train_metrics_dashboard_url=None, serve_metrics_dashboard_url=None, serve_deployment_metrics_dashboard_url=None, serve_llm_metrics_dashboard_url=None, persistent_metrics_url=None, connect_url=None, jupyter_notebook_url=None, ray_dashboard_url=None, access_token=None, service_proxy_url=None, tensorboard_available=None, cluster_config_last_modified_at=None, host_name=None, head_node_ip=None, ssh_authorized_keys=None, ssh_private_key=None, anyscaled_config=None, anyscaled_config_generated_at=None, default_build_id=None, idle_timeout_last_activity_at=None, ray_version=None, ray_version_last_updated_at=None, user_service_url=None, ray_component_activities_last_reported_at=None, activity_details=None, maximum_uptime_will_terminate_cluster_at=None, idle_termination_status=None, ray_dashboard_snapshot_last_reported_at=None, build=None, cloud=None, creator=None, compute_template=None, idle_time_remaining_seconds=None, access=None, project=None, local_vars_configuration=None): # noqa: E501
|
158
160
|
"""DecoratedSession - a model defined in OpenAPI""" # noqa: E501
|
159
161
|
if local_vars_configuration is None:
|
160
162
|
local_vars_configuration = Configuration()
|
@@ -187,6 +189,7 @@ class DecoratedSession(object):
|
|
187
189
|
self._train_metrics_dashboard_url = None
|
188
190
|
self._serve_metrics_dashboard_url = None
|
189
191
|
self._serve_deployment_metrics_dashboard_url = None
|
192
|
+
self._serve_llm_metrics_dashboard_url = None
|
190
193
|
self._persistent_metrics_url = None
|
191
194
|
self._connect_url = None
|
192
195
|
self._jupyter_notebook_url = None
|
@@ -266,6 +269,8 @@ class DecoratedSession(object):
|
|
266
269
|
self.serve_metrics_dashboard_url = serve_metrics_dashboard_url
|
267
270
|
if serve_deployment_metrics_dashboard_url is not None:
|
268
271
|
self.serve_deployment_metrics_dashboard_url = serve_deployment_metrics_dashboard_url
|
272
|
+
if serve_llm_metrics_dashboard_url is not None:
|
273
|
+
self.serve_llm_metrics_dashboard_url = serve_llm_metrics_dashboard_url
|
269
274
|
if persistent_metrics_url is not None:
|
270
275
|
self.persistent_metrics_url = persistent_metrics_url
|
271
276
|
if connect_url is not None:
|
@@ -959,6 +964,29 @@ class DecoratedSession(object):
|
|
959
964
|
|
960
965
|
self._serve_deployment_metrics_dashboard_url = serve_deployment_metrics_dashboard_url
|
961
966
|
|
967
|
+
@property
|
968
|
+
def serve_llm_metrics_dashboard_url(self):
|
969
|
+
"""Gets the serve_llm_metrics_dashboard_url of this DecoratedSession. # noqa: E501
|
970
|
+
|
971
|
+
URL for Serve LLM Grafana dashboard for this Session. This field will only be populated after the Session finishes starting. # noqa: E501
|
972
|
+
|
973
|
+
:return: The serve_llm_metrics_dashboard_url of this DecoratedSession. # noqa: E501
|
974
|
+
:rtype: str
|
975
|
+
"""
|
976
|
+
return self._serve_llm_metrics_dashboard_url
|
977
|
+
|
978
|
+
@serve_llm_metrics_dashboard_url.setter
|
979
|
+
def serve_llm_metrics_dashboard_url(self, serve_llm_metrics_dashboard_url):
|
980
|
+
"""Sets the serve_llm_metrics_dashboard_url of this DecoratedSession.
|
981
|
+
|
982
|
+
URL for Serve LLM Grafana dashboard for this Session. This field will only be populated after the Session finishes starting. # noqa: E501
|
983
|
+
|
984
|
+
:param serve_llm_metrics_dashboard_url: The serve_llm_metrics_dashboard_url of this DecoratedSession. # noqa: E501
|
985
|
+
:type: str
|
986
|
+
"""
|
987
|
+
|
988
|
+
self._serve_llm_metrics_dashboard_url = serve_llm_metrics_dashboard_url
|
989
|
+
|
962
990
|
@property
|
963
991
|
def persistent_metrics_url(self):
|
964
992
|
"""Gets the persistent_metrics_url of this DecoratedSession. # noqa: E501
|
@@ -1055,7 +1083,7 @@ class DecoratedSession(object):
|
|
1055
1083
|
def access_token(self):
|
1056
1084
|
"""Gets the access_token of this DecoratedSession. # noqa: E501
|
1057
1085
|
|
1058
|
-
|
1086
|
+
[DEPRECATED] Call GET /api/v2/authentication/{cluster_id}/cluster_access_token to get this. # noqa: E501
|
1059
1087
|
|
1060
1088
|
:return: The access_token of this DecoratedSession. # noqa: E501
|
1061
1089
|
:rtype: str
|
@@ -1066,7 +1094,7 @@ class DecoratedSession(object):
|
|
1066
1094
|
def access_token(self, access_token):
|
1067
1095
|
"""Sets the access_token of this DecoratedSession.
|
1068
1096
|
|
1069
|
-
|
1097
|
+
[DEPRECATED] Call GET /api/v2/authentication/{cluster_id}/cluster_access_token to get this. # noqa: E501
|
1070
1098
|
|
1071
1099
|
:param access_token: The access_token of this DecoratedSession. # noqa: E501
|
1072
1100
|
:type: str
|
@@ -37,7 +37,8 @@ class WorkspaceDataplaneProxiedArtifacts(object):
|
|
37
37
|
'skip_packages_tracking': 'str',
|
38
38
|
'environment_variables': 'list[str]',
|
39
39
|
'dockerfile': 'str',
|
40
|
-
'dockerfile_draft': 'str'
|
40
|
+
'dockerfile_draft': 'str',
|
41
|
+
'latest_snapshot_uri': 'str'
|
41
42
|
}
|
42
43
|
|
43
44
|
attribute_map = {
|
@@ -45,10 +46,11 @@ class WorkspaceDataplaneProxiedArtifacts(object):
|
|
45
46
|
'skip_packages_tracking': 'skip_packages_tracking',
|
46
47
|
'environment_variables': 'environment_variables',
|
47
48
|
'dockerfile': 'dockerfile',
|
48
|
-
'dockerfile_draft': 'dockerfile_draft'
|
49
|
+
'dockerfile_draft': 'dockerfile_draft',
|
50
|
+
'latest_snapshot_uri': 'latest_snapshot_uri'
|
49
51
|
}
|
50
52
|
|
51
|
-
def __init__(self, requirements=None, skip_packages_tracking=None, environment_variables=None, dockerfile=None, dockerfile_draft=None, local_vars_configuration=None): # noqa: E501
|
53
|
+
def __init__(self, requirements=None, skip_packages_tracking=None, environment_variables=None, dockerfile=None, dockerfile_draft=None, latest_snapshot_uri=None, local_vars_configuration=None): # noqa: E501
|
52
54
|
"""WorkspaceDataplaneProxiedArtifacts - a model defined in OpenAPI""" # noqa: E501
|
53
55
|
if local_vars_configuration is None:
|
54
56
|
local_vars_configuration = Configuration()
|
@@ -59,6 +61,7 @@ class WorkspaceDataplaneProxiedArtifacts(object):
|
|
59
61
|
self._environment_variables = None
|
60
62
|
self._dockerfile = None
|
61
63
|
self._dockerfile_draft = None
|
64
|
+
self._latest_snapshot_uri = None
|
62
65
|
self.discriminator = None
|
63
66
|
|
64
67
|
if requirements is not None:
|
@@ -71,6 +74,8 @@ class WorkspaceDataplaneProxiedArtifacts(object):
|
|
71
74
|
self.dockerfile = dockerfile
|
72
75
|
if dockerfile_draft is not None:
|
73
76
|
self.dockerfile_draft = dockerfile_draft
|
77
|
+
if latest_snapshot_uri is not None:
|
78
|
+
self.latest_snapshot_uri = latest_snapshot_uri
|
74
79
|
|
75
80
|
@property
|
76
81
|
def requirements(self):
|
@@ -187,6 +192,29 @@ class WorkspaceDataplaneProxiedArtifacts(object):
|
|
187
192
|
|
188
193
|
self._dockerfile_draft = dockerfile_draft
|
189
194
|
|
195
|
+
@property
|
196
|
+
def latest_snapshot_uri(self):
|
197
|
+
"""Gets the latest_snapshot_uri of this WorkspaceDataplaneProxiedArtifacts. # noqa: E501
|
198
|
+
|
199
|
+
The URI of the latest snapshot of the workspace. # noqa: E501
|
200
|
+
|
201
|
+
:return: The latest_snapshot_uri of this WorkspaceDataplaneProxiedArtifacts. # noqa: E501
|
202
|
+
:rtype: str
|
203
|
+
"""
|
204
|
+
return self._latest_snapshot_uri
|
205
|
+
|
206
|
+
@latest_snapshot_uri.setter
|
207
|
+
def latest_snapshot_uri(self, latest_snapshot_uri):
|
208
|
+
"""Sets the latest_snapshot_uri of this WorkspaceDataplaneProxiedArtifacts.
|
209
|
+
|
210
|
+
The URI of the latest snapshot of the workspace. # noqa: E501
|
211
|
+
|
212
|
+
:param latest_snapshot_uri: The latest_snapshot_uri of this WorkspaceDataplaneProxiedArtifacts. # noqa: E501
|
213
|
+
:type: str
|
214
|
+
"""
|
215
|
+
|
216
|
+
self._latest_snapshot_uri = latest_snapshot_uri
|
217
|
+
|
190
218
|
def to_dict(self):
|
191
219
|
"""Returns the model properties as a dict"""
|
192
220
|
result = {}
|
anyscale/cluster_compute.py
CHANGED
@@ -4,20 +4,20 @@ from typing import Optional, Tuple, Union
|
|
4
4
|
from anyscale.authenticate import get_auth_api_client
|
5
5
|
from anyscale.cli_logger import BlockLogger
|
6
6
|
from anyscale.client.openapi_client.api.default_api import DefaultApi
|
7
|
+
from anyscale.client.openapi_client.models import (
|
8
|
+
ComputeTemplateQuery,
|
9
|
+
CreateComputeTemplate,
|
10
|
+
)
|
7
11
|
from anyscale.cloud_utils import get_cloud_id_and_name, get_last_used_cloud
|
8
12
|
from anyscale.sdk.anyscale_client import (
|
9
13
|
ArchiveStatus,
|
10
14
|
ComputeTemplateConfig,
|
11
|
-
CreateComputeTemplate,
|
12
15
|
)
|
13
16
|
from anyscale.sdk.anyscale_client.api.default_api import DefaultApi as SDKDefaultApi
|
14
17
|
from anyscale.sdk.anyscale_client.models.cluster_compute_config import (
|
15
18
|
ClusterComputeConfig,
|
16
19
|
)
|
17
20
|
from anyscale.sdk.anyscale_client.models.compute_template import ComputeTemplate
|
18
|
-
from anyscale.sdk.anyscale_client.models.compute_template_query import (
|
19
|
-
ComputeTemplateQuery,
|
20
|
-
)
|
21
21
|
from anyscale.utils.cloud_utils import get_organization_default_cloud
|
22
22
|
|
23
23
|
|
@@ -7,12 +7,8 @@ from typing import Optional
|
|
7
7
|
import click
|
8
8
|
|
9
9
|
from anyscale.authenticate import get_auth_api_client
|
10
|
+
from anyscale.client.openapi_client.models import StartSessionOptions
|
10
11
|
from anyscale.formatters import common_formatter
|
11
|
-
from anyscale.sdk.anyscale_client import (
|
12
|
-
StartSessionOptions,
|
13
|
-
TerminateSessionOptions,
|
14
|
-
UpdateSession,
|
15
|
-
)
|
16
12
|
|
17
13
|
|
18
14
|
@click.group(
|
@@ -56,29 +52,6 @@ def get_session(session_id: str) -> None:
|
|
56
52
|
print(common_formatter.prettify_json(response.to_dict()))
|
57
53
|
|
58
54
|
|
59
|
-
@sessions.command(name="update", short_help="Updates a Session.")
|
60
|
-
@click.argument("session_id", required=True)
|
61
|
-
@click.argument("cluster_config", required=False)
|
62
|
-
@click.option(
|
63
|
-
"--idle-timeout", required=False, help="Idle timeout (in minutes)", type=int,
|
64
|
-
)
|
65
|
-
def update_session(
|
66
|
-
session_id: str, cluster_config: Optional[str], idle_timeout: Optional[int]
|
67
|
-
) -> None:
|
68
|
-
"""
|
69
|
-
Updates Session SESSION_ID with CLUSTER_CONFIG and/or idle-timeout.
|
70
|
-
"""
|
71
|
-
|
72
|
-
api_client = get_auth_api_client().anyscale_api_client
|
73
|
-
update_data = UpdateSession(
|
74
|
-
cluster_config=cluster_config, idle_timeout=idle_timeout
|
75
|
-
)
|
76
|
-
response = api_client.update_session(session_id, update_data)
|
77
|
-
print(response)
|
78
|
-
|
79
|
-
print(common_formatter.prettify_json(response.to_dict()))
|
80
|
-
|
81
|
-
|
82
55
|
@sessions.command(name="delete", short_help="Deletes a Session.")
|
83
56
|
@click.argument("session_id", required=True)
|
84
57
|
def delete_session(session_id: str) -> None:
|
@@ -108,45 +81,3 @@ def start_session(session_id: str, cluster_config: Optional[str]) -> None:
|
|
108
81
|
start_session_options = StartSessionOptions(cluster_config=cluster_config)
|
109
82
|
response = api_client.start_session(session_id, start_session_options)
|
110
83
|
print(common_formatter.prettify_json(response.to_dict()))
|
111
|
-
|
112
|
-
|
113
|
-
@sessions.command(name="terminate", short_help="Sets session goal state to Terminated.")
|
114
|
-
@click.argument("session_id", required=True)
|
115
|
-
@click.argument("workers_only", required=False, default=False)
|
116
|
-
@click.argument("keep_min_workers", required=False, default=False)
|
117
|
-
@click.argument("delete", required=False, default=False)
|
118
|
-
@click.argument("take_snapshot", required=False, default=True)
|
119
|
-
def terminate_session(
|
120
|
-
session_id: str,
|
121
|
-
workers_only: bool,
|
122
|
-
keep_min_workers: bool,
|
123
|
-
delete: bool,
|
124
|
-
take_snapshot: bool,
|
125
|
-
) -> None:
|
126
|
-
"""Sets session goal state to Terminated.
|
127
|
-
A session with goal state Terminated will eventually
|
128
|
-
transition from its current state to Terminated, or
|
129
|
-
remain Terminated if its current state is already Terminated.
|
130
|
-
Retrieves the corresponding session operation.
|
131
|
-
|
132
|
-
workers_only: Default False. Only destroy the workers when stopping.
|
133
|
-
|
134
|
-
keep_min_workers: Default False. Retain the minimal amount of workers
|
135
|
-
specified in the config when stopping.
|
136
|
-
|
137
|
-
delete: Default False. Delete the session after terminating.
|
138
|
-
|
139
|
-
take_snapshot: Default True. Takes a snapshot to preserve the state of
|
140
|
-
the session before terminating. The state will be restored
|
141
|
-
the next time the session is started.
|
142
|
-
"""
|
143
|
-
|
144
|
-
api_client = get_auth_api_client().anyscale_api_client
|
145
|
-
terminate_session_options = TerminateSessionOptions(
|
146
|
-
workers_only=workers_only,
|
147
|
-
keep_min_workers=keep_min_workers,
|
148
|
-
delete=delete,
|
149
|
-
take_snapshot=take_snapshot,
|
150
|
-
)
|
151
|
-
response = api_client.terminate_session(session_id, terminate_session_options)
|
152
|
-
print(common_formatter.prettify_json(response.to_dict()))
|
@@ -436,7 +436,7 @@ def cloud_config_update(
|
|
436
436
|
"--provider",
|
437
437
|
help="The cloud provider type.",
|
438
438
|
required=True,
|
439
|
-
type=click.Choice(["aws", "gcp", "generic"], case_sensitive=False),
|
439
|
+
type=click.Choice(["aws", "gcp", "azure", "generic"], case_sensitive=False),
|
440
440
|
)
|
441
441
|
@click.option(
|
442
442
|
"--region",
|
@@ -861,18 +861,19 @@ def register_cloud( # noqa: PLR0913, PLR0912, C901
|
|
861
861
|
skip_verifications=skip_verifications,
|
862
862
|
auto_add_user=enable_auto_add_user,
|
863
863
|
)
|
864
|
-
elif provider
|
864
|
+
elif provider in ("azure", "generic"):
|
865
865
|
# For the 'generic' provider type, for the time being, most fields are optional; only 'name', 'provider', and 'compute-stack' are required.
|
866
866
|
if not name:
|
867
867
|
raise click.ClickException("Please provide a value for --name.")
|
868
868
|
|
869
869
|
if compute_stack != ComputeStack.K8S:
|
870
870
|
raise click.ClickException(
|
871
|
-
"--compute-stack=k8s must be passed
|
871
|
+
"--compute-stack=k8s must be passed to register this Anyscale cloud."
|
872
872
|
)
|
873
873
|
|
874
|
-
CloudController().
|
874
|
+
CloudController().register_azure_or_generic_cloud(
|
875
875
|
name=name,
|
876
|
+
provider=provider,
|
876
877
|
auto_add_user=enable_auto_add_user,
|
877
878
|
region=region,
|
878
879
|
cloud_storage_bucket_name=cloud_storage_bucket_name,
|
@@ -39,7 +39,7 @@ def image_cli() -> None:
|
|
39
39
|
@click.option(
|
40
40
|
"--ray-version",
|
41
41
|
"-r",
|
42
|
-
help="The Ray version (X.Y.Z) specified for this image specified by either an image URI or a containerfile. If
|
42
|
+
help="The Ray version (X.Y.Z) specified for this image specified by either an image URI or a containerfile. If you don't specify a Ray version, Anyscale defaults to the latest Ray version available at the time of the Anyscale CLI/SDK release.",
|
43
43
|
type=str,
|
44
44
|
default=None,
|
45
45
|
)
|
@@ -100,7 +100,7 @@ def get(name: str) -> None:
|
|
100
100
|
@click.option(
|
101
101
|
"--ray-version",
|
102
102
|
"-r",
|
103
|
-
help="The Ray version (X.Y.Z) specified for this image specified by either an image URI or a containerfile. If
|
103
|
+
help="The Ray version (X.Y.Z) specified for this image specified by either an image URI or a containerfile. If you don't specify a Ray version, Anyscale defaults to the latest Ray version available at the time of the Anyscale CLI/SDK release.",
|
104
104
|
type=str,
|
105
105
|
default=None,
|
106
106
|
)
|
@@ -148,7 +148,7 @@ def job_cli() -> None:
|
|
148
148
|
required=False,
|
149
149
|
default=None,
|
150
150
|
type=str,
|
151
|
-
help="The Ray version (X.Y.Z) to the image specified by --image-uri. This is only used when --image-uri is provided. If
|
151
|
+
help="The Ray version (X.Y.Z) to the image specified by --image-uri. This is only used when --image-uri is provided. If you don't specify a Ray version, Anyscale defaults to the latest Ray version available at the time of the Anyscale CLI/SDK release.",
|
152
152
|
)
|
153
153
|
@click.option(
|
154
154
|
"--max-retries",
|
@@ -113,7 +113,7 @@ def _read_name_from_config_file(path: str):
|
|
113
113
|
required=False,
|
114
114
|
default=None,
|
115
115
|
type=str,
|
116
|
-
help="The Ray version (X.Y.Z) to the image specified by --image-uri. This is only used when --image-uri is provided. If
|
116
|
+
help="The Ray version (X.Y.Z) to the image specified by --image-uri. This is only used when --image-uri is provided. If you don't specify a Ray version, Anyscale defaults to the latest Ray version available at the time of the Anyscale CLI/SDK release.",
|
117
117
|
)
|
118
118
|
@click.option(
|
119
119
|
"--compute-config",
|
@@ -86,7 +86,7 @@ def workspace_cli() -> None:
|
|
86
86
|
required=False,
|
87
87
|
default=None,
|
88
88
|
type=str,
|
89
|
-
help="The Ray version (X.Y.Z) to the image specified by --image-uri. This is only used when --image-uri is provided. If
|
89
|
+
help="The Ray version (X.Y.Z) to the image specified by --image-uri. This is only used when --image-uri is provided. If you don't specify a Ray version, Anyscale defaults to the latest Ray version available at the time of the Anyscale CLI/SDK release.",
|
90
90
|
)
|
91
91
|
@click.option(
|
92
92
|
"--compute-config",
|
@@ -8,8 +8,7 @@ from packaging import version
|
|
8
8
|
|
9
9
|
from anyscale.authenticate import get_auth_api_client
|
10
10
|
from anyscale.cli_logger import BlockLogger
|
11
|
-
from anyscale.client.openapi_client.models
|
12
|
-
from anyscale.client.openapi_client.models.session import Session
|
11
|
+
from anyscale.client.openapi_client.models import Build, CreateComputeTemplate, Session
|
13
12
|
from anyscale.cluster_compute import (
|
14
13
|
get_cluster_compute_from_name,
|
15
14
|
get_selected_cloud_id_or_default,
|
@@ -19,7 +18,6 @@ from anyscale.links import DOCS_CLUSTER
|
|
19
18
|
from anyscale.sdk.anyscale_client import (
|
20
19
|
ComputeTemplateConfig,
|
21
20
|
CreateCluster,
|
22
|
-
CreateComputeTemplate,
|
23
21
|
StartClusterOptions,
|
24
22
|
UpdateCluster,
|
25
23
|
)
|
@@ -2184,9 +2184,10 @@ class CloudController(BaseController):
|
|
2184
2184
|
f"{quota_error_str}\n\nFor instructions on how to increase quotas, visit this link: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html#request-increase"
|
2185
2185
|
)
|
2186
2186
|
|
2187
|
-
def
|
2187
|
+
def register_azure_or_generic_cloud( # noqa: PLR0913
|
2188
2188
|
self,
|
2189
2189
|
name: str,
|
2190
|
+
provider: str,
|
2190
2191
|
auto_add_user: bool = False,
|
2191
2192
|
# Optional cloud-resource-scoped parameters.
|
2192
2193
|
# Some of these are conditionally required.
|
@@ -2198,8 +2199,11 @@ class CloudController(BaseController):
|
|
2198
2199
|
nfs_mount_path: Optional[str] = None,
|
2199
2200
|
kubernetes_zones: Optional[List[str]] = None,
|
2200
2201
|
) -> None:
|
2202
|
+
cloud_provider = (
|
2203
|
+
CloudProviders.AZURE if provider == "azure" else CloudProviders.GENERIC
|
2204
|
+
)
|
2201
2205
|
self.cloud_event_producer.init_trace_context(
|
2202
|
-
CloudAnalyticsEventCommandName.REGISTER,
|
2206
|
+
CloudAnalyticsEventCommandName.REGISTER, cloud_provider,
|
2203
2207
|
)
|
2204
2208
|
self.cloud_event_producer.produce(
|
2205
2209
|
CloudAnalyticsEventName.COMMAND_START, succeeded=True
|
@@ -2228,7 +2232,7 @@ class CloudController(BaseController):
|
|
2228
2232
|
write_cloud=WriteCloud(
|
2229
2233
|
name=name,
|
2230
2234
|
region=region,
|
2231
|
-
provider=
|
2235
|
+
provider=cloud_provider,
|
2232
2236
|
is_bring_your_own_resource=True,
|
2233
2237
|
cluster_management_stack_version=ClusterManagementStackVersions.V2,
|
2234
2238
|
auto_add_user=auto_add_user,
|
@@ -2984,6 +2988,7 @@ class CloudController(BaseController):
|
|
2984
2988
|
assert cloud_provider in (
|
2985
2989
|
CloudProviders.AWS,
|
2986
2990
|
CloudProviders.GCP,
|
2991
|
+
CloudProviders.AZURE,
|
2987
2992
|
CloudProviders.GENERIC,
|
2988
2993
|
), f"Cloud provider {cloud_provider} not supported yet"
|
2989
2994
|
|
@@ -3018,9 +3023,10 @@ class CloudController(BaseController):
|
|
3018
3023
|
|
3019
3024
|
# set cloud state to DELETING
|
3020
3025
|
try:
|
3021
|
-
if (
|
3022
|
-
|
3023
|
-
|
3026
|
+
if cloud_provider in (
|
3027
|
+
CloudProviders.AWS,
|
3028
|
+
CloudProviders.AZURE,
|
3029
|
+
CloudProviders.GENERIC,
|
3024
3030
|
):
|
3025
3031
|
with self.log.spinner("Preparing to delete Anyscale cloud..."):
|
3026
3032
|
response = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
|
@@ -25,6 +25,7 @@ from anyscale.client.openapi_client.models import (
|
|
25
25
|
ApplyProductionServiceV2Model,
|
26
26
|
CloudAnalyticsEventName,
|
27
27
|
CloudProviders,
|
28
|
+
ComputeTemplateQuery,
|
28
29
|
CreateExperimentalWorkspace,
|
29
30
|
ServiceEventCurrentState,
|
30
31
|
SessionState,
|
@@ -33,7 +34,6 @@ from anyscale.controllers.base_controller import BaseController
|
|
33
34
|
from anyscale.project_utils import get_default_project
|
34
35
|
from anyscale.sdk.anyscale_client.models import (
|
35
36
|
ComputeNodeType,
|
36
|
-
ComputeTemplateQuery,
|
37
37
|
CreateClusterCompute,
|
38
38
|
CreateClusterComputeConfig,
|
39
39
|
ServiceConfig,
|
@@ -50,7 +50,7 @@ SERVICE_VERIFICATION_TIMEOUT_MINUTES = 30 # for a single rollout
|
|
50
50
|
MAXIMUM_UPTIME_MINUTES = 15
|
51
51
|
IDLE_TERMINATION_MINUTES = 5
|
52
52
|
HEAD_NODE_TYPE_AWS = "m5.xlarge" # on demand price ~$0.20 per hour
|
53
|
-
HEAD_NODE_TYPE_GCP = "
|
53
|
+
HEAD_NODE_TYPE_GCP = "n2-highmem-2" # on demand price ~$0.13 per hour
|
54
54
|
CREATE_COMPUTE_CONFIG_TIMEOUT_SECONDS = 600 # 10 minutes
|
55
55
|
|
56
56
|
# Workspace verification will fail fast if any of the following logs are found
|
anyscale/image/commands.py
CHANGED
@@ -85,7 +85,7 @@ image_uri: str = anyscale.image.register("docker.io/myuser/myimage:v2", name="my
|
|
85
85
|
_REGISTER_ARG_DOCSTRINGS = {
|
86
86
|
"image_uri": "The URI of the BYOD image to register.",
|
87
87
|
"name": "Name for the container image. If the name already exists, a new version will be built. Otherwise, a new container image will be created.",
|
88
|
-
"ray_version": "The Ray version (X.Y.Z) specified for this image specified by either an image URI or a containerfile. If
|
88
|
+
"ray_version": "The Ray version (X.Y.Z) specified for this image specified by either an image URI or a containerfile. If you don't specify a Ray version, Anyscale defaults to the latest Ray version available at the time of the Anyscale CLI/SDK release.",
|
89
89
|
"registry_login_secret": "Name or identifier of the secret containing credentials to authenticate to the docker registry hosting the image.", # pragma: allowlist secret
|
90
90
|
}
|
91
91
|
|