From 136873d49ef07811c6014d2915997c8fbd59a35b Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 14 Feb 2015 14:42:20 -0500 Subject: [PATCH] Cleanup public IPs for EC2 classic instances and fix some default ENI things. --- moto/ec2/models.py | 55 +++++++++++++++---- .../responses/elastic_network_interfaces.py | 5 ++ moto/ec2/responses/instances.py | 16 +++--- moto/ec2/utils.py | 12 +++- .../test_elastic_network_interfaces.py | 19 ++++--- tests/test_ec2/test_instances.py | 26 ++++++--- 6 files changed, 97 insertions(+), 36 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index e5216bd10..ac0bb4ddc 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -69,6 +69,7 @@ from .utils import ( random_internet_gateway_id, random_ip, random_key_pair, + random_private_ip, random_public_ip, random_reservation_id, random_route_table_id, @@ -174,9 +175,12 @@ class NetworkInterface(object): security_group_ids = properties.get('SecurityGroups', []) - subnet_id = properties['SubnetId'] ec2_backend = ec2_backends[region_name] - subnet = ec2_backend.get_subnet(subnet_id) + subnet_id = properties.get('SubnetId') + if subnet_id: + subnet = ec2_backend.get_subnet(subnet_id) + else: + subnet = None private_ip_address = properties.get('PrivateIpAddress', None) @@ -224,7 +228,7 @@ class NetworkInterfaceBackend(object): super(NetworkInterfaceBackend, self).__init__() def create_network_interface(self, subnet, private_ip_address, group_ids=None, **kwargs): - eni = NetworkInterface(self, subnet, private_ip_address, group_ids=group_ids) + eni = NetworkInterface(self, subnet, private_ip_address, group_ids=group_ids, **kwargs) self.enis[eni.id] = eni return eni @@ -297,10 +301,14 @@ class Instance(BotoInstance, TaggedEC2Resource): self.instance_type = kwargs.get("instance_type", "m1.small") self.vpc_id = None self.subnet_id = kwargs.get("subnet_id") + in_ec2_classic = not bool(self.subnet_id) self.key_name = kwargs.get("key_name") self.source_dest_check = "true" self.launch_time = datetime.utcnow().isoformat() - self.private_ip_address = kwargs.get('private_ip_address') + associate_public_ip = kwargs.get("associate_public_ip", False) + if in_ec2_classic: + # If we are in EC2-Classic, autoassign a public IP + associate_public_ip = True self.block_device_mapping = BlockDeviceMapping() self.block_device_mapping['/dev/sda1'] = BlockDeviceType(volume_id=random_volume_id()) @@ -326,9 +334,26 @@ class Instance(BotoInstance, TaggedEC2Resource): self.vpc_id = subnet.vpc_id self.prep_nics(kwargs.get("nics", {}), - subnet_id=kwargs.get("subnet_id"), + subnet_id=self.subnet_id, private_ip=kwargs.get("private_ip"), - associate_public_ip=kwargs.get("associate_public_ip")) + associate_public_ip=associate_public_ip) + + @property + def private_ip(self): + return self.nics[0].private_ip_address + + @property + def private_dns(self): + return "ip-{0}.ec2.internal".format(self.private_ip) + + @property + def public_ip(self): + return self.nics[0].public_ip + + @property + def public_dns(self): + if self.public_ip: + return "ec2-{0}.compute-1.amazonaws.com".format(self.public_ip) @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): @@ -346,7 +371,7 @@ class Instance(BotoInstance, TaggedEC2Resource): instance_type=properties.get("InstanceType", "m1.small"), subnet_id=properties.get("SubnetId"), key_name=properties.get("KeyName"), - private_ip_address=properties.get('PrivateIpAddress'), + private_ip=properties.get('PrivateIpAddress'), ) return reservation.instances[0] @@ -407,6 +432,9 @@ class Instance(BotoInstance, TaggedEC2Resource): def prep_nics(self, nic_spec, subnet_id=None, private_ip=None, associate_public_ip=None): self.nics = {} + if not private_ip: + private_ip = random_private_ip() + # Primary NIC defaults primary_nic = {'SubnetId': subnet_id, 'PrivateIpAddress': private_ip, @@ -434,7 +462,10 @@ class Instance(BotoInstance, TaggedEC2Resource): if device_index == 0 and primary_nic: nic.update(primary_nic) - subnet = self.ec2_backend.get_subnet(nic['SubnetId']) + if 'SubnetId' in nic: + subnet = self.ec2_backend.get_subnet(nic['SubnetId']) + else: + subnet = None group_id = nic.get('SecurityGroupId') group_ids = [group_id] if group_id else [] @@ -468,13 +499,13 @@ class Instance(BotoInstance, TaggedEC2Resource): if attribute_name == 'AvailabilityZone': return self.placement elif attribute_name == 'PrivateDnsName': - return self.private_dns_name + return self.private_dns elif attribute_name == 'PublicDnsName': - return self.public_dns_name + return self.public_dns elif attribute_name == 'PrivateIp': - return self.private_ip_address + return self.private_ip elif attribute_name == 'PublicIp': - return self.ip_address + return self.public_ip raise UnformattedGetAttTemplateException() diff --git a/moto/ec2/responses/elastic_network_interfaces.py b/moto/ec2/responses/elastic_network_interfaces.py index a7e270a93..10c5df36d 100644 --- a/moto/ec2/responses/elastic_network_interfaces.py +++ b/moto/ec2/responses/elastic_network_interfaces.py @@ -25,6 +25,11 @@ class ElasticNetworkInterfaces(BaseResponse): def describe_network_interfaces(self): # Partially implemented. Supports only network-interface-id and group-id filters filters = filters_from_querystring(self.querystring) + eni_ids = self._get_multi_param('NetworkInterfaceId.') + if 'network-interface-id' not in filters and eni_ids: + # Network interfaces can be filtered by passing the 'network-interface-id' + # filter or by passing the NetworkInterfaceId parameter + filters['network-interface-id'] = eni_ids enis = self.ec2_backend.describe_network_interfaces(filters) template = self.response_template(DESCRIBE_NETWORK_INTERFACES_RESPONSE) return template.render(enis=enis) diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 62909e2ea..f7c03206d 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -198,8 +198,8 @@ EC2_RUN_INSTANCES = """{{ instance.nics[0].subnet.id }} {{ instance.nics[0].subnet.vpc_id }} - {{ instance.nics[0].private_ip_address }} + {{ instance.private_ip }} {% if instance.nics[0].public_ip %} {{ instance.nics[0].public_ip }} {% endif %} diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 90cf607e7..11fb32ffe 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -130,6 +130,12 @@ def random_public_ip(): random.choice(range(255))) +def random_private_ip(): + return '10.{0}.{1}.{2}'.format(random.choice(range(255)), + random.choice(range(255)), + random.choice(range(255))) + + def random_ip(): return "127.{0}.{1}.{2}".format( random.randint(0, 255), @@ -331,7 +337,7 @@ filter_dict_attribute_mapping = { def passes_filter_dict(instance, filter_dict): for filter_name, filter_values in filter_dict.items(): - + if filter_name in filter_dict_attribute_mapping: instance_attr = filter_dict_attribute_mapping[filter_name] instance_value = get_object_value(instance, instance_attr) @@ -347,14 +353,16 @@ def passes_filter_dict(instance, filter_dict): filter_name) return True + def instance_value_in_filter_values(instance_value, filter_values): if isinstance(instance_value, list): if not set(filter_values).intersection(set(instance_value)): return False - elif instance_value not in filter_values: + elif instance_value not in filter_values: return False return True + def filter_reservations(reservations, filter_dict): result = [] for reservation in reservations: diff --git a/tests/test_ec2/test_elastic_network_interfaces.py b/tests/test_ec2/test_elastic_network_interfaces.py index f60c6c5aa..40c17aa40 100644 --- a/tests/test_ec2/test_elastic_network_interfaces.py +++ b/tests/test_ec2/test_elastic_network_interfaces.py @@ -76,14 +76,14 @@ def test_elastic_network_interfaces_with_groups(): subnet = conn.create_subnet(vpc.id, "10.0.0.0/18") security_group1 = conn.create_security_group('test security group #1', 'this is a test security group') security_group2 = conn.create_security_group('test security group #2', 'this is a test security group') - conn.create_network_interface(subnet.id, groups=[security_group1.id,security_group2.id]) + conn.create_network_interface(subnet.id, groups=[security_group1.id, security_group2.id]) all_enis = conn.get_all_network_interfaces() all_enis.should.have.length_of(1) eni = all_enis[0] eni.groups.should.have.length_of(2) - set([group.id for group in eni.groups]).should.equal(set([security_group1.id,security_group2.id])) + set([group.id for group in eni.groups]).should.equal(set([security_group1.id, security_group2.id])) @requires_boto_gte("2.12.0") @@ -122,25 +122,30 @@ def test_elastic_network_interfaces_filtering(): security_group1 = conn.create_security_group('test security group #1', 'this is a test security group') security_group2 = conn.create_security_group('test security group #2', 'this is a test security group') - eni1 = conn.create_network_interface(subnet.id, groups=[security_group1.id,security_group2.id]) + eni1 = conn.create_network_interface(subnet.id, groups=[security_group1.id, security_group2.id]) eni2 = conn.create_network_interface(subnet.id, groups=[security_group1.id]) eni3 = conn.create_network_interface(subnet.id) all_enis = conn.get_all_network_interfaces() all_enis.should.have.length_of(3) + # Filter by NetworkInterfaceId + enis_by_id = conn.get_all_network_interfaces([eni1.id]) + enis_by_id.should.have.length_of(1) + set([eni.id for eni in enis_by_id]).should.equal(set([eni1.id])) + # Filter by ENI ID - enis_by_id = conn.get_all_network_interfaces(filters={'network-interface-id':eni1.id}) + enis_by_id = conn.get_all_network_interfaces(filters={'network-interface-id': eni1.id}) enis_by_id.should.have.length_of(1) set([eni.id for eni in enis_by_id]).should.equal(set([eni1.id])) # Filter by Security Group enis_by_group = conn.get_all_network_interfaces(filters={'group-id':security_group1.id}) enis_by_group.should.have.length_of(2) - set([eni.id for eni in enis_by_group]).should.equal(set([eni1.id,eni2.id])) + set([eni.id for eni in enis_by_group]).should.equal(set([eni1.id, eni2.id])) # Filter by ENI ID and Security Group - enis_by_group = conn.get_all_network_interfaces(filters={'network-interface-id':eni1.id, 'group-id':security_group1.id}) + enis_by_group = conn.get_all_network_interfaces(filters={'network-interface-id': eni1.id, 'group-id': security_group1.id}) enis_by_group.should.have.length_of(1) set([eni.id for eni in enis_by_group]).should.equal(set([eni1.id])) @@ -157,7 +162,7 @@ def test_elastic_network_interfaces_cloudformation(): conn.create_stack( "test_stack", template_body=template_json, - ) + ) ec2_conn = boto.ec2.connect_to_region("us-west-1") eni = ec2_conn.get_all_network_interfaces()[0] diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index 95f047d5c..e16bbb126 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -491,23 +491,23 @@ def test_instance_with_nic_attach_detach(): eni = conn.create_network_interface(subnet.id, groups=[security_group2.id]) # Check initial instance and ENI data - instance.interfaces.should.have.length_of(0) + instance.interfaces.should.have.length_of(1) eni.groups.should.have.length_of(1) set([group.id for group in eni.groups]).should.equal(set([security_group2.id])) # Attach - conn.attach_network_interface(eni.id, instance.id, device_index=0) + conn.attach_network_interface(eni.id, instance.id, device_index=1) # Check attached instance and ENI data instance.update() - instance.interfaces.should.have.length_of(1) - instance_eni = instance.interfaces[0] + instance.interfaces.should.have.length_of(2) + instance_eni = instance.interfaces[1] instance_eni.id.should.equal(eni.id) instance_eni.groups.should.have.length_of(2) set([group.id for group in instance_eni.groups]).should.equal(set([security_group1.id,security_group2.id])) - eni = conn.get_all_network_interfaces(eni.id)[0] + eni = conn.get_all_network_interfaces(filters={'network-interface-id': eni.id})[0] eni.groups.should.have.length_of(2) set([group.id for group in eni.groups]).should.equal(set([security_group1.id,security_group2.id])) @@ -516,9 +516,9 @@ def test_instance_with_nic_attach_detach(): # Check detached instance and ENI data instance.update() - instance.interfaces.should.have.length_of(0) + instance.interfaces.should.have.length_of(1) - eni = conn.get_all_network_interfaces(eni.id)[0] + eni = conn.get_all_network_interfaces(filters={'network-interface-id': eni.id})[0] eni.groups.should.have.length_of(1) set([group.id for group in eni.groups]).should.equal(set([security_group2.id])) @@ -530,6 +530,18 @@ def test_instance_with_nic_attach_detach(): cm.exception.request_id.should_not.be.none +@mock_ec2 +def test_ec2_classic_has_public_ip_address(): + conn = boto.connect_ec2('the_key', 'the_secret') + reservation = conn.run_instances('ami-1234abcd', key_name="keypair_name") + instance = reservation.instances[0] + instance.ip_address.should_not.equal(None) + instance.public_dns_name.should.contain(instance.ip_address) + + instance.private_ip_address.should_not.equal(None) + instance.private_dns_name.should.contain(instance.private_ip_address) + + @mock_ec2 def test_run_instance_with_keypair(): conn = boto.connect_ec2('the_key', 'the_secret')