Skip to content

Commit d511c3d

Browse files
author
faustas@techconsult.lt
committed
Storage methods
2 parents aa5b4a5 + 2af1406 commit d511c3d

File tree

11 files changed

+160
-27
lines changed

11 files changed

+160
-27
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,21 +233,21 @@ Set up environment and install dependencies:
233233
# run at project root, python3 and virtualenv must be installed
234234
virtualenv ENV
235235
source ENV/bin/activate
236-
pip install -r requirements.txt
236+
pip install -r requirements.txt or pip install -r requirements-dev.txt if changes to the api need to be made
237237
```
238238

239239
Install the package in editable mode, as mentioned in
240-
[https://pytest.org/latest/goodpractises.html](https://pytest.org/latest/goodpractises.html)
240+
[https://docs.pytest.org/en/stable/goodpractices.html](https://docs.pytest.org/en/stable/goodpractices.html)
241241

242242
```python
243243
# run at project root
244244
pip install -e .
245245
```
246246

247-
Tests located in `project_root/tests/` directory. Run with:
247+
Tests located in `project_root/test/` directory. Run with:
248248

249249
```python
250-
py.test tests/
250+
py.test test/
251251
```
252252

253253
To test against all supported python versions, run:

docs/Storage.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,64 @@ Warning: data loss is permanent.
8181
storage.destroy()
8282

8383
```
84+
85+
86+
## Clone
87+
88+
Clone the storage using StorageManager.
89+
Returns an object based on the API's response.
90+
Method requires title and zone to be passed while tier is optional.
91+
92+
```python
93+
94+
storage_clone = storage.clone(title='title of storage clone', zone=ZONE.Helsinki, tier=None)
95+
96+
```
97+
98+
99+
## Cancel clone operation
100+
101+
Cancels a running cloning operation and deletes the incomplete copy using StorageManager.
102+
Needs to be called from the cloned storage (object returned by clone operation) and not the storage that is being cloned.
103+
104+
```python
105+
106+
storage_clone.cancel_cloning()
107+
108+
```
109+
110+
111+
## Create backup
112+
113+
Creates a point-in-time backup of a storage resource using StorageManager.
114+
Method requires title to be passed.
115+
116+
```python
117+
118+
storage_backup = storage.create_backup('Backup title')
119+
120+
```
121+
122+
123+
## Restore backup
124+
125+
Restores the origin storage with data from the specified backup storage using StorageManager.
126+
Must be called from a storage object created by create_backup and not the original one.
127+
128+
```python
129+
130+
storage_backup.restore_backup()
131+
132+
```
133+
134+
135+
## Templatize storage
136+
137+
Creates an exact copy of an existing storage resource which can be used as a template for creating new servers using StorageManager.
138+
Method requires title to be passed.
139+
140+
```python
141+
142+
storage.templatize('Template title')
143+
144+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{"storage":
2+
{
3+
"access": "private",
4+
"created": "2020-09-21T17:36:50Z",
5+
"license": 0,
6+
"origin": "01ec5c26-a25d-4752-94e4-27bd88b62816",
7+
"progress": "0",
8+
"servers": {"server": []},
9+
"size": 666,
10+
"state": "maintenance",
11+
"title": "test-backup",
12+
"type": "backup",
13+
"uuid": "01350eec-6ebf-4418-abe4-e8bb1d5c9643",
14+
"zone": "fi-hel1"
15+
}
16+
}

test/test_storage.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def test_clone_storage(self, manager):
4040
storage = manager.get_storage("01d4fcd4-e446-433b-8a9c-551a1284952e")
4141

4242
Mock.mock_post("storage/01d4fcd4-e446-433b-8a9c-551a1284952e/clone")
43-
cloned_storage = manager.clone_storage(storage, 'cloned-storage-test', 'fi-hel1')
43+
cloned_storage = storage.clone('cloned-storage-test', 'fi-hel1')
4444
assert type(cloned_storage).__name__ == "Storage"
4545
assert cloned_storage.size == 666
4646
assert cloned_storage.tier == "maxiops"
@@ -56,7 +56,7 @@ def test_cancel_clone_storage(self, manager):
5656
cloned_storage = manager.clone_storage(storage, 'cloned-storage-test', 'fi-hel1')
5757

5858
Mock.mock_post("storage/01d3e9ad-8ff5-4a52-9fa2-48938e488e78/cancel", empty_content=True)
59-
res = manager.cancel_clone_storage(cloned_storage)
59+
res = cloned_storage.cancel_cloning()
6060
assert res == {}
6161

6262
@responses.activate
@@ -73,24 +73,33 @@ def test_eject_cd_rom(self, manager):
7373

7474
@responses.activate
7575
def test_create_storage_backup(self, manager):
76+
data = Mock.mock_get("storage/01d4fcd4-e446-433b-8a9c-551a1284952e")
77+
storage = manager.get_storage("01d4fcd4-e446-433b-8a9c-551a1284952e")
78+
7679
data = Mock.mock_post("storage/01d4fcd4-e446-433b-8a9c-551a1284952e/backup")
77-
storage = manager.create_storage_backup("01d4fcd4-e446-433b-8a9c-551a1284952e", "test-backup")
78-
assert storage.title == "test-backup"
79-
assert storage.size == 666
80-
assert storage.zone == "fi-hel1"
80+
storage_backup = storage.create_backup("test-backup")
81+
assert storage_backup.title == "test-backup"
82+
assert storage_backup.size == 666
83+
assert storage_backup.zone == "fi-hel1"
8184

8285
@responses.activate
8386
def test_restore_storage_backup(self, manager):
87+
data = Mock.mock_get("storage/01350eec-6ebf-4418-abe4-e8bb1d5c9643")
88+
storage_backup = manager.get_storage("01350eec-6ebf-4418-abe4-e8bb1d5c9643")
89+
8490
data = Mock.mock_post("storage/01350eec-6ebf-4418-abe4-e8bb1d5c9643/restore", empty_content=True)
85-
res = manager.restore_storage_backup("01350eec-6ebf-4418-abe4-e8bb1d5c9643")
91+
res = storage_backup.restore_backup()
8692
assert res == {}
8793

8894
@responses.activate
8995
def test_templatize_storage(self, manager):
96+
data = Mock.mock_get("storage/01d4fcd4-e446-433b-8a9c-551a1284952e")
97+
storage = manager.get_storage("01d4fcd4-e446-433b-8a9c-551a1284952e")
98+
9099
data = Mock.mock_post("storage/01d4fcd4-e446-433b-8a9c-551a1284952e/templatize")
91-
storage = manager.templatize_storage("01d4fcd4-e446-433b-8a9c-551a1284952e", "my server template")
92-
assert storage.title == "my server template"
93-
assert storage.type == "template"
100+
storage_template = storage.templatize("my server template")
101+
assert storage_template.title == "my server template"
102+
assert storage_template.type == "template"
94103

95104
@responses.activate
96105
def test_create_storage_import(self, manager):

upcloud_api/cloud_manager/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ def put_request(self, endpoint, body=None, timeout=-1, request_to_api=True):
7474
"""
7575
return self.request('PUT', endpoint, body, timeout, request_to_api=request_to_api)
7676

77+
def delete_request(self, endpoint, body=None, timeout=-1):
78+
"""
79+
Perform a DELETE request to a given endpoint in UpCloud's API.
80+
"""
81+
return self.request('DELETE', endpoint, body, timeout)
82+
7783
def __error_middleware(self, res, res_json):
7884
"""
7985
Middleware that raises an exception when HTTP statuscode is an error code.

upcloud_api/cloud_manager/firewall_mixin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def delete_firewall_rule(self, server_uuid, firewall_rule_position):
5959
Delete a firewall rule based on a server uuid and rule position.
6060
"""
6161
url = '/server/{0}/firewall_rule/{1}'.format(server_uuid, firewall_rule_position)
62-
return self.request('DELETE', url)
62+
return self.delete_request(url)
6363

6464
def configure_firewall(self, server, firewall_rule_bodies):
6565
"""

upcloud_api/cloud_manager/ip_address_mixin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def attach_ip(self, server, family='IPv4'):
4141
}
4242
}
4343

44-
res = self.request('POST', '/ip_address', body)
44+
res = self.post_request('/ip_address', body)
4545
return IPAddress(cloud_manager=self, **res['ip_address'])
4646

4747
def modify_ip(self, ip_addr, ptr_record):
@@ -56,7 +56,7 @@ def modify_ip(self, ip_addr, ptr_record):
5656
}
5757
}
5858

59-
res = self.request('PUT', '/ip_address/' + str(ip_addr), body)
59+
res = self.put_request('/ip_address/' + str(ip_addr), body)
6060
return IPAddress(cloud_manager=self, **res['ip_address'])
6161

6262
def release_ip(self, ip_addr):
@@ -65,4 +65,4 @@ def release_ip(self, ip_addr):
6565
6666
Accepts an IPAddress instance (object) or its address (string).
6767
"""
68-
return self.request('DELETE', '/ip_address/' + str(ip_addr))
68+
return self.delete_request('/ip_address/' + str(ip_addr))

upcloud_api/cloud_manager/server_mixin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def modify_server(self, UUID, **kwargs):
138138
Exception('{0} is not an updateable field'.format(arg))
139139
body['server'][arg] = kwargs[arg]
140140

141-
res = self.request('PUT', '/server/{0}'.format(UUID), body)
141+
res = self.put_request('/server/{0}'.format(UUID), body)
142142
server = res['server']
143143

144144
# Populate subobjects
@@ -164,7 +164,7 @@ def delete_server(self, UUID):
164164
165165
Returns an empty object.
166166
"""
167-
return self.request('DELETE', '/server/{0}'.format(UUID))
167+
return self.delete_request('/server/{0}'.format(UUID))
168168

169169
def get_server_data(self, UUID):
170170
"""

upcloud_api/cloud_manager/storage_mixin.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def _modify_storage(self, storage, size, title, backup_rule={}):
5252
body['storage']['title'] = title
5353
if backup_rule:
5454
body['storage']['backup_rule'] = backup_rule
55-
return self.request('PUT', '/storage/' + str(storage), body)
55+
return self.put_request('/storage/' + str(storage), body)
5656

5757
def modify_storage(self, storage, size, title, backup_rule={}):
5858
"""
@@ -65,7 +65,7 @@ def delete_storage(self, UUID):
6565
"""
6666
Destroy a Storage object.
6767
"""
68-
return self.request('DELETE', '/storage/' + UUID)
68+
return self.delete_request('/storage/' + UUID)
6969

7070
def clone_storage(self, storage, title, zone, tier=None):
7171
"""
@@ -74,14 +74,14 @@ def clone_storage(self, storage, title, zone, tier=None):
7474
body = {'storage': {'title': title, 'zone': zone}}
7575
if tier:
7676
body['storage']['tier'] = tier
77-
res = self.request('POST', '/storage/{0}/clone'.format(str(storage)), body)
77+
res = self.post_request('/storage/{0}/clone'.format(str(storage)), body)
7878
return Storage(cloud_manager=self, **res['storage'])
7979

8080
def cancel_clone_storage(self, storage):
8181
"""
8282
Cancels a running cloning operation and deletes the incomplete copy.
8383
"""
84-
return self.request('POST', '/storage/{0}/cancel'.format(str(storage)))
84+
return self.post_request('/storage/{0}/cancel'.format(str(storage)))
8585

8686
def attach_storage(self, server, storage, storage_type, address):
8787
"""

upcloud_api/cloud_manager/tag_mixin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def create_tag(self, name, description=None, servers=[]):
3333
"""
3434
servers = [str(server) for server in servers]
3535
body = {'tag': Tag(name, description, servers).to_dict()}
36-
res = self.request('POST', '/tag', body)
36+
res = self.post_request('/tag', body)
3737

3838
return Tag(cloud_manager=self, **res['tag'])
3939

@@ -44,7 +44,7 @@ def _modify_tag(self, name, description, servers, new_name):
4444
Private method used by the Tag class and TagManager.modify_tag.
4545
"""
4646
body = {'tag': Tag(new_name, description, servers).to_dict()}
47-
res = self.request('PUT', '/tag/' + name, body)
47+
res = self.put_request('/tag/' + name, body)
4848
return res['tag']
4949

5050
def modify_tag(self, name, description=None, servers=None, new_name=None):
@@ -82,4 +82,4 @@ def remove_tags(self, server, tags):
8282

8383
def delete_tag(self, tag):
8484
"""Delete the Tag. Returns and empty object."""
85-
return self.request('DELETE', '/tag/' + str(tag))
85+
return self.delete_request('/tag/' + str(tag))

0 commit comments

Comments
 (0)