diff --git a/moto/ec2/models.py b/moto/ec2/models.py index ce9c3ef5b..118fc6804 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -104,6 +104,7 @@ from .utils import ( random_internet_gateway_id, random_ip, random_ipv6_cidr, + randor_ipv4_cidr, random_launch_template_id, random_nat_gateway_id, random_key_pair, @@ -112,6 +113,8 @@ from .utils import ( random_reservation_id, random_route_table_id, generate_route_id, + generate_vpc_end_point_id, + create_dns_entries, split_route_id, random_security_group_id, random_snapshot_id, @@ -2735,6 +2738,7 @@ class VPCBackend(object): def __init__(self): self.vpcs = {} + self.vpc_end_points = {} self.vpc_refs[self.__class__].add(weakref.ref(self)) super(VPCBackend, self).__init__() @@ -2877,6 +2881,66 @@ class VPCBackend(object): vpc = self.get_vpc(vpc_id) return vpc.associate_vpc_cidr_block(cidr_block, amazon_provided_ipv6_cidr_block) + def create_vpc_endpoint(self, + vpc_id, + service_name, + type=None, + policy_document=False, + route_table_ids=None, + subnet_ids=[], + network_interface_ids=[], + dns_entries=None, + client_token=None, + security_group=None, + tag_specifications=None, + private_dns_enabled=None + ): + + vpc_endpoint_id = generate_vpc_end_point_id(vpc_id) + + #validates if vpc is present or not. + self.get_vpc(vpc_id) + + if type == "interface" or "Interface ": + + network_interface_ids = [] + for subnet_id in subnet_ids: + self.get_subnet(subnet_id) + eni = self.create_network_interface(subnet_id, random_private_ip()) + network_interface_ids.append(eni.id) + + dns_entries = create_dns_entries(service_name, vpc_endpoint_id) + + else : + # considering gateway if type is not mentioned. + service_destination_cidr = randor_ipv4_cidr() + + for route_table_id in route_table_ids: + self.create_route( + route_table_id, + service_destination_cidr + ) + + vpc_end_point = VPCEndPoint( + vpc_endpoint_id, + vpc_id, + service_name, + type, + policy_document, + route_table_ids, + subnet_ids, + network_interface_ids, + [dns_entries], + client_token, + security_group, + tag_specifications, + private_dns_enabled + ) + + self.vpc_end_points[vpc_endpoint_id] = vpc_end_point + + return vpc_end_point + class VPCPeeringConnectionStatus(object): def __init__(self, code="initiating-request", message=""): @@ -3485,6 +3549,40 @@ class Route(object): return route_table +class VPCEndPoint(TaggedEC2Resource): + def __init__( + self, + id, + vpc_id, + service_name, + type=None, + policy_document=False, + route_table_ids=None, + subnet_ids =None, + network_interface_ids=None, + dns_entries=None, + client_token=None, + security_group=None, + tag_specifications=None, + private_dns_enabled=None, + ): + + self.id = id + self.vpc_id = vpc_id + self.service_name = service_name + self.type = type + self.policy_document = policy_document + self.route_table_ids = route_table_ids + self.network_interface_ids = network_interface_ids + self.subnet_ids = subnet_ids + self.client_token = client_token + self.security_group = security_group + self.tag_specifications = tag_specifications + self.private_dns_enabled = private_dns_enabled + self.created_at = datetime.utcnow() + self.dns_entries = dns_entries + + class RouteBackend(object): def __init__(self): super(RouteBackend, self).__init__() diff --git a/moto/ec2/responses/vpcs.py b/moto/ec2/responses/vpcs.py index 0fd198378..2af4c0b29 100644 --- a/moto/ec2/responses/vpcs.py +++ b/moto/ec2/responses/vpcs.py @@ -163,6 +163,36 @@ class VPCs(BaseResponse): cidr_block_state="disassociating", ) + def create_vpc_endpoint(self): + vpc_id = self._get_param("VpcId") + service_name = self._get_param("ServiceName") + route_table_ids = self._get_multi_param("RouteTableId") + subnet_ids = self._get_multi_param("SubnetId") + type = self._get_param("VpcEndpointType") + policy_document = self._get_param("PolicyDocument") + client_token = self._get_param("ClientToken") + tag_specifications = self._get_param("TagSpecifications") + private_dns_enabled = self._get_param("PrivateDNSEnabled") + security_group = self._get_param("SecurityGroup") + + vpc_end_point = self.ec2_backend.create_vpc_endpoint( + vpc_id=vpc_id, + service_name=service_name, + type=type, + policy_document=policy_document, + route_table_ids=route_table_ids, + subnet_ids=subnet_ids, + client_token=client_token, + security_group=security_group, + tag_specifications=tag_specifications, + private_dns_enabled=private_dns_enabled + ) + + template = self.response_template(CREATE_VPC_END_POINT) + return template.render( + vpc_end_point=vpc_end_point + ) + CREATE_VPC_RESPONSE = """ @@ -384,3 +414,38 @@ IPV6_DISASSOCIATE_VPC_CIDR_BLOCK_RESPONSE = """ """ + +CREATE_VPC_END_POINT = """ + + {{ vpc_end_point.policy_document }} + available + false + {{ vpc_end_point.service_name }} + {{ vpc_end_point.vpc_id }} + {{ vpc_end_point.id }} + + {% for routeid in vpc_end_point.route_table_ids %} + {{ routeid }} + {% endfor %} + + + {% for network_interface_id in vpc_end_point.network_interface_ids %} + {{ network_interface_id }} + {% endfor %} + + + {% for subnetId in vpc_end_point.subnet_ids %} + {{ subnetId }} + {% endfor %} + + + {% for entry in vpc_end_point.dns_entries %} + + {{ entry["hosted_zone_id"] }} + {{ entry["dns_name"] }} + + {% endfor %} + + {{ vpc_end_point.created_at }} + +""" \ No newline at end of file diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 3b363e45d..408ee9be5 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -181,6 +181,10 @@ def random_ip(): ) +def randor_ipv4_cidr(): + return "10.0.{}.{}/16".format(random.randint(0, 255), random.randint(0, 255)) + + def random_ipv6_cidr(): return "2400:6500:{}:{}::/56".format(random_resource_id(4), random_resource_id(4)) @@ -189,6 +193,18 @@ def generate_route_id(route_table_id, cidr_block): return "%s~%s" % (route_table_id, cidr_block) +def generate_vpc_end_point_id(vpc_id): + return "%s-%s" % ('vpce', vpc_id[4:]) + + +def create_dns_entries(service_name, vpc_endpoint_id): + dns_entries = {} + dns_entries["dns_name"] = "{}-{}.{}".format(vpc_endpoint_id, + random_resource_id(8), service_name) + dns_entries["hosted_zone_id"] = random_resource_id(13).upper() + return dns_entries + + def split_route_id(route_id): values = route_id.split("~") return values[0], values[1] diff --git a/tests/test_ec2/test_route_tables.py b/tests/test_ec2/test_route_tables.py index 347464691..182776f95 100644 --- a/tests/test_ec2/test_route_tables.py +++ b/tests/test_ec2/test_route_tables.py @@ -618,3 +618,39 @@ def test_describe_route_tables_with_nat_gateway(): nat_gw_routes[0]["DestinationCidrBlock"].should.equal("0.0.0.0/0") nat_gw_routes[0]["NatGatewayId"].should.equal(nat_gw_id) nat_gw_routes[0]["State"].should.equal("active") + + +@mock_ec2 +def test_create_vpc_end_point(): + + ec2 = boto3.client("ec2", region_name="us-west-1") + vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16") + subnet = ec2.create_subnet(VpcId=vpc["Vpc"]["VpcId"], + CidrBlock="10.0.0.0/24") + + route_table = ec2.create_route_table(VpcId=vpc["Vpc"]["VpcId"]) + + vpc_end_point = ec2.create_vpc_endpoint( + VpcId=vpc["Vpc"]["VpcId"], + ServiceName="com.amazonaws.us-east-1.s3", + RouteTableIds=[route_table["RouteTable"]["RouteTableId"]] + ) + + vpc_end_point["VpcEndpoint"]["ServiceName"].\ + should.equal("com.amazonaws.us-east-1.s3") + vpc_end_point["VpcEndpoint"]["RouteTableIds"][0].\ + should.equal(route_table["RouteTable"]["RouteTableId"]) + vpc_end_point["VpcEndpoint"]["VpcId"].should.equal(vpc["Vpc"]["VpcId"]) + + vpc_end_point = ec2.create_vpc_endpoint( + VpcId=vpc["Vpc"]["VpcId"], + ServiceName="com.amazonaws.us-east-1.s3", + SubnetIds=[subnet["Subnet"]["SubnetId"]], + VpcEndpointType="interface" + ) + + vpc_end_point["VpcEndpoint"]["ServiceName"].\ + should.equal("com.amazonaws.us-east-1.s3") + vpc_end_point["VpcEndpoint"]["SubnetIds"][0].\ + should.equal(subnet["Subnet"]["SubnetId"]) + vpc_end_point["VpcEndpoint"]["VpcId"].should.equal(vpc["Vpc"]["VpcId"])