diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py index fd237e2e4..ac6492bf5 100644 --- a/moto/ec2/responses/elastic_block_store.py +++ b/moto/ec2/responses/elastic_block_store.py @@ -268,7 +268,7 @@ CREATE_SNAPSHOT_RESPONSE = """ length 12. + with pytest.raises(ClientError) as ex: + image.modify_attribute( + ImageId=image_id, + Attribute="launchPermission", + OperationType="add", + UserIds=["1234567890123"], + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAMIAttributeItemValue") + + # Error: Add with user ID that is < length 12. + with pytest.raises(ClientError) as ex: + image.modify_attribute( + ImageId=image_id, + Attribute="launchPermission", + OperationType="add", + UserIds=["12345678901"], + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAMIAttributeItemValue") + + # Error: Add with one invalid user ID among other valid IDs, ensure no + # partial changes. + with pytest.raises(ClientError) as ex: + image.modify_attribute( + ImageId=image_id, + Attribute="launchPermission", + OperationType="add", + UserIds=["123456789011", "foo", "123456789022"], + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAMIAttributeItemValue") + + permissions = image.describe_attribute(Attribute="launchPermission")[ + "LaunchPermissions" + ] + permissions.should.equal([]) + + # Error: Add with invalid image ID + with pytest.raises(ClientError) as ex: + image.modify_attribute( + ImageId="ami-abcd1234", + Attribute="launchPermission", + OperationType="add", + UserGroups=["all"], + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAMIID.NotFound") + + # Error: Remove with invalid image ID + with pytest.raises(ClientError) as ex: + image.modify_attribute( + ImageId="ami-abcd1234", + Attribute="launchPermission", + OperationType="remove", + UserGroups=["all"], + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAMIID.NotFound") + + @mock_ec2 def test_ami_describe_non_existent(): ec2 = boto3.resource("ec2", region_name="us-west-1") diff --git a/tests/test_ec2/test_availability_zones_and_regions.py b/tests/test_ec2/test_availability_zones_and_regions.py index 830d4c2bf..32d99098b 100644 --- a/tests/test_ec2/test_availability_zones_and_regions.py +++ b/tests/test_ec2/test_availability_zones_and_regions.py @@ -7,6 +7,7 @@ import sure # noqa from moto import mock_ec2, mock_ec2_deprecated +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_regions(): conn = boto.connect_ec2("the_key", "the_secret") @@ -16,6 +17,7 @@ def test_describe_regions(): region.endpoint.should.contain(region.name) +# Has boto3 equivalent @mock_ec2_deprecated def test_availability_zones(): conn = boto.connect_ec2("the_key", "the_secret") diff --git a/tests/test_ec2/test_customer_gateways.py b/tests/test_ec2/test_customer_gateways.py index 14d091ad0..dcaa95ad8 100644 --- a/tests/test_ec2/test_customer_gateways.py +++ b/tests/test_ec2/test_customer_gateways.py @@ -1,12 +1,15 @@ from __future__ import unicode_literals import boto +import boto3 import sure # noqa import pytest from boto.exception import EC2ResponseError +from botocore.exceptions import ClientError -from moto import mock_ec2_deprecated +from moto import mock_ec2_deprecated, mock_ec2 +# Has boto3 equivalent @mock_ec2_deprecated def test_create_customer_gateways(): conn = boto.connect_vpc("the_key", "the_secret") @@ -19,6 +22,18 @@ def test_create_customer_gateways(): customer_gateway.ip_address.should.equal("205.251.242.54") +@mock_ec2 +def test_create_customer_gateways_boto3(): + ec2 = boto3.client("ec2", region_name="us-east-1") + + customer_gateway = create_customer_gateway(ec2) + customer_gateway.should.have.key("CustomerGatewayId").match(r"cgw-\w+") + customer_gateway.should.have.key("Type").equal("ipsec.1") + customer_gateway.should.have.key("BgpAsn").equal("65534") + customer_gateway.should.have.key("IpAddress").equal("205.251.242.54") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_customer_gateways(): conn = boto.connect_vpc("the_key", "the_secret") @@ -28,6 +43,17 @@ def test_describe_customer_gateways(): cgws[0].id.should.match(customer_gateway.id) +@mock_ec2 +def test_describe_customer_gateways_boto3(): + ec2 = boto3.client("ec2", region_name="us-east-1") + + customer_gateway = create_customer_gateway(ec2) + cgws = ec2.describe_customer_gateways()["CustomerGateways"] + cgws.should.have.length_of(1) + cgws[0]["CustomerGatewayId"].should.match(customer_gateway["CustomerGatewayId"]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_customer_gateways(): conn = boto.connect_vpc("the_key", "the_secret") @@ -42,8 +68,40 @@ def test_delete_customer_gateways(): cgws.should.have.length_of(1) +@mock_ec2 +def test_delete_customer_gateways_boto3(): + ec2 = boto3.client("ec2", region_name="us-east-1") + + customer_gateway = create_customer_gateway(ec2) + + cgws = ec2.describe_customer_gateways()["CustomerGateways"] + cgws.should.have.length_of(1) + + ec2.delete_customer_gateway(CustomerGatewayId=customer_gateway["CustomerGatewayId"]) + cgws = ec2.describe_customer_gateways()["CustomerGateways"] + cgws.should.have.length_of(1) + cgws[0].should.have.key("State").equal("deleted") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_customer_gateways_bad_id(): conn = boto.connect_vpc("the_key", "the_secret") with pytest.raises(EC2ResponseError) as cm: conn.delete_customer_gateway("cgw-0123abcd") + + +@mock_ec2 +def test_delete_customer_gateways_bad_id_boto3(): + ec2 = boto3.client("ec2", region_name="us-east-1") + with pytest.raises(ClientError) as ex: + ec2.delete_customer_gateway(CustomerGatewayId="cgw-0123abcd") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidCustomerGatewayID.NotFound") + + +def create_customer_gateway(ec2): + return ec2.create_customer_gateway( + Type="ipsec.1", PublicIp="205.251.242.54", BgpAsn=65534 + )["CustomerGateway"] diff --git a/tests/test_ec2/test_dhcp_options.py b/tests/test_ec2/test_dhcp_options.py index 8a44f6905..96ffed11d 100644 --- a/tests/test_ec2/test_dhcp_options.py +++ b/tests/test_ec2/test_dhcp_options.py @@ -5,15 +5,18 @@ import pytest import boto3 import boto from boto.exception import EC2ResponseError +from botocore.exceptions import ClientError import sure # noqa -from moto import mock_ec2, mock_ec2_deprecated +from moto import mock_ec2, mock_ec2_deprecated, settings +from unittest import SkipTest SAMPLE_DOMAIN_NAME = "example.com" SAMPLE_NAME_SERVERS = ["10.0.0.6", "10.0.0.7"] +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_options_associate(): """associate dhcp option""" @@ -25,6 +28,27 @@ def test_dhcp_options_associate(): rval.should.be.equal(True) +@mock_ec2 +def test_dhcp_options_associate_boto3(): + """ associate dhcp option """ + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + client.associate_dhcp_options(DhcpOptionsId=dhcp_options.id, VpcId=vpc.id) + # + vpc.reload() + vpc.dhcp_options_id.should.equal(dhcp_options.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_options_associate_invalid_dhcp_id(): """associate dhcp option bad dhcp options id""" @@ -38,6 +62,21 @@ def test_dhcp_options_associate_invalid_dhcp_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_dhcp_options_associate_invalid_dhcp_id_boto3(): + """ associate dhcp option bad dhcp options id """ + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + with pytest.raises(ClientError) as ex: + client.associate_dhcp_options(DhcpOptionsId="foo", VpcId=vpc.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidDhcpOptionID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_options_associate_invalid_vpc_id(): """associate dhcp option invalid vpc id""" @@ -51,6 +90,26 @@ def test_dhcp_options_associate_invalid_vpc_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_dhcp_options_associate_invalid_vpc_id_boto3(): + """ associate dhcp option invalid vpc id """ + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + + with pytest.raises(ClientError) as ex: + client.associate_dhcp_options(DhcpOptionsId=dhcp_options.id, VpcId="foo") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidVpcID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_options_delete_with_vpc(): """Test deletion of dhcp options with vpc""" @@ -77,6 +136,37 @@ def test_dhcp_options_delete_with_vpc(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_dhcp_options_delete_with_vpc_boto3(): + """Test deletion of dhcp options with vpc""" + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + client.associate_dhcp_options(DhcpOptionsId=dhcp_options.id, VpcId=vpc.id) + + with pytest.raises(ClientError) as ex: + client.delete_dhcp_options(DhcpOptionsId=dhcp_options.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("DependencyViolation") + + vpc.delete() + + with pytest.raises(ClientError) as ex: + client.describe_dhcp_options(DhcpOptionsIds=[dhcp_options.id]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidDhcpOptionID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_dhcp_options(): """Create most basic dhcp option""" @@ -92,6 +182,31 @@ def test_create_dhcp_options(): ) +@mock_ec2 +def test_create_dhcp_options_boto3(): + """Create most basic dhcp option""" + ec2 = boto3.resource("ec2", region_name="us-west-1") + + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + config = dhcp_options.dhcp_configurations + config.should.have.length_of(2) + config.should.contain( + { + "Key": "domain-name-servers", + "Values": [{"Value": ip} for ip in SAMPLE_NAME_SERVERS], + } + ) + config.should.contain( + {"Key": "domain-name", "Values": [{"Value": SAMPLE_DOMAIN_NAME}]} + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_dhcp_options_invalid_options(): """Create invalid dhcp options""" @@ -111,6 +226,30 @@ def test_create_dhcp_options_invalid_options(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_create_dhcp_options_invalid_options_boto3(): + """Create invalid dhcp options""" + ec2 = boto3.resource("ec2", region_name="us-west-1") + servers = ["f", "f", "f", "f", "f"] + + with pytest.raises(ClientError) as ex: + ec2.create_dhcp_options( + DhcpConfigurations=[{"Key": "ntp-servers", "Values": servers}] + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue") + + with pytest.raises(ClientError) as ex: + ec2.create_dhcp_options( + DhcpConfigurations=[{"Key": "netbios-node-type", "Values": ["0"]}] + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_dhcp_options(): """Test dhcp options lookup by id""" @@ -124,6 +263,43 @@ def test_describe_dhcp_options(): dhcp_options.should.be.length_of(1) +@mock_ec2 +def test_describe_dhcp_options_boto3(): + """Test dhcp options lookup by id""" + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + all_options = client.describe_dhcp_options()["DhcpOptions"] + all_options.should.have.length_of(0) + + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + all_options = client.describe_dhcp_options(DhcpOptionsIds=[dhcp_options.id])[ + "DhcpOptions" + ] + all_options.should.have.length_of(1) + + all_options = client.describe_dhcp_options()["DhcpOptions"] + all_options.should.have.length_of(1) + all_options[0]["DhcpOptionsId"].should.equal(dhcp_options.id) + config = all_options[0]["DhcpConfigurations"] + config.should.have.length_of(2) + config.should.contain( + { + "Key": "domain-name-servers", + "Values": [{"Value": ip} for ip in SAMPLE_NAME_SERVERS], + } + ) + config.should.contain( + {"Key": "domain-name", "Values": [{"Value": SAMPLE_DOMAIN_NAME}]} + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_dhcp_options_invalid_id(): """get error on invalid dhcp_option_id lookup""" @@ -136,6 +312,19 @@ def test_describe_dhcp_options_invalid_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_describe_dhcp_options_invalid_id_boto3(): + """get error on invalid dhcp_option_id lookup""" + client = boto3.client("ec2", region_name="us-west-1") + + with pytest.raises(ClientError) as ex: + client.describe_dhcp_options(DhcpOptionsIds=["1"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidDhcpOptionID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_dhcp_options(): """delete dhcp option""" @@ -154,6 +343,29 @@ def test_delete_dhcp_options(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_delete_dhcp_options_boto3(): + """delete dhcp option""" + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + dhcp_option = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + + client.delete_dhcp_options(DhcpOptionsId=dhcp_option.id) + + with pytest.raises(ClientError) as ex: + client.describe_dhcp_options(DhcpOptionsIds=[dhcp_option.id]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidDhcpOptionID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_dhcp_options_invalid_id(): conn = boto.connect_vpc("the_key", "the_secret") @@ -167,6 +379,18 @@ def test_delete_dhcp_options_invalid_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_delete_dhcp_options_invalid_id_boto3(): + client = boto3.client("ec2", region_name="us-west-1") + + with pytest.raises(ClientError) as ex: + client.delete_dhcp_options(DhcpOptionsId="dopt-abcd1234") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidDhcpOptionID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_dhcp_options_malformed_id(): conn = boto.connect_vpc("the_key", "the_secret") @@ -180,6 +404,18 @@ def test_delete_dhcp_options_malformed_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_delete_dhcp_options_malformed_id_boto3(): + client = boto3.client("ec2", region_name="us-west-1") + + with pytest.raises(ClientError) as ex: + client.delete_dhcp_options(DhcpOptionsId="foo-abcd1234") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidDhcpOptionsId.Malformed") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_tagging(): conn = boto.connect_vpc("the_key", "the_secret") @@ -197,6 +433,32 @@ def test_dhcp_tagging(): dhcp_option.tags["a key"].should.equal("some value") +@mock_ec2 +def test_dhcp_tagging_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + dhcp_option = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + + dhcp_option.create_tags(Tags=[{"Key": "a tag", "Value": "some value"}]) + + tag = client.describe_tags()["Tags"][0] + tag.should.have.key("ResourceId").equal(dhcp_option.id) + tag.should.have.key("ResourceType").equal("dhcp-options") + tag.should.have.key("Key").equal("a tag") + tag.should.have.key("Value").equal("some value") + + # Refresh the DHCP options + dhcp_option = client.describe_dhcp_options()["DhcpOptions"][0] + dhcp_option["Tags"].should.equal([{"Key": "a tag", "Value": "some value"}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_options_get_by_tag(): conn = boto.connect_vpc("the_key", "the_secret") @@ -233,6 +495,83 @@ def test_dhcp_options_get_by_tag(): dhcp_options_sets.should.have.length_of(2) +@mock_ec2 +def test_dhcp_options_get_by_tag_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + dhcp1 = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": ["example.com"]}, + {"Key": "domain-name-servers", "Values": ["10.0.10.2"]}, + ] + ) + dhcp1.create_tags( + Tags=[ + {"Key": "Name", "Value": "TestDhcpOptions1"}, + {"Key": "test-tag", "Value": "test-value"}, + ] + ) + + dhcp2 = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": ["example.com"]}, + {"Key": "domain-name-servers", "Values": ["10.0.20.2"]}, + ] + ) + dhcp2.create_tags( + Tags=[ + {"Key": "Name", "Value": "TestDhcpOptions2"}, + {"Key": "test-tag", "Value": "test-value"}, + ] + ) + + dhcp_options_sets = client.describe_dhcp_options( + Filters=[ + {"Name": "tag:Name", "Values": ["TestDhcpOptions1"]}, + {"Name": "tag:test-tag", "Values": ["test-value"]}, + ] + )["DhcpOptions"] + + dhcp_options_sets.should.have.length_of(1) + config = dhcp_options_sets[0]["DhcpConfigurations"] + config.should.have.length_of(2) + config.should.contain({"Key": "domain-name", "Values": [{"Value": "example.com"}]}) + config.should.contain( + {"Key": "domain-name-servers", "Values": [{"Value": "10.0.10.2"}]} + ) + tags = dhcp_options_sets[0]["Tags"] + tags.should.have.length_of(2) + tags.should.contain({"Key": "Name", "Value": "TestDhcpOptions1"}) + tags.should.contain({"Key": "test-tag", "Value": "test-value"}) + + dhcp_options_sets = client.describe_dhcp_options( + Filters=[ + {"Name": "tag:Name", "Values": ["TestDhcpOptions2"]}, + {"Name": "tag:test-tag", "Values": ["test-value"]}, + ] + )["DhcpOptions"] + + dhcp_options_sets.should.have.length_of(1) + config = dhcp_options_sets[0]["DhcpConfigurations"] + config.should.have.length_of(2) + config.should.contain({"Key": "domain-name", "Values": [{"Value": "example.com"}]}) + config.should.contain( + {"Key": "domain-name-servers", "Values": [{"Value": "10.0.20.2"}]} + ) + tags = dhcp_options_sets[0]["Tags"] + tags.should.have.length_of(2) + tags.should.contain({"Key": "Name", "Value": "TestDhcpOptions2"}) + tags.should.contain({"Key": "test-tag", "Value": "test-value"}) + + dhcp_options_sets = client.describe_dhcp_options( + Filters=[{"Name": "tag:test-tag", "Values": ["test-value"]}] + )["DhcpOptions"] + + dhcp_options_sets.should.have.length_of(2) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_options_get_by_id(): conn = boto.connect_vpc("the_key", "the_secret") @@ -263,6 +602,47 @@ def test_dhcp_options_get_by_id(): dhcp_options_sets[0].options["domain-name-servers"][0].should.be.equal("10.0.20.2") +@mock_ec2 +def test_dhcp_options_get_by_id_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + dhcp1 = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": ["test1.com"]}, + {"Key": "domain-name-servers", "Values": ["10.0.10.2"]}, + ] + ) + dhcp1.create_tags(Tags=[{"Key": "Name", "Value": "TestDhcpOptions1"}]) + dhcp1.create_tags(Tags=[{"Key": "test-tag", "Value": "test-value"}]) + + dhcp2 = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": ["test2.com"]}, + {"Key": "domain-name-servers", "Values": ["10.0.20.2"]}, + ] + ) + dhcp1.create_tags(Tags=[{"Key": "Name", "Value": "TestDhcpOptions2"}]) + dhcp1.create_tags(Tags=[{"Key": "test-tag", "Value": "test-value"}]) + + d = client.describe_dhcp_options()["DhcpOptions"] + d.should.have.length_of(2) + + d = client.describe_dhcp_options( + Filters=[{"Name": "dhcp-options-id", "Values": [dhcp1.id]}] + )["DhcpOptions"] + + d.should.have.length_of(1) + d[0].should.have.key("DhcpOptionsId").equal(dhcp1.id) + + d = client.describe_dhcp_options( + Filters=[{"Name": "dhcp-options-id", "Values": [dhcp2.id]}] + )["DhcpOptions"] + + d.should.have.length_of(1) + d[0].should.have.key("DhcpOptionsId").equal(dhcp2.id) + + @mock_ec2 def test_dhcp_options_get_by_value_filter(): ec2 = boto3.resource("ec2", region_name="us-west-1") @@ -323,6 +703,7 @@ def test_dhcp_options_get_by_key_filter(): dhcp_options_sets.should.have.length_of(3) +# Has boto3 equivalent @mock_ec2_deprecated def test_dhcp_options_get_by_invalid_filter(): conn = boto.connect_vpc("the_key", "the_secret") @@ -333,3 +714,24 @@ def test_dhcp_options_get_by_invalid_filter(): conn.get_all_dhcp_options.when.called_with(filters=filters).should.throw( NotImplementedError ) + + +@mock_ec2 +def test_dhcp_options_get_by_invalid_filter_boto3(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Will throw a generic 500 in ServerMode") + ec2 = boto3.resource("ec2", region_name="us-west-1") + + client = boto3.client("ec2", region_name="us-west-1") + + ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + + filters = [{"Name": "invalid-filter", "Values": ["n/a"]}] + client.describe_dhcp_options.when.called_with(Filters=filters).should.throw( + NotImplementedError + ) diff --git a/tests/test_ec2/test_elastic_block_store.py b/tests/test_ec2/test_elastic_block_store.py index 784b418d0..50a96f855 100644 --- a/tests/test_ec2/test_elastic_block_store.py +++ b/tests/test_ec2/test_elastic_block_store.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import boto +import boto.ec2 import boto3 import pytest @@ -14,6 +15,7 @@ from moto.kms import mock_kms from tests import EXAMPLE_AMI_ID +# Has boto3 equivalent @mock_ec2_deprecated def test_create_and_delete_volume(): conn = boto.ec2.connect_to_region("us-east-1") @@ -51,6 +53,43 @@ def test_create_and_delete_volume(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_create_and_delete_volume_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a") + + all_volumes = client.describe_volumes()["Volumes"] + + current_volume = [item for item in all_volumes if item["VolumeId"] == volume.id] + current_volume.should.have.length_of(1) + current_volume[0]["Size"].should.equal(80) + current_volume[0]["AvailabilityZone"].should.equal("us-east-1a") + current_volume[0]["Encrypted"].should.be(False) + + with pytest.raises(ClientError) as ex: + volume.delete(DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DeleteVolume operation: Request would have succeeded, but DryRun flag is set" + ) + + volume.delete() + + all_volumes = client.describe_volumes()["Volumes"] + my_volume = [item for item in all_volumes if item["VolumeId"] == volume.id] + my_volume.should.have.length_of(0) + + # Deleting something that was already deleted should throw an error + with pytest.raises(ClientError) as ex: + volume.delete() + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidVolume.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_attached_volume(): conn = boto.ec2.connect_to_region("us-east-1") @@ -90,6 +129,47 @@ def test_delete_attached_volume(): my_volume.should.have.length_of(0) +@mock_ec2 +def test_delete_attached_volume_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + + # create an instance + instance = reservation["Instances"][0] + # create a volume + volume = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a") + # attach volume to instance + volume.attach_to_instance(InstanceId=instance["InstanceId"], Device="/dev/sdh") + + volume.state.should.equal("in-use") + volume.attachments.should.have.length_of(1) + volume.attachments[0]["InstanceId"].should.equal(instance["InstanceId"]) + volume.attachments[0]["State"].should.equal("attached") + + # attempt to delete volume + # assert raises VolumeInUseError + with pytest.raises(ClientError) as ex: + volume.delete() + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Code"].should.equal("VolumeInUse") + ex.value.response["Error"]["Message"].should.equal( + "Volume {0} is currently attached to {1}".format( + volume.id, instance["InstanceId"] + ) + ) + + volume.detach_from_instance(InstanceId=instance["InstanceId"]) + + volume.state.should.equal("available") + + volume.delete() + + all_volumes = client.describe_volumes()["Volumes"] + [v["VolumeId"] for v in all_volumes].shouldnt.contain(volume.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_encrypted_volume_dryrun(): conn = boto.ec2.connect_to_region("us-east-1") @@ -102,6 +182,19 @@ def test_create_encrypted_volume_dryrun(): ) +@mock_ec2 +def test_create_encrypted_volume_dryrun_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + with pytest.raises(ClientError) as ex: + ec2.create_volume(Size=80, AvailabilityZone="us-east-1a", DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateVolume operation: Request would have succeeded, but DryRun flag is set" + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_encrypted_volume(): conn = boto.ec2.connect_to_region("us-east-1") @@ -119,6 +212,17 @@ def test_create_encrypted_volume(): all_volumes[0].encrypted.should.be(True) +@mock_ec2 +def test_create_encrypted_volume_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a", Encrypted=True) + + all_volumes = client.describe_volumes(VolumeIds=[volume.id])["Volumes"] + all_volumes[0]["Encrypted"].should.be(True) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_filter_volume_by_id(): conn = boto.ec2.connect_to_region("us-east-1") @@ -139,6 +243,29 @@ def test_filter_volume_by_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_filter_volume_by_id_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume1 = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a") + volume2 = ec2.create_volume(Size=36, AvailabilityZone="us-east-1b") + volume3 = ec2.create_volume(Size=20, AvailabilityZone="us-east-1c") + + vol3 = client.describe_volumes(VolumeIds=[volume3.id])["Volumes"] + vol3.should.have.length_of(1) + vol3[0]["Size"].should.equal(20) + vol3[0]["AvailabilityZone"].should.equal("us-east-1c") + + vol12 = client.describe_volumes(VolumeIds=[volume1.id, volume2.id])["Volumes"] + vol12.should.have.length_of(2) + + with pytest.raises(ClientError) as ex: + client.describe_volumes(VolumeIds=["vol-does_not_exist"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Code"].should.equal("InvalidVolume.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_volume_filters(): conn = boto.ec2.connect_to_region("us-east-1") @@ -247,6 +374,76 @@ def test_volume_filters(): ).should.equal({volume2.id}) +@mock_ec2 +def test_volume_filters_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = reservation["Instances"][0] + + volume1 = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a", Encrypted=True) + volume2 = ec2.create_volume(Size=36, AvailabilityZone="us-east-1b", Encrypted=False) + volume3 = ec2.create_volume(Size=20, AvailabilityZone="us-east-1c", Encrypted=True) + + snapshot = volume3.create_snapshot(Description="testsnap") + volume4 = ec2.create_volume( + Size=25, AvailabilityZone="us-east-1a", SnapshotId=snapshot.id + ) + + ec2.create_tags( + Resources=[volume1.id], Tags=[{"Key": "testkey1", "Value": "testvalue1"}] + ) + ec2.create_tags( + Resources=[volume2.id], Tags=[{"Key": "testkey2", "Value": "testvalue2"}] + ) + + volume1.reload() + volume2.reload() + volume3.reload() + volume4.reload() + + instance = ec2.Instance(instance["InstanceId"]) + instance.reload() + + block_mapping = [ + m for m in instance.block_device_mappings if m["DeviceName"] == "/dev/sda1" + ][0] + block_volume = block_mapping["Ebs"]["VolumeId"] + + def verify_filter(name, value, expected=None): + expected = expected or block_volume + expected = expected if type(expected) == list else [expected] + volumes = client.describe_volumes(Filters=[{"Name": name, "Values": [value]}])[ + "Volumes" + ] + set([vol["VolumeId"] for vol in volumes]).should.equal(set(expected)) + + # We should probably make this less strict, i.e. figure out which formats AWS expects/approves of + attach_time = block_mapping["Ebs"]["AttachTime"].strftime("%Y-%m-%dT%H:%M:%S.000Z") + verify_filter("attachment.attach-time", attach_time) + verify_filter("attachment.device", "/dev/sda1") + verify_filter("attachment.instance-id", instance.id) + verify_filter("attachment.status", "attached") + verify_filter("size", str(volume2.size), expected=volume2.id) + verify_filter("snapshot-id", snapshot.id, expected=volume4.id) + verify_filter("status", "in-use") + verify_filter("volume-id", volume1.id, expected=volume1.id) + verify_filter("tag-key", "testkey1", expected=volume1.id) + verify_filter("tag-value", "testvalue1", expected=volume1.id) + verify_filter("tag:testkey1", "testvalue1", expected=volume1.id) + verify_filter("encrypted", "false", expected=[block_volume, volume2.id]) + verify_filter("encrypted", "true", expected=[volume1.id, volume3.id, volume4.id]) + verify_filter("availability-zone", "us-east-1b", expected=volume2.id) + # + create_time = volume4.create_time.strftime("%Y-%m-%dT%H:%M:%S.000Z") + volumes_by_attach_device = client.describe_volumes( + Filters=[{"Name": "create-time", "Values": [create_time]}] + )["Volumes"] + [vol["VolumeId"] for vol in volumes_by_attach_device].should.contain(volume4.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_volume_attach_and_detach(): conn = boto.ec2.connect_to_region("us-east-1") @@ -305,6 +502,66 @@ def test_volume_attach_and_detach(): cm3.value.request_id.should_not.be.none +@mock_ec2 +def test_volume_attach_and_detach_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = reservation["Instances"][0] + volume = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a") + + volume.reload() + volume.state.should.equal("available") + + with pytest.raises(ClientError) as ex: + volume.attach_to_instance( + InstanceId=instance["InstanceId"], Device="/dev/sdh", DryRun=True + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the AttachVolume operation: Request would have succeeded, but DryRun flag is set" + ) + + volume.attach_to_instance(InstanceId=instance["InstanceId"], Device="/dev/sdh") + + volume.state.should.equal("in-use") + volume.attachments[0]["State"].should.equal("attached") + volume.attachments[0]["InstanceId"].should.equal(instance["InstanceId"]) + + with pytest.raises(ClientError) as ex: + volume.detach_from_instance(DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DetachVolume operation: Request would have succeeded, but DryRun flag is set" + ) + + volume.detach_from_instance(InstanceId=instance["InstanceId"]) + + volume.state.should.equal("available") + + with pytest.raises(ClientError) as ex1: + volume.attach_to_instance(InstanceId="i-1234abcd", Device="/dev/sdh") + ex1.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex1.value.response["Error"]["Code"].should.equal("InvalidInstanceID.NotFound") + + with pytest.raises(ClientError) as ex2: + client.detach_volume( + VolumeId=volume.id, InstanceId=instance["InstanceId"], Device="/dev/sdh" + ) + ex2.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex2.value.response["Error"]["Code"].should.equal("InvalidAttachment.NotFound") + + with pytest.raises(ClientError) as ex3: + client.detach_volume( + VolumeId=volume.id, InstanceId="i-1234abcd", Device="/dev/sdh" + ) + ex3.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex3.value.response["Error"]["Code"].should.equal("InvalidInstanceID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_snapshot(): conn = boto.ec2.connect_to_region("us-east-1") @@ -345,6 +602,51 @@ def test_create_snapshot(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_create_snapshot_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a") + + with pytest.raises(ClientError) as ex: + volume.create_snapshot(Description="a dryrun snapshot", DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateSnapshot operation: Request would have succeeded, but DryRun flag is set" + ) + + snapshot = volume.create_snapshot(Description="a test snapshot") + snapshot.reload() + snapshot.state.should.equal("completed") + + snapshots = [ + snap + for snap in client.describe_snapshots()["Snapshots"] + if snap["SnapshotId"] == snapshot.id + ] + snapshots.should.have.length_of(1) + snapshots[0]["Description"].should.equal("a test snapshot") + snapshots[0]["StartTime"].should_not.be.none + snapshots[0]["Encrypted"].should.be(False) + + # Create snapshot without description + num_snapshots = len(client.describe_snapshots()["Snapshots"]) + + snapshot = volume.create_snapshot() + client.describe_snapshots()["Snapshots"].should.have.length_of(num_snapshots + 1) + + snapshot.delete() + client.describe_snapshots()["Snapshots"].should.have.length_of(num_snapshots) + + # Deleting something that was already deleted should throw an error + with pytest.raises(ClientError) as ex: + snapshot.delete() + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Code"].should.equal("InvalidSnapshot.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_encrypted_snapshot(): conn = boto.ec2.connect_to_region("us-east-1") @@ -360,6 +662,31 @@ def test_create_encrypted_snapshot(): snapshots[0].encrypted.should.be(True) +@mock_ec2 +@pytest.mark.parametrize("encrypted", [True, False]) +def test_create_encrypted_snapshot_boto3(encrypted): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume = ec2.create_volume( + Size=80, AvailabilityZone="us-east-1a", Encrypted=encrypted + ) + snapshot = volume.create_snapshot(Description="a test snapshot") + snapshot.encrypted.should.be(encrypted) + snapshot.reload() + snapshot.state.should.equal("completed") + + snapshots = [ + snap + for snap in client.describe_snapshots()["Snapshots"] + if snap["SnapshotId"] == snapshot.id + ] + snapshots.should.have.length_of(1) + snapshots[0]["Description"].should.equal("a test snapshot") + snapshots[0]["StartTime"].should_not.be.none + snapshots[0]["Encrypted"].should.be(encrypted) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_filter_snapshot_by_id(): conn = boto.ec2.connect_to_region("us-east-1") @@ -387,6 +714,35 @@ def test_filter_snapshot_by_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_filter_snapshot_by_id_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume1 = ec2.create_volume(Size=36, AvailabilityZone="us-east-1a") + snap1 = volume1.create_snapshot(Description="a test snapshot 1") + volume2 = ec2.create_volume(Size=42, AvailabilityZone="us-east-1a") + snap2 = volume2.create_snapshot(Description="a test snapshot 2") + volume3 = ec2.create_volume(Size=84, AvailabilityZone="us-east-1a") + snap3 = volume3.create_snapshot(Description="a test snapshot 3") + snapshots1 = client.describe_snapshots(SnapshotIds=[snap1.id])["Snapshots"] + snapshots1.should.have.length_of(1) + snapshots1[0]["VolumeId"].should.equal(volume1.id) + snapshots2 = client.describe_snapshots(SnapshotIds=[snap2.id, snap3.id])[ + "Snapshots" + ] + snapshots2.should.have.length_of(2) + for s in snapshots2: + s["StartTime"].should_not.be.none + s["VolumeId"].should.be.within([volume2.id, volume3.id]) + + with pytest.raises(ClientError) as ex: + client.describe_snapshots(SnapshotIds=["snap-does_not_exist"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidSnapshot.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_snapshot_filters(): conn = boto.ec2.connect_to_region("us-east-1") @@ -451,6 +807,60 @@ def test_snapshot_filters(): ) +@mock_ec2 +def test_snapshot_filters_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume1 = ec2.create_volume(Size=20, AvailabilityZone="us-east-1a", Encrypted=False) + volume2 = ec2.create_volume(Size=25, AvailabilityZone="us-east-1a", Encrypted=True) + + snapshot1 = volume1.create_snapshot(Description="testsnapshot1") + snapshot2 = volume1.create_snapshot(Description="testsnapshot2") + snapshot3 = volume2.create_snapshot(Description="testsnapshot3") + + ec2.create_tags( + Resources=[snapshot1.id], Tags=[{"Key": "testkey1", "Value": "testvalue1"}] + ) + ec2.create_tags( + Resources=[snapshot2.id], Tags=[{"Key": "testkey2", "Value": "testvalue2"}] + ) + + def verify_filter(name, value, expected): + expected = expected if type(expected) == list else [expected] + snapshots = client.describe_snapshots( + Filters=[{"Name": name, "Values": [value]}] + )["Snapshots"] + set([s["SnapshotId"] for s in snapshots]).should.equal(set(expected)) + + verify_filter("description", "testsnapshot1", expected=snapshot1.id) + verify_filter("snapshot-id", snapshot1.id, expected=snapshot1.id) + verify_filter("volume-id", volume1.id, expected=[snapshot1.id, snapshot2.id]) + verify_filter( + "volume-size", str(volume1.size), expected=[snapshot1.id, snapshot2.id] + ) + verify_filter("tag-key", "testkey1", expected=snapshot1.id) + verify_filter("tag-value", "testvalue1", expected=snapshot1.id) + verify_filter("tag:testkey2", "testvalue2", expected=snapshot2.id) + verify_filter("encrypted", "true", expected=snapshot3.id) + verify_filter( + "owner-id", OWNER_ID, expected=[snapshot1.id, snapshot2.id, snapshot3.id] + ) + # + # We should probably make this less strict, i.e. figure out which formats AWS expects/approves of + start_time = snapshot1.start_time.strftime("%Y-%m-%dT%H:%M:%S.000Z") + snapshots = client.describe_snapshots( + Filters=[{"Name": "start-time", "Values": [start_time]}] + )["Snapshots"] + [s["SnapshotId"] for s in snapshots].should.contain(snapshot1.id) + snapshots = client.describe_snapshots( + Filters=[{"Name": "status", "Values": ["completed"]}] + )["Snapshots"] + [s["SnapshotId"] for s in snapshots].should.contain(snapshot1.id) + [s["SnapshotId"] for s in snapshots].should.contain(snapshot2.id) + [s["SnapshotId"] for s in snapshots].should.contain(snapshot3.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_snapshot_attribute(): import copy @@ -732,6 +1142,7 @@ def test_modify_snapshot_attribute(): assert len(attributes["CreateVolumePermissions"]) == 0 +# Has boto3 equivalent @mock_ec2_deprecated def test_create_volume_from_snapshot(): conn = boto.ec2.connect_to_region("us-east-1") @@ -755,6 +1166,7 @@ def test_create_volume_from_snapshot(): new_volume.snapshot_id.should.equal(snapshot.id) +# Has boto3 equivalent @mock_ec2_deprecated def test_create_volume_from_encrypted_snapshot(): conn = boto.ec2.connect_to_region("us-east-1") @@ -770,6 +1182,27 @@ def test_create_volume_from_encrypted_snapshot(): new_volume.encrypted.should.be(True) +@mock_ec2 +@pytest.mark.parametrize("encrypted", [True, False]) +def test_create_volume_from_snapshot_boto3(encrypted): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume = ec2.create_volume( + Size=80, AvailabilityZone="us-east-1a", Encrypted=encrypted + ) + snapshot = volume.create_snapshot(Description="a test snapshot") + snapshot.reload() + snapshot.state.should.equal("completed") + + new_volume = client.create_volume( + SnapshotId=snapshot.id, AvailabilityZone="us-east-1a" + ) + new_volume["Size"].should.equal(80) + new_volume["SnapshotId"].should.equal(snapshot.id) + new_volume["Encrypted"].should.equal(encrypted) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_modify_attribute_blockDeviceMapping(): """ @@ -801,6 +1234,46 @@ def test_modify_attribute_blockDeviceMapping(): instance.block_device_mapping["/dev/sda1"].delete_on_termination.should.be(True) +@mock_ec2 +def test_modify_attribute_blockDeviceMapping_boto3(): + """ + Reproduces the missing feature explained at [0], where we want to mock a + call to modify an instance attribute of type: blockDeviceMapping. + + [0] https://github.com/spulec/moto/issues/160 + """ + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = ec2.Instance(reservation["Instances"][0]["InstanceId"]) + + with pytest.raises(ClientError) as ex: + instance.modify_attribute( + BlockDeviceMappings=[ + {"DeviceName": "/dev/sda1", "Ebs": {"DeleteOnTermination": True}} + ], + DryRun=True, + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ModifyInstanceAttribute operation: Request would have succeeded, but DryRun flag is set" + ) + + instance.modify_attribute( + BlockDeviceMappings=[ + {"DeviceName": "/dev/sda1", "Ebs": {"DeleteOnTermination": True}} + ] + ) + + instance.reload() + mapping = instance.block_device_mappings[0] + mapping.should.have.key("DeviceName").equal("/dev/sda1") + mapping["Ebs"]["DeleteOnTermination"].should.be(True) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_volume_tag_escaping(): conn = boto.ec2.connect_to_region("us-east-1") @@ -823,6 +1296,29 @@ def test_volume_tag_escaping(): dict(snaps[0].tags).should.equal({"key": ""}) +@mock_ec2 +def test_volume_tag_escaping_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + volume = ec2.create_volume(Size=10, AvailabilityZone="us-east-1a") + snapshot = client.create_snapshot(VolumeId=volume.id, Description="Desc") + snapshot = ec2.Snapshot(snapshot["SnapshotId"]) + + with pytest.raises(ClientError) as ex: + snapshot.create_tags(Tags=[{"Key": "key", "Value": ""}], DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set" + ) + + snapshot.tags.should.have.length_of(0) + + snapshot.create_tags(Tags=[{"Key": "key", "Value": ""}]) + + snapshot.tags.should.equal([{"Key": "key", "Value": ""}]) + + @mock_ec2 def test_volume_property_hidden_when_no_tags_exist(): ec2_client = boto3.client("ec2", region_name="us-east-1") diff --git a/tests/test_ec2/test_elastic_ip_addresses.py b/tests/test_ec2/test_elastic_ip_addresses.py index 572b38312..81631eb39 100644 --- a/tests/test_ec2/test_elastic_ip_addresses.py +++ b/tests/test_ec2/test_elastic_ip_addresses.py @@ -5,6 +5,7 @@ import pytest import boto import boto3 from boto.exception import EC2ResponseError +from botocore.exceptions import ClientError import sure # noqa @@ -14,6 +15,7 @@ from tests import EXAMPLE_AMI_ID import logging +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_allocate_classic(): """Allocate/release Classic EIP""" @@ -45,6 +47,40 @@ def test_eip_allocate_classic(): standard.should_not.be.within(conn.get_all_addresses()) +@mock_ec2 +def test_eip_allocate_classic_boto3(): + """Allocate/release Classic EIP""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.allocate_address(DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the AllocateAddress operation: Request would have succeeded, but DryRun flag is set" + ) + + standard = client.allocate_address() + standard.should.have.key("PublicIp") + standard.should.have.key("Domain").equal("standard") + + standard = ec2.ClassicAddress(standard["PublicIp"]) + standard.load() + + with pytest.raises(ClientError) as ex: + standard.release(DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ReleaseAddress operation: Request would have succeeded, but DryRun flag is set" + ) + + standard.release() + client.describe_addresses()["Addresses"].should.be.empty + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_allocate_vpc(): """Allocate/release VPC EIP""" @@ -65,6 +101,32 @@ def test_eip_allocate_vpc(): vpc.release() +@mock_ec2 +def test_eip_allocate_vpc_boto3(): + """Allocate/release VPC EIP""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.allocate_address(Domain="vpc", DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the AllocateAddress operation: Request would have succeeded, but DryRun flag is set" + ) + + vpc = client.allocate_address(Domain="vpc") + vpc.should.have.key("AllocationId") + vpc.should.have.key("Domain").equal("vpc") + + client.describe_addresses()["Addresses"].should.have.length_of(1) + + vpc = ec2.VpcAddress(vpc["AllocationId"]) + vpc.release() + + client.describe_addresses()["Addresses"].should.be.empty + + @mock_ec2 def test_specific_eip_allocate_vpc(): """Allocate VPC EIP with specific address""" @@ -77,6 +139,7 @@ def test_specific_eip_allocate_vpc(): logging.debug("vpc alloc_id:".format(vpc["AllocationId"])) +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_allocate_invalid_domain(): """Allocate EIP invalid domain""" @@ -89,6 +152,19 @@ def test_eip_allocate_invalid_domain(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_eip_allocate_invalid_domain_boto3(): + """Allocate EIP invalid domain""" + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.allocate_address(Domain="bogus") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_associate_classic(): """Associate/Disassociate EIP to classic instance""" @@ -140,6 +216,60 @@ def test_eip_associate_classic(): instance.terminate() +@mock_ec2 +def test_eip_associate_classic_boto3(): + """Associate/Disassociate EIP to classic instance""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = ec2.Instance(reservation["Instances"][0]["InstanceId"]) + + eip = client.allocate_address() + eip = ec2.ClassicAddress(eip["PublicIp"]) + eip.instance_id.should.be.empty + + with pytest.raises(ClientError) as ex: + client.associate_address(PublicIp=eip.public_ip) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("MissingParameter") + ex.value.response["Error"]["Message"].should.equal( + "Invalid request, expect InstanceId/NetworkId parameter." + ) + + with pytest.raises(ClientError) as ex: + client.associate_address( + InstanceId=instance.id, PublicIp=eip.public_ip, DryRun=True + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the AssociateAddress operation: Request would have succeeded, but DryRun flag is set" + ) + + client.associate_address(InstanceId=instance.id, PublicIp=eip.public_ip) + eip.reload() + eip.instance_id.should.be.equal(instance.id) + + with pytest.raises(ClientError) as ex: + client.disassociate_address(PublicIp=eip.public_ip, DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DisAssociateAddress operation: Request would have succeeded, but DryRun flag is set" + ) + + client.disassociate_address(PublicIp=eip.public_ip) + eip.reload() + eip.instance_id.should.be.equal("") + eip.release() + client.describe_addresses()["Addresses"].should.be.empty + + instance.terminate() + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_associate_vpc(): """Associate/Disassociate EIP to VPC instance""" @@ -181,6 +311,50 @@ def test_eip_associate_vpc(): instance.terminate() +@mock_ec2 +def test_eip_associate_vpc_boto3(): + """Associate/Disassociate EIP to VPC instance""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = ec2.Instance(reservation["Instances"][0]["InstanceId"]) + + eip = client.allocate_address(Domain="vpc") + eip.shouldnt.have.key("InstanceId") + eip = ec2.VpcAddress(eip["AllocationId"]) + + with pytest.raises(ClientError) as ex: + client.associate_address(AllocationId=eip.allocation_id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("MissingParameter") + ex.value.response["Error"]["Message"].should.equal( + "Invalid request, expect InstanceId/NetworkId parameter." + ) + + client.associate_address(InstanceId=instance.id, AllocationId=eip.allocation_id) + + eip.reload() + eip.instance_id.should.be.equal(instance.id) + client.disassociate_address(AssociationId=eip.association_id) + + eip.reload() + eip.instance_id.should.be.equal("") + eip.association_id.should.be.none + + with pytest.raises(ClientError) as ex: + eip.release(DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ReleaseAddress operation: Request would have succeeded, but DryRun flag is set" + ) + + eip.release() + instance.terminate() + + @mock_ec2 def test_eip_boto3_vpc_association(): """Associate EIP to VPC instance in a new subnet with boto3""" @@ -228,6 +402,7 @@ def test_eip_boto3_vpc_association(): address.instance_id.should.be.empty +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_associate_network_interface(): """Associate/Disassociate EIP to NIC""" @@ -259,6 +434,42 @@ def test_eip_associate_network_interface(): eip = None +@mock_ec2 +def test_eip_associate_network_interface_boto3(): + """Associate/Disassociate EIP to NIC""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + eni = ec2.create_network_interface(SubnetId=subnet.id) + + eip = client.allocate_address(Domain="vpc") + eip = ec2.ClassicAddress(eip["PublicIp"]) + eip.network_interface_id.should.be.empty + + with pytest.raises(ClientError) as ex: + client.associate_address(NetworkInterfaceId=eni.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("MissingParameter") + ex.value.response["Error"]["Message"].should.equal( + "Invalid request, expect PublicIp/AllocationId parameter." + ) + + client.associate_address(NetworkInterfaceId=eni.id, AllocationId=eip.allocation_id) + + eip.reload() + eip.network_interface_id.should.be.equal(eni.id) + + client.disassociate_address(AssociationId=eip.association_id) + + eip.reload() + eip.network_interface_id.should.be.empty + eip.association_id.should.be.none + eip.release() + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_reassociate(): """reassociate EIP""" @@ -292,6 +503,48 @@ def test_eip_reassociate(): instance2.terminate() +@mock_ec2 +def test_eip_reassociate_boto3(): + """reassociate EIP""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2) + instance1 = ec2.Instance(reservation["Instances"][0]["InstanceId"]) + instance2 = ec2.Instance(reservation["Instances"][1]["InstanceId"]) + + eip = client.allocate_address() + eip = ec2.ClassicAddress(eip["PublicIp"]) + client.associate_address(InstanceId=instance1.id, PublicIp=eip.public_ip) + + # Same ID is idempotent + client.associate_address(InstanceId=instance1.id, PublicIp=eip.public_ip) + + eip.reload() + eip.instance_id.should.equal(instance1.id) + + # Different ID detects resource association + with pytest.raises(ClientError) as ex: + client.associate_address( + InstanceId=instance2.id, PublicIp=eip.public_ip, AllowReassociation=False + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("Resource.AlreadyAssociated") + + client.associate_address( + InstanceId=instance2.id, PublicIp=eip.public_ip, AllowReassociation=True + ) + + eip.reload() + eip.instance_id.should.equal(instance2.id) + + eip.release() + instance1.terminate() + instance2.terminate() + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_reassociate_nic(): """reassociate EIP""" @@ -323,6 +576,44 @@ def test_eip_reassociate_nic(): eip = None +@mock_ec2 +def test_eip_reassociate_nic_boto3(): + """reassociate EIP""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + vpc = client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] + subnet = client.create_subnet(VpcId=vpc["VpcId"], CidrBlock="10.0.0.0/18")["Subnet"] + eni1 = ec2.create_network_interface(SubnetId=subnet["SubnetId"]) + eni2 = ec2.create_network_interface(SubnetId=subnet["SubnetId"]) + + eip = ec2.ClassicAddress(client.allocate_address()["PublicIp"]) + client.associate_address(NetworkInterfaceId=eni1.id, PublicIp=eip.public_ip) + + # Same ID is idempotent + client.associate_address(NetworkInterfaceId=eni1.id, PublicIp=eip.public_ip) + + eip.reload() + eip.network_interface_id.should.equal(eni1.id) + + # Different ID detects resource association + with pytest.raises(ClientError) as ex: + client.associate_address(NetworkInterfaceId=eni2.id, PublicIp=eip.public_ip) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("Resource.AlreadyAssociated") + + client.associate_address( + NetworkInterfaceId=eni2.id, PublicIp=eip.public_ip, AllowReassociation=True + ) + + eip.reload() + eip.network_interface_id.should.equal(eni2.id) + + eip.release() + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_associate_invalid_args(): """Associate EIP, invalid args""" @@ -342,6 +633,27 @@ def test_eip_associate_invalid_args(): instance.terminate() +@mock_ec2 +def test_eip_associate_invalid_args_boto3(): + """Associate EIP, invalid args """ + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = ec2.Instance(reservation["Instances"][0]["InstanceId"]) + + client.allocate_address() + + with pytest.raises(ClientError) as ex: + client.associate_address(InstanceId=instance.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("MissingParameter") + + instance.terminate() + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_disassociate_bogus_association(): """Disassociate bogus EIP""" @@ -354,6 +666,19 @@ def test_eip_disassociate_bogus_association(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_eip_disassociate_bogus_association_boto3(): + """Disassociate bogus EIP""" + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.disassociate_address(AssociationId="bogus") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("InvalidAssociationID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_release_bogus_eip(): """Release bogus EIP""" @@ -366,6 +691,19 @@ def test_eip_release_bogus_eip(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_eip_release_bogus_eip_boto3(): + """Release bogus EIP""" + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.release_address(AllocationId="bogus") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("InvalidAllocationID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_disassociate_arg_error(): """Invalid arguments disassociate address""" @@ -378,6 +716,19 @@ def test_eip_disassociate_arg_error(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_eip_disassociate_arg_error_boto3(): + """Invalid arguments disassociate address""" + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.disassociate_address() + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("MissingParameter") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_release_arg_error(): """Invalid arguments release address""" @@ -390,6 +741,19 @@ def test_eip_release_arg_error(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_eip_release_arg_error_boto3(): + """Invalid arguments release address""" + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.release_address() + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("MissingParameter") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_describe(): """Listing of allocated Elastic IP Addresses.""" @@ -430,6 +794,51 @@ def test_eip_describe(): len(conn.get_all_addresses()).should.be.equal(0) +@mock_ec2 +def test_eip_describe_boto3(): + """Listing of allocated Elastic IP Addresses.""" + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + eips = [] + number_of_classic_ips = 2 + number_of_vpc_ips = 2 + + # allocate some IPs + for _ in range(number_of_classic_ips): + eips.append(ec2.ClassicAddress(client.allocate_address()["PublicIp"])) + for _ in range(number_of_vpc_ips): + eip_id = client.allocate_address(Domain="vpc")["AllocationId"] + eips.append(ec2.VpcAddress(eip_id)) + eips.should.have.length_of(number_of_classic_ips + number_of_vpc_ips) + + # Can we find each one individually? + for eip in eips: + if eip.allocation_id: + lookup_addresses = client.describe_addresses( + AllocationIds=[eip.allocation_id] + )["Addresses"] + else: + lookup_addresses = client.describe_addresses(PublicIps=[eip.public_ip])[ + "Addresses" + ] + len(lookup_addresses).should.be.equal(1) + lookup_addresses[0]["PublicIp"].should.be.equal(eip.public_ip) + + # Can we find first two when we search for them? + lookup_addresses = client.describe_addresses( + PublicIps=[eips[0].public_ip, eips[1].public_ip] + )["Addresses"] + lookup_addresses.should.have.length_of(2) + lookup_addresses[0]["PublicIp"].should.be.equal(eips[0].public_ip) + lookup_addresses[1]["PublicIp"].should.be.equal(eips[1].public_ip) + + # Release all IPs + for eip in eips: + eip.release() + client.describe_addresses()["Addresses"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_eip_describe_none(): """Error when search for bogus IP""" @@ -442,6 +851,18 @@ def test_eip_describe_none(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_eip_describe_none_boto3(): + """Error when search for bogus IP""" + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.describe_addresses(PublicIps=["256.256.256.256"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("InvalidAddress.NotFound") + + @mock_ec2 def test_eip_filters(): service = boto3.resource("ec2", region_name="us-west-1") diff --git a/tests/test_ec2/test_elastic_network_interfaces.py b/tests/test_ec2/test_elastic_network_interfaces.py index b57aab92f..863da88e3 100644 --- a/tests/test_ec2/test_elastic_network_interfaces.py +++ b/tests/test_ec2/test_elastic_network_interfaces.py @@ -9,10 +9,11 @@ import boto.ec2 from boto.exception import EC2ResponseError import sure # noqa -from moto import mock_ec2, mock_ec2_deprecated +from moto import mock_ec2, mock_ec2_deprecated, settings from tests.helpers import requires_boto_gte +# Has boto3 equivalent @mock_ec2_deprecated def test_elastic_network_interfaces(): conn = boto.connect_vpc("the_key", "the_secret") @@ -56,6 +57,54 @@ def test_elastic_network_interfaces(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_elastic_network_interfaces_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", "us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + with pytest.raises(ClientError) as ex: + ec2.create_network_interface(SubnetId=subnet.id, DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateNetworkInterface operation: Request would have succeeded, but DryRun flag is set" + ) + + eni_id = ec2.create_network_interface(SubnetId=subnet.id).id + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(1) + eni = all_enis[0] + eni["Groups"].should.have.length_of(0) + eni["PrivateIpAddresses"].should.have.length_of(1) + eni["PrivateIpAddresses"][0]["PrivateIpAddress"].startswith("10.").should.be.true + + with pytest.raises(ClientError) as ex: + client.delete_network_interface(NetworkInterfaceId=eni_id, DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DeleteNetworkInterface operation: Request would have succeeded, but DryRun flag is set" + ) + + client.delete_network_interface(NetworkInterfaceId=eni_id) + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(0) + + with pytest.raises(ClientError) as ex: + client.delete_network_interface(NetworkInterfaceId=eni_id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal( + "InvalidNetworkInterfaceID.NotFound" + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_elastic_network_interfaces_subnet_validation(): conn = boto.connect_vpc("the_key", "the_secret") @@ -67,6 +116,18 @@ def test_elastic_network_interfaces_subnet_validation(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_elastic_network_interfaces_subnet_validation_boto3(): + client = boto3.client("ec2", "us-east-1") + + with pytest.raises(ClientError) as ex: + client.create_network_interface(SubnetId="subnet-abcd1234") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidSubnetID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_elastic_network_interfaces_with_private_ip(): conn = boto.connect_vpc("the_key", "the_secret") @@ -85,6 +146,28 @@ def test_elastic_network_interfaces_with_private_ip(): eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip) +@mock_ec2 +def test_elastic_network_interfaces_with_private_ip_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", "us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + private_ip = "54.0.0.1" + ec2.create_network_interface(SubnetId=subnet.id, PrivateIpAddress=private_ip) + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(1) + + eni = all_enis[0] + eni["Groups"].should.have.length_of(0) + + eni["PrivateIpAddresses"].should.have.length_of(1) + eni["PrivateIpAddresses"][0]["PrivateIpAddress"].should.equal(private_ip) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_elastic_network_interfaces_with_groups(): conn = boto.connect_vpc("the_key", "the_secret") @@ -110,6 +193,29 @@ def test_elastic_network_interfaces_with_groups(): ) +@mock_ec2 +def test_elastic_network_interfaces_with_groups_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", "us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + sec_group1 = ec2.create_security_group(GroupName="group #1", Description="n/a") + sec_group2 = ec2.create_security_group(GroupName="group #2", Description="n/a") + subnet.create_network_interface(Groups=[sec_group1.id, sec_group2.id]) + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(1) + + eni = all_enis[0] + eni["Groups"].should.have.length_of(2) + set([group["GroupId"] for group in eni["Groups"]]).should.equal( + set([sec_group1.id, sec_group2.id]) + ) + + +# Has boto3 equivalent @requires_boto_gte("2.12.0") @mock_ec2_deprecated def test_elastic_network_interfaces_modify_attribute(): @@ -154,6 +260,47 @@ def test_elastic_network_interfaces_modify_attribute(): eni.groups[1].id.should.equal(security_group2.id) +@mock_ec2 +def test_elastic_network_interfaces_modify_attribute_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", "us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + sec_group1 = ec2.create_security_group(GroupName="group #1", Description="n/a") + sec_group2 = ec2.create_security_group(GroupName="group #2", Description="n/a") + eni_id = subnet.create_network_interface(Groups=[sec_group1.id]).id + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(1) + + eni = all_enis[0] + eni["Groups"].should.have.length_of(1) + eni["Groups"][0]["GroupId"].should.equal(sec_group1.id) + + with pytest.raises(ClientError) as ex: + client.modify_network_interface_attribute( + NetworkInterfaceId=eni_id, Groups=[sec_group2.id], DryRun=True + ) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ModifyNetworkInterface operation: Request would have succeeded, but DryRun flag is set" + ) + + client.modify_network_interface_attribute( + NetworkInterfaceId=eni_id, Groups=[sec_group2.id] + ) + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(1) + + eni = all_enis[0] + eni["Groups"].should.have.length_of(1) + eni["Groups"][0]["GroupId"].should.equal(sec_group2.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_elastic_network_interfaces_filtering(): conn = boto.connect_vpc("the_key", "the_secret") @@ -215,6 +362,75 @@ def test_elastic_network_interfaces_filtering(): ).should.throw(NotImplementedError) +@mock_ec2 +def test_elastic_network_interfaces_filtering_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", "us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + sec_group1 = ec2.create_security_group(GroupName="group #1", Description="n/a") + sec_group2 = ec2.create_security_group(GroupName="group #2", Description="n/a") + + eni1 = subnet.create_network_interface(Groups=[sec_group1.id, sec_group2.id]) + eni2 = subnet.create_network_interface(Groups=[sec_group1.id]) + eni3 = subnet.create_network_interface(Description="test description") + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(3) + + # Filter by NetworkInterfaceId + enis_by_id = client.describe_network_interfaces(NetworkInterfaceIds=[eni1.id])[ + "NetworkInterfaces" + ] + enis_by_id.should.have.length_of(1) + set([eni["NetworkInterfaceId"] for eni in enis_by_id]).should.equal(set([eni1.id])) + + # Filter by ENI ID + enis_by_id = client.describe_network_interfaces( + Filters=[{"Name": "network-interface-id", "Values": [eni1.id]}] + )["NetworkInterfaces"] + enis_by_id.should.have.length_of(1) + set([eni["NetworkInterfaceId"] for eni in enis_by_id]).should.equal(set([eni1.id])) + + # Filter by Security Group + enis_by_group = client.describe_network_interfaces( + Filters=[{"Name": "group-id", "Values": [sec_group1.id]}] + )["NetworkInterfaces"] + enis_by_group.should.have.length_of(2) + set([eni["NetworkInterfaceId"] for eni in enis_by_group]).should.equal( + set([eni1.id, eni2.id]) + ) + + # Filter by ENI ID and Security Group + enis_by_group = client.describe_network_interfaces( + Filters=[ + {"Name": "network-interface-id", "Values": [eni1.id]}, + {"Name": "group-id", "Values": [sec_group1.id]}, + ] + )["NetworkInterfaces"] + enis_by_group.should.have.length_of(1) + set([eni["NetworkInterfaceId"] for eni in enis_by_group]).should.equal( + set([eni1.id]) + ) + + # Filter by Description + enis_by_description = client.describe_network_interfaces( + Filters=[{"Name": "description", "Values": [eni3.description]}] + )["NetworkInterfaces"] + enis_by_description.should.have.length_of(1) + enis_by_description[0]["Description"].should.equal(eni3.description) + + # Unsupported filter + if not settings.TEST_SERVER_MODE: + # ServerMode will just throw a generic 500 + filters = [{"Name": "not-implemented-filter", "Values": ["foobar"]}] + client.describe_network_interfaces.when.called_with( + Filters=filters + ).should.throw(NotImplementedError) + + @mock_ec2 def test_elastic_network_interfaces_get_by_tag_name(): ec2 = boto3.resource("ec2", region_name="us-west-2") diff --git a/tests/test_ec2/test_general.py b/tests/test_ec2/test_general.py index 5818c0c4a..f09025e04 100644 --- a/tests/test_ec2/test_general.py +++ b/tests/test_ec2/test_general.py @@ -5,6 +5,7 @@ import pytest import boto import boto3 from boto.exception import EC2ResponseError +from botocore.exceptions import ClientError import sure # noqa from botocore.exceptions import ClientError @@ -12,6 +13,7 @@ from moto import mock_ec2_deprecated, mock_ec2 from tests import EXAMPLE_AMI_ID +# Has boto3 equivalent @mock_ec2_deprecated def test_console_output(): conn = boto.connect_ec2("the_key", "the_secret") @@ -21,6 +23,7 @@ def test_console_output(): output.output.should_not.equal(None) +# Has boto3 equivalent @mock_ec2_deprecated def test_console_output_without_instance(): conn = boto.connect_ec2("the_key", "the_secret") @@ -39,3 +42,14 @@ def test_console_output_boto3(): output = instances[0].console_output() output.get("Output").should_not.equal(None) + + +@mock_ec2 +def test_console_output_without_instance_boto3(): + client = boto3.client("ec2", "us-east-1") + + with pytest.raises(ClientError) as ex: + client.get_console_output(InstanceId="i-1234abcd") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"]["RequestId"].shouldnt.be.none + ex.value.response["Error"]["Code"].should.equal("InvalidInstanceID.NotFound") diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index b3348f0e6..0723a944e 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -29,6 +29,7 @@ def add_servers(ami_id, count): conn.run_instances(ami_id) +# Has boto3 equivalent @mock_ec2_deprecated def test_add_servers(): add_servers(EXAMPLE_AMI_ID, 2) @@ -40,9 +41,22 @@ def test_add_servers(): assert instance1.image_id == EXAMPLE_AMI_ID +@mock_ec2 +def test_add_servers_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2) + + reservations = client.describe_instances()["Reservations"] + instances = reservations[0]["Instances"] + instances.should.have.length_of(2) + for i in instances: + i["ImageId"].should.equal(EXAMPLE_AMI_ID) + + ############################################ +# Has boto3 equivalent @freeze_time("2014-01-01 05:00:00") @mock_ec2_deprecated def test_instance_launch_and_terminate(): @@ -98,6 +112,70 @@ def test_instance_launch_and_terminate(): instance.state.should.equal("terminated") +@freeze_time("2014-01-01 05:00:00") +@mock_ec2 +def test_instance_launch_and_terminate_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.run_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, DryRun=True + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the RunInstance operation: Request would have succeeded, but DryRun flag is set" + ) + + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + reservation["Instances"].should.have.length_of(1) + instance = reservation["Instances"][0] + instance["State"].should.equal({"Code": 0, "Name": "pending"}) + instance_id = instance["InstanceId"] + + reservations = client.describe_instances()["Reservations"] + reservations.should.have.length_of(1) + reservations[0]["ReservationId"].should.equal(reservation["ReservationId"]) + instances = reservations[0]["Instances"] + instances.should.have.length_of(1) + instance = instances[0] + instance["InstanceId"].should.equal(instance_id) + instance["State"].should.equal({"Code": 16, "Name": "running"}) + if settings.TEST_SERVER_MODE: + # Exact value can't be determined in ServerMode + instance.should.have.key("LaunchTime") + else: + launch_time = instance["LaunchTime"].strftime("%Y-%m-%dT%H:%M:%S.000Z") + launch_time.should.equal("2014-01-01T05:00:00.000Z") + instance["VpcId"].shouldnt.equal(None) + instance["Placement"]["AvailabilityZone"].should.equal("us-east-1a") + + root_device_name = instance["RootDeviceName"] + mapping = instance["BlockDeviceMappings"][0] + mapping["DeviceName"].should.equal(root_device_name) + mapping["Ebs"]["Status"].should.equal("in-use") + volume_id = mapping["Ebs"]["VolumeId"] + volume_id.should.match(r"vol-\w+") + + volume = client.describe_volumes(VolumeIds=[volume_id])["Volumes"][0] + volume["Attachments"][0]["InstanceId"].should.equal(instance_id) + volume["State"].should.equal("in-use") + + with pytest.raises(ClientError) as ex: + client.terminate_instances(InstanceIds=[instance_id], DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the TerminateInstance operation: Request would have succeeded, but DryRun flag is set" + ) + + client.terminate_instances(InstanceIds=[instance_id]) + + reservations = client.describe_instances()["Reservations"] + instance = reservations[0]["Instances"][0] + instance["State"].should.equal({"Code": 48, "Name": "terminated"}) + + @mock_ec2 def test_instance_terminate_discard_volumes(): @@ -224,12 +302,25 @@ def test_instance_detach_volume_wrong_path(): ) +# Has boto3 equivalent @mock_ec2_deprecated def test_terminate_empty_instances(): conn = boto.connect_ec2("the_key", "the_secret") conn.terminate_instances.when.called_with([]).should.throw(EC2ResponseError) +@mock_ec2 +def test_terminate_empty_instances_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + client.terminate_instances(InstanceIds=[]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Code"].should.equal("InvalidParameterCombination") + ex.value.response["Error"]["Message"].should.equal("No instances specified") + + +# Has boto3 equivalent @freeze_time("2014-01-01 05:00:00") @mock_ec2_deprecated def test_instance_attach_volume(): @@ -264,6 +355,41 @@ def test_instance_attach_volume(): v.status.should.equal("in-use") +@freeze_time("2014-01-01 05:00:00") +@mock_ec2 +def test_instance_attach_volume_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = ec2.Instance(reservation["Instances"][0]["InstanceId"]) + + vol1 = ec2.create_volume(Size=36, AvailabilityZone="us-east-1a") + vol1.attach_to_instance(InstanceId=instance.id, Device="/dev/sda1") + vol2 = ec2.create_volume(Size=65, AvailabilityZone="us-east-1a") + vol2.attach_to_instance(InstanceId=instance.id, Device="/dev/sdb1") + vol3 = ec2.create_volume(Size=130, AvailabilityZone="us-east-1a") + vol3.attach_to_instance(InstanceId=instance.id, Device="/dev/sdc1") + + instance.reload() + + instance.block_device_mappings.should.have.length_of(3) + expected_vol3_id = [ + m["Ebs"]["VolumeId"] + for m in instance.block_device_mappings + if m["DeviceName"] == "/dev/sdc1" + ][0] + + expected_vol3 = ec2.Volume(expected_vol3_id) + expected_vol3.attachments[0]["InstanceId"].should.equal(instance.id) + expected_vol3.availability_zone.should.equal("us-east-1a") + expected_vol3.state.should.equal("in-use") + if not settings.TEST_SERVER_MODE: + # FreezeTime does not work in ServerMode + expected_vol3.attachments[0]["AttachTime"].should.equal(instance.launch_time) + expected_vol3.create_time.should.equal(instance.launch_time) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_by_id(): conn = boto.connect_ec2() @@ -291,6 +417,37 @@ def test_get_instances_by_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_get_instances_by_id_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = client.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2) + instance1 = ec2.Instance(reservation["Instances"][0]["InstanceId"]) + instance2 = ec2.Instance(reservation["Instances"][1]["InstanceId"]) + + reservations = client.describe_instances(InstanceIds=[instance1.id])["Reservations"] + reservations.should.have.length_of(1) + reservation = reservations[0] + reservation["Instances"].should.have.length_of(1) + reservation["Instances"][0]["InstanceId"].should.equal(instance1.id) + + reservations = client.describe_instances(InstanceIds=[instance1.id, instance2.id])[ + "Reservations" + ] + reservations.should.have.length_of(1) + reservation = reservations[0] + reservation["Instances"].should.have.length_of(2) + instance_ids = [instance["InstanceId"] for instance in reservation["Instances"]] + set(instance_ids).should.equal(set([instance1.id, instance2.id])) + + # Call describe_instances with a bad id should raise an error + with pytest.raises(ClientError) as ex: + client.describe_instances(InstanceIds=[instance1.id, "i-1234abcd"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidInstanceID.NotFound") + + @mock_ec2 def test_get_paginated_instances(): client = boto3.client("ec2", region_name="us-east-1") @@ -363,6 +520,7 @@ def test_create_with_volume_tags(): sorted(volume["Tags"], key=lambda i: i["Key"]).should.equal(volume_tags) +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_state(): conn = boto.connect_ec2() @@ -399,6 +557,51 @@ def test_get_instances_filtering_by_state(): ).should.throw(NotImplementedError) +@mock_ec2 +def test_get_instances_filtering_by_state_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance1, instance2, instance3 = reservation + + client.terminate_instances(InstanceIds=[instance1.id]) + + reservations = client.describe_instances( + Filters=[{"Name": "instance-state-name", "Values": ["running"]}] + )["Reservations"] + reservations.should.have.length_of(1) + # Since we terminated instance1, only instance2 and instance3 should be + # returned + instance_ids = [instance["InstanceId"] for instance in reservations[0]["Instances"]] + set(instance_ids).should.equal(set([instance2.id, instance3.id])) + + reservations = client.describe_instances( + InstanceIds=[instance2.id], + Filters=[{"Name": "instance-state-name", "Values": ["running"]}], + )["Reservations"] + reservations.should.have.length_of(1) + instance_ids = [instance["InstanceId"] for instance in reservations[0]["Instances"]] + instance_ids.should.equal([instance2.id]) + + reservations = client.describe_instances( + InstanceIds=[instance2.id], + Filters=[{"Name": "instance-state-name", "Values": ["terminated"]}], + )["Reservations"] + reservations.should.equal([]) + + # get_all_reservations should still return all 3 + reservations = client.describe_instances()["Reservations"] + reservations[0]["Instances"].should.have.length_of(3) + + if not settings.TEST_SERVER_MODE: + # ServerMode will just throw a generic 500 + filters = [{"Name": "not-implemented-filter", "Values": ["foobar"]}] + client.describe_instances.when.called_with(Filters=filters).should.throw( + NotImplementedError + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_instance_id(): conn = boto.connect_ec2() @@ -420,6 +623,29 @@ def test_get_instances_filtering_by_instance_id(): reservations.should.have.length_of(0) +@mock_ec2 +def test_get_instances_filtering_by_instance_id_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance1, instance2, instance3 = reservation + + def filter(values, exists=True): + f = [{"Name": "instance-id", "Values": values}] + r = client.describe_instances(Filters=f)["Reservations"] + if exists: + r[0]["Instances"].should.have.length_of(len(values)) + found_ids = [i["InstanceId"] for i in r[0]["Instances"]] + set(found_ids).should.equal(set(values)) + else: + r.should.have.length_of(0) + + filter(values=[instance1.id]) + filter(values=[instance1.id, instance2.id]) + filter(values=["non-existing-id"], exists=False) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_instance_type(): conn = boto.connect_ec2() @@ -463,6 +689,55 @@ def test_get_instances_filtering_by_instance_type(): reservations.should.have.length_of(0) +@mock_ec2 +def test_get_instances_filtering_by_instance_type_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + instance1 = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, InstanceType="m1.small" + )[0] + instance2 = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, InstanceType="m1.small" + )[0] + instance3 = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, InstanceType="t1.micro" + )[0] + + res = client.describe_instances( + Filters=[{"Name": "instance-type", "Values": ["m1.small"]}] + )["Reservations"] + # get_all_reservations should return instance1,2 + res.should.have.length_of(2) + res[0]["Instances"].should.have.length_of(1) + res[1]["Instances"].should.have.length_of(1) + instance_ids = [r["Instances"][0]["InstanceId"] for r in res] + set(instance_ids).should.equal(set([instance1.id, instance2.id])) + + res = client.describe_instances( + Filters=[{"Name": "instance-type", "Values": ["t1.micro"]}] + )["Reservations"] + # get_all_reservations should return one + res.should.have.length_of(1) + res[0]["Instances"].should.have.length_of(1) + res[0]["Instances"][0]["InstanceId"].should.equal(instance3.id) + + res = client.describe_instances( + Filters=[{"Name": "instance-type", "Values": ["t1.micro", "m1.small"]}] + )["Reservations"] + res.should.have.length_of(3) + res[0]["Instances"].should.have.length_of(1) + res[1]["Instances"].should.have.length_of(1) + res[2]["Instances"].should.have.length_of(1) + instance_ids = [r["Instances"][0]["InstanceId"] for r in res] + set(instance_ids).should.equal(set([instance1.id, instance2.id, instance3.id])) + + res = client.describe_instances( + Filters=[{"Name": "instance-type", "Values": ["bogus"]}] + ) + res["Reservations"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_reason_code(): conn = boto.connect_ec2() @@ -486,6 +761,35 @@ def test_get_instances_filtering_by_reason_code(): reservations[0].instances[0].id.should.equal(instance3.id) +@mock_ec2 +def test_get_instances_filtering_by_reason_code_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance1, instance2, instance3 = reservation + instance1.stop() + instance2.terminate() + + res = client.describe_instances( + Filters=[ + {"Name": "state-reason-code", "Values": ["Client.UserInitiatedShutdown"]} + ] + )["Reservations"] + # get_all_reservations should return instance1 and instance2 + res[0]["Instances"].should.have.length_of(2) + set([instance1.id, instance2.id]).should.equal( + set([i["InstanceId"] for i in res[0]["Instances"]]) + ) + + res = client.describe_instances( + Filters=[{"Name": "state-reason-code", "Values": [""]}] + )["Reservations"] + # get_all_reservations should return instance 3 + res[0]["Instances"].should.have.length_of(1) + res[0]["Instances"][0]["InstanceId"].should.equal(instance3.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_source_dest_check(): conn = boto.connect_ec2() @@ -509,6 +813,31 @@ def test_get_instances_filtering_by_source_dest_check(): source_dest_check_true[0].instances[0].id.should.equal(instance2.id) +@mock_ec2 +def test_get_instances_filtering_by_source_dest_check_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2) + instance1, instance2 = reservation + client.modify_instance_attribute( + InstanceId=instance1.id, SourceDestCheck={"Value": False} + ) + + check_false = client.describe_instances( + Filters=[{"Name": "source-dest-check", "Values": ["false"]}] + )["Reservations"] + check_true = client.describe_instances( + Filters=[{"Name": "source-dest-check", "Values": ["true"]}] + )["Reservations"] + + check_false[0]["Instances"].should.have.length_of(1) + check_false[0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + + check_true[0]["Instances"].should.have.length_of(1) + check_true[0]["Instances"][0]["InstanceId"].should.equal(instance2.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_vpc_id(): conn = boto.connect_vpc("the_key", "the_secret") @@ -537,6 +866,44 @@ def test_get_instances_filtering_by_vpc_id(): reservations2[0].instances[0].subnet_id.should.equal(subnet2.id) +@mock_ec2 +def test_get_instances_filtering_by_vpc_id_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet1 = ec2.create_subnet(VpcId=vpc1.id, CidrBlock="10.0.0.0/27") + reservation1 = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, SubnetId=subnet1.id + ) + instance1 = reservation1[0] + + vpc2 = ec2.create_vpc(CidrBlock="10.1.0.0/16") + subnet2 = ec2.create_subnet(VpcId=vpc2.id, CidrBlock="10.1.0.0/27") + reservation2 = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, SubnetId=subnet2.id + ) + instance2 = reservation2[0] + + res1 = client.describe_instances(Filters=[{"Name": "vpc-id", "Values": [vpc1.id]}])[ + "Reservations" + ] + res1.should.have.length_of(1) + res1[0]["Instances"].should.have.length_of(1) + res1[0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res1[0]["Instances"][0]["VpcId"].should.equal(vpc1.id) + res1[0]["Instances"][0]["SubnetId"].should.equal(subnet1.id) + + res2 = client.describe_instances(Filters=[{"Name": "vpc-id", "Values": [vpc2.id]}])[ + "Reservations" + ] + res2.should.have.length_of(1) + res2[0]["Instances"].should.have.length_of(1) + res2[0]["Instances"][0]["InstanceId"].should.equal(instance2.id) + res2[0]["Instances"][0]["VpcId"].should.equal(vpc2.id) + res2[0]["Instances"][0]["SubnetId"].should.equal(subnet2.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_architecture(): conn = boto.connect_ec2() @@ -548,6 +915,19 @@ def test_get_instances_filtering_by_architecture(): reservations[0].instances.should.have.length_of(1) +@mock_ec2 +def test_get_instances_filtering_by_architecture_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + + reservations = client.describe_instances( + Filters=[{"Name": "architecture", "Values": ["x86_64"]}] + )["Reservations"] + # get_all_reservations should return the instance + reservations[0]["Instances"].should.have.length_of(1) + + @mock_ec2 def test_get_instances_filtering_by_image_id(): client = boto3.client("ec2", region_name="us-east-1") @@ -654,6 +1034,7 @@ def test_get_instances_filtering_by_subnet_id(): reservations.should.have.length_of(1) +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_tag(): conn = boto.connect_ec2() @@ -701,6 +1082,56 @@ def test_get_instances_filtering_by_tag(): reservations[0].instances[1].id.should.equal(instance3.id) +@mock_ec2 +def test_get_instances_filtering_by_tag_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance1, instance2, instance3 = reservation + instance1.create_tags(Tags=[{"Key": "tag1", "Value": "value1"}]) + instance1.create_tags(Tags=[{"Key": "tag2", "Value": "value2"}]) + instance2.create_tags(Tags=[{"Key": "tag1", "Value": "value1"}]) + instance2.create_tags(Tags=[{"Key": "tag2", "Value": "wrong value"}]) + instance3.create_tags(Tags=[{"Key": "tag2", "Value": "value2"}]) + + res = client.describe_instances( + Filters=[{"Name": "tag:tag0", "Values": ["value0"]}] + ) + # describe_instances should return no instances + res["Reservations"].should.have.length_of(0) + + res = client.describe_instances( + Filters=[{"Name": "tag:tag1", "Values": ["value1"]}] + ) + # describe_instances should return both instances with this tag value + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(2) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res["Reservations"][0]["Instances"][1]["InstanceId"].should.equal(instance2.id) + + res = client.describe_instances( + Filters=[ + {"Name": "tag:tag1", "Values": ["value1"]}, + {"Name": "tag:tag2", "Values": ["value2"]}, + ] + ) + # describe_instances should return the instance with both tag values + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(1) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + + res = client.describe_instances( + Filters=[{"Name": "tag:tag2", "Values": ["value2", "bogus"]}] + ) + # describe_instances should return both instances with one of the + # acceptable tag values + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(2) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res["Reservations"][0]["Instances"][1]["InstanceId"].should.equal(instance3.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_tag_value(): conn = boto.connect_ec2() @@ -743,6 +1174,56 @@ def test_get_instances_filtering_by_tag_value(): reservations[0].instances[1].id.should.equal(instance3.id) +@mock_ec2 +def test_get_instances_filtering_by_tag_value_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance1, instance2, instance3 = reservation + instance1.create_tags(Tags=[{"Key": "tag1", "Value": "value1"}]) + instance1.create_tags(Tags=[{"Key": "tag2", "Value": "value2"}]) + instance2.create_tags(Tags=[{"Key": "tag1", "Value": "value1"}]) + instance2.create_tags(Tags=[{"Key": "tag2", "Value": "wrong value"}]) + instance3.create_tags(Tags=[{"Key": "tag2", "Value": "value2"}]) + + res = client.describe_instances( + Filters=[{"Name": "tag-value", "Values": ["value0"]}] + ) + # describe_instances should return no instances + res["Reservations"].should.have.length_of(0) + + res = client.describe_instances( + Filters=[{"Name": "tag-value", "Values": ["value1"]}] + ) + # describe_instances should return both instances with this tag value + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(2) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res["Reservations"][0]["Instances"][1]["InstanceId"].should.equal(instance2.id) + + res = client.describe_instances( + Filters=[{"Name": "tag-value", "Values": ["value2", "value1"]}] + ) + # describe_instances should return both instances with one of the + # acceptable tag values + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(3) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res["Reservations"][0]["Instances"][1]["InstanceId"].should.equal(instance2.id) + res["Reservations"][0]["Instances"][2]["InstanceId"].should.equal(instance3.id) + + res = client.describe_instances( + Filters=[{"Name": "tag-value", "Values": ["value2", "bogus"]}] + ) + # describe_instances should return both instances with one of the + # acceptable tag values + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(2) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res["Reservations"][0]["Instances"][1]["InstanceId"].should.equal(instance3.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instances_filtering_by_tag_name(): conn = boto.connect_ec2() @@ -775,6 +1256,42 @@ def test_get_instances_filtering_by_tag_name(): reservations[0].instances[2].id.should.equal(instance3.id) +@mock_ec2 +def test_get_instances_filtering_by_tag_name_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance1, instance2, instance3 = reservation + instance1.create_tags(Tags=[{"Key": "tag1", "Value": ""}]) + instance1.create_tags(Tags=[{"Key": "tag2", "Value": ""}]) + instance2.create_tags(Tags=[{"Key": "tag1", "Value": ""}]) + instance2.create_tags(Tags=[{"Key": "tag2X", "Value": ""}]) + instance3.create_tags(Tags=[{"Key": "tag3", "Value": ""}]) + + res = client.describe_instances(Filters=[{"Name": "tag-key", "Values": ["tagX"]}]) + # describe_instances should return no instances + res["Reservations"].should.have.length_of(0) + + res = client.describe_instances(Filters=[{"Name": "tag-key", "Values": ["tag1"]}]) + # describe_instances should return both instances with this tag value + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(2) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res["Reservations"][0]["Instances"][1]["InstanceId"].should.equal(instance2.id) + + res = client.describe_instances( + Filters=[{"Name": "tag-key", "Values": ["tag1", "tag3"]}] + ) + # describe_instances should return both instances with one of the + # acceptable tag values + res["Reservations"].should.have.length_of(1) + res["Reservations"][0]["Instances"].should.have.length_of(3) + res["Reservations"][0]["Instances"][0]["InstanceId"].should.equal(instance1.id) + res["Reservations"][0]["Instances"][1]["InstanceId"].should.equal(instance2.id) + res["Reservations"][0]["Instances"][2]["InstanceId"].should.equal(instance3.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_instance_start_and_stop(): conn = boto.connect_ec2("the_key", "the_secret") @@ -809,6 +1326,55 @@ def test_instance_start_and_stop(): started_instances[0].state.should.equal("pending") +@mock_ec2 +def test_instance_start_and_stop_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2) + instance1, instance2 = reservation + + instance_ids = [instance1.id, instance2.id] + + with pytest.raises(ClientError) as ex: + client.stop_instances(InstanceIds=instance_ids, DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the StopInstance operation: Request would have succeeded, but DryRun flag is set" + ) + + stopped_instances = client.stop_instances(InstanceIds=instance_ids)[ + "StoppingInstances" + ] + + for instance in stopped_instances: + instance["PreviousState"].should.equal({"Code": 16, "Name": "running"}) + instance["CurrentState"].should.equal({"Code": 64, "Name": "stopping"}) + + instance1.reload() + instance1.state.should.equal({"Code": 80, "Name": "stopped"}) + + with pytest.raises(ClientError) as ex: + client.start_instances(InstanceIds=[instance1.id], DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the StartInstance operation: Request would have succeeded, but DryRun flag is set" + ) + + instance1.reload() + # The DryRun-operation did not change anything + instance1.state.should.equal({"Code": 80, "Name": "stopped"}) + + started_instances = client.start_instances(InstanceIds=[instance1.id])[ + "StartingInstances" + ] + started_instances[0]["CurrentState"].should.equal({"Code": 0, "Name": "pending"}) + # TODO: The PreviousState is hardcoded to 'running' atm + # started_instances[0]["PreviousState"].should.equal({'Code': 80, 'Name': 'stopped'}) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_instance_reboot(): conn = boto.connect_ec2("the_key", "the_secret") @@ -827,6 +1393,30 @@ def test_instance_reboot(): instance.state.should.equal("pending") +@mock_ec2 +def test_instance_reboot_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + response = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = response[0] + instance.state.should.equal({"Code": 0, "Name": "pending"}) + instance.reload() + instance.state.should.equal({"Code": 16, "Name": "running"}) + + with pytest.raises(ClientError) as ex: + instance.reboot(DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the RebootInstance operation: Request would have succeeded, but DryRun flag is set" + ) + + instance.state.should.equal({"Code": 16, "Name": "running"}) + + instance.reboot() + instance.state.should.equal({"Code": 16, "Name": "running"}) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_instance_attribute_instance_type(): conn = boto.connect_ec2("the_key", "the_secret") @@ -848,6 +1438,30 @@ def test_instance_attribute_instance_type(): instance_attribute.get("instanceType").should.equal("m1.small") +@mock_ec2 +def test_instance_attribute_instance_type_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + response = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = response[0] + instance.instance_type.should.equal("m1.small") + + with pytest.raises(ClientError) as ex: + instance.modify_attribute(InstanceType={"Value": "m1.medium"}, DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ModifyInstanceType operation: Request would have succeeded, but DryRun flag is set" + ) + + instance.modify_attribute(InstanceType={"Value": "m1.medium"}) + + instance.instance_type.should.equal("m1.medium") + instance.describe_attribute(Attribute="instanceType")["InstanceType"].should.equal( + {"Value": "m1.medium"} + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_modify_instance_attribute_security_groups(): conn = boto.connect_ec2("the_key", "the_secret") @@ -878,6 +1492,38 @@ def test_modify_instance_attribute_security_groups(): any(g.id == sg_id2 for g in group_list).should.be.ok +@mock_ec2 +def test_modify_instance_attribute_security_groups_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + response = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = response[0] + old_groups = instance.describe_attribute(Attribute="groupSet")["Groups"] + old_groups.should.equal([]) + + sg_id = ec2.create_security_group( + GroupName="test security group", Description="this is a test security group" + ).id + sg_id2 = ec2.create_security_group( + GroupName="test security group 2", Description="this is a test security group 2" + ).id + + with pytest.raises(ClientError) as ex: + instance.modify_attribute(Groups=[sg_id, sg_id2], DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ModifyInstanceSecurityGroups operation: Request would have succeeded, but DryRun flag is set" + ) + + instance.modify_attribute(Groups=[sg_id, sg_id2]) + + new_groups = instance.describe_attribute(Attribute="groupSet")["Groups"] + new_groups.should.have.length_of(2) + new_groups.should.contain({"GroupId": sg_id}) + new_groups.should.contain({"GroupId": sg_id2}) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_instance_attribute_user_data(): conn = boto.connect_ec2("the_key", "the_secret") @@ -899,6 +1545,30 @@ def test_instance_attribute_user_data(): instance_attribute.get("userData").should.equal("this is my user data") +@mock_ec2 +def test_instance_attribute_user_data_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + res = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + instance = res[0] + + with pytest.raises(ClientError) as ex: + instance.modify_attribute( + UserData={"Value": "this is my user data"}, DryRun=True + ) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ModifyUserData operation: Request would have succeeded, but DryRun flag is set" + ) + + instance.modify_attribute(UserData={"Value": "this is my user data"}) + + attribute = instance.describe_attribute(Attribute="userData")["UserData"] + retrieved_user_data = attribute["Value"].encode("utf-8") + decode_method(retrieved_user_data).should.equal(b"this is my user data") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_instance_attribute_source_dest_check(): conn = boto.connect_ec2("the_key", "the_secret") @@ -942,6 +1612,37 @@ def test_instance_attribute_source_dest_check(): instance_attribute.get("sourceDestCheck").should.equal(True) +@mock_ec2 +def test_instance_attribute_source_dest_check_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + instance_attribute = instance.describe_attribute(Attribute="sourceDestCheck") + instance_attribute.get("SourceDestCheck").should.equal({"Value": True}) + + # Set to false (note: Boto converts bool to string, eg 'false') + + with pytest.raises(ClientError) as ex: + instance.modify_attribute(SourceDestCheck={"Value": False}, DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ModifySourceDestCheck operation: Request would have succeeded, but DryRun flag is set" + ) + + instance.modify_attribute(SourceDestCheck={"Value": False}) + + instance_attribute = instance.describe_attribute(Attribute="sourceDestCheck") + instance_attribute.get("SourceDestCheck").should.equal({"Value": False}) + + # Set back to true + instance.modify_attribute(SourceDestCheck={"Value": True}) + + instance_attribute = instance.describe_attribute(Attribute="sourceDestCheck") + instance_attribute.get("SourceDestCheck").should.equal({"Value": True}) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_user_data_with_run_instance(): user_data = b"some user data" @@ -956,6 +1657,21 @@ def test_user_data_with_run_instance(): decoded_user_data.should.equal(b"some user data") +@mock_ec2 +def test_user_data_with_run_instance_boto3(): + user_data = b"some user data" + ec2 = boto3.resource("ec2", region_name="us-east-1") + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, UserData=user_data + )[0] + + attribute = instance.describe_attribute(Attribute="userData")["UserData"] + retrieved_user_data = attribute["Value"].encode("utf-8") + decoded_user_data = decode_method(retrieved_user_data) + decoded_user_data.should.equal(b"some user data") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_security_group_name(): conn = boto.connect_ec2("the_key", "the_secret") @@ -977,6 +1693,33 @@ def test_run_instance_with_security_group_name(): instance.groups[0].name.should.equal("group1") +@mock_ec2 +def test_run_instance_with_security_group_name_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + ec2.create_security_group(GroupName="group1", Description="d", DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateSecurityGroup operation: Request would have succeeded, but DryRun flag is set" + ) + + group = ec2.create_security_group( + GroupName="group1", Description="some description" + ) + + res = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, SecurityGroups=["group1"] + ) + instance = res[0] + + instance.security_groups.should.equal( + [{"GroupName": "group1", "GroupId": group.id}] + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_security_group_id(): conn = boto.connect_ec2("the_key", "the_secret") @@ -988,6 +1731,22 @@ def test_run_instance_with_security_group_id(): instance.groups[0].name.should.equal("group1") +@mock_ec2 +def test_run_instance_with_security_group_id_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + group = ec2.create_security_group( + GroupName="group1", Description="some description" + ) + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, SecurityGroupIds=[group.id] + )[0] + + instance.security_groups.should.equal( + [{"GroupName": "group1", "GroupId": group.id}] + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_instance_type(): conn = boto.connect_ec2("the_key", "the_secret") @@ -997,6 +1756,17 @@ def test_run_instance_with_instance_type(): instance.instance_type.should.equal("t1.micro") +@mock_ec2 +def test_run_instance_with_instance_type_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, InstanceType="t1.micro" + )[0] + + instance.instance_type.should.equal("t1.micro") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_default_placement(): conn = boto.ec2.connect_to_region("us-east-1") @@ -1006,6 +1776,15 @@ def test_run_instance_with_default_placement(): instance.placement.should.equal("us-east-1a") +@mock_ec2 +def test_run_instance_with_default_placement_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + instance.placement.should.have.key("AvailabilityZone").equal("us-east-1a") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_placement(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1015,6 +1794,19 @@ def test_run_instance_with_placement(): instance.placement.should.equal("us-east-1b") +@mock_ec2 +def test_run_instance_with_placement_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, + MinCount=1, + MaxCount=1, + Placement={"AvailabilityZone": "us-east-1b"}, + )[0] + + instance.placement.should.have.key("AvailabilityZone").equal("us-east-1b") + + @mock_ec2 def test_run_instance_with_subnet_boto3(): client = boto3.client("ec2", region_name="eu-central-1") @@ -1111,6 +1903,7 @@ def test_run_instance_mapped_public_ipv4(): len(instance["PublicIpAddress"]).should.be.greater_than(0) +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_nic_autocreated(): conn = boto.connect_vpc("the_key", "the_secret") @@ -1155,6 +1948,54 @@ def test_run_instance_with_nic_autocreated(): eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip) +@mock_ec2 +def test_run_instance_with_nic_autocreated_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + security_group1 = ec2.create_security_group( + GroupName="test security group #1", Description="n/a" + ) + security_group2 = ec2.create_security_group( + GroupName="test security group #2", Description="n/a" + ) + private_ip = "10.0.0.1" + + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, + MinCount=1, + MaxCount=1, + SubnetId=subnet.id, + SecurityGroups=[security_group1.group_name], + SecurityGroupIds=[security_group2.group_id], + PrivateIpAddress=private_ip, + )[0] + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(1) + eni = all_enis[0] + + instance_eni = instance.network_interfaces_attribute + instance_eni.should.have.length_of(1) + instance_eni[0]["NetworkInterfaceId"].should.equal(eni["NetworkInterfaceId"]) + + instance.subnet_id.should.equal(subnet.id) + instance.security_groups.should.have.length_of(2) + set([group["GroupId"] for group in instance.security_groups]).should.equal( + set([security_group1.id, security_group2.id]) + ) + + eni["SubnetId"].should.equal(subnet.id) + eni["Groups"].should.have.length_of(2) + set([group["GroupId"] for group in eni["Groups"]]).should.equal( + set([security_group1.id, security_group2.id]) + ) + eni["PrivateIpAddresses"].should.have.length_of(1) + eni["PrivateIpAddresses"][0]["PrivateIpAddress"].should.equal(private_ip) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_nic_preexisting(): conn = boto.connect_vpc("the_key", "the_secret") @@ -1209,6 +2050,54 @@ def test_run_instance_with_nic_preexisting(): instance_eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip) +@mock_ec2 +def test_run_instance_with_nic_preexisting_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + security_group1 = ec2.create_security_group( + GroupName="test security group #1", Description="n/a" + ) + security_group2 = ec2.create_security_group( + GroupName="test security group #2", Description="n/a" + ) + private_ip = "54.0.0.1" + eni = ec2.create_network_interface( + SubnetId=subnet.id, + PrivateIpAddress=private_ip, + Groups=[security_group1.group_id], + ) + + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, + MinCount=1, + MaxCount=1, + NetworkInterfaces=[{"DeviceIndex": 0, "NetworkInterfaceId": eni.id,}], + SubnetId=subnet.id, + SecurityGroupIds=[security_group2.group_id], + )[0] + + instance.subnet_id.should.equal(subnet.id) + + all_enis = client.describe_network_interfaces()["NetworkInterfaces"] + all_enis.should.have.length_of(1) + + instance_enis = instance.network_interfaces_attribute + instance_enis.should.have.length_of(1) + instance_eni = instance_enis[0] + instance_eni["NetworkInterfaceId"].should.equal(eni.id) + + instance_eni["SubnetId"].should.equal(subnet.id) + instance_eni["Groups"].should.have.length_of(2) + set([group["GroupId"] for group in instance_eni["Groups"]]).should.equal( + set([security_group1.id, security_group2.id]) + ) + instance_eni["PrivateIpAddresses"].should.have.length_of(1) + instance_eni["PrivateIpAddresses"][0]["PrivateIpAddress"].should.equal(private_ip) + + +# Has boto3 equivalent @requires_boto_gte("2.32.0") @mock_ec2_deprecated def test_instance_with_nic_attach_detach(): @@ -1290,6 +2179,109 @@ def test_instance_with_nic_attach_detach(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_instance_with_nic_attach_detach_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + security_group1 = ec2.create_security_group( + GroupName="test security group #1", Description="n/a" + ) + security_group2 = ec2.create_security_group( + GroupName="test security group #2", Description="n/a" + ) + + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, + MinCount=1, + MaxCount=1, + SecurityGroupIds=[security_group1.group_id], + )[0] + + eni = ec2.create_network_interface(SubnetId=subnet.id, Groups=[security_group2.id]) + eni_id = eni.id + + # Check initial instance and ENI data + instance.network_interfaces_attribute.should.have.length_of(1) + + eni.groups.should.have.length_of(1) + set([group["GroupId"] for group in eni.groups]).should.equal( + set([security_group2.id]) + ) + + # Attach + with pytest.raises(ClientError) as ex: + client.attach_network_interface( + NetworkInterfaceId=eni_id, + InstanceId=instance.id, + DeviceIndex=1, + DryRun=True, + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the AttachNetworkInterface operation: Request would have succeeded, but DryRun flag is set" + ) + + client.attach_network_interface( + NetworkInterfaceId=eni_id, InstanceId=instance.id, DeviceIndex=1 + ) + + # Check attached instance and ENI data + instance.reload() + instance.network_interfaces_attribute.should.have.length_of(2) + instance_eni = instance.network_interfaces_attribute[1] + instance_eni["NetworkInterfaceId"].should.equal(eni_id) + instance_eni["Groups"].should.have.length_of(2) + set([group["GroupId"] for group in instance_eni["Groups"]]).should.equal( + set([security_group1.id, security_group2.id]) + ) + + eni = client.describe_network_interfaces( + Filters=[{"Name": "network-interface-id", "Values": [eni_id]}] + )["NetworkInterfaces"][0] + eni["Groups"].should.have.length_of(2) + set([group["GroupId"] for group in eni["Groups"]]).should.equal( + set([security_group1.id, security_group2.id]) + ) + + # Detach + with pytest.raises(ClientError) as ex: + client.detach_network_interface( + AttachmentId=instance_eni["Attachment"]["AttachmentId"], DryRun=True + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DetachNetworkInterface operation: Request would have succeeded, but DryRun flag is set" + ) + + client.detach_network_interface( + AttachmentId=instance_eni["Attachment"]["AttachmentId"] + ) + + # Check detached instance and ENI data + instance.reload() + instance.network_interfaces_attribute.should.have.length_of(1) + + eni = client.describe_network_interfaces( + Filters=[{"Name": "network-interface-id", "Values": [eni_id]}] + )["NetworkInterfaces"][0] + eni["Groups"].should.have.length_of(1) + set([group["GroupId"] for group in eni["Groups"]]).should.equal( + set([security_group2.id]) + ) + + # Detach with invalid attachment ID + with pytest.raises(ClientError) as ex: + client.detach_network_interface(AttachmentId="eni-attach-1234abcd") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAttachmentID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_ec2_classic_has_public_ip_address(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1303,6 +2295,21 @@ def test_ec2_classic_has_public_ip_address(): ) +@mock_ec2 +def test_ec2_classic_has_public_ip_address_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + instance.public_ip_address.should_not.equal(None) + instance.public_dns_name.should.contain( + instance.public_ip_address.replace(".", "-") + ) + instance.private_ip_address.should_not.equal(None) + instance.private_dns_name.should.contain( + instance.private_ip_address.replace(".", "-") + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_run_instance_with_keypair(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1312,6 +2319,16 @@ def test_run_instance_with_keypair(): instance.key_name.should.equal("keypair_name") +@mock_ec2 +def test_run_instance_with_keypair_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + instance = ec2.create_instances( + ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1, KeyName="keypair_name" + )[0] + + instance.key_name.should.equal("keypair_name") + + @mock_ec2 def test_run_instance_with_block_device_mappings(): ec2_client = boto3.client("ec2", region_name="us-east-1") @@ -1417,6 +2434,7 @@ def test_run_instance_with_block_device_mappings_from_snapshot(): volumes["Volumes"][0]["SnapshotId"].should.equal(snapshot.snapshot_id) +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_instance_status_no_instances(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1424,6 +2442,14 @@ def test_describe_instance_status_no_instances(): len(all_status).should.equal(0) +@mock_ec2 +def test_describe_instance_status_no_instances_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + all_status = client.describe_instance_status()["InstanceStatuses"] + all_status.should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_instance_status_with_instances(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1435,6 +2461,19 @@ def test_describe_instance_status_with_instances(): all_status[0].system_status.status.should.equal("ok") +@mock_ec2 +def test_describe_instance_status_with_instances_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + all_status = client.describe_instance_status()["InstanceStatuses"] + all_status.should.have.length_of(1) + all_status[0]["InstanceStatus"]["Status"].should.equal("ok") + all_status[0]["SystemStatus"]["Status"].should.equal("ok") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_instance_status_with_instance_filter_deprecated(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1458,6 +2497,31 @@ def test_describe_instance_status_with_instance_filter_deprecated(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_describe_instance_status_with_instance_filter_deprecated_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + + # We want to filter based on this one + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + # This is just to setup the test + ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1) + + all_status = client.describe_instance_status(InstanceIds=[instance.id])[ + "InstanceStatuses" + ] + all_status.should.have.length_of(1) + all_status[0]["InstanceId"].should.equal(instance.id) + + # Call get_all_instance_status with a bad id should raise an error + with pytest.raises(ClientError) as ex: + client.describe_instance_status(InstanceIds=[instance.id, "i-1234abcd"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidInstanceID.NotFound") + + @mock_ec2 def test_describe_instance_credit_specifications(): conn = boto3.client("ec2", region_name="us-west-1") @@ -1542,6 +2606,7 @@ def test_describe_instance_status_with_instance_filter(): sorted(found_instance_ids).should.equal(stopped_instance_ids) +# Has boto3 equivalent @requires_boto_gte("2.32.0") @mock_ec2_deprecated def test_describe_instance_status_with_non_running_instances(): @@ -1569,6 +2634,36 @@ def test_describe_instance_status_with_non_running_instances(): status3.state_name.should.equal("running") +@mock_ec2 +def test_describe_instance_status_with_non_running_instances_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + reservation = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance1, instance2, instance3 = reservation + instance1.stop() + instance2.terminate() + + all_running_status = client.describe_instance_status()["InstanceStatuses"] + all_running_status.should.have.length_of(1) + all_running_status[0]["InstanceId"].should.equal(instance3.id) + all_running_status[0]["InstanceState"].should.equal({"Code": 16, "Name": "running"}) + + all_status = client.describe_instance_status(IncludeAllInstances=True)[ + "InstanceStatuses" + ] + all_status.should.have.length_of(3) + + status1 = next((s for s in all_status if s["InstanceId"] == instance1.id), None) + status1["InstanceState"].should.equal({"Code": 80, "Name": "stopped"}) + + status2 = next((s for s in all_status if s["InstanceId"] == instance2.id), None) + status2["InstanceState"].should.equal({"Code": 48, "Name": "terminated"}) + + status3 = next((s for s in all_status if s["InstanceId"] == instance3.id), None) + status3["InstanceState"].should.equal({"Code": 16, "Name": "running"}) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_instance_by_security_group(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1596,6 +2691,36 @@ def test_get_instance_by_security_group(): assert security_group_instances[0].id == instance.id +@mock_ec2 +def test_get_instance_by_security_group_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + security_group = ec2.create_security_group(GroupName="test", Description="test") + + with pytest.raises(ClientError) as ex: + client.modify_instance_attribute( + InstanceId=instance.id, Groups=[security_group.id], DryRun=True + ) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ModifyInstanceSecurityGroups operation: Request would have succeeded, but DryRun flag is set" + ) + + client.modify_instance_attribute(InstanceId=instance.id, Groups=[security_group.id]) + + instance.reload() + security_group_instances = instance.describe_attribute(Attribute="groupSet")[ + "Groups" + ] + + security_group_instances.should.have.length_of(1) + security_group_instances.should.equal([{"GroupId": security_group.id}]) + + @mock_ec2 def test_modify_delete_on_termination(): ec2_client = boto3.resource("ec2", region_name="us-west-1") diff --git a/tests/test_ec2/test_internet_gateways.py b/tests/test_ec2/test_internet_gateways.py index 1ef6a8443..11471935c 100644 --- a/tests/test_ec2/test_internet_gateways.py +++ b/tests/test_ec2/test_internet_gateways.py @@ -8,6 +8,7 @@ import boto import boto3 from boto.exception import EC2ResponseError +from botocore.exceptions import ClientError import sure # noqa @@ -19,6 +20,7 @@ BAD_VPC = "vpc-deadbeef" BAD_IGW = "igw-deadbeef" +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_create(): """internet gateway create""" @@ -42,6 +44,31 @@ def test_igw_create(): igw.attachments.should.have.length_of(0) +@mock_ec2 +def test_igw_create_boto3(): + """ internet gateway create """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + client.describe_internet_gateways()["InternetGateways"].should.have.length_of(0) + + with pytest.raises(ClientError) as ex: + client.create_internet_gateway(DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateInternetGateway operation: Request would have succeeded, but DryRun flag is set" + ) + + igw = ec2.create_internet_gateway() + client.describe_internet_gateways()["InternetGateways"].should.have.length_of(1) + igw.id.should.match(r"igw-[0-9a-f]+") + + igw = client.describe_internet_gateways()["InternetGateways"][0] + igw["Attachments"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_attach(): """internet gateway attach""" @@ -63,6 +90,30 @@ def test_igw_attach(): igw.attachments[0].vpc_id.should.be.equal(vpc.id) +@mock_ec2 +def test_igw_attach_boto3(): + """ internet gateway attach """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + igw = ec2.create_internet_gateway() + vpc = ec2.create_vpc(CidrBlock=VPC_CIDR) + + with pytest.raises(ClientError) as ex: + vpc.attach_internet_gateway(InternetGatewayId=igw.id, DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the AttachInternetGateway operation: Request would have succeeded, but DryRun flag is set" + ) + + vpc.attach_internet_gateway(InternetGatewayId=igw.id) + + igw = client.describe_internet_gateways()["InternetGateways"][0] + igw["Attachments"].should.equal([{"State": "available", "VpcId": vpc.id}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_attach_bad_vpc(): """internet gateway fail to attach w/ bad vpc""" @@ -76,6 +127,20 @@ def test_igw_attach_bad_vpc(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_igw_attach_bad_vpc_boto3(): + """ internet gateway fail to attach w/ bad vpc """ + ec2 = boto3.resource("ec2", "us-west-1") + igw = ec2.create_internet_gateway() + + with pytest.raises(ClientError) as ex: + igw.attach_to_vpc(VpcId=BAD_VPC) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidVpcID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_attach_twice(): """internet gateway fail to attach twice""" @@ -92,6 +157,24 @@ def test_igw_attach_twice(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_igw_attach_twice_boto3(): + """ internet gateway fail to attach twice """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + igw = ec2.create_internet_gateway() + vpc1 = ec2.create_vpc(CidrBlock=VPC_CIDR) + vpc2 = ec2.create_vpc(CidrBlock=VPC_CIDR) + client.attach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc1.id) + + with pytest.raises(ClientError) as ex: + client.attach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc2.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("Resource.AlreadyAssociated") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_detach(): """internet gateway detach""" @@ -113,6 +196,31 @@ def test_igw_detach(): igw.attachments.should.have.length_of(0) +@mock_ec2 +def test_igw_detach_boto3(): + """ internet gateway detach""" + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + igw = ec2.create_internet_gateway() + vpc = ec2.create_vpc(CidrBlock=VPC_CIDR) + client.attach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc.id) + + with pytest.raises(ClientError) as ex: + client.detach_internet_gateway( + InternetGatewayId=igw.id, VpcId=vpc.id, DryRun=True + ) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DetachInternetGateway operation: Request would have succeeded, but DryRun flag is set" + ) + + client.detach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc.id) + igw = client.describe_internet_gateways()["InternetGateways"][0] + igw["Attachments"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_detach_wrong_vpc(): """internet gateway fail to detach w/ wrong vpc""" @@ -129,6 +237,24 @@ def test_igw_detach_wrong_vpc(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_igw_detach_wrong_vpc_boto3(): + """ internet gateway fail to detach w/ wrong vpc """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + igw = ec2.create_internet_gateway() + vpc1 = ec2.create_vpc(CidrBlock=VPC_CIDR) + vpc2 = ec2.create_vpc(CidrBlock=VPC_CIDR) + client.attach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc1.id) + + with pytest.raises(ClientError) as ex: + client.detach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc2.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("Gateway.NotAttached") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_detach_invalid_vpc(): """internet gateway fail to detach w/ invalid vpc""" @@ -144,6 +270,23 @@ def test_igw_detach_invalid_vpc(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_igw_detach_invalid_vpc_boto3(): + """ internet gateway fail to detach w/ invalid vpc """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + igw = ec2.create_internet_gateway() + vpc = ec2.create_vpc(CidrBlock=VPC_CIDR) + client.attach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc.id) + + with pytest.raises(ClientError) as ex: + client.detach_internet_gateway(InternetGatewayId=igw.id, VpcId=BAD_VPC) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("Gateway.NotAttached") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_detach_unattached(): """internet gateway fail to detach unattached""" @@ -158,6 +301,22 @@ def test_igw_detach_unattached(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_igw_detach_unattached_boto3(): + """ internet gateway fail to detach unattached """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + igw = ec2.create_internet_gateway() + vpc = ec2.create_vpc(CidrBlock=VPC_CIDR) + + with pytest.raises(ClientError) as ex: + client.detach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("Gateway.NotAttached") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_delete(): """internet gateway delete""" @@ -179,6 +338,30 @@ def test_igw_delete(): conn.get_all_internet_gateways().should.have.length_of(0) +@mock_ec2 +def test_igw_delete_boto3(): + """ internet gateway delete""" + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + ec2.create_vpc(CidrBlock=VPC_CIDR) + + client.describe_internet_gateways()["InternetGateways"].should.have.length_of(0) + igw = ec2.create_internet_gateway() + client.describe_internet_gateways()["InternetGateways"].should.have.length_of(1) + + with pytest.raises(ClientError) as ex: + client.delete_internet_gateway(InternetGatewayId=igw.id, DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DeleteInternetGateway operation: Request would have succeeded, but DryRun flag is set" + ) + + client.delete_internet_gateway(InternetGatewayId=igw.id) + client.describe_internet_gateways()["InternetGateways"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_delete_attached(): """internet gateway fail to delete attached""" @@ -194,6 +377,23 @@ def test_igw_delete_attached(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_igw_delete_attached_boto3(): + """ internet gateway fail to delete attached """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + igw = ec2.create_internet_gateway() + vpc = ec2.create_vpc(CidrBlock=VPC_CIDR) + client.attach_internet_gateway(InternetGatewayId=igw.id, VpcId=vpc.id) + + with pytest.raises(ClientError) as ex: + client.delete_internet_gateway(InternetGatewayId=igw.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("DependencyViolation") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_desribe(): """internet gateway fetch by id""" @@ -203,6 +403,19 @@ def test_igw_desribe(): igw.id.should.equal(igw_by_search.id) +@mock_ec2 +def test_igw_describe_boto3(): + """ internet gateway fetch by id """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + igw = ec2.create_internet_gateway() + igw_by_search = client.describe_internet_gateways(InternetGatewayIds=[igw.id])[ + "InternetGateways" + ][0] + igw.id.should.equal(igw_by_search["InternetGatewayId"]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_describe_bad_id(): """internet gateway fail to fetch by bad id""" @@ -214,6 +427,18 @@ def test_igw_describe_bad_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_igw_describe_bad_id_boto3(): + """ internet gateway fail to fetch by bad id """ + client = boto3.client("ec2", "us-west-1") + with pytest.raises(ClientError) as ex: + client.describe_internet_gateways(InternetGatewayIds=[BAD_IGW]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidInternetGatewayID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_filter_by_vpc_id(): """internet gateway filter by vpc id""" @@ -229,6 +454,25 @@ def test_igw_filter_by_vpc_id(): result[0].id.should.equal(igw1.id) +@mock_ec2 +def test_igw_filter_by_vpc_id_boto3(): + """ internet gateway filter by vpc id """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + igw1 = ec2.create_internet_gateway() + ec2.create_internet_gateway() + vpc = ec2.create_vpc(CidrBlock=VPC_CIDR) + client.attach_internet_gateway(InternetGatewayId=igw1.id, VpcId=vpc.id) + + result = client.describe_internet_gateways( + Filters=[{"Name": "attachment.vpc-id", "Values": [vpc.id]}] + ) + result["InternetGateways"].should.have.length_of(1) + result["InternetGateways"][0]["InternetGatewayId"].should.equal(igw1.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_filter_by_tags(): """internet gateway filter by vpc id""" @@ -243,6 +487,24 @@ def test_igw_filter_by_tags(): result[0].id.should.equal(igw1.id) +@mock_ec2 +def test_igw_filter_by_tags_boto3(): + """ internet gateway filter by vpc id """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + igw1 = ec2.create_internet_gateway() + igw2 = ec2.create_internet_gateway() + igw1.create_tags(Tags=[{"Key": "tests", "Value": "yes"}]) + + result = client.describe_internet_gateways( + Filters=[{"Name": "tag:tests", "Values": ["yes"]}] + ) + result["InternetGateways"].should.have.length_of(1) + result["InternetGateways"][0]["InternetGatewayId"].should.equal(igw1.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_filter_by_internet_gateway_id(): """internet gateway filter by internet gateway id""" @@ -256,6 +518,23 @@ def test_igw_filter_by_internet_gateway_id(): result[0].id.should.equal(igw1.id) +@mock_ec2 +def test_igw_filter_by_internet_gateway_id_boto3(): + """ internet gateway filter by internet gateway id """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + igw1 = ec2.create_internet_gateway() + igw2 = ec2.create_internet_gateway() + + result = client.describe_internet_gateways( + Filters=[{"Name": "internet-gateway-id", "Values": [igw1.id]}] + ) + result["InternetGateways"].should.have.length_of(1) + result["InternetGateways"][0]["InternetGatewayId"].should.equal(igw1.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_igw_filter_by_attachment_state(): """internet gateway filter by attachment state""" @@ -271,6 +550,24 @@ def test_igw_filter_by_attachment_state(): result[0].id.should.equal(igw1.id) +@mock_ec2 +def test_igw_filter_by_attachment_state_boto3(): + """ internet gateway filter by attachment state """ + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + igw1 = ec2.create_internet_gateway() + igw2 = ec2.create_internet_gateway() + vpc = ec2.create_vpc(CidrBlock=VPC_CIDR) + client.attach_internet_gateway(InternetGatewayId=igw1.id, VpcId=vpc.id) + + result = client.describe_internet_gateways( + Filters=[{"Name": "attachment.state", "Values": ["available"]}] + ) + result["InternetGateways"].should.have.length_of(1) + result["InternetGateways"][0]["InternetGatewayId"].should.equal(igw1.id) + + @mock_ec2 def test_create_internet_gateway_with_tags(): ec2 = boto3.resource("ec2", region_name="eu-central-1") diff --git a/tests/test_ec2/test_key_pairs.py b/tests/test_ec2/test_key_pairs.py index 0b326e406..3c5889993 100644 --- a/tests/test_ec2/test_key_pairs.py +++ b/tests/test_ec2/test_key_pairs.py @@ -3,10 +3,12 @@ from __future__ import unicode_literals import pytest import boto +import boto3 import sure # noqa from boto.exception import EC2ResponseError -from moto import mock_ec2_deprecated +from botocore.exceptions import ClientError +from moto import mock_ec2, mock_ec2_deprecated from .helpers import rsa_check_private_key @@ -44,12 +46,21 @@ ffsm7UIHtCBYERr9Nx0u20ldfhkgB1lhaJb5o0ZJ3pmJ38KChfyHe5EUcqRdEFo89Mp72VI2Z6UHyL17 moto@github.com""" +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_empty(): conn = boto.connect_ec2("the_key", "the_secret") assert len(conn.get_all_key_pairs()) == 0 +@mock_ec2 +def test_key_pairs_empty_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + client.describe_key_pairs()["KeyPairs"].should.be.empty + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_invalid_id(): conn = boto.connect_ec2("the_key", "the_secret") @@ -61,6 +72,19 @@ def test_key_pairs_invalid_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_key_pairs_invalid_id_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + with pytest.raises(ClientError) as ex: + client.describe_key_pairs(KeyNames=["foo"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidKeyPair.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_create(): conn = boto.connect_ec2("the_key", "the_secret") @@ -81,6 +105,7 @@ def test_key_pairs_create(): assert kps[0].name == "foo" +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_create_two(): conn = boto.connect_ec2("the_key", "the_secret") @@ -102,6 +127,43 @@ def test_key_pairs_create_two(): kps[0].name.should.equal("foo") +@mock_ec2 +def test_key_pairs_create_dryrun_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + + with pytest.raises(ClientError) as ex: + ec2.create_key_pair(KeyName="foo", DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateKeyPair operation: Request would have succeeded, but DryRun flag is set" + ) + + +@mock_ec2 +def test_key_pairs_create_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + kp = ec2.create_key_pair(KeyName="foo") + rsa_check_private_key(kp.key_material) + # Verify the client can create a key_pair as well - should behave the same + kp2 = client.create_key_pair(KeyName="foo2") + rsa_check_private_key(kp2["KeyMaterial"]) + + kp.key_material.shouldnt.equal(kp2["KeyMaterial"]) + + kps = client.describe_key_pairs()["KeyPairs"] + kps.should.have.length_of(2) + set([k["KeyName"] for k in kps]).should.equal(set(["foo", "foo2"])) + + kps = client.describe_key_pairs(KeyNames=["foo"])["KeyPairs"] + kps.should.have.length_of(1) + kps[0].should.have.key("KeyName").equal("foo") + kps[0].should.have.key("KeyFingerprint") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_create_exist(): conn = boto.connect_ec2("the_key", "the_secret") @@ -115,6 +177,19 @@ def test_key_pairs_create_exist(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_key_pairs_create_exist_boto3(): + client = boto3.client("ec2", "us-west-1") + client.create_key_pair(KeyName="foo") + + with pytest.raises(ClientError) as ex: + client.create_key_pair(KeyName="foo") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidKeyPair.Duplicate") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_delete_no_exist(): conn = boto.connect_ec2("the_key", "the_secret") @@ -123,6 +198,14 @@ def test_key_pairs_delete_no_exist(): r.should.be.ok +@mock_ec2 +def test_key_pairs_delete_no_exist_boto3(): + client = boto3.client("ec2", "us-west-1") + client.describe_key_pairs()["KeyPairs"].should.have.length_of(0) + client.delete_key_pair(KeyName="non-existing") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_delete_exist(): conn = boto.connect_ec2("the_key", "the_secret") @@ -141,6 +224,25 @@ def test_key_pairs_delete_exist(): assert len(conn.get_all_key_pairs()) == 0 +@mock_ec2 +def test_key_pairs_delete_exist_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + client.create_key_pair(KeyName="foo") + + with pytest.raises(ClientError) as ex: + client.delete_key_pair(KeyName="foo", DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DeleteKeyPair operation: Request would have succeeded, but DryRun flag is set" + ) + + client.delete_key_pair(KeyName="foo") + client.describe_key_pairs()["KeyPairs"].should.equal([]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_import(): conn = boto.connect_ec2("the_key", "the_secret") @@ -167,6 +269,41 @@ def test_key_pairs_import(): assert kps[1].name == kp2.name +@mock_ec2 +def test_key_pairs_import_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + with pytest.raises(ClientError) as ex: + client.import_key_pair( + KeyName="foo", PublicKeyMaterial=RSA_PUBLIC_KEY_OPENSSH, DryRun=True + ) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the ImportKeyPair operation: Request would have succeeded, but DryRun flag is set" + ) + + kp1 = client.import_key_pair( + KeyName="foo", PublicKeyMaterial=RSA_PUBLIC_KEY_OPENSSH + ) + print(kp1) + kp1.should.have.key("KeyName").equal("foo") + kp1.should.have.key("KeyFingerprint").equal(RSA_PUBLIC_KEY_FINGERPRINT) + + kp2 = client.import_key_pair( + KeyName="foo2", PublicKeyMaterial=RSA_PUBLIC_KEY_RFC4716 + ) + kp2.should.have.key("KeyName").equal("foo2") + kp2.should.have.key("KeyFingerprint").equal(RSA_PUBLIC_KEY_FINGERPRINT) + + kps = client.describe_key_pairs()["KeyPairs"] + kps.should.have.length_of(2) + kps[0]["KeyName"].should.equal(kp1["KeyName"]) + kps[1]["KeyName"].should.equal(kp2["KeyName"]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_import_exist(): conn = boto.connect_ec2("the_key", "the_secret") @@ -181,6 +318,23 @@ def test_key_pairs_import_exist(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_key_pairs_import_exist_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + kp = client.import_key_pair(KeyName="foo", PublicKeyMaterial=RSA_PUBLIC_KEY_OPENSSH) + kp["KeyName"].should.equal("foo") + client.describe_key_pairs()["KeyPairs"].should.have.length_of(1) + + with pytest.raises(ClientError) as ex: + client.create_key_pair(KeyName="foo") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidKeyPair.Duplicate") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pairs_invalid(): conn = boto.connect_ec2("the_key", "the_secret") @@ -204,6 +358,34 @@ def test_key_pairs_invalid(): ex.value.message.should.equal("Key is not in valid OpenSSH public key format") +@mock_ec2 +def test_key_pairs_invalid_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + with pytest.raises(ClientError) as ex: + client.import_key_pair(KeyName="foo", PublicKeyMaterial=b"") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + err = ex.value.response["Error"] + err["Code"].should.equal("InvalidKeyPair.Format") + err["Message"].should.equal("Key is not in valid OpenSSH public key format") + + with pytest.raises(ClientError) as ex: + client.import_key_pair(KeyName="foo", PublicKeyMaterial=b"garbage") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + err = ex.value.response["Error"] + err["Code"].should.equal("InvalidKeyPair.Format") + err["Message"].should.equal("Key is not in valid OpenSSH public key format") + + with pytest.raises(ClientError) as ex: + client.import_key_pair(KeyName="foo", PublicKeyMaterial=DSA_PUBLIC_KEY_OPENSSH) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + err = ex.value.response["Error"] + err["Code"].should.equal("InvalidKeyPair.Format") + err["Message"].should.equal("Key is not in valid OpenSSH public key format") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_key_pair_filters(): conn = boto.connect_ec2("the_key", "the_secret") @@ -217,3 +399,23 @@ def test_key_pair_filters(): kp_by_name = conn.get_all_key_pairs(filters={"fingerprint": kp3.fingerprint}) set([kp.name for kp in kp_by_name]).should.equal(set([kp3.name])) + + +@mock_ec2 +def test_key_pair_filters_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + _ = ec2.create_key_pair(KeyName="kpfltr1") + kp2 = ec2.create_key_pair(KeyName="kpfltr2") + kp3 = ec2.create_key_pair(KeyName="kpfltr3") + + kp_by_name = client.describe_key_pairs( + Filters=[{"Name": "key-name", "Values": ["kpfltr2"]}] + )["KeyPairs"] + set([kp["KeyName"] for kp in kp_by_name]).should.equal(set([kp2.name])) + + kp_by_name = client.describe_key_pairs( + Filters=[{"Name": "fingerprint", "Values": [kp3.key_fingerprint]}] + )["KeyPairs"] + set([kp["KeyName"] for kp in kp_by_name]).should.equal(set([kp3.name])) diff --git a/tests/test_ec2/test_network_acls.py b/tests/test_ec2/test_network_acls.py index 8a7d74a8e..d049584b6 100644 --- a/tests/test_ec2/test_network_acls.py +++ b/tests/test_ec2/test_network_acls.py @@ -9,6 +9,7 @@ from moto import mock_ec2_deprecated, mock_ec2 from moto.ec2.models import OWNER_ID +# Has boto3 equivalent @mock_ec2_deprecated def test_default_network_acl_created_with_vpc(): conn = boto.connect_vpc("the_key", "the secret") @@ -17,6 +18,17 @@ def test_default_network_acl_created_with_vpc(): all_network_acls.should.have.length_of(2) +@mock_ec2 +def test_default_network_acl_created_with_vpc_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + ec2.create_vpc(CidrBlock="10.0.0.0/16") + + all_network_acls = client.describe_network_acls()["NetworkAcls"] + all_network_acls.should.have.length_of(2) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_network_acls(): conn = boto.connect_vpc("the_key", "the secret") @@ -26,6 +38,25 @@ def test_network_acls(): all_network_acls.should.have.length_of(3) +@mock_ec2 +def test_network_create_and_list_acls_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + created_acl = ec2.create_network_acl(VpcId=vpc.id) + + all_network_acls = client.describe_network_acls()["NetworkAcls"] + all_network_acls.should.have.length_of(3) + + acl_found = [ + a for a in all_network_acls if a["NetworkAclId"] == created_acl.network_acl_id + ][0] + acl_found["VpcId"].should.equal(vpc.id) + acl_found["Tags"].should.equal([]) + acl_found["IsDefault"].should.equal(False) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_new_subnet_associates_with_default_network_acl(): conn = boto.connect_vpc("the_key", "the secret") @@ -40,6 +71,22 @@ def test_new_subnet_associates_with_default_network_acl(): [a.subnet_id for a in acl.associations].should.contain(subnet.id) +@mock_ec2 +def test_new_subnet_associates_with_default_network_acl_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + default_vpc = client.describe_vpcs()["Vpcs"][0] + + subnet = ec2.create_subnet(VpcId=default_vpc["VpcId"], CidrBlock="172.31.112.0/20") + all_network_acls = client.describe_network_acls()["NetworkAcls"] + all_network_acls.should.have.length_of(1) + + acl = all_network_acls[0] + acl["Associations"].should.have.length_of(7) + [a["SubnetId"] for a in acl["Associations"]].should.contain(subnet.id) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_network_acl_entries(): conn = boto.connect_vpc("the_key", "the secret") @@ -69,6 +116,43 @@ def test_network_acl_entries(): entries[0].rule_action.should.equal("ALLOW") +@mock_ec2 +def test_network_acl_entries_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + network_acl = ec2.create_network_acl(VpcId=vpc.id) + + client.create_network_acl_entry( + NetworkAclId=network_acl.id, + RuleNumber=110, + Protocol="6", # TCP + RuleAction="ALLOW", + CidrBlock="0.0.0.0/0", + Egress=False, + PortRange={"From": 443, "To": 443}, + ) + + all_network_acls = client.describe_network_acls()["NetworkAcls"] + all_network_acls.should.have.length_of(3) + + test_network_acl = next( + na for na in all_network_acls if na["NetworkAclId"] == network_acl.id + ) + test_network_acl.should.have.key("IsDefault").should.equal(False) + + entries = test_network_acl["Entries"] + entries.should.have.length_of(1) + entries[0]["RuleNumber"].should.equal(110) + entries[0]["Protocol"].should.equal("6") + entries[0]["RuleAction"].should.equal("ALLOW") + entries[0]["Egress"].should.equal(False) + entries[0]["PortRange"].should.equal({"To": 443, "From": 443}) + entries[0]["CidrBlock"].should.equal("0.0.0.0/0") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_network_acl_entry(): conn = boto.connect_vpc("the_key", "the secret") @@ -95,6 +179,36 @@ def test_delete_network_acl_entry(): entries.should.have.length_of(0) +@mock_ec2 +def test_delete_network_acl_entry_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + network_acl = ec2.create_network_acl(VpcId=vpc.id) + + client.create_network_acl_entry( + NetworkAclId=network_acl.id, + RuleNumber=110, + Protocol="6", # TCP + RuleAction="ALLOW", + CidrBlock="0.0.0.0/0", + Egress=False, + PortRange={"From": 443, "To": 443}, + ) + client.delete_network_acl_entry( + NetworkAclId=network_acl.id, RuleNumber=110, Egress=False + ) + + all_network_acls = client.describe_network_acls()["NetworkAcls"] + + test_network_acl = next( + na for na in all_network_acls if na["NetworkAclId"] == network_acl.id + ) + test_network_acl["Entries"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_replace_network_acl_entry(): conn = boto.connect_vpc("the_key", "the secret") @@ -133,6 +247,47 @@ def test_replace_network_acl_entry(): entries[0].rule_action.should.equal("DENY") +@mock_ec2 +def test_replace_network_acl_entry_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + network_acl = ec2.create_network_acl(VpcId=vpc.id) + + client.create_network_acl_entry( + NetworkAclId=network_acl.id, + RuleNumber=110, + Protocol="6", # TCP + RuleAction="ALLOW", + CidrBlock="0.0.0.0/0", + Egress=False, + PortRange={"From": 443, "To": 443}, + ) + client.replace_network_acl_entry( + NetworkAclId=network_acl.id, + RuleNumber=110, + Protocol="-1", + RuleAction="DENY", + CidrBlock="0.0.0.0/0", + Egress=False, + PortRange={"From": 22, "To": 22}, + ) + + all_network_acls = client.describe_network_acls()["NetworkAcls"] + + test_network_acl = next( + na for na in all_network_acls if na["NetworkAclId"] == network_acl.id + ) + entries = test_network_acl["Entries"] + entries.should.have.length_of(1) + entries[0]["RuleNumber"].should.equal(110) + entries[0]["Protocol"].should.equal("-1") + entries[0]["RuleAction"].should.equal("DENY") + entries[0]["PortRange"].should.equal({"To": 22, "From": 22}) + + +# TODO: How to convert 'associate_network_acl' to boto3? @mock_ec2_deprecated def test_associate_new_network_acl_with_subnet(): conn = boto.connect_vpc("the_key", "the secret") @@ -151,6 +306,7 @@ def test_associate_new_network_acl_with_subnet(): test_network_acl.associations[0].subnet_id.should.equal(subnet.id) +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_network_acl(): conn = boto.connect_vpc("the_key", "the secret") @@ -171,6 +327,29 @@ def test_delete_network_acl(): any(acl.id == network_acl.id for acl in updated_network_acls).shouldnt.be.ok +@mock_ec2 +def test_delete_network_acl_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + network_acl = ec2.create_network_acl(VpcId=vpc.id) + + all_network_acls = client.describe_network_acls()["NetworkAcls"] + all_network_acls.should.have.length_of(3) + + any(acl["NetworkAclId"] == network_acl.id for acl in all_network_acls).should.be.ok + + client.delete_network_acl(NetworkAclId=network_acl.id) + + updated_network_acls = client.describe_network_acls()["NetworkAcls"] + updated_network_acls.should.have.length_of(2) + + any( + acl["NetworkAclId"] == network_acl.id for acl in updated_network_acls + ).shouldnt.be.ok + + +# Has boto3 equivalent @mock_ec2_deprecated def test_network_acl_tagging(): conn = boto.connect_vpc("the_key", "the secret") @@ -189,6 +368,27 @@ def test_network_acl_tagging(): test_network_acl.tags["a key"].should.equal("some value") +@mock_ec2 +def test_network_acl_tagging_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + network_acl = ec2.create_network_acl(VpcId=vpc.id) + + network_acl.create_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + + tag = client.describe_tags()["Tags"][0] + tag.should.have.key("ResourceId").equal(network_acl.id) + tag.should.have.key("Key").equal("a key") + tag.should.have.key("Value").equal("some value") + + all_network_acls = client.describe_network_acls()["NetworkAcls"] + test_network_acl = next( + na for na in all_network_acls if na["NetworkAclId"] == network_acl.id + ) + test_network_acl["Tags"].should.equal([{"Value": "some value", "Key": "a key"}]) + + @mock_ec2 def test_new_subnet_in_new_vpc_associates_with_default_network_acl(): ec2 = boto3.resource("ec2", region_name="us-west-1") diff --git a/tests/test_ec2/test_regions.py b/tests/test_ec2/test_regions.py index 31096d532..e7d1cfe03 100644 --- a/tests/test_ec2/test_regions.py +++ b/tests/test_ec2/test_regions.py @@ -2,10 +2,12 @@ from __future__ import unicode_literals import boto.ec2 import boto.ec2.autoscale import boto.ec2.elb +import boto3 import sure from boto3 import Session from moto import mock_ec2_deprecated, mock_autoscaling_deprecated, mock_elb_deprecated +from moto import mock_autoscaling, mock_ec2, mock_elb from moto.ec2 import ec2_backends from tests import EXAMPLE_AMI_ID, EXAMPLE_AMI_ID2 @@ -30,6 +32,12 @@ def add_servers_to_region(ami_id, count, region): conn.run_instances(ami_id) +def add_servers_to_region_boto3(ami_id, count, region): + ec2 = boto3.resource("ec2", region_name=region) + ec2.create_instances(ImageId=ami_id, MinCount=count, MaxCount=count) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_add_servers_to_a_single_region(): region = "ap-northeast-1" @@ -44,6 +52,21 @@ def test_add_servers_to_a_single_region(): image_ids.should.equal([EXAMPLE_AMI_ID, EXAMPLE_AMI_ID2]) +@mock_ec2 +def test_add_servers_to_a_single_region_boto3(): + region = "ap-northeast-1" + add_servers_to_region_boto3(EXAMPLE_AMI_ID, 1, region) + add_servers_to_region_boto3(EXAMPLE_AMI_ID2, 1, region) + + client = boto3.client("ec2", region_name=region) + reservations = client.describe_instances()["Reservations"] + reservations.should.have.length_of(2) + + image_ids = [r["Instances"][0]["ImageId"] for r in reservations] + image_ids.should.equal([EXAMPLE_AMI_ID, EXAMPLE_AMI_ID2]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_add_servers_to_multiple_regions(): region1 = "us-east-1" @@ -63,6 +86,26 @@ def test_add_servers_to_multiple_regions(): ap_reservations[0].instances[0].image_id.should.equal(EXAMPLE_AMI_ID2) +@mock_ec2 +def test_add_servers_to_multiple_regions_boto3(): + region1 = "us-east-1" + region2 = "ap-northeast-1" + add_servers_to_region_boto3(EXAMPLE_AMI_ID, 1, region1) + add_servers_to_region_boto3(EXAMPLE_AMI_ID2, 1, region2) + + us_client = boto3.client("ec2", region_name=region1) + ap_client = boto3.client("ec2", region_name=region2) + us_reservations = us_client.describe_instances()["Reservations"] + ap_reservations = ap_client.describe_instances()["Reservations"] + + us_reservations.should.have.length_of(1) + ap_reservations.should.have.length_of(1) + + us_reservations[0]["Instances"][0]["ImageId"].should.equal(EXAMPLE_AMI_ID) + ap_reservations[0]["Instances"][0]["ImageId"].should.equal(EXAMPLE_AMI_ID2) + + +# Has boto3 equivalent @mock_autoscaling_deprecated @mock_elb_deprecated def test_create_autoscaling_group(): @@ -161,3 +204,69 @@ def test_create_autoscaling_group(): list(ap_group.termination_policies).should.equal( ["OldestInstance", "NewestInstance"] ) + + +@mock_autoscaling +@mock_elb +@mock_ec2 +def test_create_autoscaling_group_boto3(): + regions = [("us-east-1", "c"), ("ap-northeast-1", "a")] + for region, zone in regions: + a_zone = "{}{}".format(region, zone) + asg_name = "{}_tester_group".format(region) + lb_name = "{}_lb".format(region) + config_name = "{}_tester".format(region) + + elb_client = boto3.client("elb", region_name=region) + elb_client.create_load_balancer( + LoadBalancerName=lb_name, + Listeners=[ + {"Protocol": "http", "LoadBalancerPort": 80, "InstancePort": 8080} + ], + AvailabilityZones=[], + ) + + as_client = boto3.client("autoscaling", region_name=region) + as_client.create_launch_configuration( + LaunchConfigurationName=config_name, + ImageId=EXAMPLE_AMI_ID, + InstanceType="m1.small", + ) + + ec2_client = boto3.client("ec2", region_name=region) + subnet_id = ec2_client.describe_subnets( + Filters=[{"Name": "availability-zone", "Values": [a_zone]}] + )["Subnets"][0]["SubnetId"] + + as_client.create_auto_scaling_group( + AutoScalingGroupName=asg_name, + AvailabilityZones=[a_zone], + DefaultCooldown=60, + DesiredCapacity=2, + HealthCheckGracePeriod=100, + HealthCheckType="EC2", + LaunchConfigurationName=config_name, + LoadBalancerNames=[lb_name], + MinSize=2, + MaxSize=2, + PlacementGroup="us_test_placement", + VPCZoneIdentifier=subnet_id, + TerminationPolicies=["OldestInstance", "NewestInstance"], + ) + + groups = as_client.describe_auto_scaling_groups()["AutoScalingGroups"] + groups.should.have.length_of(1) + group = groups[0] + + group["AutoScalingGroupName"].should.equal(asg_name) + group["DesiredCapacity"].should.equal(2) + group["MaxSize"].should.equal(2) + group["MinSize"].should.equal(2) + group["VPCZoneIdentifier"].should.equal(subnet_id) + group["LaunchConfigurationName"].should.equal(config_name) + group["DefaultCooldown"].should.equal(60) + group["HealthCheckGracePeriod"].should.equal(100) + group["HealthCheckType"].should.equal("EC2") + group["LoadBalancerNames"].should.equal([lb_name]) + group["PlacementGroup"].should.equal("us_test_placement") + group["TerminationPolicies"].should.equal(["OldestInstance", "NewestInstance"]) diff --git a/tests/test_ec2/test_route_tables.py b/tests/test_ec2/test_route_tables.py index f7c07a5a1..767036874 100644 --- a/tests/test_ec2/test_route_tables.py +++ b/tests/test_ec2/test_route_tables.py @@ -8,11 +8,12 @@ from boto.exception import EC2ResponseError from botocore.exceptions import ClientError import sure # noqa -from moto import mock_ec2, mock_ec2_deprecated +from moto import mock_ec2, mock_ec2_deprecated, settings from tests import EXAMPLE_AMI_ID from tests.helpers import requires_boto_gte +# Has boto3 equivalent @mock_ec2_deprecated def test_route_tables_defaults(): conn = boto.connect_vpc("the_key", "the_secret") @@ -38,6 +39,37 @@ def test_route_tables_defaults(): all_route_tables.should.have.length_of(0) +@mock_ec2 +def test_route_tables_defaults_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + all_route_tables = client.describe_route_tables( + Filters=[{"Name": "vpc-id", "Values": [vpc.id]}] + )["RouteTables"] + all_route_tables.should.have.length_of(1) + + main_route_table = all_route_tables[0] + main_route_table["VpcId"].should.equal(vpc.id) + + routes = main_route_table["Routes"] + routes.should.have.length_of(1) + + local_route = routes[0] + local_route["GatewayId"].should.equal("local") + local_route["State"].should.equal("active") + local_route["DestinationCidrBlock"].should.equal(vpc.cidr_block) + + vpc.delete() + + all_route_tables = client.describe_route_tables( + Filters=[{"Name": "vpc-id", "Values": [vpc.id]}] + )["RouteTables"] + all_route_tables.should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_route_tables_additional(): conn = boto.connect_vpc("the_key", "the_secret") @@ -78,6 +110,52 @@ def test_route_tables_additional(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_route_tables_additional_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + route_table = vpc.create_route_table() + + all_route_tables = client.describe_route_tables( + Filters=[{"Name": "vpc-id", "Values": [vpc.id]}] + )["RouteTables"] + all_route_tables.should.have.length_of(2) + all_route_tables[0]["VpcId"].should.equal(vpc.id) + all_route_tables[1]["VpcId"].should.equal(vpc.id) + + all_route_table_ids = [r["RouteTableId"] for r in all_route_tables] + all_route_table_ids.should.contain(route_table.route_table_id) + + routes = route_table.routes + routes.should.have.length_of(1) + + local_route = routes[0] + local_route.gateway_id.should.equal("local") + local_route.state.should.equal("active") + local_route.destination_cidr_block.should.equal(vpc.cidr_block) + + with pytest.raises(ClientError) as ex: + client.delete_vpc(VpcId=vpc.id) + ex.value.response["Error"]["Code"].should.equal("DependencyViolation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + + client.delete_route_table(RouteTableId=route_table.route_table_id) + + all_route_tables = client.describe_route_tables( + Filters=[{"Name": "vpc-id", "Values": [vpc.id]}] + )["RouteTables"] + all_route_tables.should.have.length_of(1) + + with pytest.raises(ClientError) as ex: + client.delete_route_table(RouteTableId="rtb-1234abcd") + ex.value.response["Error"]["Code"].should.equal("InvalidRouteTableID.NotFound") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_route_tables_filters_standard(): conn = boto.connect_vpc("the_key", "the_secret") @@ -122,6 +200,66 @@ def test_route_tables_filters_standard(): ).should.throw(NotImplementedError) +@mock_ec2 +def test_route_tables_filters_standard_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + route_table1 = ec2.create_route_table(VpcId=vpc1.id) + + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + route_table2 = ec2.create_route_table(VpcId=vpc2.id) + + all_route_tables = client.describe_route_tables()["RouteTables"] + all_route_tables.should.have.length_of(5) + + # Filter by main route table + main_route_tables = client.describe_route_tables( + Filters=[{"Name": "association.main", "Values": ["true"]}] + )["RouteTables"] + main_route_tables.should.have.length_of(3) + main_route_table_ids = [ + route_table["RouteTableId"] for route_table in main_route_tables + ] + main_route_table_ids.should_not.contain(route_table1.id) + main_route_table_ids.should_not.contain(route_table2.id) + + # Filter by VPC + vpc1_route_tables = client.describe_route_tables( + Filters=[{"Name": "vpc-id", "Values": [vpc1.id]}] + )["RouteTables"] + vpc1_route_tables.should.have.length_of(2) + vpc1_route_table_ids = [ + route_table["RouteTableId"] for route_table in vpc1_route_tables + ] + vpc1_route_table_ids.should.contain(route_table1.id) + vpc1_route_table_ids.should_not.contain(route_table2.id) + + # Filter by VPC and main route table + vpc2_main_route_tables = client.describe_route_tables( + Filters=[ + {"Name": "association.main", "Values": ["true"]}, + {"Name": "vpc-id", "Values": [vpc2.id]}, + ] + )["RouteTables"] + vpc2_main_route_tables.should.have.length_of(1) + vpc2_main_route_table_ids = [ + route_table["RouteTableId"] for route_table in vpc2_main_route_tables + ] + vpc2_main_route_table_ids.should_not.contain(route_table1.id) + vpc2_main_route_table_ids.should_not.contain(route_table2.id) + + # Unsupported filter + if not settings.TEST_SERVER_MODE: + # ServerMode will just throw a generic 500 + filters = [{"Name": "not-implemented-filter", "Values": ["foobar"]}] + client.describe_route_tables.when.called_with(Filters=filters).should.throw( + NotImplementedError + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_route_tables_filters_associations(): conn = boto.connect_vpc("the_key", "the_secret") @@ -165,6 +303,58 @@ def test_route_tables_filters_associations(): association1_route_tables[0].associations.should.have.length_of(2) +@mock_ec2 +def test_route_tables_filters_associations_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet1 = vpc.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/24") + subnet2 = vpc.create_subnet(VpcId=vpc.id, CidrBlock="10.0.1.0/24") + subnet3 = vpc.create_subnet(VpcId=vpc.id, CidrBlock="10.0.2.0/24") + route_table1 = ec2.create_route_table(VpcId=vpc.id) + route_table2 = ec2.create_route_table(VpcId=vpc.id) + + association_id1 = client.associate_route_table( + RouteTableId=route_table1.id, SubnetId=subnet1.id + )["AssociationId"] + client.associate_route_table(RouteTableId=route_table1.id, SubnetId=subnet2.id) + client.associate_route_table(RouteTableId=route_table2.id, SubnetId=subnet3.id) + + all_route_tables = client.describe_route_tables()["RouteTables"] + all_route_tables.should.have.length_of(4) + + # Filter by association ID + association1_route_tables = client.describe_route_tables( + Filters=[ + { + "Name": "association.route-table-association-id", + "Values": [association_id1], + } + ] + )["RouteTables"] + association1_route_tables.should.have.length_of(1) + association1_route_tables[0]["RouteTableId"].should.equal(route_table1.id) + association1_route_tables[0]["Associations"].should.have.length_of(2) + + # Filter by route table ID + route_table2_route_tables = client.describe_route_tables( + Filters=[{"Name": "association.route-table-id", "Values": [route_table2.id]}] + )["RouteTables"] + route_table2_route_tables.should.have.length_of(1) + route_table2_route_tables[0]["RouteTableId"].should.equal(route_table2.id) + route_table2_route_tables[0]["Associations"].should.have.length_of(1) + + # Filter by subnet ID + subnet_route_tables = client.describe_route_tables( + Filters=[{"Name": "association.subnet-id", "Values": [subnet1.id]}] + )["RouteTables"] + subnet_route_tables.should.have.length_of(1) + subnet_route_tables[0]["RouteTableId"].should.equal(route_table1.id) + subnet_route_tables[0]["Associations"].should.have.length_of(2) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_route_table_associations(): conn = boto.connect_vpc("the_key", "the_secret") @@ -231,6 +421,81 @@ def test_route_table_associations(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_route_table_associations_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + route_table = ec2.create_route_table(VpcId=vpc.id) + + all_route_tables = client.describe_route_tables()["RouteTables"] + all_route_tables.should.have.length_of(3) + + # Refresh + r = client.describe_route_tables(RouteTableIds=[route_table.id])["RouteTables"][0] + r["Associations"].should.have.length_of(0) + + # Associate + association_id = client.associate_route_table( + RouteTableId=route_table.id, SubnetId=subnet.id + )["AssociationId"] + + # Refresh + r = client.describe_route_tables(RouteTableIds=[route_table.id])["RouteTables"][0] + r["Associations"].should.have.length_of(1) + + r["Associations"][0]["RouteTableAssociationId"].should.equal(association_id) + r["Associations"][0]["Main"].should.equal(True) + r["Associations"][0]["RouteTableId"].should.equal(route_table.id) + r["Associations"][0]["SubnetId"].should.equal(subnet.id) + + # Associate is idempotent + association_id_idempotent = client.associate_route_table( + RouteTableId=route_table.id, SubnetId=subnet.id + )["AssociationId"] + association_id_idempotent.should.equal(association_id) + + # Error: Attempt delete associated route table. + with pytest.raises(ClientError) as ex: + client.delete_route_table(RouteTableId=route_table.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("DependencyViolation") + + # Disassociate + client.disassociate_route_table(AssociationId=association_id) + + # Refresh + r = client.describe_route_tables(RouteTableIds=[route_table.id])["RouteTables"][0] + r["Associations"].should.have.length_of(0) + + # Error: Disassociate with invalid association ID + with pytest.raises(ClientError) as ex: + client.disassociate_route_table(AssociationId=association_id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAssociationID.NotFound") + + # Error: Associate with invalid subnet ID + with pytest.raises(ClientError) as ex: + client.associate_route_table( + RouteTableId=route_table.id, SubnetId="subnet-1234abcd" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidSubnetID.NotFound") + + # Error: Associate with invalid route table ID + with pytest.raises(ClientError) as ex: + client.associate_route_table(RouteTableId="rtb-1234abcd", SubnetId=subnet.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidRouteTableID.NotFound") + + +# Has boto3 equivalent @requires_boto_gte("2.16.0") @mock_ec2_deprecated def test_route_table_replace_route_table_association(): @@ -308,6 +573,99 @@ def test_route_table_replace_route_table_association(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_route_table_replace_route_table_association_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + route_table1_id = ec2.create_route_table(VpcId=vpc.id).id + route_table2_id = ec2.create_route_table(VpcId=vpc.id).id + + all_route_tables = client.describe_route_tables()["RouteTables"] + all_route_tables.should.have.length_of(4) + + # Refresh + route_table1 = client.describe_route_tables(RouteTableIds=[route_table1_id])[ + "RouteTables" + ][0] + route_table1["Associations"].should.have.length_of(0) + + # Associate + association_id1 = client.associate_route_table( + RouteTableId=route_table1_id, SubnetId=subnet.id + )["AssociationId"] + + # Refresh + route_table1 = client.describe_route_tables(RouteTableIds=[route_table1_id])[ + "RouteTables" + ][0] + route_table2 = client.describe_route_tables(RouteTableIds=[route_table2_id])[ + "RouteTables" + ][0] + + # Validate + route_table1["Associations"].should.have.length_of(1) + route_table2["Associations"].should.have.length_of(0) + + route_table1["Associations"][0]["RouteTableAssociationId"].should.equal( + association_id1 + ) + route_table1["Associations"][0]["Main"].should.equal(True) + route_table1["Associations"][0]["RouteTableId"].should.equal(route_table1_id) + route_table1["Associations"][0]["SubnetId"].should.equal(subnet.id) + + # Replace Association + association_id2 = client.replace_route_table_association( + AssociationId=association_id1, RouteTableId=route_table2_id + )["NewAssociationId"] + + # Refresh + route_table1 = client.describe_route_tables(RouteTableIds=[route_table1_id])[ + "RouteTables" + ][0] + route_table2 = client.describe_route_tables(RouteTableIds=[route_table2_id])[ + "RouteTables" + ][0] + + # Validate + route_table1["Associations"].should.have.length_of(0) + route_table2["Associations"].should.have.length_of(1) + + route_table2["Associations"][0]["RouteTableAssociationId"].should.equal( + association_id2 + ) + route_table2["Associations"][0]["Main"].should.equal(True) + route_table2["Associations"][0]["RouteTableId"].should.equal(route_table2_id) + route_table2["Associations"][0]["SubnetId"].should.equal(subnet.id) + + # Replace Association is idempotent + association_id_idempotent = client.replace_route_table_association( + AssociationId=association_id2, RouteTableId=route_table2_id + )["NewAssociationId"] + association_id_idempotent.should.equal(association_id2) + + # Error: Replace association with invalid association ID + with pytest.raises(ClientError) as ex: + client.replace_route_table_association( + AssociationId="rtbassoc-1234abcd", RouteTableId=route_table1_id + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidAssociationID.NotFound") + + # Error: Replace association with invalid route table ID + with pytest.raises(ClientError) as ex: + client.replace_route_table_association( + AssociationId=association_id2, RouteTableId="rtb-1234abcd" + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidRouteTableID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_route_table_get_by_tag(): conn = boto.connect_vpc("the_key", "the_secret") @@ -345,6 +703,7 @@ def test_route_table_get_by_tag_boto3(): route_tables[0].tags[0].should.equal({"Key": "Name", "Value": "TestRouteTable"}) +# Has boto3 equivalent @mock_ec2_deprecated def test_routes_additional(): conn = boto.connect_vpc("the_key", "the_secret") @@ -395,6 +754,60 @@ def test_routes_additional(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_routes_additional_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + main_route_table_id = client.describe_route_tables( + Filters=[{"Name": "vpc-id", "Values": [vpc.id]}] + )["RouteTables"][0]["RouteTableId"] + main_route_table = ec2.RouteTable(main_route_table_id) + + main_route_table.routes.should.have.length_of(1) + igw = ec2.create_internet_gateway() + ROUTE_CIDR = "10.0.0.4/24" + + main_route_table.create_route(DestinationCidrBlock=ROUTE_CIDR, GatewayId=igw.id) + + main_route_table.routes.should.have.length_of(2) + new_routes = [ + route + for route in main_route_table.routes + if route.destination_cidr_block != vpc.cidr_block + ] + new_routes.should.have.length_of(1) + + new_route = new_routes[0] + new_route.gateway_id.should.equal(igw.id) + new_route.instance_id.should.be.none + new_route.state.should.equal("active") + new_route.destination_cidr_block.should.equal(ROUTE_CIDR) + + client.delete_route( + RouteTableId=main_route_table.id, DestinationCidrBlock=ROUTE_CIDR + ) + + main_route_table.reload() + + main_route_table.routes.should.have.length_of(1) + new_routes = [ + route + for route in main_route_table.routes + if route.destination_cidr_block != vpc.cidr_block + ] + new_routes.should.have.length_of(0) + + with pytest.raises(ClientError) as ex: + client.delete_route( + RouteTableId=main_route_table.id, DestinationCidrBlock=ROUTE_CIDR + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidRoute.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_routes_replace(): conn = boto.connect_vpc("the_key", "the_secret") @@ -448,6 +861,78 @@ def test_routes_replace(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_routes_replace_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + main_route_table_id = client.describe_route_tables( + Filters=[ + {"Name": "vpc-id", "Values": [vpc.id]}, + {"Name": "association.main", "Values": ["true"]}, + ] + )["RouteTables"][0]["RouteTableId"] + main_route_table = ec2.RouteTable(main_route_table_id) + ROUTE_CIDR = "10.0.0.4/24" + + # Various route targets + igw = ec2.create_internet_gateway() + + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + # Create initial route + main_route_table.create_route(DestinationCidrBlock=ROUTE_CIDR, GatewayId=igw.id) + + # Replace... + def get_target_route(): + route_table = client.describe_route_tables(RouteTableIds=[main_route_table.id])[ + "RouteTables" + ][0] + routes = [ + route + for route in route_table["Routes"] + if route["DestinationCidrBlock"] != vpc.cidr_block + ] + routes.should.have.length_of(1) + return routes[0] + + client.replace_route( + RouteTableId=main_route_table.id, + DestinationCidrBlock=ROUTE_CIDR, + InstanceId=instance.id, + ) + + target_route = get_target_route() + target_route.shouldnt.have.key("GatewayId") + target_route["InstanceId"].should.equal(instance.id) + target_route["State"].should.equal("active") + target_route["DestinationCidrBlock"].should.equal(ROUTE_CIDR) + + client.replace_route( + RouteTableId=main_route_table.id, + DestinationCidrBlock=ROUTE_CIDR, + GatewayId=igw.id, + ) + + target_route = get_target_route() + target_route["GatewayId"].should.equal(igw.id) + target_route.shouldnt.have.key("InstanceId") + target_route["State"].should.equal("active") + target_route["DestinationCidrBlock"].should.equal(ROUTE_CIDR) + + with pytest.raises(ClientError) as ex: + client.replace_route( + RouteTableId="rtb-1234abcd", + DestinationCidrBlock=ROUTE_CIDR, + GatewayId=igw.id, + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidRouteTableID.NotFound") + + +# Has boto3 equivalent @requires_boto_gte("2.19.0") @mock_ec2_deprecated def test_routes_not_supported(): @@ -471,6 +956,48 @@ def test_routes_not_supported(): ).should.throw(NotImplementedError) +@mock_ec2 +def test_routes_not_supported_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + main_route_table_id = client.describe_route_tables()["RouteTables"][0][ + "RouteTableId" + ] + main_route_table = ec2.RouteTable(main_route_table_id) + + ROUTE_CIDR = "10.0.0.4/24" + + # Create + with pytest.raises(ClientError) as ex: + client.create_route( + RouteTableId=main_route_table_id, + DestinationCidrBlock=ROUTE_CIDR, + NetworkInterfaceId="eni-1234abcd", + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal( + "InvalidNetworkInterfaceID.NotFound" + ) + + igw = ec2.create_internet_gateway() + client.create_route( + RouteTableId=main_route_table_id, + DestinationCidrBlock=ROUTE_CIDR, + GatewayId=igw.id, + ) + + # Replace + if not settings.TEST_SERVER_MODE: + args = { + "RouteTableId": main_route_table.id, + "DestinationCidrBlock": ROUTE_CIDR, + "NetworkInterfaceId": "eni-1234abcd", + } + client.replace_route.when.called_with(**args).should.throw(NotImplementedError) + + +# Has boto3 equivalent @requires_boto_gte("2.34.0") @mock_ec2_deprecated def test_routes_vpc_peering_connection(): @@ -506,6 +1033,45 @@ def test_routes_vpc_peering_connection(): new_route.destination_cidr_block.should.equal(ROUTE_CIDR) +@mock_ec2 +def test_routes_vpc_peering_connection_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + main_route_table_id = client.describe_route_tables( + Filters=[ + {"Name": "vpc-id", "Values": [vpc.id]}, + {"Name": "association.main", "Values": ["true"]}, + ] + )["RouteTables"][0]["RouteTableId"] + main_route_table = ec2.RouteTable(main_route_table_id) + ROUTE_CIDR = "10.0.0.4/24" + + peer_vpc = ec2.create_vpc(CidrBlock="11.0.0.0/16") + vpc_pcx = ec2.create_vpc_peering_connection(VpcId=vpc.id, PeerVpcId=peer_vpc.id) + + main_route_table.create_route( + DestinationCidrBlock=ROUTE_CIDR, VpcPeeringConnectionId=vpc_pcx.id + ) + + # Refresh route table + main_route_table.reload() + new_routes = [ + route + for route in main_route_table.routes + if route.destination_cidr_block != vpc.cidr_block + ] + new_routes.should.have.length_of(1) + + new_route = new_routes[0] + new_route.gateway_id.should.be.none + new_route.instance_id.should.be.none + new_route.vpc_peering_connection_id.should.equal(vpc_pcx.id) + new_route.state.should.equal("active") + new_route.destination_cidr_block.should.equal(ROUTE_CIDR) + + +# Has boto3 equivalent @requires_boto_gte("2.34.0") @mock_ec2_deprecated def test_routes_vpn_gateway(): @@ -535,6 +1101,40 @@ def test_routes_vpn_gateway(): new_route.vpc_peering_connection_id.should.be.none +@mock_ec2 +def test_routes_vpn_gateway_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + main_route_table_id = client.describe_route_tables( + Filters=[ + {"Name": "vpc-id", "Values": [vpc.id]}, + {"Name": "association.main", "Values": ["true"]}, + ] + )["RouteTables"][0]["RouteTableId"] + main_route_table = ec2.RouteTable(main_route_table_id) + ROUTE_CIDR = "10.0.0.4/24" + + vpn_gw_id = client.create_vpn_gateway(Type="ipsec.1")["VpnGateway"]["VpnGatewayId"] + + main_route_table.create_route(DestinationCidrBlock=ROUTE_CIDR, GatewayId=vpn_gw_id) + + main_route_table.reload() + new_routes = [ + route + for route in main_route_table.routes + if route.destination_cidr_block != vpc.cidr_block + ] + new_routes.should.have.length_of(1) + + new_route = new_routes[0] + new_route.gateway_id.should.equal(vpn_gw_id) + new_route.instance_id.should.be.none + new_route.vpc_peering_connection_id.should.be.none + + +# Has boto3 equivalent @mock_ec2_deprecated def test_network_acl_tagging(): @@ -554,6 +1154,27 @@ def test_network_acl_tagging(): test_route_table.tags["a key"].should.equal("some value") +@mock_ec2 +def test_network_acl_tagging_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + ec2 = boto3.resource("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + route_table = ec2.create_route_table(VpcId=vpc.id) + route_table.create_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + + tag = client.describe_tags()["Tags"][0] + tag.should.have.key("ResourceType").equal("route-table") + tag.should.have.key("Key").equal("a key") + tag.should.have.key("Value").equal("some value") + + all_route_tables = client.describe_route_tables()["RouteTables"] + test_route_table = next( + na for na in all_route_tables if na["RouteTableId"] == route_table.id + ) + test_route_table["Tags"].should.equal([{"Value": "some value", "Key": "a key"}]) + + @mock_ec2 def test_create_route_with_invalid_destination_cidr_block_parameter(): ec2 = boto3.resource("ec2", region_name="us-west-1") diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 432d4441a..6a540503b 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -16,6 +16,7 @@ from moto import mock_ec2, mock_ec2_deprecated, settings from moto.ec2 import ec2_backend +# Has boto3 equivalent @mock_ec2_deprecated def test_create_and_describe_security_group(): conn = boto.connect_ec2("the_key", "the_secret") @@ -53,6 +54,41 @@ def test_create_and_describe_security_group(): set(group_names).should.equal(set(["default", "test security group"])) +@mock_ec2 +def test_create_and_describe_security_group_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + with pytest.raises(ClientError) as ex: + client.create_security_group(GroupName="test", Description="test", DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateSecurityGroup operation: Request would have succeeded, but DryRun flag is set" + ) + + security_group = ec2.create_security_group( + GroupName="test security group", Description="test" + ) + + security_group.group_name.should.equal("test security group") + security_group.description.should.equal("test") + + # Trying to create another group with the same name should throw an error + with pytest.raises(ClientError) as ex: + client.create_security_group(GroupName="test security group", Description="n/a") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidGroup.Duplicate") + + all_groups = client.describe_security_groups()["SecurityGroups"] + # The default group gets created automatically + all_groups.should.have.length_of(2) + group_names = [group["GroupName"] for group in all_groups] + set(group_names).should.equal(set(["default", "test security group"])) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_security_group_without_description_raises_error(): conn = boto.connect_ec2("the_key", "the_secret") @@ -64,6 +100,18 @@ def test_create_security_group_without_description_raises_error(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_create_security_group_without_description_raises_error_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + + with pytest.raises(ClientError) as ex: + ec2.create_security_group(GroupName="test security group", Description="") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("MissingParameter") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_default_security_group(): conn = boto.ec2.connect_to_region("us-east-1") @@ -72,6 +120,15 @@ def test_default_security_group(): groups[0].name.should.equal("default") +@mock_ec2 +def test_default_security_group_boto3(): + client = boto3.client("ec2", "us-west-1") + groups = client.describe_security_groups()["SecurityGroups"] + groups.should.have.length_of(1) + groups[0]["GroupName"].should.equal("default") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_and_describe_vpc_security_group(): conn = boto.connect_ec2("the_key", "the_secret") @@ -103,6 +160,46 @@ def test_create_and_describe_vpc_security_group(): all_groups[0].name.should.equal("test security group") +@mock_ec2 +def test_create_and_describe_vpc_security_group_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + name = "test secgr" + vpc_id = "vpc-5300000c" + security_group = ec2.create_security_group( + GroupName=name, Description="test", VpcId=vpc_id + ) + + security_group.vpc_id.should.equal(vpc_id) + + security_group.group_name.should.equal(name) + security_group.description.should.equal("test") + + # Trying to create another group with the same name in the same VPC should + # throw an error + with pytest.raises(ClientError) as ex: + ec2.create_security_group(GroupName=name, Description="n/a", VpcId=vpc_id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidGroup.Duplicate") + + # Trying to create another group in the same name without VPC should pass + ec2.create_security_group(GroupName=name, Description="non-vpc-group") + + all_groups = client.describe_security_groups()["SecurityGroups"] + all_groups.should.have.length_of(3) # 1 default, 1 vpc, 1 no-vpc + + all_groups = client.describe_security_groups( + Filters=[{"Name": "vpc_id", "Values": [vpc_id]}] + )["SecurityGroups"] + + all_groups.should.have.length_of(1) + all_groups[0]["VpcId"].should.equal(vpc_id) + all_groups[0]["GroupName"].should.equal(name) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_two_security_groups_with_same_name_in_different_vpc(): conn = boto.connect_ec2("the_key", "the_secret") @@ -124,6 +221,26 @@ def test_create_two_security_groups_with_same_name_in_different_vpc(): set(group_names).should.equal(set(["default", "test security group"])) +@mock_ec2 +def test_create_two_security_groups_with_same_name_in_different_vpc_boto3(): + ec2 = boto3.resource("ec2", "us-east-1") + client = boto3.client("ec2", "us-east-1") + + name = "test security group" + vpc_id = "vpc-5300000c" + vpc_id2 = "vpc-5300000d" + + ec2.create_security_group(GroupName=name, Description="n/a 1", VpcId=vpc_id) + ec2.create_security_group(GroupName=name, Description="n/a 2", VpcId=vpc_id2) + + all_groups = client.describe_security_groups()["SecurityGroups"] + + all_groups.should.have.length_of(3) + group_names = [group["GroupName"] for group in all_groups] + # The default group is created automatically + set(group_names).should.equal(set(["default", name])) + + @mock_ec2 def test_create_two_security_groups_in_vpc_with_ipv6_enabled(): ec2 = boto3.resource("ec2", region_name="us-west-1") @@ -137,6 +254,7 @@ def test_create_two_security_groups_in_vpc_with_ipv6_enabled(): security_group.ip_permissions_egress.should.have.length_of(2) +# Has boto3 equivalent @mock_ec2_deprecated def test_deleting_security_groups(): conn = boto.connect_ec2("the_key", "the_secret") @@ -169,6 +287,40 @@ def test_deleting_security_groups(): conn.get_all_security_groups().should.have.length_of(1) +@mock_ec2 +def test_deleting_security_groups_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + security_group1 = ec2.create_security_group(GroupName="test1", Description="test1") + ec2.create_security_group(GroupName="test2", Description="test2") + + client.describe_security_groups()["SecurityGroups"].should.have.length_of(3) + + # Deleting a group that doesn't exist should throw an error + with pytest.raises(ClientError) as ex: + client.delete_security_group(GroupName="foobar") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidGroup.NotFound") + + # Delete by name + with pytest.raises(ClientError) as ex: + client.delete_security_group(GroupName="test2", DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DeleteSecurityGroup operation: Request would have succeeded, but DryRun flag is set" + ) + + client.delete_security_group(GroupName="test2") + client.describe_security_groups()["SecurityGroups"].should.have.length_of(2) + + # Delete by group id + client.delete_security_group(GroupId=security_group1.id) + client.describe_security_groups()["SecurityGroups"].should.have.length_of(1) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_security_group_in_vpc(): conn = boto.connect_ec2("the_key", "the_secret") @@ -179,6 +331,26 @@ def test_delete_security_group_in_vpc(): conn.delete_security_group(group_id=security_group1.id) +@mock_ec2 +def test_delete_security_group_in_vpc_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + + client.describe_security_groups()["SecurityGroups"].should.have.length_of(1) + + group = ec2.create_security_group( + GroupName="test1", Description="test1", VpcId="vpc-12345" + ) + + client.describe_security_groups()["SecurityGroups"].should.have.length_of(2) + + # this should not throw an exception + client.delete_security_group(GroupId=group.id) + + client.describe_security_groups()["SecurityGroups"].should.have.length_of(1) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_authorize_ip_range_and_revoke(): conn = boto.connect_ec2("the_key", "the_secret") @@ -316,6 +488,126 @@ def test_authorize_ip_range_and_revoke(): egress_security_group.rules_egress.should.have.length_of(1) +@mock_ec2 +def test_authorize_ip_range_and_revoke_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + security_group = ec2.create_security_group(GroupName="test", Description="test") + + with pytest.raises(ClientError) as ex: + security_group.authorize_ingress( + IpProtocol="tcp", + FromPort=22, + ToPort=2222, + CidrIp="123.123.123.123/32", + DryRun=True, + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the GrantSecurityGroupIngress operation: Request would have succeeded, but DryRun flag is set" + ) + + ingress_permissions = [ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 2222, + "IpRanges": [{"CidrIp": "123.123.123.123/32"}], + } + ] + + security_group.authorize_ingress(IpPermissions=ingress_permissions) + + security_group.ip_permissions.should.have.length_of(1) + security_group.ip_permissions[0]["ToPort"].should.equal(2222) + security_group.ip_permissions[0]["IpProtocol"].should.equal("tcp") + security_group.ip_permissions[0]["IpRanges"].should.equal( + [{"CidrIp": "123.123.123.123/32"}] + ) + + # Wrong Cidr should throw error + with pytest.raises(ClientError) as ex: + wrong_permissions = copy.deepcopy(ingress_permissions) + wrong_permissions[0]["IpRanges"][0]["CidrIp"] = "123.123.123.122/32" + security_group.revoke_ingress(IpPermissions=wrong_permissions) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidPermission.NotFound") + + # Actually revoke + with pytest.raises(ClientError) as ex: + security_group.revoke_ingress(IpPermissions=ingress_permissions, DryRun=True) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the RevokeSecurityGroupIngress operation: Request would have succeeded, but DryRun flag is set" + ) + + security_group.revoke_ingress(IpPermissions=ingress_permissions) + + security_group.ip_permissions.should.have.length_of(0) + + # Test for egress as well + egress_security_group = ec2.create_security_group( + GroupName="testegress", Description="testegress", VpcId="vpc-3432589" + ) + egress_permissions = [ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 2222, + "IpRanges": [{"CidrIp": "123.123.123.123/32"}], + } + ] + + with pytest.raises(ClientError) as ex: + egress_security_group.authorize_egress( + IpPermissions=egress_permissions, DryRun=True + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the GrantSecurityGroupEgress operation: Request would have succeeded, but DryRun flag is set" + ) + + egress_security_group.authorize_egress(IpPermissions=egress_permissions) + + egress_security_group.ip_permissions_egress[0]["FromPort"].should.equal(22) + egress_security_group.ip_permissions_egress[0]["IpProtocol"].should.equal("tcp") + egress_security_group.ip_permissions_egress[0]["ToPort"].should.equal(2222) + egress_security_group.ip_permissions_egress[0]["IpRanges"].should.equal( + [{"CidrIp": "123.123.123.123/32"}] + ) + + # Wrong Cidr should throw error + with pytest.raises(ClientError) as ex: + wrong_permissions = copy.deepcopy(egress_permissions) + wrong_permissions[0]["IpRanges"][0]["CidrIp"] = "123.123.123.122/32" + security_group.revoke_egress(IpPermissions=wrong_permissions) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidPermission.NotFound") + + # Actually revoke + with pytest.raises(ClientError) as ex: + egress_security_group.revoke_egress( + IpPermissions=egress_permissions, DryRun=True, + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the RevokeSecurityGroupEgress operation: Request would have succeeded, but DryRun flag is set" + ) + + egress_security_group.revoke_egress(IpPermissions=egress_permissions) + + egress_security_group = client.describe_security_groups()["SecurityGroups"][0] + # There is still the default outbound rule + egress_security_group["IpPermissionsEgress"].should.have.length_of(1) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_authorize_other_group_and_revoke(): conn = boto.connect_ec2("the_key", "the_secret") @@ -360,6 +652,59 @@ def test_authorize_other_group_and_revoke(): security_group.rules.should.have.length_of(0) +@mock_ec2 +def test_authorize_other_group_and_revoke_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + security_group = ec2.create_security_group(GroupName="test", Description="test") + other_security_group = ec2.create_security_group( + GroupName="other", Description="other" + ) + ec2.create_security_group(GroupName="wrong", Description="wrong") + + # Note: Should be easier to use the SourceSecurityGroupNames-parameter, but that's not supported atm + permissions = [ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 2222, + "UserIdGroupPairs": [ + { + "GroupId": other_security_group.id, + "GroupName": other_security_group.group_name, + "UserId": other_security_group.owner_id, + } + ], + } + ] + security_group.authorize_ingress(IpPermissions=permissions) + + found_sec_group = client.describe_security_groups(GroupNames=["test"])[ + "SecurityGroups" + ][0] + found_sec_group["IpPermissions"][0]["ToPort"].should.equal(2222) + found_sec_group["IpPermissions"][0]["UserIdGroupPairs"][0]["GroupId"].should.equal( + other_security_group.id + ) + + # Wrong source group should throw error + with pytest.raises(ClientError) as ex: + wrong_permissions = copy.deepcopy(permissions) + wrong_permissions[0]["UserIdGroupPairs"][0]["GroupId"] = "unknown" + security_group.revoke_ingress(IpPermissions=wrong_permissions) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidGroup.NotFound") + + # Actually revoke + security_group.revoke_ingress(IpPermissions=permissions) + + found_sec_group = client.describe_security_groups(GroupNames=["test"])[ + "SecurityGroups" + ][0] + found_sec_group["IpPermissions"].should.have.length_of(0) + + @mock_ec2 def test_authorize_other_group_egress_and_revoke(): ec2 = boto3.resource("ec2", region_name="us-west-1") @@ -395,6 +740,7 @@ def test_authorize_other_group_egress_and_revoke(): sg01.ip_permissions_egress.should.have.length_of(1) +# Has boto3 equivalent @mock_ec2_deprecated def test_authorize_group_in_vpc(): conn = boto.connect_ec2("the_key", "the_secret") @@ -435,6 +781,56 @@ def test_authorize_group_in_vpc(): security_group.rules.should.have.length_of(0) +@mock_ec2 +def test_authorize_group_in_vpc_boto3(): + ec2 = boto3.resource("ec2", "ap-south-1") + client = boto3.client("ec2", region_name="ap-south-1") + vpc_id = "vpc-12345" + + # create 2 groups in a vpc + security_group = ec2.create_security_group( + GroupName="test1", Description="test1", VpcId=vpc_id + ) + other_security_group = ec2.create_security_group( + GroupName="test2", Description="test2", VpcId=vpc_id + ) + + permissions = [ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 2222, + "UserIdGroupPairs": [ + { + "GroupId": other_security_group.id, + "GroupName": other_security_group.group_name, + "UserId": other_security_group.owner_id, + } + ], + } + ] + security_group.authorize_ingress(IpPermissions=permissions) + + # Check that the rule is accurate + found_sec_group = client.describe_security_groups(GroupNames=["test1"])[ + "SecurityGroups" + ][0] + found_sec_group["IpPermissions"][0]["ToPort"].should.equal(2222) + found_sec_group["IpPermissions"][0]["UserIdGroupPairs"][0]["GroupId"].should.equal( + other_security_group.id + ) + + # Now remove the rule + security_group.revoke_ingress(IpPermissions=permissions) + + # And check that it gets revoked + found_sec_group = client.describe_security_groups(GroupNames=["test1"])[ + "SecurityGroups" + ][0] + found_sec_group["IpPermissions"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_security_groups(): conn = boto.connect_ec2() @@ -472,6 +868,43 @@ def test_get_all_security_groups(): resp.should.have.length_of(3) +@mock_ec2 +def test_describe_security_groups(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + vpc_id = "vpc-mjm05d27" + sg1 = ec2.create_security_group( + GroupName="test1", Description="test1", VpcId=vpc_id + ) + ec2.create_security_group(GroupName="test2", Description="test2") + + resp = client.describe_security_groups(GroupNames=["test1"])["SecurityGroups"] + resp.should.have.length_of(1) + resp[0].should.have.key("GroupId").equal(sg1.id) + + with pytest.raises(ClientError) as ex: + client.describe_security_groups(GroupNames=["does_not_exist"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidGroup.NotFound") + + resp = client.describe_security_groups( + Filters=[{"Name": "vpc-id", "Values": [vpc_id]}] + )["SecurityGroups"] + resp.should.have.length_of(1) + resp[0].should.have.key("GroupId").equal(sg1.id) + + resp = client.describe_security_groups( + Filters=[{"Name": "description", "Values": ["test1"]}] + )["SecurityGroups"] + resp.should.have.length_of(1) + resp[0].should.have.key("GroupId").equal(sg1.id) + + resp = client.describe_security_groups()["SecurityGroups"] + resp.should.have.length_of(3) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_authorize_bad_cidr_throws_invalid_parameter_value(): conn = boto.connect_ec2("the_key", "the_secret") @@ -485,6 +918,26 @@ def test_authorize_bad_cidr_throws_invalid_parameter_value(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_authorize_bad_cidr_throws_invalid_parameter_value_boto3(): + ec2 = boto3.resource("ec2", "us-west-1") + sec_group = ec2.create_security_group(GroupName="test", Description="test") + with pytest.raises(ClientError) as ex: + permissions = [ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 2222, + "IpRanges": [{"CidrIp": "123.123.123.123"}], + } + ] + sec_group.authorize_ingress(IpPermissions=permissions) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_security_group_tagging(): conn = boto.connect_vpc() @@ -511,6 +964,7 @@ def test_security_group_tagging(): group.tags["Test"].should.equal("Tag") +# Has boto3 equivalent @mock_ec2_deprecated def test_security_group_tag_filtering(): conn = boto.connect_ec2() @@ -521,6 +975,25 @@ def test_security_group_tag_filtering(): groups.should.have.length_of(1) +@mock_ec2 +def test_security_group_tag_filtering_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + sg = ec2.create_security_group(GroupName="test-sg", Description="Test SG") + sg.create_tags(Tags=[{"Key": "test-tag", "Value": "test-value"}]) + + groups = client.describe_security_groups( + Filters=[{"Name": "tag:test-tag", "Values": ["test-value"]}] + )["SecurityGroups"] + groups.should.have.length_of(1) + + groups = client.describe_security_groups( + Filters=[{"Name": "tag:test-tag", "Values": ["unknown"]}] + )["SecurityGroups"] + groups.should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_authorize_all_protocols_with_no_port_specification(): conn = boto.connect_ec2() @@ -534,6 +1007,24 @@ def test_authorize_all_protocols_with_no_port_specification(): sg.rules[0].to_port.should.equal(None) +@mock_ec2 +def test_authorize_all_protocols_with_no_port_specification_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + sg = ec2.create_security_group(GroupName="test", Description="test") + + permissions = [{"IpProtocol": "-1", "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}] + sg.authorize_ingress(IpPermissions=permissions) + + sg = client.describe_security_groups(GroupNames=["test"])["SecurityGroups"][0] + permission = sg["IpPermissions"][0] + permission.should.have.key("IpProtocol").equal("-1") + permission.should.have.key("IpRanges").equal([{"CidrIp": "0.0.0.0/0"}]) + permission.shouldnt.have.key("FromPort") + permission.shouldnt.have.key("ToPort") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_sec_group_rule_limit(): ec2_conn = boto.connect_ec2() @@ -599,6 +1090,123 @@ def test_sec_group_rule_limit(): cm.value.error_code.should.equal("RulesPerSecurityGroupLimitExceeded") +@mock_ec2 +@pytest.mark.parametrize( + "use_vpc", [True, False], ids=["Use VPC", "Without VPC"], +) +def test_sec_group_rule_limit_boto3(use_vpc): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + limit = 60 + if use_vpc: + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + sg = ec2.create_security_group( + GroupName="test", Description="test", VpcId=vpc.id + ) + other_sg = ec2.create_security_group( + GroupName="test_2", Description="test_other", VpcId=vpc.id + ) + else: + sg = ec2.create_security_group(GroupName="test", Description="test") + other_sg = ec2.create_security_group( + GroupName="test_2", Description="test_other" + ) + + # INGRESS + with pytest.raises(ClientError) as ex: + ip_permissions = [ + { + "IpProtocol": "-1", + "IpRanges": [{"CidrIp": "{}.0.0.0/0".format(i)} for i in range(110)], + } + ] + client.authorize_security_group_ingress( + GroupId=sg.id, IpPermissions=ip_permissions + ) + ex.value.response["Error"]["Code"].should.equal( + "RulesPerSecurityGroupLimitExceeded" + ) + + sg.reload() + sg.ip_permissions.should.be.empty + # authorize a rule targeting a different sec group (because this count too) + other_permissions = [ + { + "IpProtocol": "-1", + "UserIdGroupPairs": [ + { + "GroupId": other_sg.id, + "GroupName": other_sg.group_name, + "UserId": other_sg.owner_id, + } + ], + } + ] + client.authorize_security_group_ingress( + GroupId=sg.id, IpPermissions=other_permissions + ) + # fill the rules up the limit + permissions = [ + { + "IpProtocol": "-1", + "IpRanges": [{"CidrIp": "{}.0.0.0/0".format(i)} for i in range(limit - 1)], + } + ] + client.authorize_security_group_ingress(GroupId=sg.id, IpPermissions=permissions) + # verify that we cannot authorize past the limit for a CIDR IP + with pytest.raises(ClientError) as ex: + permissions = [{"IpProtocol": "-1", "IpRanges": [{"CidrIp": "100.0.0.0/0"}],}] + client.authorize_security_group_ingress( + GroupId=sg.id, IpPermissions=permissions + ) + ex.value.response["Error"]["Code"].should.equal( + "RulesPerSecurityGroupLimitExceeded" + ) + # verify that we cannot authorize past the limit for a different sec group + with pytest.raises(ClientError) as ex: + client.authorize_security_group_ingress( + GroupId=sg.id, IpPermissions=other_permissions + ) + ex.value.response["Error"]["Code"].should.equal( + "RulesPerSecurityGroupLimitExceeded" + ) + + # EGRESS + # authorize a rule targeting a different sec group (because this count too) + client.authorize_security_group_egress( + GroupId=sg.id, IpPermissions=other_permissions + ) + # fill the rules up the limit + # remember that by default, when created a sec group contains 1 egress rule + # so our other_sg rule + 98 CIDR IP rules + 1 by default == 100 the limit + permissions = [ + { + "IpProtocol": "-1", + "IpRanges": [ + {"CidrIp": "{}.0.0.0/0".format(i)} for i in range(1, limit - 1) + ], + } + ] + client.authorize_security_group_egress(GroupId=sg.id, IpPermissions=permissions) + # verify that we cannot authorize past the limit for a CIDR IP + with pytest.raises(ClientError) as ex: + permissions = [{"IpProtocol": "-1", "IpRanges": [{"CidrIp": "101.0.0.0/0"}],}] + client.authorize_security_group_egress(GroupId=sg.id, IpPermissions=permissions) + ex.value.response["Error"]["Code"].should.equal( + "RulesPerSecurityGroupLimitExceeded" + ) + # verify that we cannot authorize past the limit for a different sec group + with pytest.raises(ClientError) as ex: + client.authorize_security_group_egress( + GroupId=sg.id, IpPermissions=other_permissions + ) + ex.value.response["Error"]["Code"].should.equal( + "RulesPerSecurityGroupLimitExceeded" + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_sec_group_rule_limit_vpc(): ec2_conn = boto.connect_ec2() @@ -1013,6 +1621,7 @@ def test_security_group_ingress_without_multirule_after_reload(): assert len(sg_after.ip_permissions) == 1 +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_security_groups_filter_with_same_vpc_id(): conn = boto.connect_ec2("the_key", "the_secret") @@ -1035,6 +1644,33 @@ def test_get_all_security_groups_filter_with_same_vpc_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_get_all_security_groups_filter_with_same_vpc_id_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc_id = "vpc-5300000c" + security_group = ec2.create_security_group( + GroupName="test1", Description="test1", VpcId=vpc_id + ) + security_group2 = ec2.create_security_group( + GroupName="test2", Description="test2", VpcId=vpc_id + ) + + security_group.vpc_id.should.equal(vpc_id) + security_group2.vpc_id.should.equal(vpc_id) + + security_groups = client.describe_security_groups( + GroupIds=[security_group.id], Filters=[{"Name": "vpc-id", "Values": [vpc_id]}] + )["SecurityGroups"] + security_groups.should.have.length_of(1) + + with pytest.raises(ClientError) as ex: + client.describe_security_groups(GroupIds=["does_not_exist"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidGroup.NotFound") + + @mock_ec2 def test_revoke_security_group_egress(): ec2 = boto3.resource("ec2", "us-east-1") diff --git a/tests/test_ec2/test_spot_instances.py b/tests/test_ec2/test_spot_instances.py index 8554b9cea..179bbaa82 100644 --- a/tests/test_ec2/test_spot_instances.py +++ b/tests/test_ec2/test_spot_instances.py @@ -147,6 +147,7 @@ def test_request_spot_instances_default_arguments(): request.shouldnt.contain("SubnetId") +# Has boto3 equivalent @mock_ec2_deprecated def test_cancel_spot_instance_request(): conn = boto.connect_ec2() @@ -170,6 +171,42 @@ def test_cancel_spot_instance_request(): requests.should.have.length_of(0) +@mock_ec2 +def test_cancel_spot_instance_request_boto3(): + client = boto3.client("ec2", region_name="us-west-1") + + client.request_spot_instances( + SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID} + ) + + requests = client.describe_spot_instance_requests()["SpotInstanceRequests"] + requests.should.have.length_of(1) + request = requests[0] + request.should.have.key("CreateTime") + request.should.have.key("Type").equal("one-time") + request.should.have.key("SpotInstanceRequestId") + request.should.have.key("SpotPrice").equal("0.5") + request["LaunchSpecification"]["ImageId"].should.equal(EXAMPLE_AMI_ID) + + with pytest.raises(ClientError) as ex: + client.cancel_spot_instance_requests( + SpotInstanceRequestIds=[request["SpotInstanceRequestId"]], DryRun=True + ) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CancelSpotInstance operation: Request would have succeeded, but DryRun flag is set" + ) + + client.cancel_spot_instance_requests( + SpotInstanceRequestIds=[request["SpotInstanceRequestId"]] + ) + + requests = client.describe_spot_instance_requests()["SpotInstanceRequests"] + requests.should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_request_spot_instances_fulfilled(): """ @@ -195,6 +232,36 @@ def test_request_spot_instances_fulfilled(): request.state.should.equal("active") +@mock_ec2 +def test_request_spot_instances_fulfilled_boto3(): + """ + Test that moto correctly fullfills a spot instance request + """ + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + + request = client.request_spot_instances( + SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID} + ) + request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"] + + requests = client.describe_spot_instance_requests()["SpotInstanceRequests"] + requests.should.have.length_of(1) + request = requests[0] + + request["State"].should.equal("open") + + if not settings.TEST_SERVER_MODE: + ec2_backends["us-east-1"].spot_instance_requests[request_id].state = "active" + + requests = client.describe_spot_instance_requests()["SpotInstanceRequests"] + requests.should.have.length_of(1) + request = requests[0] + + request["State"].should.equal("active") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_tag_spot_instance_request(): """ @@ -214,6 +281,32 @@ def test_tag_spot_instance_request(): tag_dict.should.equal({"tag1": "value1", "tag2": "value2"}) +@mock_ec2 +def test_tag_spot_instance_request_boto3(): + """ + Test that moto correctly tags a spot instance request + """ + client = boto3.client("ec2", region_name="us-west-1") + + request = client.request_spot_instances( + SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID} + ) + request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"] + client.create_tags( + Resources=[request_id], + Tags=[{"Key": "tag1", "Value": "value1"}, {"Key": "tag2", "Value": "value2"}], + ) + + requests = client.describe_spot_instance_requests()["SpotInstanceRequests"] + requests.should.have.length_of(1) + request = requests[0] + + request["Tags"].should.have.length_of(2) + request["Tags"].should.contain({"Key": "tag1", "Value": "value1"}) + request["Tags"].should.contain({"Key": "tag2", "Value": "value2"}) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_spot_instance_requests_filtering(): """ @@ -244,6 +337,58 @@ def test_get_all_spot_instance_requests_filtering(): requests.should.have.length_of(1) +@mock_ec2 +def test_get_all_spot_instance_requests_filtering_boto3(): + """ + Test that moto correctly filters spot instance requests + """ + client = boto3.client("ec2", region_name="us-west-1") + + request1 = client.request_spot_instances( + SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID} + ) + request1_id = request1["SpotInstanceRequests"][0]["SpotInstanceRequestId"] + request2 = client.request_spot_instances( + SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID} + ) + request2_id = request2["SpotInstanceRequests"][0]["SpotInstanceRequestId"] + client.request_spot_instances( + SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID} + ) + client.create_tags( + Resources=[request1_id], + Tags=[{"Key": "tag1", "Value": "value1"}, {"Key": "tag2", "Value": "value2"}], + ) + client.create_tags( + Resources=[request2_id], + Tags=[{"Key": "tag1", "Value": "value1"}, {"Key": "tag2", "Value": "wrong"}], + ) + + requests = client.describe_spot_instance_requests( + Filters=[{"Name": "state", "Values": ["active"]}] + )["SpotInstanceRequests"] + requests.should.have.length_of(0) + + requests = client.describe_spot_instance_requests( + Filters=[{"Name": "state", "Values": ["open"]}] + )["SpotInstanceRequests"] + requests.should.have.length_of(3) + + requests = client.describe_spot_instance_requests( + Filters=[{"Name": "tag:tag1", "Values": ["value1"]}] + )["SpotInstanceRequests"] + requests.should.have.length_of(2) + + requests = client.describe_spot_instance_requests( + Filters=[ + {"Name": "tag:tag1", "Values": ["value1"]}, + {"Name": "tag:tag2", "Values": ["value2"]}, + ] + )["SpotInstanceRequests"] + requests.should.have.length_of(1) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_request_spot_instances_setting_instance_id(): conn = boto.ec2.connect_to_region("us-east-1") @@ -334,3 +479,21 @@ def test_spot_price_history(): response = client.describe_spot_price_history(InstanceTypes=i_types) price = response["SpotPriceHistory"][0] assert price["InstanceType"] in i_types + + +@mock_ec2 +def test_request_spot_instances_setting_instance_id_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + request = client.request_spot_instances( + SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID} + ) + request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"] + + if not settings.TEST_SERVER_MODE: + req = ec2_backends["us-east-1"].spot_instance_requests[request_id] + req.state = "active" + req.instance_id = "i-12345678" + + request = client.describe_spot_instance_requests()["SpotInstanceRequests"][0] + assert request["State"] == "active" + assert request["InstanceId"] == "i-12345678" diff --git a/tests/test_ec2/test_subnets.py b/tests/test_ec2/test_subnets.py index c1bcefa1f..786eef428 100644 --- a/tests/test_ec2/test_subnets.py +++ b/tests/test_ec2/test_subnets.py @@ -10,10 +10,11 @@ import pytest import sure # noqa from boto.exception import EC2ResponseError from botocore.exceptions import ClientError -from moto import mock_ec2, mock_ec2_deprecated +from moto import mock_ec2, mock_ec2_deprecated, settings from tests import EXAMPLE_AMI_ID +# Has boto3 equivalent @mock_ec2_deprecated def test_subnets(): ec2 = boto.connect_ec2("the_key", "the_secret") @@ -36,6 +37,30 @@ def test_subnets(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_subnets_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + all_subnets = client.describe_subnets()["Subnets"] + nr_of_a_zones = len(client.describe_availability_zones()["AvailabilityZones"]) + all_subnets.should.have.length_of(1 + nr_of_a_zones) + + client.delete_subnet(SubnetId=subnet.id) + + all_subnets = client.describe_subnets()["Subnets"] + all_subnets.should.have.length_of(nr_of_a_zones) + + with pytest.raises(ClientError) as ex: + client.delete_subnet(SubnetId=subnet.id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidSubnetID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_subnet_create_vpc_validation(): conn = boto.connect_vpc("the_key", "the_secret") @@ -47,6 +72,18 @@ def test_subnet_create_vpc_validation(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_subnet_create_vpc_validation_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + ec2.create_subnet(VpcId="vpc-abcd1234", CidrBlock="10.0.0.0/18") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidVpcID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_subnet_tagging(): conn = boto.connect_vpc("the_key", "the_secret") @@ -65,6 +102,25 @@ def test_subnet_tagging(): subnet.tags["a key"].should.equal("some value") +@mock_ec2 +def test_subnet_tagging_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock="10.0.0.0/18") + + subnet.create_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + + tag = client.describe_tags()["Tags"][0] + tag["Key"].should.equal("a key") + tag["Value"].should.equal("some value") + + # Refresh the subnet + subnet = client.describe_subnets(SubnetIds=[subnet.id])["Subnets"][0] + subnet["Tags"].should.equal([{"Key": "a key", "Value": "some value"}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_subnet_should_have_proper_availability_zone_set(): conn = boto.vpc.connect_to_region("us-west-1") @@ -73,6 +129,16 @@ def test_subnet_should_have_proper_availability_zone_set(): subnetA.availability_zone.should.equal("us-west-1b") +@mock_ec2 +def test_subnet_should_have_proper_availability_zone_set_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + vpcA = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnetA = ec2.create_subnet( + VpcId=vpcA.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-1b" + ) + subnetA.availability_zone.should.equal("us-west-1b") + + @mock_ec2 def test_availability_zone_in_create_subnet(): ec2 = boto3.resource("ec2", region_name="us-west-1") @@ -101,6 +167,7 @@ def test_default_subnet(): subnet.map_public_ip_on_launch.shouldnt.be.ok +# Has boto3 equivalent @mock_ec2_deprecated def test_non_default_subnet(): vpc_cli = boto.vpc.connect_to_region("us-west-1") @@ -203,6 +270,7 @@ def test_modify_subnet_attribute_validation(): ) +# Has boto3 equivalent @mock_ec2_deprecated def test_subnet_get_by_id(): ec2 = boto.ec2.connect_to_region("us-west-1") @@ -230,6 +298,38 @@ def test_subnet_get_by_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_subnet_get_by_id_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + vpcA = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnetA = ec2.create_subnet( + VpcId=vpcA.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-1a" + ) + vpcB = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnetB1 = ec2.create_subnet( + VpcId=vpcB.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-1a" + ) + subnetB2 = ec2.create_subnet( + VpcId=vpcB.id, CidrBlock="10.0.1.0/24", AvailabilityZone="us-west-1b" + ) + + subnets_by_id = client.describe_subnets(SubnetIds=[subnetA.id, subnetB1.id])[ + "Subnets" + ] + subnets_by_id.should.have.length_of(2) + subnets_by_id = tuple(map(lambda s: s["SubnetId"], subnets_by_id)) + subnetA.id.should.be.within(subnets_by_id) + subnetB1.id.should.be.within(subnets_by_id) + + with pytest.raises(ClientError) as ex: + client.describe_subnets(SubnetIds=["subnet-does_not_exist"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidSubnetID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_subnets_filtering(): ec2 = boto.ec2.connect_to_region("us-west-1") @@ -305,6 +405,101 @@ def test_get_subnets_filtering(): ).should.throw(NotImplementedError) +@mock_ec2 +def test_get_subnets_filtering_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + vpcA = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnetA = ec2.create_subnet( + VpcId=vpcA.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-1a" + ) + vpcB = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnetB1 = ec2.create_subnet( + VpcId=vpcB.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-1a" + ) + subnetB2 = ec2.create_subnet( + VpcId=vpcB.id, CidrBlock="10.0.1.0/24", AvailabilityZone="us-west-1b" + ) + + nr_of_a_zones = len(client.describe_availability_zones()["AvailabilityZones"]) + all_subnets = client.describe_subnets()["Subnets"] + all_subnets.should.have.length_of(3 + nr_of_a_zones) + + # Filter by VPC ID + subnets_by_vpc = client.describe_subnets( + Filters=[{"Name": "vpc-id", "Values": [vpcB.id]}] + )["Subnets"] + subnets_by_vpc.should.have.length_of(2) + set([subnet["SubnetId"] for subnet in subnets_by_vpc]).should.equal( + set([subnetB1.id, subnetB2.id]) + ) + + # Filter by CIDR variations + subnets_by_cidr1 = client.describe_subnets( + Filters=[{"Name": "cidr", "Values": ["10.0.0.0/24"]}] + )["Subnets"] + subnets_by_cidr1.should.have.length_of(2) + set([subnet["SubnetId"] for subnet in subnets_by_cidr1]).should.equal( + set([subnetA.id, subnetB1.id]) + ) + + subnets_by_cidr2 = client.describe_subnets( + Filters=[{"Name": "cidr-block", "Values": ["10.0.0.0/24"]}] + )["Subnets"] + subnets_by_cidr2.should.have.length_of(2) + set([subnet["SubnetId"] for subnet in subnets_by_cidr2]).should.equal( + set([subnetA.id, subnetB1.id]) + ) + + subnets_by_cidr3 = client.describe_subnets( + Filters=[{"Name": "cidrBlock", "Values": ["10.0.0.0/24"]}] + )["Subnets"] + subnets_by_cidr3.should.have.length_of(2) + set([subnet["SubnetId"] for subnet in subnets_by_cidr3]).should.equal( + set([subnetA.id, subnetB1.id]) + ) + + # Filter by VPC ID and CIDR + subnets_by_vpc_and_cidr = client.describe_subnets( + Filters=[ + {"Name": "vpc-id", "Values": [vpcB.id]}, + {"Name": "cidr", "Values": ["10.0.0.0/24"]}, + ] + )["Subnets"] + subnets_by_vpc_and_cidr.should.have.length_of(1) + subnets_by_vpc_and_cidr[0]["SubnetId"].should.equal(subnetB1.id) + + # Filter by subnet ID + subnets_by_id = client.describe_subnets( + Filters=[{"Name": "subnet-id", "Values": [subnetA.id]}] + )["Subnets"] + subnets_by_id.should.have.length_of(1) + subnets_by_id[0]["SubnetId"].should.equal(subnetA.id) + + # Filter by availabilityZone + subnets_by_az = client.describe_subnets( + Filters=[ + {"Name": "availabilityZone", "Values": ["us-west-1a"]}, + {"Name": "vpc-id", "Values": [vpcB.id]}, + ] + )["Subnets"] + subnets_by_az.should.have.length_of(1) + subnets_by_az[0]["SubnetId"].should.equal(subnetB1.id) + + # Filter by defaultForAz + subnets_by_az = client.describe_subnets( + Filters=[{"Name": "defaultForAz", "Values": ["true"]}] + )["Subnets"] + subnets_by_az.should.have.length_of(nr_of_a_zones) + + # Unsupported filter + if not settings.TEST_SERVER_MODE: + filters = [{"Name": "not-implemented-filter", "Values": ["foobar"]}] + client.describe_subnets.when.called_with(Filters=filters).should.throw( + NotImplementedError + ) + + @mock_ec2 def test_create_subnet_response_fields(): ec2 = boto3.resource("ec2", region_name="us-west-1") diff --git a/tests/test_ec2/test_tags.py b/tests/test_ec2/test_tags.py index 88939217b..ab6d04248 100644 --- a/tests/test_ec2/test_tags.py +++ b/tests/test_ec2/test_tags.py @@ -15,6 +15,7 @@ import pytest from tests import EXAMPLE_AMI_ID +# Has boto3 equivalent @mock_ec2_deprecated def test_add_tag(): conn = boto.connect_ec2("the_key", "the_secret") @@ -39,6 +40,33 @@ def test_add_tag(): existing_instance.tags["a key"].should.equal("some value") +@mock_ec2 +def test_instance_create_tags(): + ec2 = boto3.resource("ec2", "us-west-1") + client = boto3.client("ec2", "us-west-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + with pytest.raises(ClientError) as ex: + instance.create_tags( + Tags=[{"Key": "a key", "Value": "some value"}], DryRun=True + ) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set" + ) + + instance.create_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + chain = itertools.chain.from_iterable + existing_instances = list( + chain([res["Instances"] for res in client.describe_instances()["Reservations"]]) + ) + existing_instances.should.have.length_of(1) + existing_instance = existing_instances[0] + existing_instance["Tags"].should.equal([{"Key": "a key", "Value": "some value"}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_remove_tag(): conn = boto.connect_ec2("the_key", "the_secret") @@ -68,6 +96,40 @@ def test_remove_tag(): instance.remove_tag("a key", "some value") +@mock_ec2 +def test_instance_delete_tags(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + instance.create_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + + tags = client.describe_tags()["Tags"] + tag = tags[0] + tag.should.have.key("Key").equal("a key") + tag.should.have.key("Value").equal("some value") + + with pytest.raises(ClientError) as ex: + instance.delete_tags(DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the DeleteTags operation: Request would have succeeded, but DryRun flag is set" + ) + + # Specifying key only + instance.delete_tags(Tags=[{"Key": "a key"}]) + client.describe_tags()["Tags"].should.have.length_of(0) + + instance.create_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + client.describe_tags()["Tags"].should.have.length_of(1) + + # Specifying key and value + instance.delete_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + client.describe_tags()["Tags"].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_tags(): conn = boto.connect_ec2("the_key", "the_secret") @@ -82,6 +144,7 @@ def test_get_all_tags(): tag.value.should.equal("some value") +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_tags_with_special_characters(): conn = boto.connect_ec2("the_key", "the_secret") @@ -96,6 +159,20 @@ def test_get_all_tags_with_special_characters(): tag.value.should.equal("some<> value") +@mock_ec2 +def test_get_all_tags_with_special_characters_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + instance.create_tags(Tags=[{"Key": "a key", "Value": "some<> value"}]) + + tag = client.describe_tags()["Tags"][0] + tag.should.have.key("Key").equal("a key") + tag.should.have.key("Value").equal("some<> value") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_create_tags(): conn = boto.connect_ec2("the_key", "the_secret") @@ -123,6 +200,40 @@ def test_create_tags(): ) +@mock_ec2 +def test_create_tags_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + tag_list = [ + {"Key": "a key", "Value": "some value"}, + {"Key": "another key", "Value": "some other value"}, + {"Key": "blank key", "Value": ""}, + ] + + with pytest.raises(ClientError) as ex: + client.create_tags(Resources=[instance.id], Tags=tag_list, DryRun=True) + ex.value.response["Error"]["Code"].should.equal("DryRunOperation") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412) + ex.value.response["Error"]["Message"].should.equal( + "An error occurred (DryRunOperation) when calling the CreateTags operation: Request would have succeeded, but DryRun flag is set" + ) + + client.create_tags(Resources=[instance.id], Tags=tag_list) + tags = client.describe_tags()["Tags"] + tags.should.have.length_of(3) + for expected_tag in tag_list: + tags.should.contain( + { + "Key": expected_tag["Key"], + "ResourceId": instance.id, + "ResourceType": "instance", + "Value": expected_tag["Value"], + } + ) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_tag_limit_exceeded(): conn = boto.connect_ec2("the_key", "the_secret") @@ -152,6 +263,35 @@ def test_tag_limit_exceeded(): tag.value.should.equal("a value") +@mock_ec2 +def test_tag_limit_exceeded_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + tag_list = [] + for i in range(51): + tag_list.append({"Key": "{0:02d}".format(i + 1), "Value": ""}) + + with pytest.raises(ClientError) as ex: + client.create_tags(Resources=[instance.id], Tags=tag_list) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("TagLimitExceeded") + + instance.create_tags(Tags=[{"Key": "a key", "Value": "a value"}]) + with pytest.raises(ClientError) as ex: + client.create_tags(Resources=[instance.id], Tags=tag_list) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("TagLimitExceeded") + + tags = client.describe_tags()["Tags"] + tags.should.have.length_of(1) + tags[0].should.have.key("Key").equal("a key") + tags[0].should.have.key("Value").equal("a value") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_invalid_parameter_tag_null(): conn = boto.connect_ec2("the_key", "the_secret") @@ -165,6 +305,7 @@ def test_invalid_parameter_tag_null(): cm.value.request_id.should_not.be.none +# Has boto3 equivalent @mock_ec2_deprecated def test_invalid_id(): conn = boto.connect_ec2("the_key", "the_secret") @@ -181,6 +322,27 @@ def test_invalid_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_invalid_id_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + with pytest.raises(ClientError) as ex: + client.create_tags( + Resources=["ami-blah"], Tags=[{"Key": "key", "Value": "tag"}] + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidID") + + with pytest.raises(ClientError) as ex: + client.create_tags( + Resources=["blah-blah"], Tags=[{"Key": "key", "Value": "tag"}] + ) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidID") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_tags_resource_id_filter(): conn = boto.connect_ec2("the_key", "the_secret") @@ -208,6 +370,59 @@ def test_get_all_tags_resource_id_filter(): tag.value.should.equal("some value") +@mock_ec2 +def test_get_all_tags_resource_filter_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + client.create_tags( + Resources=[instance.id], + Tags=[{"Key": "an instance key", "Value": "some value"}], + ) + image = instance.create_image(Name="test-ami", Description="this is a test ami") + image.create_tags(Tags=[{"Key": "an image key", "Value": "some value"}]) + + expected = { + "Key": "an instance key", + "ResourceId": instance.id, + "ResourceType": "instance", + "Value": "some value", + } + tags = client.describe_tags( + Filters=[{"Name": "resource-id", "Values": [instance.id]}] + )["Tags"] + tags.should.equal([expected]) + tags = client.describe_tags( + Filters=[{"Name": "resource-type", "Values": ["instance"]}] + )["Tags"] + tags.should.equal([expected]) + tags = client.describe_tags( + Filters=[{"Name": "key", "Values": ["an instance key"]}] + )["Tags"] + tags.should.equal([expected]) + + expected = { + "Key": "an image key", + "ResourceId": image.id, + "ResourceType": "image", + "Value": "some value", + } + tags = client.describe_tags( + Filters=[{"Name": "resource-id", "Values": [image.id]}] + )["Tags"] + tags.should.equal([expected]) + tags = client.describe_tags( + Filters=[{"Name": "resource-type", "Values": ["image"]}] + )["Tags"] + tags.should.equal([expected]) + + tags = client.describe_tags( + Filters=[{"Name": "resource-type", "Values": ["unknown"]}] + )["Tags"] + tags.should.equal([]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_tags_resource_type_filter(): conn = boto.connect_ec2("the_key", "the_secret") @@ -235,6 +450,7 @@ def test_get_all_tags_resource_type_filter(): tag.value.should.equal("some value") +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_tags_key_filter(): conn = boto.connect_ec2("the_key", "the_secret") @@ -254,6 +470,7 @@ def test_get_all_tags_key_filter(): tag.value.should.equal("some value") +# Has boto3 equivalent @mock_ec2_deprecated def test_get_all_tags_value_filter(): conn = boto.connect_ec2("the_key", "the_secret") @@ -298,6 +515,43 @@ def test_get_all_tags_value_filter(): tags.should.have.length_of(1) +@mock_ec2 +def test_get_all_tags_value_filter_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + + def create_instance_with_tag(value): + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[ + 0 + ] + tag = {"Key": "an instance key", "Value": value} + client.create_tags(Resources=[instance.id], Tags=[tag]) + return instance + + instance_a = create_instance_with_tag("some value") + instance_b = create_instance_with_tag("some other value") + instance_c = create_instance_with_tag("other value*") + instance_d = create_instance_with_tag("other value**") + instance_e = create_instance_with_tag("other value*?") + + image = instance_a.create_image(Name="test-ami", Description="this is a test ami") + image.create_tags(Tags=[{"Key": "an image key", "Value": "some value"}]) + + def filter_by_value(query, expected): + filter = {"Name": "value", "Values": [query]} + tags = client.describe_tags(Filters=[filter])["Tags"] + set([t["ResourceId"] for t in tags]).should.equal(set(expected)) + + filter_by_value("some value", [instance_a.id, image.id]) + filter_by_value("some*value", [instance_a.id, instance_b.id, image.id]) + filter_by_value("*some*value", [instance_a.id, instance_b.id, image.id]) + filter_by_value("*some*value*", [instance_a.id, instance_b.id, image.id]) + filter_by_value("*value\*", [instance_c.id]) + filter_by_value("*value\*\*", [instance_d.id]) + filter_by_value("*value\*\?", [instance_e.id]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_retrieved_instances_must_contain_their_tags(): tag_key = "Tag name" @@ -329,6 +583,33 @@ def test_retrieved_instances_must_contain_their_tags(): retrieved_tags[tag_key].should.equal(tag_value) +@mock_ec2 +def test_retrieved_instances_must_contain_their_tags_boto3(): + tag_key = "Tag name" + tag_value = "Tag value" + tags_to_be_set = {"Key": tag_key, "Value": tag_value} + + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + instance = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)[0] + + reservations = client.describe_instances()["Reservations"] + reservations.should.have.length_of(1) + instances = reservations[0]["Instances"] + instances.should.have.length_of(1) + instances[0]["InstanceId"].should.equal(instance.id) + instances[0].shouldnt.have.key("Tags") + + client.create_tags(Resources=[instance.id], Tags=[tags_to_be_set]) + reservations = client.describe_instances()["Reservations"] + instance = reservations[0]["Instances"][0] + retrieved_tags = instance["Tags"] + + # Check whether tag is present with correct value + retrieved_tags.should.equal([{"Key": tag_key, "Value": tag_value}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_retrieved_volumes_must_contain_their_tags(): tag_key = "Tag name" @@ -352,6 +633,24 @@ def test_retrieved_volumes_must_contain_their_tags(): retrieved_tags[tag_key].should.equal(tag_value) +@mock_ec2 +def test_retrieved_volumes_must_contain_their_tags_boto3(): + tag_key = "Tag name" + tag_value = "Tag value" + tags_to_be_set = {"Key": tag_key, "Value": tag_value} + + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + volume = ec2.create_volume(Size=80, AvailabilityZone="us-east-1a") + volume.tags.should.be.none + + client.create_tags(Resources=[volume.id], Tags=[tags_to_be_set]) + + volume.reload() + volume.tags.should.equal([{"Key": tag_key, "Value": tag_value}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_retrieved_snapshots_must_contain_their_tags(): tag_key = "Tag name" @@ -376,6 +675,24 @@ def test_retrieved_snapshots_must_contain_their_tags(): retrieved_tags[tag_key].should.equal(tag_value) +@mock_ec2 +def test_retrieved_snapshots_must_contain_their_tags_boto3(): + tag_key = "Tag name" + tag_value = "Tag value" + tags_to_be_set = {"Key": tag_key, "Value": tag_value} + + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + + volume = ec2.create_volume(Size=80, AvailabilityZone="eu-west-1a") + snapshot = ec2.create_snapshot(VolumeId=volume.id) + client.create_tags(Resources=[snapshot.id], Tags=[tags_to_be_set]) + + snapshot = client.describe_snapshots(SnapshotIds=[snapshot.id])["Snapshots"][0] + snapshot["Tags"].should.equal([{"Key": tag_key, "Value": tag_value}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_filter_instances_by_wildcard_tags(): conn = boto.connect_ec2( @@ -398,6 +715,36 @@ def test_filter_instances_by_wildcard_tags(): reservations.should.have.length_of(2) +@mock_ec2 +def test_filter_instances_by_wildcard_tags_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + + reservations = ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2) + instance_a, instance_b = reservations + + instance_a.create_tags(Tags=[{"Key": "Key1", "Value": "Value1"}]) + instance_b.create_tags(Tags=[{"Key": "Key1", "Value": "Value2"}]) + + res = client.describe_instances( + Filters=[{"Name": "tag:Key1", "Values": ["Value*"]}] + ) + res["Reservations"][0]["Instances"].should.have.length_of(2) + + res = client.describe_instances(Filters=[{"Name": "tag-key", "Values": ["Key*"]}]) + res["Reservations"][0]["Instances"].should.have.length_of(2) + + res = client.describe_instances( + Filters=[{"Name": "tag-value", "Values": ["Value*"]}] + ) + res["Reservations"][0]["Instances"].should.have.length_of(2) + + res = client.describe_instances( + Filters=[{"Name": "tag-value", "Values": ["Value2*"]}] + ) + res["Reservations"][0]["Instances"].should.have.length_of(1) + + @mock_ec2 def test_create_volume_with_tags(): client = boto3.client("ec2", "us-west-2") diff --git a/tests/test_ec2/test_virtual_private_gateways.py b/tests/test_ec2/test_virtual_private_gateways.py index 38136d363..2f12afc81 100644 --- a/tests/test_ec2/test_virtual_private_gateways.py +++ b/tests/test_ec2/test_virtual_private_gateways.py @@ -1,276 +1,390 @@ -from __future__ import unicode_literals -import boto -import boto3 -import pytest -import sure # noqa - -from moto import mock_ec2_deprecated, mock_ec2 -from botocore.exceptions import ClientError - - -@mock_ec2_deprecated -def test_virtual_private_gateways(): - conn = boto.connect_vpc("the_key", "the_secret") - - vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") - vpn_gateway.should_not.be.none - vpn_gateway.id.should.match(r"vgw-\w+") - vpn_gateway.type.should.equal("ipsec.1") - vpn_gateway.state.should.equal("available") - vpn_gateway.availability_zone.should.equal("us-east-1a") - - -@mock_ec2_deprecated -def test_describe_vpn_gateway(): - conn = boto.connect_vpc("the_key", "the_secret") - vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") - - vgws = conn.get_all_vpn_gateways() - vgws.should.have.length_of(1) - - gateway = vgws[0] - gateway.id.should.match(r"vgw-\w+") - gateway.id.should.equal(vpn_gateway.id) - vpn_gateway.type.should.equal("ipsec.1") - vpn_gateway.state.should.equal("available") - vpn_gateway.availability_zone.should.equal("us-east-1a") - - -@mock_ec2 -def test_attach_unknown_vpn_gateway(): - """describe_vpn_gateways attachment.vpc-id filter""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] - - with pytest.raises(ClientError) as ex: - ec2.attach_vpn_gateway(VpcId=vpc["VpcId"], VpnGatewayId="?") - err = ex.value.response["Error"] - err["Message"].should.equal("The virtual private gateway ID '?' does not exist") - err["Code"].should.equal("InvalidVpnGatewayID.NotFound") - - -@mock_ec2 -def test_delete_unknown_vpn_gateway(): - """describe_vpn_gateways attachment.vpc-id filter""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - with pytest.raises(ClientError) as ex: - ec2.delete_vpn_gateway(VpnGatewayId="?") - err = ex.value.response["Error"] - err["Message"].should.equal("The virtual private gateway ID '?' does not exist") - err["Code"].should.equal("InvalidVpnGatewayID.NotFound") - - -@mock_ec2 -def test_detach_unknown_vpn_gateway(): - """describe_vpn_gateways attachment.vpc-id filter""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] - - with pytest.raises(ClientError) as ex: - ec2.detach_vpn_gateway(VpcId=vpc["VpcId"], VpnGatewayId="?") - err = ex.value.response["Error"] - err["Message"].should.equal("The virtual private gateway ID '?' does not exist") - err["Code"].should.equal("InvalidVpnGatewayID.NotFound") - - -@mock_ec2 -def test_describe_vpn_connections_attachment_vpc_id_filter(): - """describe_vpn_gateways attachment.vpc-id filter""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") - vpc_id = vpc["Vpc"]["VpcId"] - gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") - gateway_id = gateway["VpnGateway"]["VpnGatewayId"] - - ec2.attach_vpn_gateway(VpcId=vpc_id, VpnGatewayId=gateway_id) - - gateways = ec2.describe_vpn_gateways( - Filters=[{"Name": "attachment.vpc-id", "Values": [vpc_id]}] - ) - - gateways["VpnGateways"].should.have.length_of(1) - gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) - gateways["VpnGateways"][0]["VpcAttachments"].should.contain( - {"State": "attached", "VpcId": vpc_id} - ) - - -@mock_ec2 -def test_describe_vpn_connections_state_filter_attached(): - """describe_vpn_gateways attachment.state filter - match attached""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") - vpc_id = vpc["Vpc"]["VpcId"] - gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") - gateway_id = gateway["VpnGateway"]["VpnGatewayId"] - - ec2.attach_vpn_gateway(VpcId=vpc_id, VpnGatewayId=gateway_id) - - gateways = ec2.describe_vpn_gateways( - Filters=[{"Name": "attachment.state", "Values": ["attached"]}] - ) - - gateways["VpnGateways"].should.have.length_of(1) - gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) - gateways["VpnGateways"][0]["VpcAttachments"].should.contain( - {"State": "attached", "VpcId": vpc_id} - ) - - -@mock_ec2 -def test_describe_vpn_connections_state_filter_deatched(): - """describe_vpn_gateways attachment.state filter - don't match detatched""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") - vpc_id = vpc["Vpc"]["VpcId"] - gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") - gateway_id = gateway["VpnGateway"]["VpnGatewayId"] - - ec2.attach_vpn_gateway(VpcId=vpc_id, VpnGatewayId=gateway_id) - - gateways = ec2.describe_vpn_gateways( - Filters=[{"Name": "attachment.state", "Values": ["detached"]}] - ) - - gateways["VpnGateways"].should.have.length_of(0) - - -@mock_ec2 -def test_describe_vpn_connections_id_filter_match(): - """describe_vpn_gateways vpn-gateway-id filter - match correct id""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") - gateway_id = gateway["VpnGateway"]["VpnGatewayId"] - - gateways = ec2.describe_vpn_gateways( - Filters=[{"Name": "vpn-gateway-id", "Values": [gateway_id]}] - ) - - gateways["VpnGateways"].should.have.length_of(1) - gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) - - -@mock_ec2 -def test_describe_vpn_connections_id_filter_miss(): - """describe_vpn_gateways vpn-gateway-id filter - don't match""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") - - gateways = ec2.describe_vpn_gateways( - Filters=[{"Name": "vpn-gateway-id", "Values": ["unknown_gateway_id"]}] - ) - - gateways["VpnGateways"].should.have.length_of(0) - - -@mock_ec2 -def test_describe_vpn_connections_type_filter_match(): - """describe_vpn_gateways type filter - match""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") - gateway_id = gateway["VpnGateway"]["VpnGatewayId"] - - gateways = ec2.describe_vpn_gateways( - Filters=[{"Name": "type", "Values": ["ipsec.1"]}] - ) - - gateways["VpnGateways"].should.have.length_of(1) - gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) - - -@mock_ec2 -def test_describe_vpn_connections_type_filter_miss(): - """describe_vpn_gateways type filter - don't match""" - - ec2 = boto3.client("ec2", region_name="us-east-1") - - ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") - - gateways = ec2.describe_vpn_gateways( - Filters=[{"Name": "type", "Values": ["unknown_type"]}] - ) - - gateways["VpnGateways"].should.have.length_of(0) - - -@mock_ec2_deprecated -def test_vpn_gateway_vpc_attachment(): - conn = boto.connect_vpc("the_key", "the_secret") - vpc = conn.create_vpc("10.0.0.0/16") - vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") - - conn.attach_vpn_gateway(vpn_gateway_id=vpn_gateway.id, vpc_id=vpc.id) - - gateway = conn.get_all_vpn_gateways()[0] - attachments = gateway.attachments - attachments.should.have.length_of(1) - attachments[0].vpc_id.should.equal(vpc.id) - attachments[0].state.should.equal("attached") - - -@mock_ec2_deprecated -def test_delete_vpn_gateway(): - conn = boto.connect_vpc("the_key", "the_secret") - vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") - - conn.delete_vpn_gateway(vpn_gateway.id) - vgws = conn.get_all_vpn_gateways() - vgws.should.have.length_of(1) - vgws[0].state.should.equal("deleted") - - -@mock_ec2_deprecated -def test_vpn_gateway_tagging(): - conn = boto.connect_vpc("the_key", "the_secret") - vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") - vpn_gateway.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 subnet - vpn_gateway = conn.get_all_vpn_gateways()[0] - vpn_gateway.tags.should.have.length_of(1) - vpn_gateway.tags["a key"].should.equal("some value") - - -@mock_ec2_deprecated -def test_detach_vpn_gateway(): - - conn = boto.connect_vpc("the_key", "the_secret") - vpc = conn.create_vpc("10.0.0.0/16") - vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") - - conn.attach_vpn_gateway(vpn_gateway_id=vpn_gateway.id, vpc_id=vpc.id) - - gateway = conn.get_all_vpn_gateways()[0] - attachments = gateway.attachments - attachments.should.have.length_of(1) - attachments[0].vpc_id.should.equal(vpc.id) - attachments[0].state.should.equal("attached") - - conn.detach_vpn_gateway(vpn_gateway_id=vpn_gateway.id, vpc_id=vpc.id) - - gateway = conn.get_all_vpn_gateways()[0] - attachments = gateway.attachments - attachments.should.have.length_of(1) - attachments[0].state.should.equal("detached") +from __future__ import unicode_literals +import boto +import boto3 +import pytest +import sure # noqa + +from moto import mock_ec2_deprecated, mock_ec2 +from botocore.exceptions import ClientError + + +@mock_ec2 +def test_attach_unknown_vpn_gateway(): + """describe_vpn_gateways attachment.vpc-id filter""" + + ec2 = boto3.client("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] + + with pytest.raises(ClientError) as ex: + ec2.attach_vpn_gateway(VpcId=vpc["VpcId"], VpnGatewayId="?") + err = ex.value.response["Error"] + err["Message"].should.equal("The virtual private gateway ID '?' does not exist") + err["Code"].should.equal("InvalidVpnGatewayID.NotFound") + + +@mock_ec2 +def test_delete_unknown_vpn_gateway(): + """describe_vpn_gateways attachment.vpc-id filter""" + + ec2 = boto3.client("ec2", region_name="us-east-1") + + with pytest.raises(ClientError) as ex: + ec2.delete_vpn_gateway(VpnGatewayId="?") + err = ex.value.response["Error"] + err["Message"].should.equal("The virtual private gateway ID '?' does not exist") + err["Code"].should.equal("InvalidVpnGatewayID.NotFound") + + +@mock_ec2 +def test_detach_unknown_vpn_gateway(): + """describe_vpn_gateways attachment.vpc-id filter""" + + ec2 = boto3.client("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] + + with pytest.raises(ClientError) as ex: + ec2.detach_vpn_gateway(VpcId=vpc["VpcId"], VpnGatewayId="?") + err = ex.value.response["Error"] + err["Message"].should.equal("The virtual private gateway ID '?' does not exist") + err["Code"].should.equal("InvalidVpnGatewayID.NotFound") + + +@mock_ec2 +def test_describe_vpn_connections_attachment_vpc_id_filter(): + """describe_vpn_gateways attachment.vpc-id filter""" + + ec2 = boto3.client("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc_id = vpc["Vpc"]["VpcId"] + gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") + gateway_id = gateway["VpnGateway"]["VpnGatewayId"] + + ec2.attach_vpn_gateway(VpcId=vpc_id, VpnGatewayId=gateway_id) + + gateways = ec2.describe_vpn_gateways( + Filters=[{"Name": "attachment.vpc-id", "Values": [vpc_id]}] + ) + + gateways["VpnGateways"].should.have.length_of(1) + gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) + gateways["VpnGateways"][0]["VpcAttachments"].should.contain( + {"State": "attached", "VpcId": vpc_id} + ) + + +@mock_ec2 +def test_describe_vpn_connections_state_filter_deatched(): + """describe_vpn_gateways attachment.state filter - don't match detatched""" + + ec2 = boto3.client("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc_id = vpc["Vpc"]["VpcId"] + gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") + gateway_id = gateway["VpnGateway"]["VpnGatewayId"] + + ec2.attach_vpn_gateway(VpcId=vpc_id, VpnGatewayId=gateway_id) + + gateways = ec2.describe_vpn_gateways( + Filters=[{"Name": "attachment.state", "Values": ["detached"]}] + ) + + gateways["VpnGateways"].should.have.length_of(0) + + +# Has boto3 equivalent +@mock_ec2_deprecated +def test_virtual_private_gateways(): + conn = boto.connect_vpc("the_key", "the_secret") + + vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") + vpn_gateway.should_not.be.none + vpn_gateway.id.should.match(r"vgw-\w+") + vpn_gateway.type.should.equal("ipsec.1") + vpn_gateway.state.should.equal("available") + vpn_gateway.availability_zone.should.equal("us-east-1a") + + +@mock_ec2 +def test_virtual_private_gateways_boto3(): + client = boto3.client("ec2", region_name="us-west-1") + + vpn_gateway = client.create_vpn_gateway( + Type="ipsec.1", AvailabilityZone="us-east-1a" + )["VpnGateway"] + + vpn_gateway["VpnGatewayId"].should.match(r"vgw-\w+") + vpn_gateway["Type"].should.equal("ipsec.1") + vpn_gateway["State"].should.equal("available") + vpn_gateway["AvailabilityZone"].should.equal("us-east-1a") + + +# Has boto3 equivalent +@mock_ec2_deprecated +def test_describe_vpn_gateway(): + conn = boto.connect_vpc("the_key", "the_secret") + vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") + + vgws = conn.get_all_vpn_gateways() + vgws.should.have.length_of(1) + + gateway = vgws[0] + gateway.id.should.match(r"vgw-\w+") + gateway.id.should.equal(vpn_gateway.id) + vpn_gateway.type.should.equal("ipsec.1") + vpn_gateway.state.should.equal("available") + vpn_gateway.availability_zone.should.equal("us-east-1a") + + +@mock_ec2 +def test_describe_vpn_gateway_boto3(): + client = boto3.client("ec2", region_name="us-west-1") + vpn_gateway = client.create_vpn_gateway( + Type="ipsec.1", AvailabilityZone="us-east-1a" + )["VpnGateway"] + + vgws = client.describe_vpn_gateways()["VpnGateways"] + vgws.should.have.length_of(1) + + gateway = vgws[0] + gateway["VpnGatewayId"].should.match(r"vgw-\w+") + gateway["VpnGatewayId"].should.equal(vpn_gateway["VpnGatewayId"]) + # TODO: fixme. This currently returns the ID + # gateway["Type"].should.equal("ipsec.1") + gateway["State"].should.equal("available") + gateway["AvailabilityZone"].should.equal("us-east-1a") + + +@mock_ec2 +def test_describe_vpn_connections_state_filter_attached(): + """ describe_vpn_gateways attachment.state filter - match attached """ + + ec2 = boto3.client("ec2", region_name="us-east-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc_id = vpc["Vpc"]["VpcId"] + gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") + gateway_id = gateway["VpnGateway"]["VpnGatewayId"] + + ec2.attach_vpn_gateway(VpcId=vpc_id, VpnGatewayId=gateway_id) + + gateways = ec2.describe_vpn_gateways( + Filters=[{"Name": "attachment.state", "Values": ["attached"]}] + ) + + gateways["VpnGateways"].should.have.length_of(1) + gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) + gateways["VpnGateways"][0]["VpcAttachments"].should.contain( + {"State": "attached", "VpcId": vpc_id} + ) + + +@mock_ec2 +def test_describe_vpn_connections_id_filter_match(): + """ describe_vpn_gateways vpn-gateway-id filter - match correct id """ + + ec2 = boto3.client("ec2", region_name="us-east-1") + + gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") + gateway_id = gateway["VpnGateway"]["VpnGatewayId"] + + gateways = ec2.describe_vpn_gateways( + Filters=[{"Name": "vpn-gateway-id", "Values": [gateway_id]}] + ) + + gateways["VpnGateways"].should.have.length_of(1) + gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) + + +@mock_ec2 +def test_describe_vpn_connections_id_filter_miss(): + """ describe_vpn_gateways vpn-gateway-id filter - don't match """ + + ec2 = boto3.client("ec2", region_name="us-east-1") + + ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") + + gateways = ec2.describe_vpn_gateways( + Filters=[{"Name": "vpn-gateway-id", "Values": ["unknown_gateway_id"]}] + ) + + gateways["VpnGateways"].should.have.length_of(0) + + +@mock_ec2 +def test_describe_vpn_connections_type_filter_match(): + """ describe_vpn_gateways type filter - match """ + + ec2 = boto3.client("ec2", region_name="us-east-1") + + gateway = ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") + gateway_id = gateway["VpnGateway"]["VpnGatewayId"] + + gateways = ec2.describe_vpn_gateways( + Filters=[{"Name": "type", "Values": ["ipsec.1"]}] + ) + + gateways["VpnGateways"].should.have.length_of(1) + gateways["VpnGateways"][0]["VpnGatewayId"].should.equal(gateway_id) + + +@mock_ec2 +def test_describe_vpn_connections_type_filter_miss(): + """ describe_vpn_gateways type filter - don't match """ + + ec2 = boto3.client("ec2", region_name="us-east-1") + + ec2.create_vpn_gateway(AvailabilityZone="us-east-1a", Type="ipsec.1") + + gateways = ec2.describe_vpn_gateways( + Filters=[{"Name": "type", "Values": ["unknown_type"]}] + ) + + gateways["VpnGateways"].should.have.length_of(0) + + +# Has boto3 equivalent +@mock_ec2_deprecated +def test_vpn_gateway_vpc_attachment(): + conn = boto.connect_vpc("the_key", "the_secret") + vpc = conn.create_vpc("10.0.0.0/16") + vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") + + conn.attach_vpn_gateway(vpn_gateway_id=vpn_gateway.id, vpc_id=vpc.id) + + gateway = conn.get_all_vpn_gateways()[0] + attachments = gateway.attachments + attachments.should.have.length_of(1) + attachments[0].vpc_id.should.equal(vpc.id) + attachments[0].state.should.equal("attached") + + +@mock_ec2 +def test_vpn_gateway_vpc_attachment_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpn_gateway = client.create_vpn_gateway( + Type="ipsec.1", AvailabilityZone="us-east-1a" + )["VpnGateway"] + + client.attach_vpn_gateway(VpnGatewayId=vpn_gateway["VpnGatewayId"], VpcId=vpc.id) + + gateway = client.describe_vpn_gateways()["VpnGateways"][0] + attachments = gateway["VpcAttachments"] + attachments.should.equal([{"State": "attached", "VpcId": vpc.id}]) + + +# Has boto3 equivalent +@mock_ec2_deprecated +def test_delete_vpn_gateway(): + conn = boto.connect_vpc("the_key", "the_secret") + vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") + + conn.delete_vpn_gateway(vpn_gateway.id) + vgws = conn.get_all_vpn_gateways() + vgws.should.have.length_of(1) + vgws[0].state.should.equal("deleted") + + +@mock_ec2 +def test_delete_vpn_gateway_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + vpn_gateway = client.create_vpn_gateway( + Type="ipsec.1", AvailabilityZone="us-east-1a" + )["VpnGateway"] + + client.delete_vpn_gateway(VpnGatewayId=vpn_gateway["VpnGatewayId"]) + gateways = client.describe_vpn_gateways()["VpnGateways"] + gateways.should.have.length_of(1) + gateways[0].should.have.key("State").equal("deleted") + + +# Has boto3 equivalent +@mock_ec2_deprecated +def test_vpn_gateway_tagging(): + conn = boto.connect_vpc("the_key", "the_secret") + vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") + vpn_gateway.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 subnet + vpn_gateway = conn.get_all_vpn_gateways()[0] + vpn_gateway.tags.should.have.length_of(1) + vpn_gateway.tags["a key"].should.equal("some value") + + +@mock_ec2 +def test_vpn_gateway_tagging_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + vpn_gateway = client.create_vpn_gateway( + Type="ipsec.1", AvailabilityZone="us-east-1a" + )["VpnGateway"] + client.create_tags( + Resources=[vpn_gateway["VpnGatewayId"]], + Tags=[{"Key": "a key", "Value": "some value"}], + ) + + tag = client.describe_tags()["Tags"][0] + tag.should.have.key("Key").equal("a key") + tag.should.have.key("Value").equal("some value") + + vpn_gateway = client.describe_vpn_gateways()["VpnGateways"][0] + # TODO: Fixme: Tags is currently empty + # vpn_gateway["Tags"].should.equal([{'Key': 'a key', 'Value': 'some value'}]) + + +# Has boto3 equivalent +@mock_ec2_deprecated +def test_detach_vpn_gateway(): + + conn = boto.connect_vpc("the_key", "the_secret") + vpc = conn.create_vpc("10.0.0.0/16") + vpn_gateway = conn.create_vpn_gateway("ipsec.1", "us-east-1a") + + conn.attach_vpn_gateway(vpn_gateway_id=vpn_gateway.id, vpc_id=vpc.id) + + gateway = conn.get_all_vpn_gateways()[0] + attachments = gateway.attachments + attachments.should.have.length_of(1) + attachments[0].vpc_id.should.equal(vpc.id) + attachments[0].state.should.equal("attached") + + conn.detach_vpn_gateway(vpn_gateway_id=vpn_gateway.id, vpc_id=vpc.id) + + gateway = conn.get_all_vpn_gateways()[0] + attachments = gateway.attachments + attachments.should.have.length_of(1) + attachments[0].state.should.equal("detached") + + +@mock_ec2 +def test_detach_vpn_gateway_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpn_gateway = client.create_vpn_gateway( + Type="ipsec.1", AvailabilityZone="us-east-1a" + ) + vpn_gateway = vpn_gateway["VpnGateway"] + + client.attach_vpn_gateway(VpnGatewayId=vpn_gateway["VpnGatewayId"], VpcId=vpc.id) + + gateway = client.describe_vpn_gateways()["VpnGateways"][0] + attachments = gateway["VpcAttachments"] + attachments.should.equal([{"State": "attached", "VpcId": vpc.id}]) + + client.detach_vpn_gateway(VpnGatewayId=vpn_gateway["VpnGatewayId"], VpcId=vpc.id) + + gateway = client.describe_vpn_gateways()["VpnGateways"][0] + attachments = gateway["VpcAttachments"] + attachments.should.equal([{"State": "detached", "VpcId": vpc.id}]) diff --git a/tests/test_ec2/test_vpc_peering.py b/tests/test_ec2/test_vpc_peering.py index a7da7be28..522d4118c 100644 --- a/tests/test_ec2/test_vpc_peering.py +++ b/tests/test_ec2/test_vpc_peering.py @@ -13,6 +13,7 @@ from moto import mock_ec2, mock_ec2_deprecated from tests.helpers import requires_boto_gte +# Has boto3 equivalent @requires_boto_gte("2.32.0") @mock_ec2_deprecated def test_vpc_peering_connections(): @@ -26,6 +27,26 @@ def test_vpc_peering_connections(): return vpc_pcx +def create_vpx_pcx(ec2, client): + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + peer_vpc = ec2.create_vpc(CidrBlock="11.0.0.0/16") + vpc_pcx = client.create_vpc_peering_connection(VpcId=vpc.id, PeerVpcId=peer_vpc.id) + vpc_pcx = vpc_pcx["VpcPeeringConnection"] + return vpc_pcx + + +@mock_ec2 +def test_vpc_peering_connections_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + + vpc_pcx = create_vpx_pcx(ec2, client) + + vpc_pcx.should.have.key("VpcPeeringConnectionId") + vpc_pcx["Status"]["Code"].should.equal("initiating-request") + + +# Has boto3 equivalent @requires_boto_gte("2.32.0") @mock_ec2_deprecated def test_vpc_peering_connections_get_all(): @@ -38,6 +59,18 @@ def test_vpc_peering_connections_get_all(): all_vpc_pcxs[0]._status.code.should.equal("pending-acceptance") +@mock_ec2 +def test_vpc_peering_connections_get_all_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc_pcx = create_vpx_pcx(ec2, client) + + all_vpc_pcxs = client.describe_vpc_peering_connections()["VpcPeeringConnections"] + all_vpc_pcxs.should.have.length_of(1) + all_vpc_pcxs[0]["Status"]["Code"].should.equal("pending-acceptance") + + +# Has boto3 equivalent @requires_boto_gte("2.32.0") @mock_ec2_deprecated def test_vpc_peering_connections_accept(): @@ -58,6 +91,29 @@ def test_vpc_peering_connections_accept(): all_vpc_pcxs[0]._status.code.should.equal("active") +@mock_ec2 +def test_vpc_peering_connections_accept_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc_pcx = create_vpx_pcx(ec2, client) + vpc_pcx_id = vpc_pcx["VpcPeeringConnectionId"] + + vpc_pcx = client.accept_vpc_peering_connection(VpcPeeringConnectionId=vpc_pcx_id) + vpc_pcx = vpc_pcx["VpcPeeringConnection"] + vpc_pcx["Status"]["Code"].should.equal("active") + + with pytest.raises(ClientError) as ex: + client.reject_vpc_peering_connection(VpcPeeringConnectionId=vpc_pcx_id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidStateTransition") + + all_vpc_pcxs = client.describe_vpc_peering_connections()["VpcPeeringConnections"] + all_vpc_pcxs.should.have.length_of(1) + all_vpc_pcxs[0]["Status"]["Code"].should.equal("active") + + +# Has boto3 equivalent @requires_boto_gte("2.32.0") @mock_ec2_deprecated def test_vpc_peering_connections_reject(): @@ -78,6 +134,27 @@ def test_vpc_peering_connections_reject(): all_vpc_pcxs[0]._status.code.should.equal("rejected") +@mock_ec2 +def test_vpc_peering_connections_reject_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc_pcx = create_vpx_pcx(ec2, client) + vpc_pcx_id = vpc_pcx["VpcPeeringConnectionId"] + + client.reject_vpc_peering_connection(VpcPeeringConnectionId=vpc_pcx_id) + + with pytest.raises(ClientError) as ex: + client.accept_vpc_peering_connection(VpcPeeringConnectionId=vpc_pcx_id) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidStateTransition") + + all_vpc_pcxs = client.describe_vpc_peering_connections()["VpcPeeringConnections"] + all_vpc_pcxs.should.have.length_of(1) + all_vpc_pcxs[0]["Status"]["Code"].should.equal("rejected") + + +# Has boto3 equivalent @requires_boto_gte("2.32.1") @mock_ec2_deprecated def test_vpc_peering_connections_delete(): @@ -98,6 +175,28 @@ def test_vpc_peering_connections_delete(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_vpc_peering_connections_delete_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc_pcx = create_vpx_pcx(ec2, client) + vpc_pcx_id = vpc_pcx["VpcPeeringConnectionId"] + + client.delete_vpc_peering_connection(VpcPeeringConnectionId=vpc_pcx_id) + + all_vpc_pcxs = client.describe_vpc_peering_connections()["VpcPeeringConnections"] + all_vpc_pcxs.should.have.length_of(1) + all_vpc_pcxs[0]["Status"]["Code"].should.equal("deleted") + + with pytest.raises(ClientError) as ex: + client.delete_vpc_peering_connection(VpcPeeringConnectionId="pcx-1234abcd") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal( + "InvalidVpcPeeringConnectionId.NotFound" + ) + + @mock_ec2 def test_vpc_peering_connections_cross_region(): # create vpc in us-west-1 and ap-northeast-1 diff --git a/tests/test_ec2/test_vpcs.py b/tests/test_ec2/test_vpcs.py index bd10df56b..324786a4d 100644 --- a/tests/test_ec2/test_vpcs.py +++ b/tests/test_ec2/test_vpcs.py @@ -16,6 +16,7 @@ SAMPLE_DOMAIN_NAME = "example.com" SAMPLE_NAME_SERVERS = ["10.0.0.6", "10.0.0.7"] +# Has boto3 equivalent @mock_ec2_deprecated def test_vpcs(): conn = boto.connect_vpc("the_key", "the_secret") @@ -37,6 +38,29 @@ def test_vpcs(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_create_and_delete_vpc(): + ec2 = boto3.resource("ec2", region_name="eu-north-1") + client = boto3.client("ec2", region_name="eu-north-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc.cidr_block.should.equal("10.0.0.0/16") + + all_vpcs = client.describe_vpcs()["Vpcs"] + all_vpcs.should.have.length_of(2) + + vpc.delete() + + all_vpcs = client.describe_vpcs()["Vpcs"] + all_vpcs.should.have.length_of(1) + + with pytest.raises(ClientError) as ex: + client.delete_vpc(VpcId="vpc-1234abcd") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidVpcID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_defaults(): conn = boto.connect_vpc("the_key", "the_secret") @@ -53,6 +77,28 @@ def test_vpc_defaults(): conn.get_all_security_groups(filters={"vpc-id": [vpc.id]}).should.have.length_of(0) +@mock_ec2 +def test_vpc_defaults_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-north-1") + client = boto3.client("ec2", region_name="eu-north-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + client.describe_vpcs()["Vpcs"].should.have.length_of(2) + client.describe_route_tables()["RouteTables"].should.have.length_of(2) + client.describe_security_groups(Filters=[{"Name": "vpc-id", "Values": [vpc.id]}])[ + "SecurityGroups" + ].should.have.length_of(1) + + vpc.delete() + + client.describe_vpcs()["Vpcs"].should.have.length_of(1) + client.describe_route_tables()["RouteTables"].should.have.length_of(1) + client.describe_security_groups(Filters=[{"Name": "vpc-id", "Values": [vpc.id]}])[ + "SecurityGroups" + ].should.have.length_of(0) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_isdefault_filter(): conn = boto.connect_vpc("the_key", "the_secret") @@ -62,6 +108,23 @@ def test_vpc_isdefault_filter(): conn.get_all_vpcs(filters={"isDefault": "true"}).should.have.length_of(1) +@mock_ec2 +def test_vpc_isdefault_filter_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + client.describe_vpcs(Filters=[{"Name": "isDefault", "Values": ["true"]}])[ + "Vpcs" + ].should.have.length_of(1) + + vpc.delete() + + client.describe_vpcs(Filters=[{"Name": "isDefault", "Values": ["true"]}])[ + "Vpcs" + ].should.have.length_of(1) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_multiple_vpcs_default_filter(): conn = boto.connect_vpc("the_key", "the_secret") @@ -74,6 +137,22 @@ def test_multiple_vpcs_default_filter(): vpc[0].cidr_block.should.equal("172.31.0.0/16") +@mock_ec2 +def test_multiple_vpcs_default_filter_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + ec2.create_vpc(CidrBlock="10.8.0.0/16") + ec2.create_vpc(CidrBlock="10.0.0.0/16") + ec2.create_vpc(CidrBlock="192.168.0.0/16") + client.describe_vpcs()["Vpcs"].should.have.length_of(4) + vpc = client.describe_vpcs(Filters=[{"Name": "isDefault", "Values": ["true"]}])[ + "Vpcs" + ] + vpc.should.have.length_of(1) + vpc[0]["CidrBlock"].should.equal("172.31.0.0/16") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_state_available_filter(): conn = boto.connect_vpc("the_key", "the_secret") @@ -84,6 +163,22 @@ def test_vpc_state_available_filter(): conn.get_all_vpcs(filters={"state": "available"}).should.have.length_of(2) +@mock_ec2 +def test_vpc_state_available_filter_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + ec2.create_vpc(CidrBlock="10.1.0.0/16") + client.describe_vpcs(Filters=[{"Name": "state", "Values": ["available"]}])[ + "Vpcs" + ].should.have.length_of(3) + vpc.delete() + client.describe_vpcs(Filters=[{"Name": "state", "Values": ["available"]}])[ + "Vpcs" + ].should.have.length_of(2) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_tagging(): conn = boto.connect_vpc() @@ -100,6 +195,23 @@ def test_vpc_tagging(): vpc.tags["a key"].should.equal("some value") +@mock_ec2 +def test_vpc_tagging_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + vpc.create_tags(Tags=[{"Key": "a key", "Value": "some value"}]) + tag = client.describe_tags()["Tags"][0] + tag.should.have.key("Key").equal("a key") + tag.should.have.key("Value").equal("some value") + + # Refresh the vpc + vpc = client.describe_vpcs(VpcIds=[vpc.id])["Vpcs"][0] + vpc["Tags"].should.equal([{"Key": "a key", "Value": "some value"}]) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_id(): conn = boto.connect_vpc() @@ -120,6 +232,28 @@ def test_vpc_get_by_id(): cm.value.request_id.should_not.be.none +@mock_ec2 +def test_vpc_get_by_id_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + ec2.create_vpc(CidrBlock="10.0.0.0/16") + + vpcs = client.describe_vpcs(VpcIds=[vpc1.id, vpc2.id])["Vpcs"] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + with pytest.raises(ClientError) as ex: + client.describe_vpcs(VpcIds=["vpc-does_not_exist"]) + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidVpcID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_cidr_block(): conn = boto.connect_vpc() @@ -134,6 +268,24 @@ def test_vpc_get_by_cidr_block(): vpc2.id.should.be.within(vpc_ids) +@mock_ec2 +def test_vpc_get_by_cidr_block_boto3(): + ec2 = boto3.resource("ec2", region_name="eu-west-1") + client = boto3.client("ec2", region_name="eu-west-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + ec2.create_vpc(CidrBlock="10.0.0.0/24") + + vpcs = client.describe_vpcs(Filters=[{"Name": "cidr", "Values": ["10.0.0.0/16"]}])[ + "Vpcs" + ] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_dhcp_options_id(): conn = boto.connect_vpc() @@ -152,6 +304,33 @@ def test_vpc_get_by_dhcp_options_id(): vpc2.id.should.be.within(vpc_ids) +@mock_ec2 +def test_vpc_get_by_dhcp_options_id_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + ec2.create_vpc(CidrBlock="10.0.0.0/24") + + client.associate_dhcp_options(DhcpOptionsId=dhcp_options.id, VpcId=vpc1.id) + client.associate_dhcp_options(DhcpOptionsId=dhcp_options.id, VpcId=vpc2.id) + + vpcs = client.describe_vpcs( + Filters=[{"Name": "dhcp-options-id", "Values": [dhcp_options.id]}] + )["Vpcs"] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_tag(): conn = boto.connect_vpc() @@ -170,6 +349,28 @@ def test_vpc_get_by_tag(): vpc2.id.should.be.within(vpc_ids) +@mock_ec2 +def test_vpc_get_by_tag_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc3 = ec2.create_vpc(CidrBlock="10.0.0.0/24") + + vpc1.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc2.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc3.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC2"}]) + + vpcs = client.describe_vpcs(Filters=[{"Name": "tag:Name", "Values": ["TestVPC"]}])[ + "Vpcs" + ] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_tag_key_superset(): conn = boto.connect_vpc() @@ -190,6 +391,30 @@ def test_vpc_get_by_tag_key_superset(): vpc2.id.should.be.within(vpc_ids) +@mock_ec2 +def test_vpc_get_by_tag_key_superset_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc3 = ec2.create_vpc(CidrBlock="10.0.0.0/24") + + vpc1.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc1.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + vpc2.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc2.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + vpc3.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + + vpcs = client.describe_vpcs(Filters=[{"Name": "tag-key", "Values": ["Name"]}])[ + "Vpcs" + ] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_tag_key_subset(): conn = boto.connect_vpc() @@ -210,6 +435,30 @@ def test_vpc_get_by_tag_key_subset(): vpc2.id.should.be.within(vpc_ids) +@mock_ec2 +def test_vpc_get_by_tag_key_subset_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc3 = ec2.create_vpc(CidrBlock="10.0.0.0/24") + + vpc1.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc1.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + vpc2.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc2.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + vpc3.create_tags(Tags=[{"Key": "Test", "Value": "TestVPC2"}]) + + vpcs = client.describe_vpcs( + Filters=[{"Name": "tag-key", "Values": ["Name", "Key"]}] + )["Vpcs"] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_tag_value_superset(): conn = boto.connect_vpc() @@ -230,6 +479,30 @@ def test_vpc_get_by_tag_value_superset(): vpc2.id.should.be.within(vpc_ids) +@mock_ec2 +def test_vpc_get_by_tag_value_superset_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc3 = ec2.create_vpc(CidrBlock="10.0.0.0/24") + + vpc1.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc1.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + vpc2.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc2.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + vpc3.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + + vpcs = client.describe_vpcs(Filters=[{"Name": "tag-value", "Values": ["TestVPC"]}])[ + "Vpcs" + ] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_get_by_tag_value_subset(): conn = boto.connect_vpc() @@ -249,6 +522,28 @@ def test_vpc_get_by_tag_value_subset(): vpc2.id.should.be.within(vpc_ids) +@mock_ec2 +def test_vpc_get_by_tag_value_subset_boto3(): + ec2 = boto3.resource("ec2", region_name="us-east-1") + client = boto3.client("ec2", region_name="us-east-1") + vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + vpc2 = ec2.create_vpc(CidrBlock="10.0.0.0/16") + ec2.create_vpc(CidrBlock="10.0.0.0/24") + + vpc1.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc1.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + vpc2.create_tags(Tags=[{"Key": "Name", "Value": "TestVPC"}]) + vpc2.create_tags(Tags=[{"Key": "Key", "Value": "TestVPC2"}]) + + vpcs = client.describe_vpcs( + Filters=[{"Name": "tag-value", "Values": ["TestVPC", "TestVPC2"]}] + )["Vpcs"] + vpcs.should.have.length_of(2) + vpc_ids = tuple(map(lambda v: v["VpcId"], vpcs)) + vpc1.id.should.be.within(vpc_ids) + vpc2.id.should.be.within(vpc_ids) + + @mock_ec2 def test_default_vpc(): ec2 = boto3.resource("ec2", region_name="us-west-1") @@ -382,6 +677,7 @@ def test_vpc_modify_enable_dns_hostnames(): attr.get("Value").should.be.ok +# Has boto3 equivalent @mock_ec2_deprecated def test_vpc_associate_dhcp_options(): conn = boto.connect_vpc() @@ -394,6 +690,24 @@ def test_vpc_associate_dhcp_options(): dhcp_options.id.should.equal(vpc.dhcp_options_id) +@mock_ec2 +def test_vpc_associate_dhcp_options_boto3(): + ec2 = boto3.resource("ec2", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + dhcp_options = ec2.create_dhcp_options( + DhcpConfigurations=[ + {"Key": "domain-name", "Values": [SAMPLE_DOMAIN_NAME]}, + {"Key": "domain-name-servers", "Values": SAMPLE_NAME_SERVERS}, + ] + ) + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + + client.associate_dhcp_options(DhcpOptionsId=dhcp_options.id, VpcId=vpc.id) + + vpc.reload() + dhcp_options.id.should.equal(vpc.dhcp_options_id) + + @mock_ec2 def test_associate_vpc_ipv4_cidr_block(): ec2 = boto3.resource("ec2", region_name="us-west-1") diff --git a/tests/test_ec2/test_vpn_connections.py b/tests/test_ec2/test_vpn_connections.py index 985e8fd89..9c251df90 100644 --- a/tests/test_ec2/test_vpn_connections.py +++ b/tests/test_ec2/test_vpn_connections.py @@ -5,10 +5,11 @@ import boto3 import pytest import sure # noqa from boto.exception import EC2ResponseError -from botocore.client import ClientError +from botocore.exceptions import ClientError from moto import mock_ec2, mock_ec2_deprecated +# Has boto3 equivalent @mock_ec2_deprecated def test_create_vpn_connections(): conn = boto.connect_vpc("the_key", "the_secret") @@ -20,6 +21,17 @@ def test_create_vpn_connections(): vpn_connection.type.should.equal("ipsec.1") +@mock_ec2 +def test_create_vpn_connections_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + vpn_connection = client.create_vpn_connection( + Type="ipsec.1", VpnGatewayId="vgw-0123abcd", CustomerGatewayId="cgw-0123abcd" + )["VpnConnection"] + vpn_connection["VpnConnectionId"].should.match(r"vpn-\w+") + vpn_connection["Type"].should.equal("ipsec.1") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_vpn_connections(): conn = boto.connect_vpc("the_key", "the_secret") @@ -33,6 +45,24 @@ def test_delete_vpn_connections(): list_of_vpn_connections[0].state.should.equal("deleted") +@mock_ec2 +def test_delete_vpn_connections_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + vpn_connection = client.create_vpn_connection( + Type="ipsec.1", VpnGatewayId="vgw-0123abcd", CustomerGatewayId="cgw-0123abcd" + )["VpnConnection"] + + cnx = client.describe_vpn_connections()["VpnConnections"] + cnx.should.have.length_of(1) + + client.delete_vpn_connection(VpnConnectionId=vpn_connection["VpnConnectionId"]) + + cnx = client.describe_vpn_connections()["VpnConnections"] + cnx.should.have.length_of(1) + cnx[0].should.have.key("State").equal("deleted") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_delete_vpn_connections_bad_id(): conn = boto.connect_vpc("the_key", "the_secret") @@ -40,6 +70,17 @@ def test_delete_vpn_connections_bad_id(): conn.delete_vpn_connection("vpn-0123abcd") +@mock_ec2 +def test_delete_vpn_connections_bad_id_boto3(): + client = boto3.client("ec2", region_name="us-east-1") + with pytest.raises(ClientError) as ex: + client.delete_vpn_connection(VpnConnectionId="vpn-0123abcd") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["ResponseMetadata"].should.have.key("RequestId") + ex.value.response["Error"]["Code"].should.equal("InvalidVpnConnectionID.NotFound") + + +# Has boto3 equivalent @mock_ec2_deprecated def test_describe_vpn_connections(): conn = boto.connect_vpc("the_key", "the_secret")