11from __future__ import unicode_literals
22from __future__ import absolute_import
3+
4+ import itertools
35from 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
811def 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
0 commit comments