Updated Branches: refs/heads/trunk abf8f8e1a -> 5a68e0070
LIBCLOUD-468: Add VPC subnet calls to the EC2 driver. Signed-off-by: Tomaz Muraus <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/ee69f99a Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/ee69f99a Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/ee69f99a Branch: refs/heads/trunk Commit: ee69f99a6f0de10b7bd86f078f2d38cffbb120dd Parents: abf8f8e Author: Chris DeRamus <[email protected]> Authored: Thu Dec 26 07:22:29 2013 -0500 Committer: Tomaz Muraus <[email protected]> Committed: Thu Dec 26 14:13:23 2013 +0100 ---------------------------------------------------------------------- libcloud/compute/drivers/ec2.py | 154 +++++++++++++++++++ .../test/compute/fixtures/ec2/create_subnet.xml | 11 ++ .../test/compute/fixtures/ec2/delete_subnet.xml | 4 + .../compute/fixtures/ec2/describe_subnets.xml | 37 +++++ libcloud/test/compute/test_ec2.py | 39 +++++ 5 files changed, 245 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/ee69f99a/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index c3d2cc3..61d6f4b 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -595,6 +595,23 @@ class EC2Network(object): % (self.id, self.name)) +class EC2NetworkSubnet(object): + """ + Represents information about a VPC (Virtual Private Cloud) subnet + + Note: This class is EC2 specific. + """ + + def __init__(self, id, name, state, extra=None): + self.id = id + self.name = name + self.state = state + self.extra = extra or {} + + def __repr__(self): + return (('<EC2NetworkSubnet: id=%s, name=%s') % (self.id, self.name)) + + class BaseEC2NodeDriver(NodeDriver): """ Base Amazon EC2 node driver. @@ -961,6 +978,75 @@ class BaseEC2NodeDriver(NodeDriver): return EC2Network(vpc_id, name, cidr_block, extra=extra) + def _to_subnets(self, response): + return [self._to_subnet(el) for el in response.findall( + fixxpath(xpath='subnetSet/item', namespace=NAMESPACE)) + ] + + def _to_subnet(self, element): + # Get the subnet ID + subnet_id = findtext(element=element, + xpath='subnetId', + namespace=NAMESPACE) + + # Get our tag items + tag_items = findall(element=element, + xpath='tagSet/item', + namespace=NAMESPACE) + + # Loop through all tag items to build our dictionary + tags = {} + for tag in tag_items: + key = findtext(element=tag, + xpath='key', + namespace=NAMESPACE) + + value = findtext(element=tag, + xpath='value', + namespace=NAMESPACE) + + tags[key] = value + + # If we don't get anything back then use the subnet_id + name = tags.get('Name', subnet_id) + + state = findtext(element=element, + xpath='state', + namespace=NAMESPACE) + + # Build our extra attributes map + extra_attributes_map = { + 'cidr_block': { + 'xpath': 'cidrBlock', + 'type': str + }, + 'available_ips': { + 'xpath': 'availableIpAddressCount', + 'type': int + }, + 'zone': { + 'xpath': 'availabilityZone', + 'type': str + }, + 'vpc_id': { + 'xpath': 'vpcId', + 'type': str + } + } + + # Define and build our extra dictionary + extra = {} + for attribute, values in extra_attributes_map.items(): + type_func = values['type'] + value = findattr(element=element, xpath=values['xpath'], + namespace=NAMESPACE) + extra[attribute] = type_func(value) + + # Also include our tags + extra['tags'] = tags + + return EC2NetworkSubnet(subnet_id, name, state, extra=extra) + def ex_list_reserved_nodes(self): """ List all reserved instances/nodes which can be purchased from Amazon @@ -1557,6 +1643,74 @@ class BaseEC2NodeDriver(NodeDriver): return element == 'true' + def ex_list_subnets(self): + """ + Return a list of :class:`EC2NetworkSubnet` objects for the + current region. + + :rtype: ``list`` of :class:`EC2NetworkSubnet` + """ + params = {'Action': 'DescribeSubnets'} + + return self._to_subnets( + self.connection.request(self.path, params=params).object + ) + + def ex_create_subnet(self, vpc_id, cidr_block, + availability_zone, name=None): + """ + Create a network subnet within a VPC + + :param vpc_id: The ID of the VPC that the subnet should be + associated with + :type vpc_id: ``str`` + + :param cidr_block: The CIDR block assigned to the subnet + :type cidr_block: ``str`` + + :param availability_zone: The availability zone where the subnet + should reside + :type availability_zone: ``str`` + + :param name: An optional name for the network + :type name: ``str`` + + :rtype: :class: `EC2NetworkSubnet` + """ + params = {'Action': 'CreateSubnet', + 'VpcId': vpc_id, + 'CidrBlock': cidr_block, + 'AvailabilityZone': availability_zone} + + response = self.connection.request(self.path, params=params).object + element = response.findall(fixxpath(xpath='subnet', + namespace=NAMESPACE))[0] + + subnet = self._to_subnet(element) + + if name is not None: + self.ex_create_tags(subnet, {'Name': name}) + + return subnet + + def ex_delete_subnet(self, subnet_id): + """ + Deletes a VPC subnet. + + :param subnet_id: The ID of the subnet + :type subnet_id: ``str`` + + :rtype: ``bool`` + """ + params = {'Action': 'DeleteSubnet', 'SubnetId': subnet_id} + + result = self.connection.request(self.path, params=params).object + element = findtext(element=result, + xpath='return', + namespace=NAMESPACE) + + return element == 'true' + def ex_list_security_groups(self): """ List existing Security Groups. http://git-wip-us.apache.org/repos/asf/libcloud/blob/ee69f99a/libcloud/test/compute/fixtures/ec2/create_subnet.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/create_subnet.xml b/libcloud/test/compute/fixtures/ec2/create_subnet.xml new file mode 100644 index 0000000..b04a69e --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/create_subnet.xml @@ -0,0 +1,11 @@ +<CreateSubnetResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> + <requestId>e94b315e-6424-4536-b48d-0dfb47732c72</requestId> + <subnet> + <subnetId>subnet-ce0e7ce6</subnetId> + <state>pending</state> + <vpcId>vpc-532135d1</vpcId> + <cidrBlock>192.168.51.128/26</cidrBlock> + <availableIpAddressCount>59</availableIpAddressCount> + <availabilityZone>us-east-1b</availabilityZone> + </subnet> +</CreateSubnetResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/ee69f99a/libcloud/test/compute/fixtures/ec2/delete_subnet.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/delete_subnet.xml b/libcloud/test/compute/fixtures/ec2/delete_subnet.xml new file mode 100644 index 0000000..dd5ed64 --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/delete_subnet.xml @@ -0,0 +1,4 @@ +<DeleteSubnetResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> + <requestId>5cd6fa89-35bd-4aac-99ed-na8af7</requestId> + <return>true</return> +</DeleteSubnetResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/ee69f99a/libcloud/test/compute/fixtures/ec2/describe_subnets.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/describe_subnets.xml b/libcloud/test/compute/fixtures/ec2/describe_subnets.xml new file mode 100644 index 0000000..3d14d50 --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/describe_subnets.xml @@ -0,0 +1,37 @@ +<DescribeSubnetsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> + <requestId>67bba371-8044-45a7-b4a3-d72fef8b96d8</requestId> + <subnetSet> + <item> + <subnetId>subnet-ce0e7ce5</subnetId> + <state>available</state> + <vpcId>vpc-532135d1</vpcId> + <cidrBlock>192.168.51.0/25</cidrBlock> + <availableIpAddressCount>123</availableIpAddressCount> + <availabilityZone>us-east-1a</availabilityZone> + <defaultForAz>false</defaultForAz> + <mapPublicIpOnLaunch>false</mapPublicIpOnLaunch> + <tagSet> + <item> + <key>Name</key> + <value>Test Subnet 1</value> + </item> + </tagSet> + </item> + <item> + <subnetId>subnet-ce0e7ce6</subnetId> + <state>available</state> + <vpcId>vpc-532135d1</vpcId> + <cidrBlock>192.168.51.128/64</cidrBlock> + <availableIpAddressCount>59</availableIpAddressCount> + <availabilityZone>us-east-1b</availabilityZone> + <defaultForAz>false</defaultForAz> + <mapPublicIpOnLaunch>false</mapPublicIpOnLaunch> + <tagSet> + <item> + <key>Name</key> + <value>Test Subnet 2</value> + </item> + </tagSet> + </item> + </subnetSet> +</DescribeSubnetsResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/ee69f99a/libcloud/test/compute/test_ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index 44116a0..3283d55 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -811,6 +811,33 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin): resp = self.driver.ex_delete_network(vpc) self.assertTrue(resp) + def test_ex_list_subnets(self): + subnets = self.driver.ex_list_subnets() + + self.assertEqual(len(subnets), 2) + + self.assertEqual('subnet-ce0e7ce5', subnets[0].id) + self.assertEqual('available', subnets[0].state) + self.assertEqual(123, subnets[0].extra['available_ips']) + + self.assertEqual('subnet-ce0e7ce6', subnets[1].id) + self.assertEqual('available', subnets[1].state) + self.assertEqual(59, subnets[1].extra['available_ips']) + + def test_ex_create_subnet(self): + subnet = self.driver.ex_create_subnet('vpc-532135d1', + '192.168.51.128/26', + 'us-east-1b', + name='Test Subnet') + + self.assertEqual('subnet-ce0e7ce6', subnet.id) + self.assertEqual('pending', subnet.state) + self.assertEqual('vpc-532135d1', subnet.extra['vpc_id']) + + def test_ex_delete_subnet(self): + resp = self.driver.ex_delete_subnet('subnet-ce0e7ce6') + self.assertTrue(resp) + class EC2USWest1Tests(EC2Tests): region = 'us-west-1' @@ -1106,6 +1133,18 @@ class EC2MockHttp(MockHttpTestCase): body = self.fixtures.load('delete_vpc.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _DescribeSubnets(self, method, url, body, headers): + body = self.fixtures.load('describe_subnets.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _CreateSubnet(self, method, url, body, headers): + body = self.fixtures.load('create_subnet.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _DeleteSubnet(self, method, url, body, headers): + body = self.fixtures.load('delete_subnet.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + class EucMockHttp(EC2MockHttp): fixtures = ComputeFileFixtures('ec2')
