Skip to content

Commit e25c06f

Browse files
committed
Merge branch 'release/v1.4.0'
2 parents f098937 + f85ebd9 commit e25c06f

File tree

10 files changed

+152
-38
lines changed

10 files changed

+152
-38
lines changed

.github/workflows/unit_tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Install dependencies
2525
run: |
2626
python -m pip install --upgrade pip
27-
python -m pip install pytest pytest-cov pytest-responses responses
27+
python -m pip install pytest pytest-cov pytest-responses responses python-dotenv
2828
2929
- name: Test with pytest and coverage
3030
run: |
@@ -33,4 +33,4 @@ jobs:
3333
- name: 'Upload coverage to Codecov'
3434
uses: codecov/codecov-action@v1
3535
with:
36-
fail_ci_if_error: true
36+
fail_ci_if_error: false

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changelog
22
=========
33

4+
* Added support for permanent deletion of volumes
5+
* Added a Volume class method that inits a new Volume instance from a dict
6+
* Added a few integration tests for permanent deletion of volumes
7+
48
v1.3.0 (2023-05-25)
59
-------------------
610

datacrunch/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = '1.3.0'
1+
VERSION = '1.4.0'

datacrunch/volumes/volumes.py

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def __init__(self,
2020
location: str = "FIN1",
2121
instance_id: str = None,
2222
ssh_key_ids: List[str] = [],
23+
deleted_at: str = None,
2324
) -> None:
2425
"""Initialize the volume object
2526
@@ -45,6 +46,8 @@ def __init__(self,
4546
:type instance_id: str
4647
:param ssh_key_ids: list of ssh keys ids
4748
:type ssh_key_ids: List[str]
49+
:param deleted_at: the time the volume was deleted (UTC), defaults to None
50+
:type deleted_at: str, optional
4851
"""
4952
self._id = id
5053
self._status = status
@@ -57,6 +60,7 @@ def __init__(self,
5760
self._location = location
5861
self._instance_id = instance_id
5962
self._ssh_key_ids = ssh_key_ids
63+
self._deleted_at = deleted_at
6064

6165
@property
6266
def id(self) -> str:
@@ -157,6 +161,26 @@ def ssh_key_ids(self) -> List[str]:
157161
"""
158162
return self._ssh_key_ids
159163

164+
@property
165+
def deleted_at(self) -> Optional[str]:
166+
"""Get the time when the volume was deleted (UTC)
167+
168+
:return: time
169+
:rtype: str
170+
"""
171+
return self._deleted_at
172+
173+
@classmethod
174+
def create_from_dict(cls: 'Volume', volume_dict: dict) -> 'Volume':
175+
"""Create a Volume object from a dictionary
176+
177+
:param volume_dict: dictionary representing the volume
178+
:type volume_dict: dict
179+
:return: Volume
180+
:rtype: Volume
181+
"""
182+
return cls(**volume_dict)
183+
160184
def __str__(self) -> str:
161185
"""Returns a string of the json representation of the volume
162186
@@ -182,21 +206,7 @@ def get(self, status: str = None) -> List[Volume]:
182206
"""
183207
volumes_dict = self._http_client.get(
184208
VOLUMES_ENDPOINT, params={'status': status}).json()
185-
volumes = list(map(lambda volume_dict: Volume(
186-
id=volume_dict['id'],
187-
status=volume_dict['status'],
188-
name=volume_dict['name'],
189-
size=volume_dict['size'],
190-
type=volume_dict['type'],
191-
is_os_volume=volume_dict['is_os_volume'],
192-
created_at=volume_dict['created_at'],
193-
target=volume_dict['target'] if 'target' in volume_dict else None,
194-
location=volume_dict['location'],
195-
instance_id=volume_dict['instance_id'] if 'instance_id' in volume_dict else None,
196-
ssh_key_ids=volume_dict['ssh_key_ids'] if 'ssh_key_ids' in volume_dict else [
197-
],
198-
), volumes_dict))
199-
return volumes
209+
return list(map(Volume.create_from_dict, volumes_dict))
200210

201211
def get_by_id(self, id: str) -> Volume:
202212
"""Get a specific volume by its
@@ -208,21 +218,20 @@ def get_by_id(self, id: str) -> Volume:
208218
"""
209219
volume_dict = self._http_client.get(
210220
VOLUMES_ENDPOINT + f'/{id}').json()
211-
volume = Volume(
212-
id=volume_dict['id'],
213-
status=volume_dict['status'],
214-
name=volume_dict['name'],
215-
size=volume_dict['size'],
216-
type=volume_dict['type'],
217-
is_os_volume=volume_dict['is_os_volume'],
218-
created_at=volume_dict['created_at'],
219-
target=volume_dict['target'] if 'target' in volume_dict else None,
220-
location=volume_dict['location'],
221-
instance_id=volume_dict['instance_id'] if 'instance_id' in volume_dict else None,
222-
ssh_key_ids=volume_dict['ssh_key_ids'] if 'ssh_key_ids' in volume_dict else [
223-
],
224-
)
225-
return volume
221+
222+
return Volume.create_from_dict(volume_dict)
223+
224+
def get_in_trash(self) -> List[Volume]:
225+
"""Get all volumes that are in trash
226+
227+
:return: list of volume details objects
228+
:rtype: List[Volume]
229+
"""
230+
volumes_dicts = self._http_client.get(
231+
VOLUMES_ENDPOINT + '/trash'
232+
).json()
233+
234+
return list(map(Volume.create_from_dict, volumes_dicts))
226235

227236
def create(self,
228237
type: str,
@@ -358,7 +367,7 @@ def increase_size(self, id_list: Union[List[str], str], size: int) -> None:
358367
self._http_client.put(VOLUMES_ENDPOINT, json=payload)
359368
return
360369

361-
def delete(self, id_list: Union[List[str], str]) -> None:
370+
def delete(self, id_list: Union[List[str], str], is_permanent: bool = False) -> None:
362371
"""Delete multiple volumes or single volume
363372
Note: if attached to any instances, they need to be shut-down (offline)
364373
@@ -368,6 +377,7 @@ def delete(self, id_list: Union[List[str], str]) -> None:
368377
payload = {
369378
"id": id_list,
370379
"action": VolumeActions.DELETE,
380+
"is_permanent": is_permanent
371381
}
372382

373383
self._http_client.put(VOLUMES_ENDPOINT, json=payload)

examples/storage_volumes.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,11 @@
6262
# clone multiple volumes at once
6363
datacrunch.volumes.clone([nvme_volume_id, hdd_volume_id])
6464

65-
# delete volumes
65+
# delete volumes (move to trash for 96h, not permanent)
6666
datacrunch.volumes.delete([nvme_volume_id, hdd_volume_id])
67+
68+
# get all volumes in trash
69+
volumes_in_trash = datacrunch.volumes.get_in_trash()
70+
71+
# delete volumes permanently
72+
datacrunch.volumes.delete([nvme_volume_id, hdd_volume_id], is_permanent=True)

pytest.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
[pytest]
2-
testpaths = tests/unit_tests
2+
testpaths =
3+
tests/unit_tests
4+
tests/integration_tests

tests/integration_tests/__init__.py

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
import pytest
3+
from dotenv import load_dotenv
4+
from datacrunch.datacrunch import DataCrunchClient
5+
6+
"""
7+
Make sure to run the server and the account has enough balance before running the tests
8+
"""
9+
10+
BASE_URL = "http://localhost:3010/v1"
11+
12+
# Load env variables, make sure there's an env file with valid client credentials
13+
load_dotenv()
14+
CLIENT_SECRET = os.getenv('DATACRUNCH_CLIENT_SECRET')
15+
CLIENT_ID = os.getenv('DATACRUNCH_CLIENT_ID')
16+
17+
18+
@pytest.fixture
19+
def datacrunch_client():
20+
return DataCrunchClient(CLIENT_ID, CLIENT_SECRET, BASE_URL)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
import pytest
3+
from datacrunch.datacrunch import DataCrunchClient
4+
5+
IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true"
6+
7+
8+
@pytest.mark.skipif(IN_GITHUB_ACTIONS, reason="Test doesn't work in Github Actions.")
9+
@pytest.mark.withoutresponses
10+
class TestVolumes():
11+
12+
def test_get_volumes_from_trash(self, datacrunch_client: DataCrunchClient):
13+
# create new volume
14+
volume = datacrunch_client.volumes.create(
15+
type=datacrunch_client.constants.volume_types.NVMe, name="test_volume", size=100)
16+
17+
# delete volume
18+
datacrunch_client.volumes.delete(volume.id)
19+
20+
# get volumes from trash
21+
volumes = datacrunch_client.volumes.get_in_trash()
22+
23+
# assert volume is in trash
24+
assert volume.id in [v.id for v in volumes]
25+
26+
# cleaning: permanently delete the volume
27+
datacrunch_client.volumes.delete(volume.id, is_permanent=True)
28+
29+
def test_permanently_delete_detached_volumes(seld, datacrunch_client):
30+
# create new volume
31+
volume = datacrunch_client.volumes.create(
32+
type=datacrunch_client.constants.volume_types.NVMe, name="test_volume", size=100)
33+
34+
# permanently delete the detached volume
35+
datacrunch_client.volumes.delete(volume.id, is_permanent=True)
36+
37+
# make sure the volume is not in trash
38+
volumes = datacrunch_client.volumes.get_in_trash()
39+
40+
# assert volume is not in trash
41+
assert volume.id not in [v.id for v in volumes]
42+
43+
# get the volume
44+
volume = datacrunch_client.volumes.get_by_id(volume.id)
45+
46+
# assert volume status is deleted
47+
assert volume.status == datacrunch_client.constants.volume_status.DELETED
48+
49+
def test_permanently_delete_a_deleted_volume_from_trash(self, datacrunch_client):
50+
# create new volume
51+
volume = datacrunch_client.volumes.create(
52+
type=datacrunch_client.constants.volume_types.NVMe, name="test_volume", size=100)
53+
54+
# delete volume
55+
datacrunch_client.volumes.delete(volume.id)
56+
57+
# permanently delete the volume
58+
datacrunch_client.volumes.delete(volume.id, is_permanent=True)
59+
60+
# get the volume
61+
volume = datacrunch_client.volumes.get_by_id(volume.id)
62+
63+
# assert volume status is deleted
64+
assert volume.status == datacrunch_client.constants.volume_status.DELETED
65+
66+
# make sure the volume is not in trash
67+
volumes = datacrunch_client.volumes.get_in_trash()
68+
69+
# assert volume is not in trash
70+
assert volume.id not in [v.id for v in volumes]

tests/unit_tests/volumes/test_volumes.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,8 @@ def test_delete_volume_successful(self, volumes_service, endpoint):
467467
match=[
468468
responses.json_params_matcher({
469469
"id": NVME_VOL_ID,
470-
"action": VolumeActions.DELETE
470+
"action": VolumeActions.DELETE,
471+
"is_permanent": False
471472
})
472473
]
473474
)
@@ -489,7 +490,8 @@ def test_delete_volume_failed(self, volumes_service, endpoint):
489490
match=[
490491
responses.json_params_matcher({
491492
"id": NVME_VOL_ID,
492-
"action": VolumeActions.DELETE
493+
"action": VolumeActions.DELETE,
494+
"is_permanent": False
493495
})
494496
]
495497
)

0 commit comments

Comments
 (0)