diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 11fb32ffe..0b2b2ff49 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -310,7 +310,9 @@ def get_object_value(obj, attr): def is_tag_filter(filter_name): - return filter_name.startswith('tag:') + return (filter_name.startswith('tag:') or + filter_name.startswith('tag-value') or + filter_name.startswith('tag-key')) def get_obj_tag(obj, filter_name): @@ -318,10 +320,24 @@ def get_obj_tag(obj, filter_name): tags = dict((tag['key'], tag['value']) for tag in obj.get_tags()) return tags.get(tag_name) +def get_obj_tag_names(obj): + tags = set((tag['key'] for tag in obj.get_tags())) + return tags + +def get_obj_tag_values(obj): + tags = set((tag['value'] for tag in obj.get_tags())) + return tags def tag_filter_matches(obj, filter_name, filter_values): - tag_value = get_obj_tag(obj, filter_name) - return tag_value in filter_values + if filter_name == 'tag-key': + tag_names = get_obj_tag_names(obj) + return len(set(filter_values).intersection(tag_names)) > 0 + elif filter_name == 'tag-value': + tag_values = get_obj_tag_values(obj) + return len(set(filter_values).intersection(tag_values)) > 0 + else: + tag_value = get_obj_tag(obj, filter_name) + return tag_value in filter_values filter_dict_attribute_mapping = { @@ -331,7 +347,8 @@ filter_dict_attribute_mapping = { 'source-dest-check': 'source_dest_check', 'vpc-id': 'vpc_id', 'group-id': 'security_groups', - 'instance.group-id': 'security_groups' + 'instance.group-id': 'security_groups', + 'instance-type': 'instance_type' } diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index e16bbb126..640a24eaf 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -139,6 +139,48 @@ def test_get_instances_filtering_by_instance_id(): reservations = conn.get_all_instances(filters={'instance-id': 'non-existing-id'}) reservations.should.have.length_of(0) + +@mock_ec2 +def test_get_instances_filtering_by_instance_type(): + conn = boto.connect_ec2() + reservation1 = conn.run_instances('ami-1234abcd', instance_type='m1.small') + instance1 = reservation1.instances[0] + reservation2 = conn.run_instances('ami-1234abcd', instance_type='m1.small') + instance2 = reservation2.instances[0] + reservation3 = conn.run_instances('ami-1234abcd', instance_type='t1.micro') + instance3 = reservation3.instances[0] + + reservations = conn.get_all_instances(filters={'instance-type': 'm1.small'}) + # get_all_instances should return instance1,2 + reservations.should.have.length_of(2) + reservations[0].instances.should.have.length_of(1) + reservations[1].instances.should.have.length_of(1) + instance_ids = [ reservations[0].instances[0].id, + reservations[1].instances[0].id ] + set(instance_ids).should.equal(set([instance1.id, instance2.id])) + + reservations = conn.get_all_instances(filters={'instance-type': 't1.micro'}) + # get_all_instances should return one + reservations.should.have.length_of(1) + reservations[0].instances.should.have.length_of(1) + reservations[0].instances[0].id.should.equal(instance3.id) + + reservations = conn.get_all_instances(filters={'instance-type': ['t1.micro', 'm1.small']}) + reservations.should.have.length_of(3) + reservations[0].instances.should.have.length_of(1) + reservations[1].instances.should.have.length_of(1) + reservations[2].instances.should.have.length_of(1) + instance_ids = [ + reservations[0].instances[0].id, + reservations[1].instances[0].id, + reservations[2].instances[0].id, + ] + set(instance_ids).should.equal(set([instance1.id, instance2.id, instance3.id])) + + reservations = conn.get_all_instances(filters={'instance-type': 'bogus'}) + #bogus instance-type should return none + reservations.should.have.length_of(0) + @mock_ec2 def test_get_instances_filtering_by_reason_code(): conn = boto.connect_ec2() @@ -240,6 +282,73 @@ def test_get_instances_filtering_by_tag(): reservations[0].instances[0].id.should.equal(instance1.id) reservations[0].instances[1].id.should.equal(instance3.id) +@mock_ec2 +def test_get_instances_filtering_by_tag_value(): + conn = boto.connect_ec2() + reservation = conn.run_instances('ami-1234abcd', min_count=3) + instance1, instance2, instance3 = reservation.instances + instance1.add_tag('tag1', 'value1') + instance1.add_tag('tag2', 'value2') + instance2.add_tag('tag1', 'value1') + instance2.add_tag('tag2', 'wrong value') + instance3.add_tag('tag2', 'value2') + + reservations = conn.get_all_instances(filters={'tag-value' : 'value0'}) + # get_all_instances should return no instances + reservations.should.have.length_of(0) + + reservations = conn.get_all_instances(filters={'tag-value' : 'value1'}) + # get_all_instances should return both instances with this tag value + reservations.should.have.length_of(1) + reservations[0].instances.should.have.length_of(2) + reservations[0].instances[0].id.should.equal(instance1.id) + reservations[0].instances[1].id.should.equal(instance2.id) + + reservations = conn.get_all_instances(filters={'tag-value' : ['value2', 'value1']}) + # get_all_instances should return both instances with one of the acceptable tag values + reservations.should.have.length_of(1) + reservations[0].instances.should.have.length_of(3) + reservations[0].instances[0].id.should.equal(instance1.id) + reservations[0].instances[1].id.should.equal(instance2.id) + reservations[0].instances[2].id.should.equal(instance3.id) + + reservations = conn.get_all_instances(filters={'tag-value' : ['value2', 'bogus']}) + # get_all_instances should return both instances with one of the acceptable tag values + reservations.should.have.length_of(1) + reservations[0].instances.should.have.length_of(2) + reservations[0].instances[0].id.should.equal(instance1.id) + reservations[0].instances[1].id.should.equal(instance3.id) + +@mock_ec2 +def test_get_instances_filtering_by_tag_name(): + conn = boto.connect_ec2() + reservation = conn.run_instances('ami-1234abcd', min_count=3) + instance1, instance2, instance3 = reservation.instances + instance1.add_tag('tag1') + instance1.add_tag('tag2') + instance2.add_tag('tag1') + instance2.add_tag('tag2X') + instance3.add_tag('tag3') + + reservations = conn.get_all_instances(filters={'tag-key' : 'tagX'}) + # get_all_instances should return no instances + reservations.should.have.length_of(0) + + reservations = conn.get_all_instances(filters={'tag-key' : 'tag1'}) + # get_all_instances should return both instances with this tag value + reservations.should.have.length_of(1) + reservations[0].instances.should.have.length_of(2) + reservations[0].instances[0].id.should.equal(instance1.id) + reservations[0].instances[1].id.should.equal(instance2.id) + + reservations = conn.get_all_instances(filters={'tag-key' : ['tag1', 'tag3']}) + # get_all_instances should return both instances with one of the acceptable tag values + reservations.should.have.length_of(1) + reservations[0].instances.should.have.length_of(3) + reservations[0].instances[0].id.should.equal(instance1.id) + reservations[0].instances[1].id.should.equal(instance2.id) + reservations[0].instances[2].id.should.equal(instance3.id) + @mock_ec2 def test_instance_start_and_stop(): conn = boto.connect_ec2('the_key', 'the_secret')