From 53fdf330ee05289879d427d65cf368fdda47d292 Mon Sep 17 00:00:00 2001 From: Peter Van Bouwel Date: Sat, 8 Nov 2014 12:12:20 +0100 Subject: [PATCH 1/5] Tests are added that verify that when a tag is being set on an (EBS) volume or on an instance that upon retrieval from the resource, the tag are set on the instance. Important is that the tags are set using create_tags but that the presence is validated by getting the resource using either (get_all_volumes or get_all_instances). When running the tests using nosetests it shows that the tags for the instances are correctly retrieved but the tags for the volume are missing --- tests/test_ec2/test_tags.py | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/test_ec2/test_tags.py b/tests/test_ec2/test_tags.py index 69bae7410..1dcb75d34 100644 --- a/tests/test_ec2/test_tags.py +++ b/tests/test_ec2/test_tags.py @@ -3,6 +3,7 @@ import itertools import boto from boto.exception import EC2ResponseError +from boto.ec2.instance import Reservation import sure # noqa from moto import mock_ec2 @@ -253,3 +254,57 @@ def test_get_all_tags_value_filter(): tags = conn.get_all_tags(filters={'value': '*value\*\?'}) tags.should.have.length_of(1) + + +@mock_ec2 +def test_create_tags_must_set_tags_on_retrieved_instances(): + tag_key = 'Tag name' + tag_value = 'Tag value' + tags_to_be_set = {tag_key: tag_value} + + conn = boto.connect_ec2('the_key', 'the_secret') + reservation = conn.run_instances('ami-1234abcd') + reservation.should.be.a(Reservation) + reservation.instances.should.have.length_of(1) + instance = reservation.instances[0] + + reservations = conn.get_all_instances() + reservations.should.have.length_of(1) + reservations[0].id.should.equal(reservation.id) + instances = reservations[0].instances + instances.should.have.length_of(1) + instances[0].id.should.equal(instance.id) + + conn.create_tags([instance.id], tags_to_be_set) + reservations = conn.get_all_instances() + instance = reservations[0].instances[0] + retrieved_tags = instance.tags + + #Cleanup of instance + conn.terminate_instances([instances[0].id]) + + #Check whether tag is present with correct value + retrieved_tags[tag_key].should.equal(tag_value) + + +@mock_ec2 +def test_create_tags_must_set_tags_on_retrieved_volumes(): + tag_key = 'Tag name' + tag_value = 'Tag value' + tags_to_be_set = {tag_key: tag_value} + conn = boto.connect_ec2('the_key', 'the_secret') + volume = conn.create_volume(80, "us-east-1a") + + all_volumes = conn.get_all_volumes() + volume = all_volumes[0] + conn.create_tags([volume.id], tags_to_be_set) + + #Fetch the volume again + all_volumes = conn.get_all_volumes() + volume = all_volumes[0] + retrieved_tags = volume.tags + + volume.delete() + + #Check whether tag is present with correct value + retrieved_tags[tag_key].should.equal(tag_value) \ No newline at end of file From 17356fe56c0502de0b9cdee2e54ecf62f806b76f Mon Sep 17 00:00:00 2001 From: Peter Van Bouwel Date: Sun, 9 Nov 2014 12:21:19 +0100 Subject: [PATCH 2/5] Extend the DESCRIBE_VOLUMES_RESPONSE to include the tagSet as documented by AWS on http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-ItemType-DescribeVolumesSetItemResponseType.html . This is needed to pass the test that was added in previous commit. --- moto/ec2/models.py | 4 ++++ moto/ec2/responses/elastic_block_store.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 377699164..df89be4ce 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1255,6 +1255,10 @@ class Volume(object): else: return 'available' + def get_tags(self): + tags = ec2_backend.describe_tags(filters={'resource-id': [self.id]}) + return tags + class Snapshot(object): def __init__(self, snapshot_id, volume, description): diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py index 868cf2215..93009294b 100644 --- a/moto/ec2/responses/elastic_block_store.py +++ b/moto/ec2/responses/elastic_block_store.py @@ -132,6 +132,16 @@ DESCRIBE_VOLUMES_RESPONSE = """ Date: Tue, 11 Nov 2014 10:26:02 +0100 Subject: [PATCH 4/5] Alter get_tags to use the backend associated with the taggable object. Also give volume and snapshot an additional backend attribute. --- moto/ec2/models.py | 20 +++++++++++++++----- tests/test_ec2/test_tags.py | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 4684e02b3..8f9220142 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -109,7 +109,15 @@ class StateReason(object): class TaggedEC2Resource(object): def get_tags(self, *args, **kwargs): - tags = ec2_backend.describe_tags(filters={'resource-id': [self.id]}) + if hasattr(self,"ec2_backend"): + backend = self.ec2_backend + elif hasattr(self,"ebs_backend"): + backend = self.ebs_backend + else: + raise NotImplementedError("Tagging of an object with backend that differs from ec2_backend or ebs_backend.") + + + tags = backend.describe_tags(filters={'resource-id': [self.id]}) return tags def get_filter_value(self, filter_name): @@ -1224,11 +1232,12 @@ class VolumeAttachment(object): class Volume(TaggedEC2Resource): - def __init__(self, volume_id, size, zone): + def __init__(self, ebs_backend, volume_id, size, zone): self.id = volume_id self.size = size self.zone = zone self.attachment = None + self.ebs_backend = ebs_backend @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -1253,11 +1262,12 @@ class Volume(TaggedEC2Resource): class Snapshot(TaggedEC2Resource): - def __init__(self, snapshot_id, volume, description): + def __init__(self, ebs_backend, snapshot_id, volume, description): self.id = snapshot_id self.volume = volume self.description = description self.create_volume_permission_groups = set() + self.ebs_backend = ebs_backend class EBSBackend(object): @@ -1270,7 +1280,7 @@ class EBSBackend(object): def create_volume(self, size, zone_name): volume_id = random_volume_id() zone = self.get_zone_by_name(zone_name) - volume = Volume(volume_id, size, zone) + volume = Volume(self, volume_id, size, zone) self.volumes[volume_id] = volume return volume @@ -1312,7 +1322,7 @@ class EBSBackend(object): def create_snapshot(self, volume_id, description): snapshot_id = random_snapshot_id() volume = self.get_volume(volume_id) - snapshot = Snapshot(snapshot_id, volume, description) + snapshot = Snapshot(self, snapshot_id, volume, description) self.snapshots[snapshot_id] = snapshot return snapshot diff --git a/tests/test_ec2/test_tags.py b/tests/test_ec2/test_tags.py index 15717519d..72e72fb86 100644 --- a/tests/test_ec2/test_tags.py +++ b/tests/test_ec2/test_tags.py @@ -315,8 +315,8 @@ def test_retrieved_snapshots_must_contain_their_tags(): tag_key = 'Tag name' tag_value = 'Tag value' tags_to_be_set = {tag_key: tag_value} - conn = boto.connect_ec2('the_key', 'the_secret') - volume = conn.create_volume(80, "us-east-1a") + conn = boto.connect_ec2(aws_access_key_id='the_key', aws_secret_access_key='the_secret') + volume = conn.create_volume(80, "eu-west-1a") snapshot = conn.create_snapshot(volume.id) conn.create_tags([snapshot.id], tags_to_be_set) From 63c7e224a2f6a1e0dfe9fa1d4dbf61a755ee2bee Mon Sep 17 00:00:00 2001 From: Peter Van Bouwel Date: Tue, 11 Nov 2014 19:41:16 +0100 Subject: [PATCH 5/5] Always use ec2_backend to get the tag information in order to have a cleaner get_tags method. --- moto/ec2/models.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 8f9220142..05650904a 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -109,15 +109,7 @@ class StateReason(object): class TaggedEC2Resource(object): def get_tags(self, *args, **kwargs): - if hasattr(self,"ec2_backend"): - backend = self.ec2_backend - elif hasattr(self,"ebs_backend"): - backend = self.ebs_backend - else: - raise NotImplementedError("Tagging of an object with backend that differs from ec2_backend or ebs_backend.") - - - tags = backend.describe_tags(filters={'resource-id': [self.id]}) + tags = self.ec2_backend.describe_tags(filters={'resource-id': [self.id]}) return tags def get_filter_value(self, filter_name): @@ -1232,12 +1224,12 @@ class VolumeAttachment(object): class Volume(TaggedEC2Resource): - def __init__(self, ebs_backend, volume_id, size, zone): + def __init__(self, volume_id, size, zone): self.id = volume_id self.size = size self.zone = zone self.attachment = None - self.ebs_backend = ebs_backend + self.ec2_backend = ec2_backend @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json): @@ -1262,12 +1254,12 @@ class Volume(TaggedEC2Resource): class Snapshot(TaggedEC2Resource): - def __init__(self, ebs_backend, snapshot_id, volume, description): + def __init__(self, snapshot_id, volume, description): self.id = snapshot_id self.volume = volume self.description = description self.create_volume_permission_groups = set() - self.ebs_backend = ebs_backend + self.ec2_backend = ec2_backend class EBSBackend(object): @@ -1280,7 +1272,7 @@ class EBSBackend(object): def create_volume(self, size, zone_name): volume_id = random_volume_id() zone = self.get_zone_by_name(zone_name) - volume = Volume(self, volume_id, size, zone) + volume = Volume(volume_id, size, zone) self.volumes[volume_id] = volume return volume @@ -1322,7 +1314,7 @@ class EBSBackend(object): def create_snapshot(self, volume_id, description): snapshot_id = random_snapshot_id() volume = self.get_volume(volume_id) - snapshot = Snapshot(self, snapshot_id, volume, description) + snapshot = Snapshot(snapshot_id, volume, description) self.snapshots[snapshot_id] = snapshot return snapshot