moto/tests/test_ec2/test_amis.py

518 lines
21 KiB
Python
Raw Normal View History

from __future__ import unicode_literals
# Ensure 'assert_raises' context manager support for Python 2.6
import tests.backport_assert_raises # noqa
from nose.tools import assert_raises
2013-02-22 04:13:01 +00:00
import boto
import boto.ec2
from boto.exception import EC2ResponseError, JSONResponseError
2013-08-03 21:21:25 +00:00
import sure # noqa
2013-02-22 04:13:01 +00:00
from moto import mock_ec2
from tests.helpers import requires_boto_gte
2013-02-22 04:13:01 +00:00
@mock_ec2
def test_ami_create_and_delete():
conn = boto.connect_ec2('the_key', 'the_secret')
2013-03-05 13:14:43 +00:00
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
with assert_raises(JSONResponseError) as ex:
image_id = conn.create_image(instance.id, "test-ami", "this is a test ami", dry_run=True)
ex.exception.reason.should.equal('DryRunOperation')
ex.exception.status.should.equal(400)
ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateImage operation: Request would have succeeded, but DryRun flag is set')
image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
all_images = conn.get_all_images()
image = all_images[0]
image.id.should.equal(image_id)
image.virtualization_type.should.equal(instance.virtualization_type)
image.architecture.should.equal(instance.architecture)
image.kernel_id.should.equal(instance.kernel)
image.platform.should.equal(instance.platform)
image.creationDate.should_not.be.none
instance.terminate()
# Validate auto-created volume and snapshot
volumes = conn.get_all_volumes()
volumes.should.have.length_of(1)
volume = volumes[0]
snapshots = conn.get_all_snapshots()
snapshots.should.have.length_of(1)
snapshot = snapshots[0]
image.block_device_mapping.current_value.snapshot_id.should.equal(snapshot.id)
snapshot.description.should.equal("Auto-created snapshot for AMI {0}".format(image.id))
snapshot.volume_id.should.equal(volume.id)
# Deregister
with assert_raises(JSONResponseError) as ex:
success = conn.deregister_image(image_id, dry_run=True)
ex.exception.reason.should.equal('DryRunOperation')
ex.exception.status.should.equal(400)
ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the DeregisterImage operation: Request would have succeeded, but DryRun flag is set')
success = conn.deregister_image(image_id)
success.should.be.true
with assert_raises(EC2ResponseError) as cm:
conn.deregister_image(image_id)
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
2013-03-11 04:12:22 +00:00
@requires_boto_gte("2.14.0")
2014-09-29 19:06:36 +00:00
@mock_ec2
def test_ami_copy():
conn = boto.ec2.connect_to_region("us-west-1")
2014-09-29 19:06:36 +00:00
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
source_image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
instance.terminate()
2014-09-29 19:06:36 +00:00
source_image = conn.get_all_images(image_ids=[source_image_id])[0]
# Boto returns a 'CopyImage' object with an image_id attribute here. Use the image_id to fetch the full info.
with assert_raises(JSONResponseError) as ex:
copy_image_ref = conn.copy_image(source_image.region.name, source_image.id, "test-copy-ami", "this is a test copy ami", dry_run=True)
ex.exception.reason.should.equal('DryRunOperation')
ex.exception.status.should.equal(400)
ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CopyImage operation: Request would have succeeded, but DryRun flag is set')
2014-09-29 19:06:36 +00:00
copy_image_ref = conn.copy_image(source_image.region.name, source_image.id, "test-copy-ami", "this is a test copy ami")
copy_image_id = copy_image_ref.image_id
copy_image = conn.get_all_images(image_ids=[copy_image_id])[0]
copy_image.id.should.equal(copy_image_id)
copy_image.virtualization_type.should.equal(source_image.virtualization_type)
copy_image.architecture.should.equal(source_image.architecture)
copy_image.kernel_id.should.equal(source_image.kernel_id)
copy_image.platform.should.equal(source_image.platform)
# Validate auto-created volume and snapshot
conn.get_all_volumes().should.have.length_of(2)
conn.get_all_snapshots().should.have.length_of(2)
copy_image.block_device_mapping.current_value.snapshot_id.should_not.equal(
source_image.block_device_mapping.current_value.snapshot_id)
# Copy from non-existent source ID.
with assert_raises(EC2ResponseError) as cm:
conn.copy_image(source_image.region.name, 'ami-abcd1234', "test-copy-ami", "this is a test copy ami")
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
# Copy from non-existent source region.
with assert_raises(EC2ResponseError) as cm:
invalid_region = 'us-east-1' if (source_image.region.name != 'us-east-1') else 'us-west-1'
conn.copy_image(invalid_region, source_image.id, "test-copy-ami", "this is a test copy ami")
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_ami_tagging():
conn = boto.connect_vpc('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
conn.create_image(instance.id, "test-ami", "this is a test ami")
image = conn.get_all_images()[0]
with assert_raises(JSONResponseError) as ex:
image.add_tag("a key", "some value", dry_run=True)
ex.exception.reason.should.equal('DryRunOperation')
ex.exception.status.should.equal(400)
ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set')
image.add_tag("a key", "some value")
tag = conn.get_all_tags()[0]
tag.name.should.equal("a key")
tag.value.should.equal("some value")
# Refresh the DHCP options
image = conn.get_all_images()[0]
image.tags.should.have.length_of(1)
image.tags["a key"].should.equal("some value")
@mock_ec2
def test_ami_create_from_missing_instance():
conn = boto.connect_ec2('the_key', 'the_secret')
2013-03-05 13:14:43 +00:00
args = ["i-abcdefg", "test-ami", "this is a test ami"]
with assert_raises(EC2ResponseError) as cm:
conn.create_image(*args)
cm.exception.code.should.equal('InvalidInstanceID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_ami_pulls_attributes_from_instance():
conn = boto.connect_ec2('the_key', 'the_secret')
2013-03-05 13:14:43 +00:00
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
instance.modify_attribute("kernel", "test-kernel")
2013-02-28 05:08:35 +00:00
image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
image = conn.get_image(image_id)
2013-03-05 13:35:18 +00:00
image.kernel_id.should.equal('test-kernel')
@mock_ec2
def test_ami_filters():
conn = boto.connect_ec2('the_key', 'the_secret')
reservationA = conn.run_instances('ami-1234abcd')
instanceA = reservationA.instances[0]
instanceA.modify_attribute("architecture", "i386")
instanceA.modify_attribute("kernel", "k-1234abcd")
instanceA.modify_attribute("platform", "windows")
instanceA.modify_attribute("virtualization_type", "hvm")
imageA_id = conn.create_image(instanceA.id, "test-ami-A", "this is a test ami")
imageA = conn.get_image(imageA_id)
reservationB = conn.run_instances('ami-abcd1234')
instanceB = reservationB.instances[0]
instanceB.modify_attribute("architecture", "x86_64")
instanceB.modify_attribute("kernel", "k-abcd1234")
instanceB.modify_attribute("platform", "linux")
instanceB.modify_attribute("virtualization_type", "paravirtual")
imageB_id = conn.create_image(instanceB.id, "test-ami-B", "this is a test ami")
imageB = conn.get_image(imageB_id)
imageB.set_launch_permissions(group_names=("all"))
amis_by_architecture = conn.get_all_images(filters={'architecture': 'x86_64'})
set([ami.id for ami in amis_by_architecture]).should.equal(set([imageB.id]))
amis_by_kernel = conn.get_all_images(filters={'kernel-id': 'k-abcd1234'})
set([ami.id for ami in amis_by_kernel]).should.equal(set([imageB.id]))
amis_by_virtualization = conn.get_all_images(filters={'virtualization-type': 'paravirtual'})
set([ami.id for ami in amis_by_virtualization]).should.equal(set([imageB.id]))
amis_by_platform = conn.get_all_images(filters={'platform': 'windows'})
set([ami.id for ami in amis_by_platform]).should.equal(set([imageA.id]))
amis_by_id = conn.get_all_images(filters={'image-id': imageA.id})
set([ami.id for ami in amis_by_id]).should.equal(set([imageA.id]))
amis_by_state = conn.get_all_images(filters={'state': 'available'})
set([ami.id for ami in amis_by_state]).should.equal(set([imageA.id, imageB.id]))
amis_by_name = conn.get_all_images(filters={'name': imageA.name})
set([ami.id for ami in amis_by_name]).should.equal(set([imageA.id]))
amis_by_public = conn.get_all_images(filters={'is-public': True})
set([ami.id for ami in amis_by_public]).should.equal(set([imageB.id]))
amis_by_nonpublic = conn.get_all_images(filters={'is-public': False})
set([ami.id for ami in amis_by_nonpublic]).should.equal(set([imageA.id]))
@mock_ec2
def test_ami_filtering_via_tag():
conn = boto.connect_vpc('the_key', 'the_secret')
reservationA = conn.run_instances('ami-1234abcd')
instanceA = reservationA.instances[0]
imageA_id = conn.create_image(instanceA.id, "test-ami-A", "this is a test ami")
imageA = conn.get_image(imageA_id)
imageA.add_tag("a key", "some value")
reservationB = conn.run_instances('ami-abcd1234')
instanceB = reservationB.instances[0]
imageB_id = conn.create_image(instanceB.id, "test-ami-B", "this is a test ami")
imageB = conn.get_image(imageB_id)
imageB.add_tag("another key", "some other value")
amis_by_tagA = conn.get_all_images(filters={'tag:a key': 'some value'})
set([ami.id for ami in amis_by_tagA]).should.equal(set([imageA.id]))
amis_by_tagB = conn.get_all_images(filters={'tag:another key': 'some other value'})
set([ami.id for ami in amis_by_tagB]).should.equal(set([imageB.id]))
@mock_ec2
def test_getting_missing_ami():
conn = boto.connect_ec2('the_key', 'the_secret')
with assert_raises(EC2ResponseError) as cm:
conn.get_image('ami-missing')
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_getting_malformed_ami():
conn = boto.connect_ec2('the_key', 'the_secret')
with assert_raises(EC2ResponseError) as cm:
conn.get_image('foo-missing')
cm.exception.code.should.equal('InvalidAMIID.Malformed')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
@mock_ec2
def test_ami_attribute_group_permissions():
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
image = conn.get_image(image_id)
# Baseline
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.name.should.equal('launch_permission')
attributes.attrs.should.have.length_of(0)
ADD_GROUP_ARGS = {'image_id': image.id,
'attribute': 'launchPermission',
'operation': 'add',
'groups': 'all'}
REMOVE_GROUP_ARGS = {'image_id': image.id,
'attribute': 'launchPermission',
'operation': 'remove',
'groups': 'all'}
# Add 'all' group and confirm
with assert_raises(JSONResponseError) as ex:
conn.modify_image_attribute(**dict(ADD_GROUP_ARGS, **{'dry_run': True}))
ex.exception.reason.should.equal('DryRunOperation')
ex.exception.status.should.equal(400)
ex.exception.message.should.equal('An error occurred (DryRunOperation) when calling the ModifyImageAttribute operation: Request would have succeeded, but DryRun flag is set')
conn.modify_image_attribute(**ADD_GROUP_ARGS)
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs['groups'].should.have.length_of(1)
attributes.attrs['groups'].should.equal(['all'])
image = conn.get_image(image_id)
image.is_public.should.equal(True)
# Add is idempotent
conn.modify_image_attribute.when.called_with(**ADD_GROUP_ARGS).should_not.throw(EC2ResponseError)
# Remove 'all' group and confirm
conn.modify_image_attribute(**REMOVE_GROUP_ARGS)
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs.should.have.length_of(0)
image = conn.get_image(image_id)
image.is_public.should.equal(False)
# Remove is idempotent
conn.modify_image_attribute.when.called_with(**REMOVE_GROUP_ARGS).should_not.throw(EC2ResponseError)
@mock_ec2
def test_ami_attribute_user_permissions():
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
image = conn.get_image(image_id)
# Baseline
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.name.should.equal('launch_permission')
attributes.attrs.should.have.length_of(0)
# Both str and int values should work.
USER1 = '123456789011'
USER2 = 123456789022
ADD_USERS_ARGS = {'image_id': image.id,
'attribute': 'launchPermission',
'operation': 'add',
'user_ids': [USER1, USER2]}
REMOVE_USERS_ARGS = {'image_id': image.id,
'attribute': 'launchPermission',
'operation': 'remove',
'user_ids': [USER1, USER2]}
REMOVE_SINGLE_USER_ARGS = {'image_id': image.id,
'attribute': 'launchPermission',
'operation': 'remove',
'user_ids': [USER1]}
# Add multiple users and confirm
conn.modify_image_attribute(**ADD_USERS_ARGS)
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs['user_ids'].should.have.length_of(2)
set(attributes.attrs['user_ids']).should.equal(set([str(USER1), str(USER2)]))
image = conn.get_image(image_id)
image.is_public.should.equal(False)
# Add is idempotent
conn.modify_image_attribute.when.called_with(**ADD_USERS_ARGS).should_not.throw(EC2ResponseError)
# Remove single user and confirm
conn.modify_image_attribute(**REMOVE_SINGLE_USER_ARGS)
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs['user_ids'].should.have.length_of(1)
set(attributes.attrs['user_ids']).should.equal(set([str(USER2)]))
image = conn.get_image(image_id)
image.is_public.should.equal(False)
# Remove multiple users and confirm
conn.modify_image_attribute(**REMOVE_USERS_ARGS)
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs.should.have.length_of(0)
image = conn.get_image(image_id)
image.is_public.should.equal(False)
# Remove is idempotent
conn.modify_image_attribute.when.called_with(**REMOVE_USERS_ARGS).should_not.throw(EC2ResponseError)
@mock_ec2
def test_ami_attribute_user_and_group_permissions():
"""
Boto supports adding/removing both users and groups at the same time.
Just spot-check this -- input variations, idempotency, etc are validated
via user-specific and group-specific tests above.
"""
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
image = conn.get_image(image_id)
# Baseline
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.name.should.equal('launch_permission')
attributes.attrs.should.have.length_of(0)
USER1 = '123456789011'
USER2 = '123456789022'
ADD_ARGS = {'image_id': image.id,
'attribute': 'launchPermission',
'operation': 'add',
'groups': ['all'],
'user_ids': [USER1, USER2]}
REMOVE_ARGS = {'image_id': image.id,
'attribute': 'launchPermission',
'operation': 'remove',
'groups': ['all'],
'user_ids': [USER1, USER2]}
# Add and confirm
conn.modify_image_attribute(**ADD_ARGS)
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs['user_ids'].should.have.length_of(2)
set(attributes.attrs['user_ids']).should.equal(set([USER1, USER2]))
set(attributes.attrs['groups']).should.equal(set(['all']))
image = conn.get_image(image_id)
image.is_public.should.equal(True)
# Remove and confirm
conn.modify_image_attribute(**REMOVE_ARGS)
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs.should.have.length_of(0)
image = conn.get_image(image_id)
image.is_public.should.equal(False)
@mock_ec2
def test_ami_attribute_error_cases():
conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd')
instance = reservation.instances[0]
image_id = conn.create_image(instance.id, "test-ami", "this is a test ami")
image = conn.get_image(image_id)
# Error: Add with group != 'all'
with assert_raises(EC2ResponseError) as cm:
conn.modify_image_attribute(image.id,
attribute='launchPermission',
operation='add',
groups='everyone')
cm.exception.code.should.equal('InvalidAMIAttributeItemValue')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
# Error: Add with user ID that isn't an integer.
with assert_raises(EC2ResponseError) as cm:
conn.modify_image_attribute(image.id,
attribute='launchPermission',
operation='add',
user_ids='12345678901A')
cm.exception.code.should.equal('InvalidAMIAttributeItemValue')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
# Error: Add with user ID that is > length 12.
with assert_raises(EC2ResponseError) as cm:
conn.modify_image_attribute(image.id,
attribute='launchPermission',
operation='add',
user_ids='1234567890123')
cm.exception.code.should.equal('InvalidAMIAttributeItemValue')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
# Error: Add with user ID that is < length 12.
with assert_raises(EC2ResponseError) as cm:
conn.modify_image_attribute(image.id,
attribute='launchPermission',
operation='add',
user_ids='12345678901')
cm.exception.code.should.equal('InvalidAMIAttributeItemValue')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
# Error: Add with one invalid user ID among other valid IDs, ensure no partial changes.
with assert_raises(EC2ResponseError) as cm:
conn.modify_image_attribute(image.id,
attribute='launchPermission',
operation='add',
user_ids=['123456789011', 'foo', '123456789022'])
cm.exception.code.should.equal('InvalidAMIAttributeItemValue')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
attributes = conn.get_image_attribute(image.id, attribute='launchPermission')
attributes.attrs.should.have.length_of(0)
# Error: Add with invalid image ID
with assert_raises(EC2ResponseError) as cm:
conn.modify_image_attribute("ami-abcd1234",
attribute='launchPermission',
operation='add',
groups='all')
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none
# Error: Remove with invalid image ID
with assert_raises(EC2ResponseError) as cm:
conn.modify_image_attribute("ami-abcd1234",
attribute='launchPermission',
operation='remove',
groups='all')
cm.exception.code.should.equal('InvalidAMIID.NotFound')
cm.exception.status.should.equal(400)
cm.exception.request_id.should_not.be.none