verda 1.17.4__tar.gz → 1.19.0__tar.gz
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.
- {verda-1.17.4 → verda-1.19.0}/PKG-INFO +6 -5
- {verda-1.17.4 → verda-1.19.0}/README.md +5 -4
- {verda-1.17.4 → verda-1.19.0}/pyproject.toml +1 -1
- {verda-1.17.4 → verda-1.19.0}/verda/_verda.py +4 -0
- verda-1.19.0/verda/clusters/__init__.py +5 -0
- verda-1.19.0/verda/clusters/_clusters.py +297 -0
- {verda-1.17.4 → verda-1.19.0}/verda/constants.py +16 -0
- {verda-1.17.4 → verda-1.19.0}/verda/instances/_instances.py +4 -3
- {verda-1.17.4 → verda-1.19.0}/verda/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/_version.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/authentication/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/authentication/_authentication.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/balance/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/balance/_balance.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/containers/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/containers/_containers.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/exceptions.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/helpers.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/http_client/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/http_client/_http_client.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/images/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/images/_images.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/inference_client/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/inference_client/_inference_client.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/instance_types/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/instance_types/_instance_types.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/instances/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/locations/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/locations/_locations.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/ssh_keys/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/ssh_keys/_ssh_keys.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/startup_scripts/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/startup_scripts/_startup_scripts.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/volume_types/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/volume_types/_volume_types.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/volumes/__init__.py +0 -0
- {verda-1.17.4 → verda-1.19.0}/verda/volumes/_volumes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: verda
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Summary: Official Python SDK for Verda (formerly DataCrunch) Public API
|
|
5
5
|
Author: Verda Cloud Oy
|
|
6
6
|
Author-email: Verda Cloud Oy <info@verda.com>
|
|
@@ -84,9 +84,10 @@ This package was originally published under `datacrunch` name, see [MIGRATION.md
|
|
|
84
84
|
```python
|
|
85
85
|
import os
|
|
86
86
|
from verda import VerdaClient
|
|
87
|
+
from verda.constants import Actions
|
|
87
88
|
|
|
88
89
|
# Get credentials from environment variables
|
|
89
|
-
CLIENT_ID = os.environ
|
|
90
|
+
CLIENT_ID = os.environ['VERDA_CLIENT_ID']
|
|
90
91
|
CLIENT_SECRET = os.environ['VERDA_CLIENT_SECRET']
|
|
91
92
|
|
|
92
93
|
# Create client
|
|
@@ -103,10 +104,10 @@ This package was originally published under `datacrunch` name, see [MIGRATION.md
|
|
|
103
104
|
description='example instance')
|
|
104
105
|
|
|
105
106
|
# Delete instance
|
|
106
|
-
verda.instances.action(instance.id,
|
|
107
|
+
verda.instances.action(instance.id, Actions.DELETE)
|
|
107
108
|
```
|
|
108
109
|
|
|
109
|
-
More examples can be found in the
|
|
110
|
+
More examples can be found in the [/examples](./examples) folder or in the [documentation](https://datacrunch-python.readthedocs.io/en/latest/).
|
|
110
111
|
|
|
111
112
|
## Development
|
|
112
113
|
|
|
@@ -148,7 +149,7 @@ CLIENT_SECRET = 'secret'
|
|
|
148
149
|
CLIENT_ID = 'your-id'
|
|
149
150
|
|
|
150
151
|
# Create client
|
|
151
|
-
verda = VerdaClient(CLIENT_ID, CLIENT_SECRET
|
|
152
|
+
verda = VerdaClient(CLIENT_ID, CLIENT_SECRET)
|
|
152
153
|
```
|
|
153
154
|
|
|
154
155
|
Run it:
|
|
@@ -58,9 +58,10 @@ This package was originally published under `datacrunch` name, see [MIGRATION.md
|
|
|
58
58
|
```python
|
|
59
59
|
import os
|
|
60
60
|
from verda import VerdaClient
|
|
61
|
+
from verda.constants import Actions
|
|
61
62
|
|
|
62
63
|
# Get credentials from environment variables
|
|
63
|
-
CLIENT_ID = os.environ
|
|
64
|
+
CLIENT_ID = os.environ['VERDA_CLIENT_ID']
|
|
64
65
|
CLIENT_SECRET = os.environ['VERDA_CLIENT_SECRET']
|
|
65
66
|
|
|
66
67
|
# Create client
|
|
@@ -77,10 +78,10 @@ This package was originally published under `datacrunch` name, see [MIGRATION.md
|
|
|
77
78
|
description='example instance')
|
|
78
79
|
|
|
79
80
|
# Delete instance
|
|
80
|
-
verda.instances.action(instance.id,
|
|
81
|
+
verda.instances.action(instance.id, Actions.DELETE)
|
|
81
82
|
```
|
|
82
83
|
|
|
83
|
-
More examples can be found in the
|
|
84
|
+
More examples can be found in the [/examples](./examples) folder or in the [documentation](https://datacrunch-python.readthedocs.io/en/latest/).
|
|
84
85
|
|
|
85
86
|
## Development
|
|
86
87
|
|
|
@@ -122,7 +123,7 @@ CLIENT_SECRET = 'secret'
|
|
|
122
123
|
CLIENT_ID = 'your-id'
|
|
123
124
|
|
|
124
125
|
# Create client
|
|
125
|
-
verda = VerdaClient(CLIENT_ID, CLIENT_SECRET
|
|
126
|
+
verda = VerdaClient(CLIENT_ID, CLIENT_SECRET)
|
|
126
127
|
```
|
|
127
128
|
|
|
128
129
|
Run it:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from verda._version import __version__
|
|
2
2
|
from verda.authentication import AuthenticationService
|
|
3
3
|
from verda.balance import BalanceService
|
|
4
|
+
from verda.clusters import ClustersService
|
|
4
5
|
from verda.constants import Constants
|
|
5
6
|
from verda.containers import ContainersService
|
|
6
7
|
from verda.http_client import HTTPClient
|
|
@@ -79,5 +80,8 @@ class VerdaClient:
|
|
|
79
80
|
self.containers: ContainersService = ContainersService(self._http_client, inference_key)
|
|
80
81
|
"""Containers service. Deploy, manage, and monitor container deployments"""
|
|
81
82
|
|
|
83
|
+
self.clusters: ClustersService = ClustersService(self._http_client)
|
|
84
|
+
"""Clusters service. Create and manage compute clusters"""
|
|
85
|
+
|
|
82
86
|
|
|
83
87
|
__all__ = ['VerdaClient']
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import time
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from dataclasses_json import dataclass_json
|
|
6
|
+
|
|
7
|
+
from verda.constants import Actions, ClusterStatus, ErrorCodes, Locations
|
|
8
|
+
from verda.exceptions import APIException
|
|
9
|
+
from verda.http_client import HTTPClient
|
|
10
|
+
|
|
11
|
+
CLUSTERS_ENDPOINT = '/clusters'
|
|
12
|
+
|
|
13
|
+
# Default shared volume size is 30TB
|
|
14
|
+
DEFAULT_SHARED_VOLUME_SIZE = 30000
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass_json
|
|
18
|
+
@dataclass
|
|
19
|
+
class ClusterWorkerNode:
|
|
20
|
+
"""Represents a worker node in a cluster.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
id: Unique identifier for the node.
|
|
24
|
+
status: Current status of the node.
|
|
25
|
+
hostname: Network hostname of the node.
|
|
26
|
+
private_ip: Private IP address of the node.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
id: str
|
|
30
|
+
status: str
|
|
31
|
+
hostname: str
|
|
32
|
+
private_ip: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass_json
|
|
36
|
+
@dataclass
|
|
37
|
+
class SharedVolume:
|
|
38
|
+
"""Represents a shared volume in a cluster.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
id: Unique identifier for the volume.
|
|
42
|
+
name: Name of the volume.
|
|
43
|
+
size_in_gigabytes: Size of the volume in gigabytes.
|
|
44
|
+
mount_point: Mount point of the volume.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
id: str
|
|
48
|
+
name: str
|
|
49
|
+
size_in_gigabytes: int
|
|
50
|
+
mount_point: str | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass_json
|
|
54
|
+
@dataclass
|
|
55
|
+
class Cluster:
|
|
56
|
+
"""Represents a compute cluster with multiple nodes.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
id: Unique identifier for the cluster.
|
|
60
|
+
hostname: Human-readable hostname of the cluster.
|
|
61
|
+
description: Description of the cluster.
|
|
62
|
+
status: Current operational status of the cluster.
|
|
63
|
+
created_at: Timestamp of cluster creation.
|
|
64
|
+
location: Datacenter location code (default: Locations.FIN_03).
|
|
65
|
+
cluster_type: Type of the cluster.
|
|
66
|
+
worker_nodes: List of nodes in the cluster.
|
|
67
|
+
ssh_key_ids: List of SSH key IDs associated with the cluster nodes.
|
|
68
|
+
image: Image ID or type used for cluster nodes.
|
|
69
|
+
startup_script_id: ID of the startup script to run on nodes.
|
|
70
|
+
public_ip: IP address of the jumphost.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
id: str
|
|
74
|
+
hostname: str
|
|
75
|
+
description: str
|
|
76
|
+
status: str
|
|
77
|
+
created_at: str
|
|
78
|
+
location: str
|
|
79
|
+
cluster_type: str
|
|
80
|
+
worker_nodes: list[ClusterWorkerNode]
|
|
81
|
+
shared_volumes: list[SharedVolume]
|
|
82
|
+
ssh_key_ids: list[str]
|
|
83
|
+
|
|
84
|
+
image: str | None = None
|
|
85
|
+
startup_script_id: str | None = None
|
|
86
|
+
ip: str | None = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ClustersService:
|
|
90
|
+
"""Service for managing compute clusters through the API.
|
|
91
|
+
|
|
92
|
+
This service provides methods to create, retrieve, and manage compute clusters.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, http_client: HTTPClient) -> None:
|
|
96
|
+
"""Initializes the ClustersService with an HTTP client.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
http_client: HTTP client for making API requests.
|
|
100
|
+
"""
|
|
101
|
+
self._http_client = http_client
|
|
102
|
+
|
|
103
|
+
def get(self, status: str | None = None) -> list[Cluster]:
|
|
104
|
+
"""Retrieves all clusters or clusters with specific status.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
status: Optional status filter for clusters. If None, returns all
|
|
108
|
+
non-deleted clusters.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of cluster objects matching the criteria.
|
|
112
|
+
"""
|
|
113
|
+
clusters_dict = self._http_client.get(CLUSTERS_ENDPOINT, params={'status': status}).json()
|
|
114
|
+
return [
|
|
115
|
+
Cluster.from_dict(cluster_dict, infer_missing=True) for cluster_dict in clusters_dict
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
def get_by_id(self, id: str) -> Cluster:
|
|
119
|
+
"""Retrieves a specific cluster by its ID.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
id: Unique identifier of the cluster to retrieve.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Cluster object with the specified ID.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
HTTPError: If the cluster is not found or other API error occurs.
|
|
129
|
+
"""
|
|
130
|
+
cluster_dict = self._http_client.get(CLUSTERS_ENDPOINT + f'/{id}').json()
|
|
131
|
+
return Cluster.from_dict(cluster_dict, infer_missing=True)
|
|
132
|
+
|
|
133
|
+
def create(
|
|
134
|
+
self,
|
|
135
|
+
cluster_type: str,
|
|
136
|
+
image: str,
|
|
137
|
+
hostname: str,
|
|
138
|
+
*,
|
|
139
|
+
description: str = '',
|
|
140
|
+
ssh_key_ids: list = [],
|
|
141
|
+
location: str = Locations.FIN_03,
|
|
142
|
+
startup_script_id: str | None = None,
|
|
143
|
+
shared_volume_name: str | None = None,
|
|
144
|
+
shared_volume_size: int | None = None,
|
|
145
|
+
wait_for_status: str | None = ClusterStatus.PROVISIONING,
|
|
146
|
+
max_wait_time: float = 900,
|
|
147
|
+
initial_interval: float = 1.0,
|
|
148
|
+
max_interval: float = 10,
|
|
149
|
+
backoff_coefficient: float = 2.0,
|
|
150
|
+
) -> Cluster:
|
|
151
|
+
"""Creates and deploys a new compute cluster.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
hostname: Name for the cluster.
|
|
155
|
+
cluster_type: Cluster type.
|
|
156
|
+
image: Image type or ID for cluster nodes.
|
|
157
|
+
description: Human-readable description of the cluster.
|
|
158
|
+
ssh_key_ids: List of SSH key IDs to associate with cluster nodes.
|
|
159
|
+
location: Datacenter location code (default: Locations.FIN_03).
|
|
160
|
+
startup_script_id: Optional ID of startup script to run on nodes.
|
|
161
|
+
shared_volume_name: Optional name for the shared volume.
|
|
162
|
+
shared_volume_size: Optional size for the shared volume, in GB, default to 30TB.
|
|
163
|
+
wait_for_status: Status to wait for the cluster to reach, default to PROVISIONING. If None, no wait is performed.
|
|
164
|
+
max_wait_time: Maximum total wait for the cluster to start creating, in seconds (default: 900)
|
|
165
|
+
initial_interval: Initial interval, in seconds (default: 1.0)
|
|
166
|
+
max_interval: The longest single delay allowed between retries, in seconds (default: 10)
|
|
167
|
+
backoff_coefficient: Coefficient to calculate the next retry interval (default 2.0)
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
The newly created cluster object.
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
HTTPError: If cluster creation fails or other API error occurs.
|
|
174
|
+
TimeoutError: If cluster does not start creating within max_wait_time.
|
|
175
|
+
"""
|
|
176
|
+
payload = {
|
|
177
|
+
'hostname': hostname,
|
|
178
|
+
'cluster_type': cluster_type,
|
|
179
|
+
'image': image,
|
|
180
|
+
'description': description,
|
|
181
|
+
'ssh_key_ids': ssh_key_ids,
|
|
182
|
+
'contract': 'PAY_AS_YOU_GO',
|
|
183
|
+
'location_code': location,
|
|
184
|
+
'startup_script_id': startup_script_id,
|
|
185
|
+
'shared_volume': {
|
|
186
|
+
'name': shared_volume_name if shared_volume_name else hostname + '-shared-volume',
|
|
187
|
+
'size': shared_volume_size if shared_volume_size else DEFAULT_SHARED_VOLUME_SIZE,
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
response = self._http_client.post(CLUSTERS_ENDPOINT, json=payload).json()
|
|
191
|
+
id = response['id']
|
|
192
|
+
|
|
193
|
+
if not wait_for_status:
|
|
194
|
+
return self.get_by_id(id)
|
|
195
|
+
|
|
196
|
+
# Wait for cluster to enter creating state with timeout
|
|
197
|
+
# TODO(shamrin) extract backoff logic, _instances module has the same code
|
|
198
|
+
deadline = time.monotonic() + max_wait_time
|
|
199
|
+
for i in itertools.count():
|
|
200
|
+
cluster = self.get_by_id(id)
|
|
201
|
+
if cluster.status == wait_for_status:
|
|
202
|
+
return cluster
|
|
203
|
+
|
|
204
|
+
if cluster.status == ClusterStatus.ERROR:
|
|
205
|
+
raise APIException(ErrorCodes.SERVER_ERROR, f'Cluster {id} entered error state')
|
|
206
|
+
|
|
207
|
+
if cluster.status == ClusterStatus.DISCONTINUED:
|
|
208
|
+
raise APIException(ErrorCodes.SERVER_ERROR, f'Cluster {id} was discontinued')
|
|
209
|
+
|
|
210
|
+
now = time.monotonic()
|
|
211
|
+
if now >= deadline:
|
|
212
|
+
raise TimeoutError(
|
|
213
|
+
f'Cluster {id} did not enter creating state within {max_wait_time:.1f} seconds'
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
interval = min(initial_interval * backoff_coefficient**i, max_interval, deadline - now)
|
|
217
|
+
time.sleep(interval)
|
|
218
|
+
|
|
219
|
+
def action(self, id_list: list[str] | str, action: str) -> None:
|
|
220
|
+
"""Performs an action on one or more clusters.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
id_list: Single cluster ID or list of cluster IDs to act upon.
|
|
224
|
+
action: Action to perform on the clusters. Only `delete` is supported.
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
HTTPError: If the action fails or other API error occurs.
|
|
228
|
+
"""
|
|
229
|
+
if action != Actions.DELETE:
|
|
230
|
+
raise ValueError(f'Invalid action: {action}. Only DELETE is supported.')
|
|
231
|
+
|
|
232
|
+
# TODO(shamrin) change public API to support `delete`
|
|
233
|
+
action = 'discontinue'
|
|
234
|
+
|
|
235
|
+
if isinstance(id_list, str):
|
|
236
|
+
payload = {'actions': [{'id': id_list, 'action': action}]}
|
|
237
|
+
else:
|
|
238
|
+
payload = {'actions': [{'id': id, 'action': action} for id in id_list]}
|
|
239
|
+
|
|
240
|
+
self._http_client.put(CLUSTERS_ENDPOINT, json=payload)
|
|
241
|
+
|
|
242
|
+
def delete(self, cluster_id: str) -> None:
|
|
243
|
+
"""Deletes a cluster.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
cluster_id: ID of the cluster to delete.
|
|
247
|
+
"""
|
|
248
|
+
self.action(cluster_id, 'delete')
|
|
249
|
+
|
|
250
|
+
def is_available(
|
|
251
|
+
self,
|
|
252
|
+
cluster_type: str,
|
|
253
|
+
location_code: str | None = None,
|
|
254
|
+
) -> bool:
|
|
255
|
+
"""Checks if a specific cluster type is available for deployment.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
cluster_type: Type of cluster to check availability for.
|
|
259
|
+
location_code: Optional datacenter location code.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
True if the cluster type is available, False otherwise.
|
|
263
|
+
"""
|
|
264
|
+
query_params = {'location_code': location_code}
|
|
265
|
+
url = f'/cluster-availability/{cluster_type}'
|
|
266
|
+
response = self._http_client.get(url, query_params).text
|
|
267
|
+
return response == 'true'
|
|
268
|
+
|
|
269
|
+
def get_availabilities(self, location_code: str | None = None) -> list[str]:
|
|
270
|
+
"""Retrieves a list of available cluster types across locations.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
location_code: Optional datacenter location code to filter by.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
List of available cluster types and their details.
|
|
277
|
+
"""
|
|
278
|
+
query_params = {'location_code': location_code}
|
|
279
|
+
response = self._http_client.get('/cluster-availability', params=query_params).json()
|
|
280
|
+
availabilities = response[0]['availabilities']
|
|
281
|
+
return availabilities
|
|
282
|
+
|
|
283
|
+
def get_cluster_images(
|
|
284
|
+
self,
|
|
285
|
+
cluster_type: str | None = None,
|
|
286
|
+
) -> list[str]:
|
|
287
|
+
"""Retrieves a list of available images for a given cluster type (optional).
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
cluster_type: Type of cluster to get images for.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of available images for the given cluster type.
|
|
294
|
+
"""
|
|
295
|
+
query_params = {'instance_type': cluster_type}
|
|
296
|
+
images = self._http_client.get('/images/cluster', params=query_params).json()
|
|
297
|
+
return [image['image_type'] for image in images]
|
|
@@ -56,6 +56,19 @@ class VolumeStatus:
|
|
|
56
56
|
return
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
class ClusterStatus:
|
|
60
|
+
"""Cluster status."""
|
|
61
|
+
|
|
62
|
+
ORDERED = 'ordered'
|
|
63
|
+
PROVISIONING = 'provisioning'
|
|
64
|
+
RUNNING = 'running'
|
|
65
|
+
DISCONTINUED = 'discontinued'
|
|
66
|
+
ERROR = 'error'
|
|
67
|
+
|
|
68
|
+
def __init__(self):
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
|
|
59
72
|
class VolumeTypes:
|
|
60
73
|
"""Storage volume types."""
|
|
61
74
|
|
|
@@ -110,6 +123,9 @@ class Constants:
|
|
|
110
123
|
self.volume_status: VolumeStatus = VolumeStatus()
|
|
111
124
|
"""Possible volume statuses"""
|
|
112
125
|
|
|
126
|
+
self.cluster_status: ClusterStatus = ClusterStatus()
|
|
127
|
+
"""Possible cluster statuses"""
|
|
128
|
+
|
|
113
129
|
self.volume_types: VolumeTypes = VolumeTypes()
|
|
114
130
|
"""Available volume types"""
|
|
115
131
|
|
|
@@ -171,8 +171,8 @@ class InstancesService:
|
|
|
171
171
|
'description': description,
|
|
172
172
|
'location_code': location,
|
|
173
173
|
'os_volume': os_volume,
|
|
174
|
-
'volumes': volumes,
|
|
175
|
-
'existing_volumes': existing_volumes,
|
|
174
|
+
'volumes': volumes or [],
|
|
175
|
+
'existing_volumes': existing_volumes or [],
|
|
176
176
|
'is_spot': is_spot,
|
|
177
177
|
'coupon': coupon,
|
|
178
178
|
}
|
|
@@ -183,6 +183,7 @@ class InstancesService:
|
|
|
183
183
|
id = self._http_client.post(INSTANCES_ENDPOINT, json=payload).text
|
|
184
184
|
|
|
185
185
|
# Wait for instance to enter provisioning state with timeout
|
|
186
|
+
# TODO(shamrin) extract backoff logic, _clusters module has the same code
|
|
186
187
|
deadline = time.monotonic() + max_wait_time
|
|
187
188
|
for i in itertools.count():
|
|
188
189
|
instance = self.get_by_id(id)
|
|
@@ -256,5 +257,5 @@ class InstancesService:
|
|
|
256
257
|
List of available instance types and their details.
|
|
257
258
|
"""
|
|
258
259
|
is_spot = str(is_spot).lower() if is_spot is not None else None
|
|
259
|
-
query_params = {'isSpot': is_spot, '
|
|
260
|
+
query_params = {'isSpot': is_spot, 'location_code': location_code}
|
|
260
261
|
return self._http_client.get('/instance-availability', params=query_params).json()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|