Skip to content

Commit e14cde3

Browse files
author
Elias Nygren
committed
improve stop_and_destroy
1 parent 1bbbbee commit e14cde3

File tree

2 files changed

+53
-16
lines changed

2 files changed

+53
-16
lines changed

upcloud_api/server.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from __future__ import unicode_literals
22
from __future__ import absolute_import
3+
4+
import itertools
35
from time import sleep
46

5-
from upcloud_api import Storage, IP_address
7+
from upcloud_api import Storage, IP_address, UpCloudAPIError
8+
from upcloud_api.utils import try_it_n_times
69

710

811
def login_user_block(username, ssh_keys, create_password=True):
@@ -429,7 +432,7 @@ def _wait_for_state_change(self, target_states, update_interval=10):
429432

430433
def ensure_started(self):
431434
"""
432-
Start a server and waits (blocking wait) until a it is fully started.
435+
Start a server and waits (blocking wait) until it is fully started.
433436
"""
434437
# server is either starting or stopping (or error)
435438
if self.state in ['maintenance', 'error']:
@@ -445,35 +448,45 @@ def ensure_started(self):
445448
# something went wrong, fail explicitly
446449
raise Exception('unknown server state: ' + self.state)
447450

448-
def stop_and_destroy(self):
451+
def stop_and_destroy(self, sync=True):
449452
"""
450-
Destroy a server and its storages.
453+
Destroy a server and its storages. Stops the server before destroying.
451454
452-
Stops the server (blocking wait) before destroying.
455+
Syncs the server state from the API, use sync=False to disable.
453456
"""
454-
def destroy_storages():
455-
# list view does not return all server info, populate if necessary
456-
if not hasattr(self, 'storage_devices'):
457-
self.populate()
457+
def _self_destruct():
458+
"""destroy the server and all storages attached to it."""
459+
460+
# try_it_n_times util is used as a convenience because
461+
# Servers and Storages can fluctuate between "maintenance" and their
462+
# original state due to several different reasons especially when
463+
# destroying infrastructure.
458464

459-
# destroy the server and all storages attached to it
460-
self.destroy()
465+
# first destroy server
466+
try_it_n_times(operation=self.destroy,
467+
expected_error_codes=['SERVER_STATE_ILLEGAL'],
468+
custom_error='stopping server failed')
469+
470+
# storages may be deleted instantly after server DELETE
461471
for storage in self.storage_devices:
462-
storage.destroy()
472+
try_it_n_times(operation=storage.destroy,
473+
expected_error_codes=['STORAGE_STATE_ILLEGAL'],
474+
custom_error='destroying storage failed')
475+
476+
if sync:
477+
self.populate()
463478

464479
# server is either starting or stopping (or error)
465480
if self.state in ['maintenance', 'error']:
466481
self._wait_for_state_change(['stopped', 'started'])
467482

468-
# server is started
469-
if self.state != 'stopped':
483+
if self.state == 'started':
470484
self.stop()
471485
self._wait_for_state_change(['stopped'])
472486

473487
if self.state == 'stopped':
474-
destroy_storages()
488+
_self_destruct()
475489
else:
476-
# something went wrong, fail explicitly
477490
raise Exception('unknown server state: ' + self.state)
478491

479492
@classmethod

upcloud_api/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import itertools
2+
from time import sleep
3+
4+
from upcloud_api import UpCloudAPIError
5+
6+
7+
def try_it_n_times(operation, expected_error_codes, custom_error='operation failed', n=5):
8+
"""
9+
Try a given operation (API call) n times.
10+
11+
Raises if the API call fails with an error_code that is not expected.
12+
Raises if the API call has not succeeded within n attempts.
13+
Waits 3 seconds betwee each attempt.
14+
"""
15+
for i in itertools.count():
16+
try:
17+
operation()
18+
break
19+
except UpCloudAPIError as e:
20+
if e.error_code not in expected_error_codes:
21+
raise e
22+
sleep(3)
23+
if i >= n - 1:
24+
raise Exception(custom_error)

0 commit comments

Comments
 (0)