|
6 | 6 | :license: MIT, see LICENSE for more details. |
7 | 7 | """ |
8 | 8 | import collections |
| 9 | +import itertools |
| 10 | +import json |
| 11 | +import logging |
9 | 12 |
|
10 | 13 | from SoftLayer import exceptions |
11 | 14 | from SoftLayer import utils |
| 15 | +import time |
| 16 | + |
| 17 | +logger = logging.getLogger(__name__) |
12 | 18 |
|
13 | 19 | DEFAULT_SUBNET_MASK = ','.join(['hardware', |
14 | 20 | 'datacenter', |
@@ -639,3 +645,94 @@ def get_nas_credentials(self, identifier, **kwargs): |
639 | 645 | """ |
640 | 646 | result = self.network_storage.getObject(id=identifier, **kwargs) |
641 | 647 | 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