anyscale 0.26.17__py3-none-any.whl → 0.26.19__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/docgen/models.md +2 -2
- anyscale/anyscale-cloud-setup.yaml +0 -4
- anyscale/client/README.md +12 -37
- anyscale/client/openapi_client/__init__.py +11 -20
- anyscale/client/openapi_client/api/default_api.py +115 -2004
- anyscale/client/openapi_client/models/__init__.py +11 -20
- anyscale/client/openapi_client/models/aws_config.py +402 -0
- anyscale/client/openapi_client/models/baseimagesenum.py +68 -1
- anyscale/client/openapi_client/models/cloud_deployment.py +397 -0
- anyscale/client/openapi_client/models/{webterminal_list_response.py → clouddeployment_list_response.py} +15 -15
- anyscale/client/openapi_client/models/file_storage.py +206 -0
- anyscale/client/openapi_client/models/gcp_config.py +402 -0
- anyscale/client/openapi_client/models/kubernetes_config.py +150 -0
- anyscale/client/openapi_client/models/{monitor_logs_extension.py → networking_mode.py} +7 -7
- anyscale/client/openapi_client/models/object_storage.py +178 -0
- anyscale/client/openapi_client/models/{sessiondetails_response.py → pcp_config.py} +23 -22
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +68 -1
- anyscale/client/openapi_client/models/workspace_template_readme.py +181 -0
- anyscale/client/openapi_client/models/{archivedlogsinfo_response.py → workspacetemplatereadme_response.py} +11 -11
- anyscale/commands/cloud_commands.py +55 -7
- anyscale/connect_utils/prepare_cluster.py +19 -14
- anyscale/controllers/cloud_controller.py +60 -3
- 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.17.dist-info → anyscale-0.26.19.dist-info}/METADATA +1 -1
- {anyscale-0.26.17.dist-info → anyscale-0.26.19.dist-info}/RECORD +33 -42
- anyscale/client/openapi_client/models/archived_logs_info.py +0 -164
- anyscale/client/openapi_client/models/create_experimental_workspace_from_job.py +0 -123
- anyscale/client/openapi_client/models/create_session_from_snapshot_options.py +0 -538
- anyscale/client/openapi_client/models/create_session_in_db.py +0 -434
- anyscale/client/openapi_client/models/create_session_response.py +0 -174
- anyscale/client/openapi_client/models/createsessionresponse_response.py +0 -121
- anyscale/client/openapi_client/models/external_service_status.py +0 -147
- anyscale/client/openapi_client/models/external_service_status_response.py +0 -250
- anyscale/client/openapi_client/models/externalservicestatusresponse_response.py +0 -121
- anyscale/client/openapi_client/models/session_describe.py +0 -175
- anyscale/client/openapi_client/models/session_details.py +0 -148
- anyscale/client/openapi_client/models/session_history_item.py +0 -146
- anyscale/client/openapi_client/models/sessiondescribe_response.py +0 -121
- anyscale/client/openapi_client/models/sessionhistoryitem_list_response.py +0 -147
- anyscale/client/openapi_client/models/update_compute_template.py +0 -146
- anyscale/client/openapi_client/models/update_compute_template_config.py +0 -464
- {anyscale-0.26.17.dist-info → anyscale-0.26.19.dist-info}/LICENSE +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.19.dist-info}/NOTICE +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.19.dist-info}/WHEEL +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.19.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.17.dist-info → anyscale-0.26.19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
"""
|
4
|
+
Managed Ray API
|
5
|
+
|
6
|
+
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501
|
7
|
+
|
8
|
+
The version of the OpenAPI document: 0.1.0
|
9
|
+
Generated by: https://openapi-generator.tech
|
10
|
+
"""
|
11
|
+
|
12
|
+
|
13
|
+
import pprint
|
14
|
+
import re # noqa: F401
|
15
|
+
|
16
|
+
import six
|
17
|
+
|
18
|
+
from openapi_client.configuration import Configuration
|
19
|
+
|
20
|
+
|
21
|
+
class WorkspaceTemplateReadme(object):
|
22
|
+
"""NOTE: This class is auto generated by OpenAPI Generator.
|
23
|
+
Ref: https://openapi-generator.tech
|
24
|
+
|
25
|
+
Do not edit the class manually.
|
26
|
+
"""
|
27
|
+
|
28
|
+
"""
|
29
|
+
Attributes:
|
30
|
+
openapi_types (dict): The key is attribute name
|
31
|
+
and the value is attribute type.
|
32
|
+
attribute_map (dict): The key is attribute name
|
33
|
+
and the value is json key in definition.
|
34
|
+
"""
|
35
|
+
openapi_types = {
|
36
|
+
'content': 'str',
|
37
|
+
'title': 'str',
|
38
|
+
'description': 'str'
|
39
|
+
}
|
40
|
+
|
41
|
+
attribute_map = {
|
42
|
+
'content': 'content',
|
43
|
+
'title': 'title',
|
44
|
+
'description': 'description'
|
45
|
+
}
|
46
|
+
|
47
|
+
def __init__(self, content=None, title=None, description=None, local_vars_configuration=None): # noqa: E501
|
48
|
+
"""WorkspaceTemplateReadme - a model defined in OpenAPI""" # noqa: E501
|
49
|
+
if local_vars_configuration is None:
|
50
|
+
local_vars_configuration = Configuration()
|
51
|
+
self.local_vars_configuration = local_vars_configuration
|
52
|
+
|
53
|
+
self._content = None
|
54
|
+
self._title = None
|
55
|
+
self._description = None
|
56
|
+
self.discriminator = None
|
57
|
+
|
58
|
+
self.content = content
|
59
|
+
self.title = title
|
60
|
+
self.description = description
|
61
|
+
|
62
|
+
@property
|
63
|
+
def content(self):
|
64
|
+
"""Gets the content of this WorkspaceTemplateReadme. # noqa: E501
|
65
|
+
|
66
|
+
The content of the readme. # noqa: E501
|
67
|
+
|
68
|
+
:return: The content of this WorkspaceTemplateReadme. # noqa: E501
|
69
|
+
:rtype: str
|
70
|
+
"""
|
71
|
+
return self._content
|
72
|
+
|
73
|
+
@content.setter
|
74
|
+
def content(self, content):
|
75
|
+
"""Sets the content of this WorkspaceTemplateReadme.
|
76
|
+
|
77
|
+
The content of the readme. # noqa: E501
|
78
|
+
|
79
|
+
:param content: The content of this WorkspaceTemplateReadme. # noqa: E501
|
80
|
+
:type: str
|
81
|
+
"""
|
82
|
+
if self.local_vars_configuration.client_side_validation and content is None: # noqa: E501
|
83
|
+
raise ValueError("Invalid value for `content`, must not be `None`") # noqa: E501
|
84
|
+
|
85
|
+
self._content = content
|
86
|
+
|
87
|
+
@property
|
88
|
+
def title(self):
|
89
|
+
"""Gets the title of this WorkspaceTemplateReadme. # noqa: E501
|
90
|
+
|
91
|
+
The title of the workspace template # noqa: E501
|
92
|
+
|
93
|
+
:return: The title of this WorkspaceTemplateReadme. # noqa: E501
|
94
|
+
:rtype: str
|
95
|
+
"""
|
96
|
+
return self._title
|
97
|
+
|
98
|
+
@title.setter
|
99
|
+
def title(self, title):
|
100
|
+
"""Sets the title of this WorkspaceTemplateReadme.
|
101
|
+
|
102
|
+
The title of the workspace template # noqa: E501
|
103
|
+
|
104
|
+
:param title: The title of this WorkspaceTemplateReadme. # noqa: E501
|
105
|
+
:type: str
|
106
|
+
"""
|
107
|
+
if self.local_vars_configuration.client_side_validation and title is None: # noqa: E501
|
108
|
+
raise ValueError("Invalid value for `title`, must not be `None`") # noqa: E501
|
109
|
+
|
110
|
+
self._title = title
|
111
|
+
|
112
|
+
@property
|
113
|
+
def description(self):
|
114
|
+
"""Gets the description of this WorkspaceTemplateReadme. # noqa: E501
|
115
|
+
|
116
|
+
The description of the workspace template # noqa: E501
|
117
|
+
|
118
|
+
:return: The description of this WorkspaceTemplateReadme. # noqa: E501
|
119
|
+
:rtype: str
|
120
|
+
"""
|
121
|
+
return self._description
|
122
|
+
|
123
|
+
@description.setter
|
124
|
+
def description(self, description):
|
125
|
+
"""Sets the description of this WorkspaceTemplateReadme.
|
126
|
+
|
127
|
+
The description of the workspace template # noqa: E501
|
128
|
+
|
129
|
+
:param description: The description of this WorkspaceTemplateReadme. # noqa: E501
|
130
|
+
:type: str
|
131
|
+
"""
|
132
|
+
if self.local_vars_configuration.client_side_validation and description is None: # noqa: E501
|
133
|
+
raise ValueError("Invalid value for `description`, must not be `None`") # noqa: E501
|
134
|
+
|
135
|
+
self._description = description
|
136
|
+
|
137
|
+
def to_dict(self):
|
138
|
+
"""Returns the model properties as a dict"""
|
139
|
+
result = {}
|
140
|
+
|
141
|
+
for attr, _ in six.iteritems(self.openapi_types):
|
142
|
+
value = getattr(self, attr)
|
143
|
+
if isinstance(value, list):
|
144
|
+
result[attr] = list(map(
|
145
|
+
lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
|
146
|
+
value
|
147
|
+
))
|
148
|
+
elif hasattr(value, "to_dict"):
|
149
|
+
result[attr] = value.to_dict()
|
150
|
+
elif isinstance(value, dict):
|
151
|
+
result[attr] = dict(map(
|
152
|
+
lambda item: (item[0], item[1].to_dict())
|
153
|
+
if hasattr(item[1], "to_dict") else item,
|
154
|
+
value.items()
|
155
|
+
))
|
156
|
+
else:
|
157
|
+
result[attr] = value
|
158
|
+
|
159
|
+
return result
|
160
|
+
|
161
|
+
def to_str(self):
|
162
|
+
"""Returns the string representation of the model"""
|
163
|
+
return pprint.pformat(self.to_dict())
|
164
|
+
|
165
|
+
def __repr__(self):
|
166
|
+
"""For `print` and `pprint`"""
|
167
|
+
return self.to_str()
|
168
|
+
|
169
|
+
def __eq__(self, other):
|
170
|
+
"""Returns true if both objects are equal"""
|
171
|
+
if not isinstance(other, WorkspaceTemplateReadme):
|
172
|
+
return False
|
173
|
+
|
174
|
+
return self.to_dict() == other.to_dict()
|
175
|
+
|
176
|
+
def __ne__(self, other):
|
177
|
+
"""Returns true if both objects are not equal"""
|
178
|
+
if not isinstance(other, WorkspaceTemplateReadme):
|
179
|
+
return True
|
180
|
+
|
181
|
+
return self.to_dict() != other.to_dict()
|
@@ -18,7 +18,7 @@ import six
|
|
18
18
|
from openapi_client.configuration import Configuration
|
19
19
|
|
20
20
|
|
21
|
-
class
|
21
|
+
class WorkspacetemplatereadmeResponse(object):
|
22
22
|
"""NOTE: This class is auto generated by OpenAPI Generator.
|
23
23
|
Ref: https://openapi-generator.tech
|
24
24
|
|
@@ -33,7 +33,7 @@ class ArchivedlogsinfoResponse(object):
|
|
33
33
|
and the value is json key in definition.
|
34
34
|
"""
|
35
35
|
openapi_types = {
|
36
|
-
'result': '
|
36
|
+
'result': 'WorkspaceTemplateReadme'
|
37
37
|
}
|
38
38
|
|
39
39
|
attribute_map = {
|
@@ -41,7 +41,7 @@ class ArchivedlogsinfoResponse(object):
|
|
41
41
|
}
|
42
42
|
|
43
43
|
def __init__(self, result=None, local_vars_configuration=None): # noqa: E501
|
44
|
-
"""
|
44
|
+
"""WorkspacetemplatereadmeResponse - a model defined in OpenAPI""" # noqa: E501
|
45
45
|
if local_vars_configuration is None:
|
46
46
|
local_vars_configuration = Configuration()
|
47
47
|
self.local_vars_configuration = local_vars_configuration
|
@@ -53,21 +53,21 @@ class ArchivedlogsinfoResponse(object):
|
|
53
53
|
|
54
54
|
@property
|
55
55
|
def result(self):
|
56
|
-
"""Gets the result of this
|
56
|
+
"""Gets the result of this WorkspacetemplatereadmeResponse. # noqa: E501
|
57
57
|
|
58
58
|
|
59
|
-
:return: The result of this
|
60
|
-
:rtype:
|
59
|
+
:return: The result of this WorkspacetemplatereadmeResponse. # noqa: E501
|
60
|
+
:rtype: WorkspaceTemplateReadme
|
61
61
|
"""
|
62
62
|
return self._result
|
63
63
|
|
64
64
|
@result.setter
|
65
65
|
def result(self, result):
|
66
|
-
"""Sets the result of this
|
66
|
+
"""Sets the result of this WorkspacetemplatereadmeResponse.
|
67
67
|
|
68
68
|
|
69
|
-
:param result: The result of this
|
70
|
-
:type:
|
69
|
+
:param result: The result of this WorkspacetemplatereadmeResponse. # noqa: E501
|
70
|
+
:type: WorkspaceTemplateReadme
|
71
71
|
"""
|
72
72
|
if self.local_vars_configuration.client_side_validation and result is None: # noqa: E501
|
73
73
|
raise ValueError("Invalid value for `result`, must not be `None`") # noqa: E501
|
@@ -108,14 +108,14 @@ class ArchivedlogsinfoResponse(object):
|
|
108
108
|
|
109
109
|
def __eq__(self, other):
|
110
110
|
"""Returns true if both objects are equal"""
|
111
|
-
if not isinstance(other,
|
111
|
+
if not isinstance(other, WorkspacetemplatereadmeResponse):
|
112
112
|
return False
|
113
113
|
|
114
114
|
return self.to_dict() == other.to_dict()
|
115
115
|
|
116
116
|
def __ne__(self, other):
|
117
117
|
"""Returns true if both objects are not equal"""
|
118
|
-
if not isinstance(other,
|
118
|
+
if not isinstance(other, WorkspacetemplatereadmeResponse):
|
119
119
|
return True
|
120
120
|
|
121
121
|
return self.to_dict() != other.to_dict()
|
@@ -1112,7 +1112,16 @@ def add_collaborators(cloud: str, users_file: str,) -> None:
|
|
1112
1112
|
type=str,
|
1113
1113
|
required=False,
|
1114
1114
|
)
|
1115
|
-
|
1115
|
+
@click.option(
|
1116
|
+
"--output",
|
1117
|
+
"-o",
|
1118
|
+
help="File to write the full cloud YAML to.",
|
1119
|
+
type=str,
|
1120
|
+
required=False,
|
1121
|
+
)
|
1122
|
+
def get_cloud(
|
1123
|
+
cloud_id: Optional[str], name: Optional[str], output: Optional[str]
|
1124
|
+
) -> None:
|
1116
1125
|
"""
|
1117
1126
|
Retrieve a cloud by its name or ID and display its details.
|
1118
1127
|
|
@@ -1131,9 +1140,20 @@ def get_cloud(cloud_id: Optional[str], name: Optional[str]) -> None:
|
|
1131
1140
|
log.error("Cloud not found.")
|
1132
1141
|
return
|
1133
1142
|
|
1134
|
-
|
1143
|
+
if output:
|
1144
|
+
# Include all cloud deployments for the cloud.
|
1145
|
+
result = CloudController().get_cloud_deployments(
|
1146
|
+
cloud_id=cloud.id, cloud_name=cloud.name
|
1147
|
+
)
|
1135
1148
|
|
1136
|
-
|
1149
|
+
with open(output, "w") as f:
|
1150
|
+
yaml.dump(result, f, sort_keys=False)
|
1151
|
+
|
1152
|
+
else:
|
1153
|
+
cloud_dict = (
|
1154
|
+
cloud.to_dict() if hasattr(cloud, "to_dict") else cloud.__dict__
|
1155
|
+
)
|
1156
|
+
print(yaml.dump(cloud_dict, sort_keys=False))
|
1137
1157
|
|
1138
1158
|
except ValueError as e:
|
1139
1159
|
log.error(f"Error retrieving cloud: {e}")
|
@@ -1189,15 +1209,38 @@ def get_default_cloud() -> None:
|
|
1189
1209
|
type=bool,
|
1190
1210
|
required=False,
|
1191
1211
|
default=False,
|
1212
|
+
is_flag=True,
|
1192
1213
|
)
|
1193
1214
|
@click.option(
|
1194
1215
|
"--out",
|
1195
|
-
help="Output file name for the report.",
|
1216
|
+
help="Output file name for the report. (Default jobs_report.html)",
|
1196
1217
|
type=str,
|
1197
1218
|
required=False,
|
1198
|
-
default=
|
1219
|
+
default=None,
|
1220
|
+
)
|
1221
|
+
@click.option(
|
1222
|
+
"--sort-by",
|
1223
|
+
help=(
|
1224
|
+
"Column to sort by. (Default created_at). "
|
1225
|
+
"created_at: Job creation time. "
|
1226
|
+
"gpu: Unused GPU hours. "
|
1227
|
+
"cpu: Unused CPU hours. "
|
1228
|
+
"instances: Number of instances."
|
1229
|
+
),
|
1230
|
+
type=click.Choice(["created_at", "gpu", "cpu", "instances"], case_sensitive=False),
|
1231
|
+
required=False,
|
1232
|
+
default="created_at",
|
1199
1233
|
)
|
1200
|
-
|
1234
|
+
@click.option(
|
1235
|
+
"--sort-order",
|
1236
|
+
help="Sort order. (Default desc)",
|
1237
|
+
type=click.Choice(["asc", "desc"], case_sensitive=False),
|
1238
|
+
required=False,
|
1239
|
+
default="desc",
|
1240
|
+
)
|
1241
|
+
def generate_jobs_report(
|
1242
|
+
cloud_id: str, csv: bool, out: Optional[str], sort_by: str, sort_order: str
|
1243
|
+
) -> None:
|
1201
1244
|
"""
|
1202
1245
|
Generate a report of the jobs created in the last 7 days in HTML format.
|
1203
1246
|
Shows unused CPU-hours, unused GPU-hours, and other data.
|
@@ -1205,7 +1248,12 @@ def generate_jobs_report(cloud_id: str, csv: bool, out: str) -> None:
|
|
1205
1248
|
:param csv: Outputs the report in CSV format.
|
1206
1249
|
:param out: Output file name for the report.
|
1207
1250
|
"""
|
1251
|
+
if out is None:
|
1252
|
+
out = "jobs_report.html" if not csv else "jobs_report.csv"
|
1253
|
+
|
1208
1254
|
try:
|
1209
|
-
CloudController().generate_jobs_report(
|
1255
|
+
CloudController().generate_jobs_report(
|
1256
|
+
cloud_id, csv, out, sort_by, sort_order == "asc"
|
1257
|
+
)
|
1210
1258
|
except ValueError as e:
|
1211
1259
|
log.error(f"Error generating jobs report: {e}")
|
@@ -56,6 +56,7 @@ BUILD_STEPS = [
|
|
56
56
|
# Default minutes for autosuspend.
|
57
57
|
DEFAULT_AUTOSUSPEND_TIMEOUT = 120
|
58
58
|
|
59
|
+
|
59
60
|
# Default docker images to use for connect clusters.
|
60
61
|
def _get_base_image(image: str, ray_version: str, cpu_or_gpu: str) -> str:
|
61
62
|
py_version = "".join(str(x) for x in sys.version_info[0:2])
|
@@ -557,34 +558,36 @@ class PrepareClusterBlock:
|
|
557
558
|
"""
|
558
559
|
Get the default cluster env build based on the local python and ray versions.
|
559
560
|
"""
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
561
|
+
major, minor = sys.version_info[:2]
|
562
|
+
MIN_PY_VER = (3, 8)
|
563
|
+
MAX_PY_VER = (3, 12)
|
564
|
+
|
565
|
+
if not (MIN_PY_VER <= (major, minor) <= MAX_PY_VER):
|
564
566
|
raise ValueError(
|
565
|
-
"No default
|
566
|
-
|
567
|
-
|
567
|
+
f"No default container image for python version {major}.{minor}."
|
568
|
+
f"Please use a Python version between {MIN_PY_VER[0]}.{MIN_PY_VER[1]} "
|
569
|
+
f"and {MAX_PY_VER[0]}.{MAX_PY_VER[1]}."
|
568
570
|
)
|
571
|
+
|
569
572
|
ray_version = self._ray.__version__
|
570
573
|
if version.parse(ray_version) < version.parse(MINIMUM_RAY_VERSION):
|
571
574
|
raise ValueError(
|
572
|
-
f"No default
|
575
|
+
f"No default container image for Ray version {ray_version}. Please upgrade "
|
573
576
|
f"to a version >= {MINIMUM_RAY_VERSION}."
|
574
577
|
)
|
575
578
|
if "dev0" in ray_version:
|
576
579
|
raise ValueError(
|
577
580
|
f"Your locally installed Ray version is {ray_version}. "
|
578
|
-
"There is no default
|
581
|
+
"There is no default container image for nightly versions of Ray."
|
579
582
|
)
|
580
583
|
try:
|
581
584
|
build = self.api_client.get_default_cluster_env_build_api_v2_builds_default_py_version_ray_version_get(
|
582
|
-
f"py{
|
585
|
+
f"py{major}{minor}", ray_version
|
583
586
|
).result
|
584
587
|
return build
|
585
588
|
except Exception: # noqa: BLE001
|
586
589
|
raise RuntimeError(
|
587
|
-
f"Failed to get default
|
590
|
+
f"Failed to get default container image for Ray: {ray_version} on Python: py{major}{minor}"
|
588
591
|
)
|
589
592
|
|
590
593
|
def _get_cluster_build(
|
@@ -815,14 +818,16 @@ class PrepareClusterBlock:
|
|
815
818
|
return compute_template_id
|
816
819
|
|
817
820
|
def _register_compute_template(
|
818
|
-
self, project_id: str, config_object: ComputeTemplateConfig
|
821
|
+
self, project_id: str, config_object: ComputeTemplateConfig # noqa: ARG002
|
819
822
|
) -> str:
|
820
823
|
"""
|
821
|
-
Register compute template with a default name and return the compute template id.
|
824
|
+
Register compute template with a default name and return the compute template id.
|
825
|
+
"""
|
822
826
|
created_template = self.api_client.create_compute_template_api_v2_compute_templates_post(
|
823
827
|
create_compute_template=CreateComputeTemplate(
|
824
828
|
name=gen_valid_name("autogenerated-config"),
|
825
|
-
|
829
|
+
# project ID is deprecated in compute config
|
830
|
+
project_id=None,
|
826
831
|
config=config_object,
|
827
832
|
anonymous=True,
|
828
833
|
)
|
@@ -1388,6 +1388,43 @@ class CloudController(BaseController):
|
|
1388
1388
|
cloud_id, CloudProviders.AWS, functions_to_verify, yes,
|
1389
1389
|
)
|
1390
1390
|
|
1391
|
+
def get_cloud_deployments(self, cloud_id: str, cloud_name: str) -> Dict[str, Any]:
|
1392
|
+
cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(
|
1393
|
+
cloud_id=cloud_id,
|
1394
|
+
).result
|
1395
|
+
|
1396
|
+
if cloud.is_aioa:
|
1397
|
+
raise ValueError(
|
1398
|
+
"Listing cloud deployments is only supported for customer-hosted clouds."
|
1399
|
+
)
|
1400
|
+
|
1401
|
+
try:
|
1402
|
+
deployments = self.api_client.get_cloud_deployments_api_v2_clouds_cloud_id_deployments_get(
|
1403
|
+
cloud_id=cloud_id,
|
1404
|
+
).results
|
1405
|
+
except Exception as e: # noqa: BLE001
|
1406
|
+
raise ClickException(
|
1407
|
+
f"Failed to get cloud deployments for cloud {cloud_name} ({cloud_id}). Error: {e}"
|
1408
|
+
)
|
1409
|
+
|
1410
|
+
# Avoid displaying fields with empty values (since the values for optional fields default to None).
|
1411
|
+
def remove_empty_values(d):
|
1412
|
+
if isinstance(d, dict):
|
1413
|
+
return {
|
1414
|
+
k: remove_empty_values(v)
|
1415
|
+
for k, v in d.items()
|
1416
|
+
if remove_empty_values(v)
|
1417
|
+
}
|
1418
|
+
return d
|
1419
|
+
|
1420
|
+
return {
|
1421
|
+
"id": cloud_id,
|
1422
|
+
"name": cloud_name,
|
1423
|
+
"deployments": [
|
1424
|
+
remove_empty_values(deployment.to_dict()) for deployment in deployments
|
1425
|
+
],
|
1426
|
+
}
|
1427
|
+
|
1391
1428
|
def get_cloud_config(
|
1392
1429
|
self, cloud_name: Optional[str] = None, cloud_id: Optional[str] = None,
|
1393
1430
|
) -> CloudDeploymentConfig:
|
@@ -3650,7 +3687,9 @@ class CloudController(BaseController):
|
|
3650
3687
|
|
3651
3688
|
### End of edit cloud ###
|
3652
3689
|
|
3653
|
-
def generate_jobs_report(
|
3690
|
+
def generate_jobs_report(
|
3691
|
+
self, cloud_id: str, csv: bool, out_path: str, sort: str, sort_order_asc: bool
|
3692
|
+
) -> None:
|
3654
3693
|
end_time = datetime.now()
|
3655
3694
|
start_time = end_time - timedelta(days=7)
|
3656
3695
|
|
@@ -3690,7 +3729,25 @@ class CloudController(BaseController):
|
|
3690
3729
|
for job in full_results
|
3691
3730
|
if job.job_state in TERMINAL_HA_JOB_STATES and job.job_report is not None
|
3692
3731
|
]
|
3693
|
-
|
3732
|
+
if sort == "created_at":
|
3733
|
+
filtered_results.sort(
|
3734
|
+
key=lambda x: x.created_at, reverse=not sort_order_asc
|
3735
|
+
)
|
3736
|
+
elif sort == "gpu":
|
3737
|
+
filtered_results.sort(
|
3738
|
+
key=lambda x: x.job_report.unused_gpu_hours or 0,
|
3739
|
+
reverse=not sort_order_asc,
|
3740
|
+
)
|
3741
|
+
elif sort == "cpu":
|
3742
|
+
filtered_results.sort(
|
3743
|
+
key=lambda x: x.job_report.unused_cpu_hours or 0,
|
3744
|
+
reverse=not sort_order_asc,
|
3745
|
+
)
|
3746
|
+
elif sort == "instances":
|
3747
|
+
filtered_results.sort(
|
3748
|
+
key=lambda x: x.job_report.max_instances_launched or 0,
|
3749
|
+
reverse=not sort_order_asc,
|
3750
|
+
)
|
3694
3751
|
|
3695
3752
|
with open(out_path, "w") as out_file:
|
3696
3753
|
if csv:
|
@@ -3710,7 +3767,7 @@ class CloudController(BaseController):
|
|
3710
3767
|
max_instances_launched = job.job_report.max_instances_launched or ""
|
3711
3768
|
|
3712
3769
|
out_file.write(
|
3713
|
-
f"{job.job_id},{job.job_name},{job_state},{str(job.created_at)},{finished_at},{duration},{unused_cpu_hours},{unused_gpu_hours},{max_instances_launched}\n
|
3770
|
+
f'"{job.job_id}","{job.job_name}","{job_state}","{str(job.created_at)}","{finished_at}","{duration}","{unused_cpu_hours}","{unused_gpu_hours}","{max_instances_launched}"\n'
|
3714
3771
|
)
|
3715
3772
|
else:
|
3716
3773
|
out_file.write(
|