Skip to content

Commit b81961b

Browse files
committed
Merge pull request softlayer#249 from CrackerJackMack/wait
Fixes CCI wait_for_transaction
2 parents f7215a3 + fa4b87a commit b81961b

3 files changed

Lines changed: 201 additions & 68 deletions

File tree

SoftLayer/CLI/modules/cci.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ def execute(self, args):
463463
output.append(t)
464464

465465
if args.get('--wait'):
466-
ready = cci.wait_for_transaction(
466+
ready = cci.wait_for_ready(
467467
result['id'], int(args.get('--wait') or 1))
468468
t.add_row(['ready', ready])
469469
else:
@@ -648,7 +648,7 @@ def execute(self, args):
648648
cci = CCIManager(self.client)
649649

650650
cci_id = resolve_id(cci.resolve_ids, args.get('<identifier>'), 'CCI')
651-
ready = cci.wait_for_transaction(cci_id, int(args.get('--wait') or 0))
651+
ready = cci.wait_for_ready(cci_id, int(args.get('--wait') or 0))
652652

653653
if ready:
654654
return "READY"

SoftLayer/managers/cci.py

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from time import sleep
1111
from itertools import repeat
1212

13-
from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin
13+
from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin, lookup
1414

1515

1616
class CCIManager(IdentifierMixin, object):
@@ -165,6 +165,7 @@ def get_instance(self, instance_id, **kwargs):
165165
'maxMemory',
166166
'datacenter',
167167
'activeTransaction[id, transactionStatus[friendlyName,name]]',
168+
'lastOperatingSystemReload.id',
168169
'blockDevices',
169170
'blockDeviceTemplateGroup[id, name, globalIdentifier]',
170171
'postInstallScriptUri',
@@ -337,16 +338,55 @@ def _generate_create_dict(
337338

338339
def wait_for_transaction(self, instance_id, limit, delay=1):
339340
""" Waits on a CCI transaction for the specified amount of time.
341+
is really just a wrapper for wait_for_ready(pending=True).
342+
Provided for backwards compatibility.
343+
344+
345+
:param int instance_id: The instance ID with the pending transaction
346+
:param int limit: The maximum amount of time to wait.
347+
:param int delay: The number of seconds to sleep before checks.
348+
Defaults to 1.
349+
"""
350+
351+
return self.wait_for_ready(instance_id, limit, delay=delay,
352+
pending=True)
353+
354+
def wait_for_ready(self, instance_id, limit, delay=1, pending=False):
355+
""" Determine if a CCI is ready and available. In some cases
356+
though, that can mean that no transactions are running. The default
357+
arguments imply a CCI is operational and ready for use by having
358+
network connectivity and remote access is available. Setting
359+
``pending=True`` will ensure future API calls
360+
against this instance will not error due to pending
361+
transactions such as OS Reloads and cancellations.
340362
341363
:param int instance_id: The instance ID with the pending transaction
342364
:param int limit: The maximum amount of time to wait.
343365
:param int delay: The number of seconds to sleep before checks.
344366
Defaults to 1.
367+
:param bool pending: Wait for pending transactions not related to
368+
provisioning or reloads such as monitoring.
345369
"""
346-
for count, new_instance in enumerate(repeat(instance_id)):
370+
for count, new_instance in enumerate(repeat(instance_id), start=1):
347371
instance = self.get_instance(new_instance)
348-
if not instance.get('activeTransaction', {}).get('id') and \
349-
instance.get('provisionDate'):
372+
last_reload = lookup(instance, 'lastOperatingSystemReload', 'id')
373+
active_transaction = lookup(instance, 'activeTransaction', 'id')
374+
375+
reloading = all((
376+
active_transaction,
377+
last_reload,
378+
last_reload == active_transaction
379+
))
380+
381+
# only check for outstanding transactions if requested
382+
outstanding = False
383+
if pending:
384+
outstanding = active_transaction
385+
386+
# return True if the instance has only if the instance has
387+
# finished provisioning and isn't currently reloading the OS.
388+
if instance.get('provisionDate') \
389+
and not reloading and not outstanding:
350390
return True
351391

352392
if count >= limit:

SoftLayer/tests/managers/cci_tests.py

Lines changed: 155 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -502,50 +502,182 @@ def test_generate_multi_disk(self):
502502
self.assertTrue(data.get('blockDevices'))
503503
self.assertEqual(data['blockDevices'], assert_data['blockDevices'])
504504

505+
def test_change_port_speed_public(self):
506+
cci_id = 1
507+
speed = 100
508+
self.cci.change_port_speed(cci_id, True, speed)
509+
510+
service = self.client['Virtual_Guest']
511+
f = service.setPublicNetworkInterfaceSpeed
512+
f.assert_called_once_with(speed, id=cci_id)
513+
514+
def test_change_port_speed_private(self):
515+
cci_id = 2
516+
speed = 10
517+
self.cci.change_port_speed(cci_id, False, speed)
518+
519+
service = self.client['Virtual_Guest']
520+
f = service.setPrivateNetworkInterfaceSpeed
521+
f.assert_called_once_with(speed, id=cci_id)
522+
523+
def test_edit(self):
524+
# Test editing user data
525+
service = self.client['Virtual_Guest']
526+
527+
self.cci.edit(100, userdata='my data')
528+
529+
service.setUserMetadata.assert_called_once_with(['my data'], id=100)
530+
531+
# Now test a blank edit
532+
self.assertTrue(self.cci.edit, 100)
533+
534+
# Finally, test a full edit
535+
args = {
536+
'hostname': 'new-host',
537+
'domain': 'new.sftlyr.ws',
538+
'notes': 'random notes',
539+
}
540+
541+
self.cci.edit(100, **args)
542+
service.editObject.assert_called_once_with(args, id=100)
543+
544+
545+
class CCIWaitReadyGoTests(unittest.TestCase):
546+
547+
def setUp(self):
548+
self.client = MagicMock()
549+
self.cci = CCIManager(self.client)
550+
self.guestObject = self.client['Virtual_Guest'].getObject
551+
552+
@patch('SoftLayer.managers.cci.CCIManager.wait_for_ready')
553+
def test_wait_interface(self, ready):
554+
# verify interface to wait_for_ready is intact
555+
self.cci.wait_for_transaction(1, 1)
556+
ready.assert_called_once_with(1, 1, delay=1, pending=True)
557+
558+
def test_active_not_provisioned(self):
559+
# active transaction and no provision date should be false
560+
self.guestObject.side_effect = [
561+
{'activeTransaction': {'id': 1}},
562+
]
563+
value = self.cci.wait_for_ready(1, 1)
564+
self.assertFalse(value)
565+
566+
def test_active_and_provisiondate(self):
567+
# active transaction and provision date should be True
568+
self.guestObject.side_effect = [
569+
{'activeTransaction': {'id': 1},
570+
'provisionDate': 'aaa'},
571+
]
572+
value = self.cci.wait_for_ready(1, 1)
573+
self.assertTrue(value)
574+
575+
def test_active_provision_pending(self):
576+
# active transaction and provision date
577+
# and pending should be false
578+
self.guestObject.side_effect = [
579+
{'activeTransaction': {'id': 1},
580+
'provisionDate': 'aaa'},
581+
]
582+
value = self.cci.wait_for_ready(1, 1, pending=True)
583+
self.assertFalse(value)
584+
585+
def test_active_reload(self):
586+
# actively running reload
587+
self.guestObject.side_effect = [
588+
{
589+
'activeTransaction': {'id': 1},
590+
'provisionDate': 'aaa',
591+
'lastOperatingSystemReload': {'id': 1},
592+
},
593+
]
594+
value = self.cci.wait_for_ready(1, 1)
595+
self.assertFalse(value)
596+
597+
def test_reload_no_pending(self):
598+
# reload complete, maintance transactions
599+
self.guestObject.side_effect = [
600+
{
601+
'activeTransaction': {'id': 2},
602+
'provisionDate': 'aaa',
603+
'lastOperatingSystemReload': {'id': 1},
604+
},
605+
]
606+
value = self.cci.wait_for_ready(1, 1)
607+
self.assertTrue(value)
608+
609+
def test_reload_pending(self):
610+
# reload complete, pending maintance transactions
611+
self.guestObject.side_effect = [
612+
{
613+
'activeTransaction': {'id': 2},
614+
'provisionDate': 'aaa',
615+
'lastOperatingSystemReload': {'id': 1},
616+
},
617+
]
618+
value = self.cci.wait_for_ready(1, 1, pending=True)
619+
self.assertFalse(value)
620+
505621
@patch('SoftLayer.managers.cci.sleep')
506-
def test_wait(self, _sleep):
507-
guestObject = self.client['Virtual_Guest'].getObject
622+
def test_ready_iter_once_incomplete(self, _sleep):
623+
self.guestObject = self.client['Virtual_Guest'].getObject
508624

625+
# no iteration, false
626+
self.guestObject.side_effect = [
627+
{'activeTransaction': {'id': 1}},
628+
]
629+
value = self.cci.wait_for_ready(1, 1)
630+
self.assertFalse(value)
631+
self.assertFalse(_sleep.called)
632+
633+
@patch('SoftLayer.managers.cci.sleep')
634+
def test_iter_once_complete(self, _sleep):
635+
# no iteration, true
636+
self.guestObject.side_effect = [
637+
{'provisionDate': 'aaa'},
638+
]
639+
value = self.cci.wait_for_ready(1, 1)
640+
self.assertTrue(value)
641+
self.assertFalse(_sleep.called)
642+
643+
@patch('SoftLayer.managers.cci.sleep')
644+
def test_iter_four_complete(self, _sleep):
509645
# test 4 iterations with positive match
510-
guestObject.side_effect = [
646+
self.guestObject.side_effect = [
511647
{'activeTransaction': {'id': 1}},
512648
{'activeTransaction': {'id': 1}},
513649
{'activeTransaction': {'id': 1}},
514650
{'provisionDate': 'aaa'},
515-
{'provisionDate': 'aaa'}
516651
]
517652

518-
value = self.cci.wait_for_transaction(1, 4)
653+
value = self.cci.wait_for_ready(1, 4)
519654
self.assertTrue(value)
520655
_sleep.assert_has_calls([call(1), call(1), call(1)])
521-
guestObject.assert_has_calls([
656+
self.guestObject.assert_has_calls([
522657
call(id=1, mask=ANY), call(id=1, mask=ANY),
523658
call(id=1, mask=ANY), call(id=1, mask=ANY),
524659
])
525660

661+
@patch('SoftLayer.managers.cci.sleep')
662+
def test_iter_two_incomplete(self, _sleep):
526663
# test 2 iterations, with no matches
527-
_sleep.reset_mock()
528-
guestObject.reset_mock()
529-
530-
guestObject.side_effect = [
531-
{'activeTransaction': {'id': 1}},
664+
self.guestObject.side_effect = [
532665
{'activeTransaction': {'id': 1}},
533666
{'activeTransaction': {'id': 1}},
534667
{'provisionDate': 'aaa'}
535668
]
536-
value = self.cci.wait_for_transaction(1, 2)
669+
value = self.cci.wait_for_ready(1, 2)
537670
self.assertFalse(value)
538-
_sleep.assert_has_calls([call(1), call(1)])
539-
guestObject.assert_has_calls([
671+
_sleep.assert_called_once_with(1)
672+
self.guestObject.assert_has_calls([
540673
call(id=1, mask=ANY), call(id=1, mask=ANY),
541-
call(id=1, mask=ANY)
542674
])
543675

676+
@patch('SoftLayer.managers.cci.sleep')
677+
def test_iter_ten_incomplete(self, _sleep):
544678
# 10 iterations at 10 second sleeps with no
545679
# matching values.
546-
_sleep.reset_mock()
547-
guestObject.reset_mock()
548-
guestObject.side_effect = [
680+
self.guestObject.side_effect = [
549681
{},
550682
{'activeTransaction': {'id': 1}},
551683
{'activeTransaction': {'id': 1}},
@@ -556,57 +688,18 @@ def test_wait(self, _sleep):
556688
{'activeTransaction': {'id': 1}},
557689
{'activeTransaction': {'id': 1}},
558690
{'activeTransaction': {'id': 1}},
559-
{'activeTransaction': {'id': 1}}
560691
]
561-
value = self.cci.wait_for_transaction(1, 10, 10)
692+
value = self.cci.wait_for_ready(1, 10, 10)
562693
self.assertFalse(value)
563-
guestObject.assert_has_calls([
694+
self.guestObject.assert_has_calls([
564695
call(id=1, mask=ANY), call(id=1, mask=ANY),
565696
call(id=1, mask=ANY), call(id=1, mask=ANY),
566697
call(id=1, mask=ANY), call(id=1, mask=ANY),
567698
call(id=1, mask=ANY), call(id=1, mask=ANY),
568699
call(id=1, mask=ANY), call(id=1, mask=ANY),
569-
call(id=1, mask=ANY)
570700
])
701+
# should only be 9 calls to sleep, last iteration
702+
# should return a value and skip the sleep
571703
_sleep.assert_has_calls([
572704
call(10), call(10), call(10), call(10), call(10),
573-
call(10), call(10), call(10), call(10), call(10)])
574-
575-
def test_change_port_speed_public(self):
576-
cci_id = 1
577-
speed = 100
578-
self.cci.change_port_speed(cci_id, True, speed)
579-
580-
service = self.client['Virtual_Guest']
581-
f = service.setPublicNetworkInterfaceSpeed
582-
f.assert_called_once_with(speed, id=cci_id)
583-
584-
def test_change_port_speed_private(self):
585-
cci_id = 2
586-
speed = 10
587-
self.cci.change_port_speed(cci_id, False, speed)
588-
589-
service = self.client['Virtual_Guest']
590-
f = service.setPrivateNetworkInterfaceSpeed
591-
f.assert_called_once_with(speed, id=cci_id)
592-
593-
def test_edit(self):
594-
# Test editing user data
595-
service = self.client['Virtual_Guest']
596-
597-
self.cci.edit(100, userdata='my data')
598-
599-
service.setUserMetadata.assert_called_once_with(['my data'], id=100)
600-
601-
# Now test a blank edit
602-
self.assertTrue(self.cci.edit, 100)
603-
604-
# Finally, test a full edit
605-
args = {
606-
'hostname': 'new-host',
607-
'domain': 'new.sftlyr.ws',
608-
'notes': 'random notes',
609-
}
610-
611-
self.cci.edit(100, **args)
612-
service.editObject.assert_called_once_with(args, id=100)
705+
call(10), call(10), call(10), call(10)])

0 commit comments

Comments
 (0)