diff --git a/aws_ec2_assign_elastic_ip/__init__.py b/aws_ec2_assign_elastic_ip/__init__.py index 3e46357..757422a 100644 --- a/aws_ec2_assign_elastic_ip/__init__.py +++ b/aws_ec2_assign_elastic_ip/__init__.py @@ -2,6 +2,7 @@ import logging import logging.config import sys +import time if sys.platform in ['win32', 'cygwin']: import ntpath as ospath @@ -68,6 +69,9 @@ def main(): logger.info('Would assign IP {0}'.format(address.public_ip)) else: _assign_address(instance_id, address) + if args.verify: + if not _verify_associated(instance_id, address): + sys.exit(1) def _assign_address(instance_id, address): @@ -102,6 +106,60 @@ def _assign_address(instance_id, address): logger.info('Successfully associated Elastic IP {0} with {1}'.format( address.public_ip, instance_id)) +def _verify_associated(instance_id, address, retries=10, retry_interval=0.1): + """ + Check that address is actually associated with instance_id by querying + the EC2 API. + + :type instance_id: str + :param instance_id: Instance ID + :type address: boto.ec2.address + :param address: Elastic IP address + :type retries: int + :param retries: Number of times to retry failures + :type retry_interval: float + :param retry_interval: Sleep interval between retries + :returns: bool -- True if EIP is actually associated + """ + + logger.debug('Verifying that {0} is associated with {1}'.format( + instance_id, address.public_ip)) + + actual = connection.get_all_addresses(addresses=[address.public_ip])[0] + + if actual.instance_id == instance_id: + logger.info( + 'Verified that {0} is really associated with {1} ({2}/{3})'.format( + actual.public_ip, instance_id, actual.network_interface_id, + actual.association_id)) + return True + + if actual.instance_id: + logger.error(('Somehow {0!r} is associated with {1!r},' + + ' not expected instance {2!r}').format(address.public_ip, + actual.instance_id, + instance_id)) + return False + + if actual.association_id: + logger.warning( + 'Elastic IP {0} is already associated with {1!r}'.format( + actual.public_ip, actual.network_interface_id)) + else: + logger.info('Elastic IP {0} is not yet associated'.format( + address.public_ip)) + + if retries > 0: + logger.info('Retrying verification...') + time.sleep(retry_interval) + return _verify_associated(instance_id=instance_id, address=address, + retries=retries - 1, + retry_interval=retry_interval) + + logger.error( + 'Ran out of retries verifying association of {0!r} to {1!r}'.format( + address.public_ip, instance_id)) + return False def _get_unassociated_address(): """ Return the first unassociated EIP we can find diff --git a/aws_ec2_assign_elastic_ip/command_line_options.py b/aws_ec2_assign_elastic_ip/command_line_options.py index a5a16d5..b3a89d0 100644 --- a/aws_ec2_assign_elastic_ip/command_line_options.py +++ b/aws_ec2_assign_elastic_ip/command_line_options.py @@ -39,6 +39,10 @@ help=( 'Turn on dry run mode. No address will be assigned,\n' 'we will only print which we whould take')) +PARSER.add_argument( + '--skip-verify', + help='Skip verification that the EIP was successfully associated', + action='store_false', dest='verify') PARSER.add_argument( '--valid-ips', help=(