Skip to content

Commit 865eb70

Browse files
committed
add method to wait for security group API completion
1 parent 2ccd6bc commit 865eb70

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

SoftLayer/managers/network.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66
:license: MIT, see LICENSE for more details.
77
"""
88
import collections
9+
import itertools
10+
import json
11+
import logging
912

1013
from SoftLayer import exceptions
1114
from SoftLayer import utils
15+
import time
16+
17+
logger = logging.getLogger(__name__)
1218

1319
DEFAULT_SUBNET_MASK = ','.join(['hardware',
1420
'datacenter',
@@ -639,3 +645,94 @@ def get_nas_credentials(self, identifier, **kwargs):
639645
"""
640646
result = self.network_storage.getObject(id=identifier, **kwargs)
641647
return result
648+
649+
def wait_for_sg_request(self, group_id, request_id, limit=60, delay=5):
650+
"""Wait for a Security Group API request to complete.
651+
652+
Security Group API requests may trigger firewall updates that complete
653+
ansynchronously. Firewall update completion is signalled
654+
via an Audit Log event. This method polls Security Group audit logs until the
655+
specified request is complete (either successfully or unsuccessfully).
656+
657+
:param int group_id: The security group ID with the pending API request
658+
:param int request_id: The request ID of the API request we want to verify completion for.
659+
Security Group APIs that may complete asynchronously contain this value in the requestId field of their
660+
return value.
661+
:param int limit: The maximum amount of time to wait for completion.
662+
:param int delay: The number of seconds to sleep before polling checks. Defaults to 5.
663+
664+
Example::
665+
666+
# Will return once firewall updates for Security Group API request 'abc123' are
667+
complete for security group 123456.
668+
net_mgr.wait_for_sg_request(123456, 'abc123')
669+
"""
670+
671+
# Audit log event name for successfully API completion
672+
SUCCESS_EVENT_NAME='Network Component Added to Security Group'
673+
674+
# Audit log event name for unsuccessful API completion
675+
FAILURE_EVENT_NAME='Network Component Removed from Security Group'
676+
677+
logger.debug('wait %s seconds for request %s completion on Security Group %s' % (limit, request_id, group_id))
678+
wait_until = time.time() + limit
679+
680+
for sg_id in itertools.repeat(group_id):
681+
try:
682+
# get all event logs for the specified security group
683+
logs = self.client.call("Event_Log",
684+
'getAllObjects',
685+
filter={'objectId': {'operation': sg_id}})
686+
687+
#
688+
# look for a log for the indicated request, there will be
689+
# only 1 SG log per request_id.
690+
#
691+
completion_log = None
692+
for one_log in logs:
693+
if not 'metaData' in one_log:
694+
continue
695+
metadata = json.loads(one_log['metaData'])
696+
if not 'requestId' in metadata:
697+
continue
698+
if metadata['requestId'] == request_id:
699+
if one_log['eventName'] == SUCCESS_EVENT_NAME or one_log['eventName'] == FAILURE_EVENT_NAME:
700+
completion_log=one_log
701+
break
702+
703+
#
704+
# check if it's a successful or failure completion log
705+
#
706+
if completion_log is not None:
707+
if completion_log['eventName'] == SUCCESS_EVENT_NAME:
708+
# return True if firewall updates for the Security Group request
709+
# completed successfully.
710+
logger.debug('request %s complete successfully' % request_id)
711+
return True
712+
elif completion_log['eventName'] == FAILURE_EVENT_NAME:
713+
logger.debug('request %s complete with failure' % request_id)
714+
raise exceptions.SoftLayerError("Security Group %s request %s did not complete"
715+
" within the specified timeout (%s seconds)" % (sg_id, request_id, limit))
716+
else:
717+
logger.warning('unexpected log event: %s' % completion_log['eventName'])
718+
raise exceptions.SoftLayerError("Security Group internal error, rcvd %s "
719+
% completion_log['eventName'])
720+
721+
#
722+
# no completion log yet, delay and try again
723+
#
724+
logger.info("%s not complete.", str(request_id))
725+
726+
except exceptions.SoftLayerAPIError as exception:
727+
# if the call is excepting unexpectedly, scale back how
728+
# frequently we call it.
729+
delay = (delay * 2) + random.randint(0, 9)
730+
logger.exception()
731+
732+
now = time.time()
733+
if now > wait_until:
734+
raise exceptions.SoftLayerError("Security Group %s request %s did not complete"
735+
" within the specified timeout %s" % (sg_id, request_id, limit))
736+
737+
logger.debug('Auto retry in %s seconds', str(min(delay, wait_until - now)))
738+
time.sleep(min(delay, wait_until - now))

0 commit comments

Comments
 (0)