diff --git a/CHANGES.rst b/CHANGES.rst index 50beb3809d..a506003ae9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -31,6 +31,9 @@ General Compute ~~~~~~~ +- Added ``ex_assign_public_ip`` to ``create_node`` in ec2 driver + [Kyle Long] + - Allow user to filter VPC by project in the CloudStack driver by passing ``project`` argument to the ``ex_list_vps`` method. (GITHUB-516) diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index 9930f7374d..dfeee7cbec 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -2312,6 +2312,15 @@ def create_node(self, **kwargs): :keyword ex_placement_group: The name of the placement group to launch the instance into. :type ex_placement_group: ``str`` + + :keyword ex_assign_public_ip: If True, the instance will + be assigned a public ip address. + Note : It takes takes a short + while for the instance to be + assigned the public ip so the + node returned will NOT have + the public ip assigned yet. + :type ex_assign_public_ip: ``bool`` """ image = kwargs["image"] size = kwargs["size"] @@ -2345,13 +2354,14 @@ def create_node(self, **kwargs): ' combinated with ex_subnet') security_group_ids = kwargs.get('ex_security_group_ids', None) + security_group_id_params = {} if security_group_ids: if not isinstance(security_group_ids, (tuple, list)): security_group_ids = [security_group_ids] for sig in range(len(security_group_ids)): - params['SecurityGroupId.%d' % (sig + 1,)] =\ + security_group_id_params['SecurityGroupId.%d' % (sig + 1,)] =\ security_group_ids[sig] if 'location' in kwargs: @@ -2397,12 +2407,40 @@ def create_node(self, **kwargs): if 'ex_ebs_optimized' in kwargs: params['EbsOptimized'] = kwargs['ex_ebs_optimized'] + subnet_id = None if 'ex_subnet' in kwargs: - params['SubnetId'] = kwargs['ex_subnet'].id + subnet_id = kwargs['ex_subnet'].id if 'ex_placement_group' in kwargs and kwargs['ex_placement_group']: params['Placement.GroupName'] = kwargs['ex_placement_group'] + assign_public_ip = kwargs.get('ex_assign_public_ip', False) + # In the event that a public ip is requested a NetworkInterface + # needs to be specified. Some properties that would + # normally be at the root (security group ids and subnet id) + # need to be moved to the level of the NetworkInterface because + # the NetworkInterface is no longer created implicitly + if assign_public_ip: + root_key = 'NetworkInterface.1.' + params[root_key + 'AssociatePublicIpAddress'] = "true" + # This means that when the instance is terminated, the + # NetworkInterface we created for the instance will be + # deleted automatically + params[root_key + 'DeleteOnTermination'] = "true" + # Required to be 0 if we are associating a public ip + params[root_key + 'DeviceIndex'] = "0" + + if subnet_id: + params[root_key + 'SubnetId'] = subnet_id + + for key, security_group_id in security_group_id_params.items(): + key = root_key + key + params[key] = security_group_id + else: + params.update(security_group_id_params) + if subnet_id: + params['SubnetId'] = subnet_id + object = self.connection.request(self.path, params=params).object nodes = self._to_nodes(object, 'instancesSet/item') diff --git a/libcloud/test/compute/fixtures/ec2/run_instances_with_subnet_and_security_group.xml b/libcloud/test/compute/fixtures/ec2/run_instances_with_subnet_and_security_group.xml new file mode 100644 index 0000000000..b43bea9f32 --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/run_instances_with_subnet_and_security_group.xml @@ -0,0 +1,87 @@ + + + ebfcb9e2-fe5f-4afd-ac91-6a9946305e32 + r-11111111 + 111111111111 + + + + i-11111111 + ami-11111111 + + 0 + pending + + ip-192-0-2-5.us-east-1.compute.internal + + + 0 + + m3.medium + 2015-09-30T14:37:59.000Z + + us-east-1a + + default + + aki-111111111 + + disabled + + subnet-11111111 + vpc-11111111 + 192.0.2.5 + true + + + sg-11111111 + test-security-group + + + + pending + pending + + x86_64 + ebs + /dev/sda + + paravirtual + + xen + + + eni-11111111 + subnet-11111111 + vpc-11111111 + + 111111111111 + in-use + 06:ea:24:17:f7:77 + 192.0.2.5 + true + + + sg-11111111 + test-security-group + + + + eni-attach-11111111 + 0 + attaching + 2015-09-30T14:37:59.000Z + true + + + + 192.0.2.5 + true + + + + + false + + + diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index 6a5cdeb9f6..a1ff899c05 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -116,6 +116,26 @@ def test_create_node_with_ex_mincount(self): self.assertEqual(node.extra['tags']['Name'], 'foo') self.assertEqual(len(node.extra['tags']), 1) + def test_create_node_with_ex_assign_public_ip(self): + # assertions are done in _create_ex_assign_public_ip_RunInstances + EC2MockHttp.type = 'create_ex_assign_public_ip' + image = NodeImage(id='ami-11111111', + name=self.image_name, + driver=self.driver) + size = NodeSize('m1.small', 'Small Instance', None, None, None, None, + driver=self.driver) + subnet = EC2NetworkSubnet('subnet-11111111', "test_subnet", "pending") + self.driver.create_node( + name='foo', + image=image, + size=size, + ex_subnet=subnet, + ex_security_group_ids=[ + 'sg-11111111' + ], + ex_assign_public_ip=True, + ) + def test_create_node_with_metadata(self): image = NodeImage(id='ami-be3adfd7', name=self.image_name, @@ -1245,6 +1265,17 @@ def _RunInstances(self, method, url, body, headers): body = self.fixtures.load('run_instances.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _create_ex_assign_public_ip_RunInstances(self, method, url, body, headers): + self.assertUrlContainsQueryParams(url, { + 'NetworkInterface.1.AssociatePublicIpAddress': "true", + 'NetworkInterface.1.DeleteOnTermination': "true", + 'NetworkInterface.1.DeviceIndex': "0", + 'NetworkInterface.1.SubnetId': "subnet-11111111", + 'NetworkInterface.1.SecurityGroupId.1': "sg-11111111", + }) + body = self.fixtures.load('run_instances_with_subnet_and_security_group.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _ex_security_groups_RunInstances(self, method, url, body, headers): self.assertUrlContainsQueryParams(url, {'SecurityGroup.1': 'group1'}) self.assertUrlContainsQueryParams(url, {'SecurityGroup.2': 'group2'})