diff --git a/ec2api/api/cloud.py b/ec2api/api/cloud.py index e8b5a746..05ee3a49 100644 --- a/ec2api/api/cloud.py +++ b/ec2api/api/cloud.py @@ -819,10 +819,10 @@ class CloudController(object): @module_and_param_types(volume, 'str', 'int', 'snap_id', 'str', 'int', - 'bool', 'str') + 'bool', 'str', 'str') def create_volume(self, context, availability_zone=None, size=None, snapshot_id=None, volume_type=None, iops=None, - encrypted=None, kms_key_id=None): + encrypted=None, kms_key_id=None, client_token=None): """Creates an EBS volume. Args: @@ -846,6 +846,8 @@ class CloudController(object): kms_key_id (str): The full ARN of AWS KMS master key to use when creating the encrypted volume. Not used now. + client_token (str): Unique, case-sensitive identifier that you + provide to ensure the idempotency of the request. Returns: Information about the volume. @@ -1691,13 +1693,15 @@ class VpcCloudController(CloudController): 'dummy', 'int', 'str', - 'sg_ids') + 'sg_ids', + 'str') def create_network_interface(self, context, subnet_id, private_ip_address=None, private_ip_addresses=None, secondary_private_ip_address_count=None, description=None, - security_group_id=None): + security_group_id=None, + client_token=None): """Creates a network interface in the specified subnet. Args: @@ -1724,6 +1728,8 @@ class VpcCloudController(CloudController): description (str): A description for the network interface. security_group_id (list of str): The list of security group IDs for the network interface. + client_token (str): Unique, case-sensitive identifier that you + provide to ensure the idempotency of the request. Returns: The network interface that was created. diff --git a/ec2api/api/image.py b/ec2api/api/image.py index d1d701a9..2384f778 100644 --- a/ec2api/api/image.py +++ b/ec2api/api/image.py @@ -156,13 +156,13 @@ def create_image(context, instance_id, name=None, description=None, image['os_id'] = os_image_id db_api.update_item(context, image) except Exception: - LOG.exception('Failed to complete image %s creation', image.id) + LOG.exception('Failed to complete image %s creation', image['id']) try: image['state'] = 'failed' db_api.update_item(context, image) except Exception: LOG.warning("Couldn't set 'failed' state for db image %s", - image.id, exc_info=True) + image['id'], exc_info=True) try: os_instance.start() diff --git a/ec2api/api/network_interface.py b/ec2api/api/network_interface.py index 1b03a09d..53223c06 100644 --- a/ec2api/api/network_interface.py +++ b/ec2api/api/network_interface.py @@ -47,7 +47,22 @@ def create_network_interface(context, subnet_id, private_ip_addresses=None, secondary_private_ip_address_count=None, description=None, - security_group_id=None): + security_group_id=None, + client_token=None): + + if client_token: + result = describe_network_interfaces(context, + filter=[{'name': 'client-token', + 'value': [client_token]}]) + if result['networkInterfaceSet']: + if len(result['networkInterfaceSet']) > 1: + LOG.error('describe_network_interfaces returns %s ' + 'network_interfaces, but 1 is expected.', + len(result['networkInterfaceSet'])) + LOG.error('Requested client token: %s', client_token) + LOG.error('Result: %s', result) + return result['networkInterfaceSet'][0] + subnet = ec2utils.get_db_item(context, subnet_id) if subnet is None: raise exception.InvalidSubnetIDNotFound(id=subnet_id) @@ -206,6 +221,7 @@ class NetworkInterfaceDescriber(common.TaggableItemsDescriber): 'attachment.attach.time': ('attachment', 'attachTime'), 'attachment.delete-on-termination': ('attachment', 'deleteOnTermination'), + 'client-token': 'clientToken', 'description': 'description', 'group-id': ['groupSet', 'groupId'], 'group-name': ['groupSet', 'groupName'], diff --git a/ec2api/api/volume.py b/ec2api/api/volume.py index 7f3b1468..2610e20e 100644 --- a/ec2api/api/volume.py +++ b/ec2api/api/volume.py @@ -37,7 +37,21 @@ Validator = common.Validator def create_volume(context, availability_zone=None, size=None, snapshot_id=None, volume_type=None, iops=None, - encrypted=None, kms_key_id=None): + encrypted=None, kms_key_id=None, client_token=None): + + if client_token: + result = describe_volumes(context, + filter=[{'name': 'client-token', + 'value': [client_token]}]) + if result['volumeSet']: + if len(result['volumeSet']) > 1: + LOG.error('describe_volumes returns %s ' + 'volumes, but 1 is expected.', + len(result['volumeSet'])) + LOG.error('Requested client token: %s', client_token) + LOG.error('Result: %s', result) + return result['volumeSet'][0] + if snapshot_id is not None: snapshot = ec2utils.get_db_item(context, snapshot_id) os_snapshot_id = snapshot['os_id'] @@ -121,6 +135,7 @@ class VolumeDescriber(common.TaggableItemsDescriber): SORT_KEY = 'volumeId' FILTER_MAP = { 'availability-zone': 'availabilityZone', + 'client-token': 'clientToken', 'create-time': 'createTime', 'encrypted': 'encrypted', 'size': 'size',