Skip to content

Commit db27741

Browse files
author
Ruslan Gainutdinov
committed
feat: add on_spot_discontinue on os_volume and delete_permanently on instance action
1 parent 4bd8212 commit db27741

File tree

3 files changed

+90
-5
lines changed

3 files changed

+90
-5
lines changed

tests/unit_tests/instances/test_instances.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import json
12
import pytest
23
import responses # https://github.com/getsentry/responses
34

45
from verda.constants import Actions, ErrorCodes, Locations
56
from verda.exceptions import APIException
6-
from verda.instances import Instance, InstancesService
7+
from verda.instances import Instance, InstancesService, OSVolume
78

89
INVALID_REQUEST = ErrorCodes.INVALID_REQUEST
910
INVALID_REQUEST_MESSAGE = 'Your existence is invalid'
@@ -266,6 +267,31 @@ def test_create_spot_instance_successful(self, instances_service, endpoint):
266267
assert responses.assert_call_count(endpoint, 1) is True
267268
assert responses.assert_call_count(url, 1) is True
268269

270+
def test_create_spot_instance(self, instances_service, endpoint):
271+
# arrange
272+
responses.add(responses.POST, endpoint, body=INSTANCE_ID, status=200)
273+
url = endpoint + '/' + INSTANCE_ID
274+
responses.add(responses.GET, url, json=PAYLOAD[0], status=200)
275+
276+
SPOT_INSTANCE_OS_VOLUME = OSVolume(name='spot-instance-os-volume', size=50, on_spot_discontinue='delete_permanently')
277+
278+
# act
279+
instances_service.create(
280+
instance_type=INSTANCE_TYPE,
281+
image=INSTANCE_IMAGE,
282+
ssh_key_ids=[SSH_KEY_ID],
283+
hostname=INSTANCE_HOSTNAME,
284+
description=INSTANCE_DESCRIPTION,
285+
os_volume=SPOT_INSTANCE_OS_VOLUME,
286+
)
287+
288+
# assert
289+
request_body = responses.calls[0].request.body.decode('utf-8')
290+
body = json.loads(request_body)
291+
assert body['os_volume']['name'] == SPOT_INSTANCE_OS_VOLUME.name
292+
assert body['os_volume']['size'] == SPOT_INSTANCE_OS_VOLUME.size
293+
assert body['os_volume']['on_spot_discontinue'] == 'delete_permanently'
294+
269295
def test_create_instance_attached_os_volume_successful(self, instances_service, endpoint):
270296
# arrange - add response mock
271297
# create instance
@@ -340,6 +366,28 @@ def test_action_successful(self, instances_service, endpoint):
340366
assert result is None
341367
assert responses.assert_call_count(url, 1) is True
342368

369+
def test_action_with_delete_permanently_sends_payload(self, instances_service, endpoint):
370+
# arrange
371+
url = endpoint
372+
responses.add(responses.PUT, url, status=202)
373+
volume_ids = [OS_VOLUME_ID]
374+
375+
# act
376+
instances_service.action(
377+
id_list=[INSTANCE_ID],
378+
action=Actions.DELETE,
379+
volume_ids=volume_ids,
380+
delete_permanently=True,
381+
)
382+
383+
# assert
384+
request_body = responses.calls[0].request.body.decode('utf-8')
385+
body = json.loads(request_body)
386+
assert body['id'] == [INSTANCE_ID]
387+
assert body['action'] == Actions.DELETE
388+
assert body['volume_ids'] == volume_ids
389+
assert body['delete_permanently'] is True
390+
343391
def test_action_failed(self, instances_service, endpoint):
344392
# arrange - add response mock
345393
url = endpoint

verda/instances/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
from ._instances import Contract, Instance, InstancesService, Pricing
1+
from ._instances import (
2+
Contract,
3+
Instance,
4+
InstancesService,
5+
OnSpotDiscontinue,
6+
Pricing,
7+
OSVolume,
8+
)

verda/instances/_instances.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,33 @@
33
from dataclasses import dataclass
44
from typing import Literal
55

6-
from dataclasses_json import dataclass_json
6+
from dataclasses_json import Undefined, dataclass_json
77

88
from verda.constants import InstanceStatus, Locations
99

1010
INSTANCES_ENDPOINT = '/instances'
1111

1212
Contract = Literal['LONG_TERM', 'PAY_AS_YOU_GO', 'SPOT']
1313
Pricing = Literal['DYNAMIC_PRICE', 'FIXED_PRICE']
14+
OnSpotDiscontinue = Literal['keep_detached', 'move_to_trash', 'delete_permanently']
15+
16+
@dataclass_json(undefined=Undefined.EXCLUDE)
17+
@dataclass
18+
class OSVolume:
19+
"""Represents an operating system volume.
20+
21+
Attributes:
22+
name: Name of the volume.
23+
size: Size of the volume in GB.
24+
on_spot_discontinue: What to do with the volume on spot discontinue.
25+
- keep_detached: Keep the volume detached.
26+
- move_to_trash: Move the volume to trash.
27+
- delete_permanently: Delete the volume permanently.
28+
Defaults to keep_detached.
29+
"""
30+
name: str
31+
size: int
32+
on_spot_discontinue: OnSpotDiscontinue | None = None
1433

1534

1635
@dataclass_json
@@ -123,7 +142,7 @@ def create(
123142
startup_script_id: str | None = None,
124143
volumes: list[dict] | None = None,
125144
existing_volumes: list[str] | None = None,
126-
os_volume: dict | None = None,
145+
os_volume: OSVolume | None = None,
127146
is_spot: bool = False,
128147
contract: Contract | None = None,
129148
pricing: Pricing | None = None,
@@ -204,21 +223,32 @@ def action(
204223
id_list: list[str] | str,
205224
action: str,
206225
volume_ids: list[str] | None = None,
226+
delete_permanently: bool = False,
207227
) -> None:
208228
"""Performs an action on one or more instances.
209229
210230
Args:
211231
id_list: Single instance ID or list of instance IDs to act upon.
212232
action: Action to perform on the instances.
213233
volume_ids: Optional list of volume IDs to delete.
234+
delete_permanently: When deleting (or discontinuing), delete the
235+
given volume IDs permanently. Only applicable when volume_ids
236+
is also provided.
214237
215238
Raises:
216239
HTTPError: If the action fails or other API error occurs.
217240
"""
218241
if type(id_list) is str:
219242
id_list = [id_list]
220243

221-
payload = {'id': id_list, 'action': action, 'volume_ids': volume_ids}
244+
payload = {
245+
'id': id_list,
246+
'action': action,
247+
'volume_ids': volume_ids,
248+
}
249+
250+
if delete_permanently:
251+
payload['delete_permanently'] = True
222252

223253
self._http_client.put(INSTANCES_ENDPOINT, json=payload)
224254
return

0 commit comments

Comments
 (0)