Skip to content

Commit e249c70

Browse files
Merge pull request softlayer#869 from allmightyspiff/odyhunter-delay_exponential
vs manager wait_for_ready exception handling
2 parents 0800ae6 + 0bac762 commit e249c70

2 files changed

Lines changed: 62 additions & 40 deletions

File tree

SoftLayer/managers/vs.py

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
"""
88
import datetime
99
import itertools
10+
import logging
11+
import random
1012
import socket
1113
import time
1214
import warnings
1315

1416
from SoftLayer import exceptions
1517
from SoftLayer.managers import ordering
1618
from SoftLayer import utils
19+
20+
LOGGER = logging.getLogger(__name__)
1721
# pylint: disable=no-self-use
1822

1923

@@ -392,23 +396,20 @@ def _generate_create_dict(
392396

393397
return data
394398

395-
def wait_for_transaction(self, instance_id, limit, delay=1):
399+
def wait_for_transaction(self, instance_id, limit, delay=10):
396400
"""Waits on a VS transaction for the specified amount of time.
397401
398402
This is really just a wrapper for wait_for_ready(pending=True).
399403
Provided for backwards compatibility.
400404
401-
402405
:param int instance_id: The instance ID with the pending transaction
403406
:param int limit: The maximum amount of time to wait.
404-
:param int delay: The number of seconds to sleep before checks.
405-
Defaults to 1.
407+
:param int delay: The number of seconds to sleep before checks. Defaults to 10.
406408
"""
407409

408-
return self.wait_for_ready(instance_id, limit, delay=delay,
409-
pending=True)
410+
return self.wait_for_ready(instance_id, limit, delay=delay, pending=True)
410411

411-
def wait_for_ready(self, instance_id, limit, delay=1, pending=False):
412+
def wait_for_ready(self, instance_id, limit, delay=10, pending=False):
412413
"""Determine if a VS is ready and available.
413414
414415
In some cases though, that can mean that no transactions are running.
@@ -420,8 +421,7 @@ def wait_for_ready(self, instance_id, limit, delay=1, pending=False):
420421
421422
:param int instance_id: The instance ID with the pending transaction
422423
:param int limit: The maximum amount of time to wait.
423-
:param int delay: The number of seconds to sleep before checks.
424-
Defaults to 1.
424+
:param int delay: The number of seconds to sleep before checks. Defaults to 10.
425425
:param bool pending: Wait for pending transactions not related to
426426
provisioning or reloads such as monitoring.
427427
@@ -435,36 +435,37 @@ def wait_for_ready(self, instance_id, limit, delay=1, pending=False):
435435
mask = """id,
436436
lastOperatingSystemReload.id,
437437
activeTransaction.id,provisionDate"""
438-
instance = self.get_instance(new_instance, mask=mask)
439-
last_reload = utils.lookup(instance,
440-
'lastOperatingSystemReload',
441-
'id')
442-
active_transaction = utils.lookup(instance,
443-
'activeTransaction',
444-
'id')
445-
446-
reloading = all((
447-
active_transaction,
448-
last_reload,
449-
last_reload == active_transaction,
450-
))
451-
452-
# only check for outstanding transactions if requested
453-
outstanding = False
454-
if pending:
455-
outstanding = active_transaction
456-
457-
# return True if the instance has finished provisioning
458-
# and isn't currently reloading the OS.
459-
if all([instance.get('provisionDate'),
460-
not reloading,
461-
not outstanding]):
462-
return True
438+
try:
439+
instance = self.get_instance(new_instance, mask=mask)
440+
last_reload = utils.lookup(instance, 'lastOperatingSystemReload', 'id')
441+
active_transaction = utils.lookup(instance, 'activeTransaction', 'id')
442+
443+
reloading = all((
444+
active_transaction,
445+
last_reload,
446+
last_reload == active_transaction,
447+
))
448+
449+
# only check for outstanding transactions if requested
450+
outstanding = False
451+
if pending:
452+
outstanding = active_transaction
453+
454+
# return True if the instance has finished provisioning
455+
# and isn't currently reloading the OS.
456+
if all([instance.get('provisionDate'),
457+
not reloading,
458+
not outstanding]):
459+
return True
460+
LOGGER.info("%s not ready.", str(instance_id))
461+
except exceptions.SoftLayerAPIError as exception:
462+
delay = (delay * 2) + random.randint(0, 9)
463+
LOGGER.info('Exception: %s', str(exception))
463464

464465
now = time.time()
465466
if now >= until:
466467
return False
467-
468+
LOGGER.info('Auto retry in %s seconds', str(min(delay, until - now)))
468469
time.sleep(min(delay, until - now))
469470

470471
def verify_create_instance(self, **kwargs):

tests/managers/vs_tests.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44
55
:license: MIT, see LICENSE for more details.
6+
67
"""
78
import mock
89

910
import SoftLayer
11+
from SoftLayer import exceptions
1012
from SoftLayer import fixtures
1113
from SoftLayer import testing
1214

@@ -748,7 +750,7 @@ def set_up(self):
748750
def test_wait_interface(self, ready):
749751
# verify interface to wait_for_ready is intact
750752
self.vs.wait_for_transaction(1, 1)
751-
ready.assert_called_once_with(1, 1, delay=1, pending=True)
753+
ready.assert_called_once_with(1, 1, delay=10, pending=True)
752754

753755
def test_active_not_provisioned(self):
754756
# active transaction and no provision date should be false
@@ -820,7 +822,7 @@ def test_ready_iter_once_incomplete(self, _sleep):
820822
self.guestObject.side_effect = [
821823
{'activeTransaction': {'id': 1}},
822824
]
823-
value = self.vs.wait_for_ready(1, 0)
825+
value = self.vs.wait_for_ready(1, 0, delay=1)
824826
self.assertFalse(value)
825827
self.assertFalse(_sleep.called)
826828

@@ -830,7 +832,7 @@ def test_iter_once_complete(self, _sleep):
830832
self.guestObject.side_effect = [
831833
{'provisionDate': 'aaa'},
832834
]
833-
value = self.vs.wait_for_ready(1, 1)
835+
value = self.vs.wait_for_ready(1, 1, delay=1)
834836
self.assertTrue(value)
835837
self.assertFalse(_sleep.called)
836838

@@ -844,7 +846,7 @@ def test_iter_four_complete(self, _sleep):
844846
{'provisionDate': 'aaa'},
845847
]
846848

847-
value = self.vs.wait_for_ready(1, 4)
849+
value = self.vs.wait_for_ready(1, 4, delay=1)
848850
self.assertTrue(value)
849851
_sleep.assert_has_calls([mock.call(1), mock.call(1), mock.call(1)])
850852
self.guestObject.assert_has_calls([
@@ -862,7 +864,7 @@ def test_iter_two_incomplete(self, _sleep, _time):
862864
{'provisionDate': 'aaa'}
863865
]
864866
_time.side_effect = [0, 1, 2]
865-
value = self.vs.wait_for_ready(1, 2)
867+
value = self.vs.wait_for_ready(1, 2, delay=1)
866868
self.assertFalse(value)
867869
_sleep.assert_called_once_with(1)
868870
self.guestObject.assert_has_calls([
@@ -881,3 +883,22 @@ def test_iter_20_incomplete(self, _sleep, _time):
881883
self.guestObject.assert_has_calls([mock.call(id=1, mask=mock.ANY)])
882884

883885
_sleep.assert_has_calls([mock.call(10)])
886+
887+
@mock.patch('SoftLayer.managers.vs.VSManager.get_instance')
888+
@mock.patch('random.randint')
889+
@mock.patch('time.time')
890+
@mock.patch('time.sleep')
891+
def test_exception_from_api(self, _sleep, _time, _random, vs):
892+
"""Tests escalating scale back when an excaption is thrown"""
893+
self.guestObject.return_value = {'activeTransaction': {'id': 1}}
894+
vs.side_effect = exceptions.TransportError(104, "Its broken")
895+
_time.side_effect = [0, 0, 2, 6, 14, 20, 100]
896+
_random.side_effect = [0, 0, 0, 0, 0]
897+
value = self.vs.wait_for_ready(1, 20, delay=1)
898+
_sleep.assert_has_calls([
899+
mock.call(2),
900+
mock.call(4),
901+
mock.call(8),
902+
mock.call(6)
903+
])
904+
self.assertFalse(value)

0 commit comments

Comments
 (0)