Repository: libcloud Updated Branches: refs/heads/trunk c35222511 -> f813ea17b
Add Routes to GCE driver. Closes #410 Signed-off-by: Eric Johnson <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/f813ea17 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/f813ea17 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/f813ea17 Branch: refs/heads/trunk Commit: f813ea17b1372a9e5e577647d175d37eff4c6b11 Parents: c352225 Author: Eric Johnson <[email protected]> Authored: Tue Dec 2 22:46:46 2014 +0000 Committer: Eric Johnson <[email protected]> Committed: Mon Dec 8 20:51:38 2014 +0000 ---------------------------------------------------------------------- CHANGES.rst | 4 + libcloud/compute/drivers/gce.py | 168 +++++++++++++++++++ .../compute/fixtures/gce/global_routes.json | 58 +++++++ .../fixtures/gce/global_routes_lcdemoroute.json | 27 +++ .../gce/global_routes_lcdemoroute_delete.json | 13 ++ .../fixtures/gce/global_routes_post.json | 13 ++ ...ration_global_routes_lcdemoroute_delete.json | 13 ++ ...operations_operation_global_routes_post.json | 13 ++ libcloud/test/compute/test_gce.py | 58 ++++++- 9 files changed, 366 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index 8fddb8a..e4858e7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,6 +16,10 @@ General Compute ~~~~~~~ +- Add Routes to GCE driver. + (GITHUB-410) + [Eric Johnson] + - Add missing ``ubuntu-os-cloud`` images to the GCE driver. (LIBCLOUD-632, GITHUB-385) [Borja Martin] http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/compute/drivers/gce.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py index 6efb912..c8bbeca 100644 --- a/libcloud/compute/drivers/gce.py +++ b/libcloud/compute/drivers/gce.py @@ -296,6 +296,34 @@ class GCENetwork(UuidMixin): self.id, self.name, self.cidr) +class GCERoute(UuidMixin): + """A GCE Route object class.""" + def __init__(self, id, name, dest_range, priority, network="default", + tags=None, driver=None, extra=None): + self.id = str(id) + self.name = name + self.dest_range = dest_range + self.priority = priority + self.network = network + self.tags = tags + self.driver = driver + self.extra = extra + UuidMixin.__init__(self) + + def destroy(self): + """ + Destroy this route + + :return: True if successful + :rtype: ``bool`` + """ + return self.driver.ex_destroy_route(route=self) + + def __repr__(self): + return '<GCERoute id="%s" name="%s" dest_range="%s" network="%s">' % ( + self.id, self.name, self.dest_range, self.network.name) + + class GCENodeSize(NodeSize): """A GCE Node Size (MachineType) class.""" def __init__(self, id, name, ram, disk, bandwidth, price, driver, @@ -783,6 +811,20 @@ class GCENodeDriver(NodeDriver): list_locations = [self._to_node_location(l) for l in response['items']] return list_locations + def ex_list_routes(self): + """ + Return the list of routes. + + :return: A list of route objects. + :rtype: ``list`` of :class:`GCERoute` + """ + list_routes = [] + request = '/global/routes' + response = self.connection.request(request, method='GET').object + list_routes = [self._to_route(n) for n in + response.get('items', [])] + return list_routes + def ex_list_networks(self): """ Return the list of networks. @@ -1254,6 +1296,70 @@ class GCENodeDriver(NodeDriver): return self.ex_get_image(name) + def ex_create_route(self, name, dest_range, priority=500, + network="default", tags=None, next_hop=None, + description=None): + """ + Create a route. + + :param name: Name of route to be created + :type name: ``str`` + + :param dest_range: Address range of route in CIDR format. + :type dest_range: ``str`` + + :param priority: Priority value, lower values take precedence + :type priority: ``int`` + + :param network: The network the route belongs to. Can be either the + full URL of the network or a libcloud object. + :type network: ``str`` or ``GCENetwork`` + + :param tags: List of instance-tags for routing, empty for all nodes + :type tags: ``list`` of ``str`` or ``None`` + + :param next_hop: Next traffic hop. Use ``None`` for the default + Internet gateway, or specify an instance or IP + address. + :type next_hop: ``str``, ``GCENode``, or ``None`` + + :param description: Custom description for the route. + :type description: ``str`` or ``None`` + + :return: Route object + :rtype: :class:`GCERoute` + """ + route_data = {} + route_data['name'] = name + route_data['destRange'] = dest_range + route_data['priority'] = priority + route_data['description'] = description + if isinstance(network, str) and network.startswith('https://'): + network_uri = network + elif isinstance(network, str): + network = self.ex_get_network(network) + network_uri = network.extra['selfLink'] + else: + network_uri = network.extra['selfLink'] + route_data['network'] = network_uri + route_data['tags'] = tags + if next_hop is None: + url = 'https://www.googleapis.com/compute/%s/projects/%s/%s' % ( + API_VERSION, self.project, + "global/gateways/default-internet-gateway") + route_data['nextHopGateway'] = url + elif isinstance(next_hop, str): + route_data['nextHopIp'] = next_hop + else: + node = self.ex_get_node(next_hop) + route_data['nextHopInstance'] = node.extra['selfLink'] + + request = '/global/routes' + self.connection.async_request(request, method='POST', + data=route_data) + + return self.ex_get_route(name) + def ex_create_network(self, name, cidr, description=None): """ Create a network. @@ -2333,6 +2439,20 @@ class GCENodeDriver(NodeDriver): self.connection.async_request(request, method='DELETE') return True + def ex_destroy_route(self, route): + """ + Destroy a route. + + :param route: Route object to destroy + :type route: :class:`GCERoute` + + :return: True if successful + :rtype: ``bool`` + """ + request = '/global/routes/%s' % (route.name) + self.connection.async_request(request, method='DELETE') + return True + def ex_destroy_network(self, network): """ Destroy a network. @@ -2630,6 +2750,20 @@ class GCENodeDriver(NodeDriver): image = self._match_images('ubuntu-os-cloud', partial_name) return image + def ex_get_route(self, name): + """ + Return a Route object based on a route name. + + :param name: The name of the route + :type name: ``str`` + + :return: A Route object for the named route + :rtype: :class:`GCERoute` + """ + request = '/global/routes/%s' % (name) + response = self.connection.request(request, method='GET').object + return self._to_route(response) + def ex_get_network(self, name): """ Return a Network object based on a network name. @@ -3477,6 +3611,40 @@ class GCENodeDriver(NodeDriver): cidr=network.get('IPv4Range'), driver=self, extra=extra) + def _to_route(self, route): + """ + Return a Route object from the json-response dictionary. + + :param route: The dictionary describing the route. + :type route: ``dict`` + + :return: Route object + :rtype: :class:`GCERoute` + """ + extra = {} + + extra['selfLink'] = route.get('selfLink') + extra['description'] = route.get('description') + extra['creationTimestamp'] = route.get('creationTimestamp') + network = route.get('network') + priority = route.get('priority') + + if 'nextHopInstance' in route: + extra['nextHopInstance'] = route['nextHopInstance'] + if 'nextHopIp' in route: + extra['nextHopIp'] = route['nextHopIp'] + if 'nextHopNetwork' in route: + extra['nextHopNetwork'] = route['nextHopNetwork'] + if 'nextHopGateway' in route: + extra['nextHopGateway'] = route['nextHopGateway'] + if 'warnings' in route: + extra['warnings'] = route['warnings'] + + return GCERoute(id=route['id'], name=route['name'], + dest_range=route.get('destRange'), priority=priority, + network=network, tags=route.get('tags'), + driver=self, extra=extra) + def _to_node_image(self, image): """ Return an Image object from the json-response dictionary. http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/test/compute/fixtures/gce/global_routes.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/global_routes.json b/libcloud/test/compute/fixtures/gce/global_routes.json new file mode 100644 index 0000000..d42c884 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_routes.json @@ -0,0 +1,58 @@ +{ + "kind": "compute#routeList", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes", + "id": "projects/project_name/global/routes", + "items": [ + { + "kind": "compute#route", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/default-route-17d11bbbba01ce80", + "id": "15220239546867835355", + "creationTimestamp": "2014-01-21T10:30:55.592-08:00", + "name": "default-route-17d11bbbba01ce80", + "description": "Default route to the virtual network.", + "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/default", + "destRange": "10.240.0.0/16", + "priority": 1000, + "nextHopNetwork": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/default" + }, + { + "kind": "compute#route", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/default-route-e1808a2caeaf17fb", + "id": "4898173129042082424", + "creationTimestamp": "2014-01-21T10:30:55.584-08:00", + "name": "default-route-e1808a2caeaf17fb", + "description": "Default route to the Internet.", + "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/default", + "destRange": "0.0.0.0/0", + "priority": 1000, + "nextHopGateway": "https://www.googleapis.com/compute/v1/projects/project_name/global/gateways/default-internet-gateway" + }, + { + "kind": "compute#route", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/lcdemoroute", + "id": "14575183394193523469", + "creationTimestamp": "2014-11-25T11:00:45.062-08:00", + "name": "lcdemoroute", + "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/default", + "tags": [ + "tag1", + "tag2" + ], + "destRange": "192.168.25.0/24", + "priority": 1000, + "nextHopInstance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-f/instances/libcloud-100", + "warnings": [ + { + "code": "NEXT_HOP_CANNOT_IP_FORWARD", + "message": "Next hop instance 'https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-f/instances/libcloud-100' cannot forward ip traffic. The next hop instance must have canIpForward set.", + "data": [ + { + "key": "instance", + "value": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-f/instances/libcloud-100" + } + ] + } + ] + } + ] +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute.json b/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute.json new file mode 100644 index 0000000..5931ecc --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute.json @@ -0,0 +1,27 @@ +{ + "kind": "compute#route", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/lcdemoroute", + "id": "14575183394193523469", + "creationTimestamp": "2014-11-25T11:00:45.062-08:00", + "name": "lcdemoroute", + "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/default", + "tags": [ + "tag1", + "tag2" + ], + "destRange": "192.168.25.0/24", + "priority": 1000, + "nextHopInstance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-f/instances/libcloud-100", + "warnings": [ + { + "code": "NEXT_HOP_CANNOT_IP_FORWARD", + "message": "Next hop instance 'https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-f/instances/libcloud-100' cannot forward ip traffic. The next hop instance must have canIpForward set.", + "data": [ + { + "key": "instance", + "value": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-f/instances/libcloud-100" + } + ] + } + ] +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute_delete.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute_delete.json b/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute_delete.json new file mode 100644 index 0000000..2dd42c4 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_routes_lcdemoroute_delete.json @@ -0,0 +1,13 @@ +{ + "kind": "compute#operation", + "id": "17322940416642455149", + "name": "operation-global_routes_lcdemoroute_delete", + "operationType": "destroy", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/lcdemoroute", + "status": "PENDING", + "user": "[email protected]", + "progress": 0, + "insertTime": "2014-11-25T11:00:44.049-08:00", + "startTime": "2014-11-25T11:00:44.385-08:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_routes_lcdemoroute_delete" +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/test/compute/fixtures/gce/global_routes_post.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/global_routes_post.json b/libcloud/test/compute/fixtures/gce/global_routes_post.json new file mode 100644 index 0000000..a491f8f --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_routes_post.json @@ -0,0 +1,13 @@ +{ + "kind": "compute#operation", + "id": "17322940416642455149", + "name": "operation-global_routes_post", + "operationType": "insert", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/lcdemoroute", + "status": "PENDING", + "user": "[email protected]", + "progress": 0, + "insertTime": "2014-11-25T11:00:44.049-08:00", + "startTime": "2014-11-25T11:00:44.385-08:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_routes_post" +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_lcdemoroute_delete.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_lcdemoroute_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_lcdemoroute_delete.json new file mode 100644 index 0000000..ff6d42d --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_lcdemoroute_delete.json @@ -0,0 +1,13 @@ +{ + "kind": "compute#operation", + "id": "17322940416642455149", + "name": "operation-global_route_lcdemoroute", + "operationType": "destroy", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/lcdemoroute", + "status": "DONE", + "user": "[email protected]", + "progress": 100, + "insertTime": "2014-11-25T11:00:44.049-08:00", + "startTime": "2014-11-25T11:00:44.385-08:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_route_lcdemoroute" +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_post.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_post.json new file mode 100644 index 0000000..bcddbcb --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_routes_post.json @@ -0,0 +1,13 @@ +{ + "kind": "compute#operation", + "id": "17322940416642455149", + "name": "operation-global_routes_lcdemoroute_post", + "operationType": "insert", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/routes/lcdemoroute", + "status": "DONE", + "user": "[email protected]", + "progress": 100, + "insertTime": "2014-11-25T11:00:44.049-08:00", + "startTime": "2014-11-25T11:00:44.385-08:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_routes_lcdemoroute_post" +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/f813ea17/libcloud/test/compute/test_gce.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py index de91aaf..b46e04d 100644 --- a/libcloud/test/compute/test_gce.py +++ b/libcloud/test/compute/test_gce.py @@ -28,7 +28,8 @@ from libcloud.compute.drivers.gce import (GCENodeDriver, API_VERSION, GCEFirewall, GCEForwardingRule, GCENetwork, GCEZone, - GCENodeImage) + GCENodeImage, + GCERoute) from libcloud.common.google import (GoogleBaseAuthConnection, GoogleInstalledAppAuthConnection, GoogleBaseConnection, @@ -157,6 +158,11 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin): self.assertEqual(len(locations), 5) self.assertEqual(locations[0].name, 'europe-west1-a') + def test_ex_list_routes(self): + routes = self.driver.ex_list_routes() + self.assertEqual(len(routes), 3) + self.assertTrue('lcdemoroute' in [route.name for route in routes]) + def test_ex_list_networks(self): networks = self.driver.ex_list_networks() self.assertEqual(len(networks), 3) @@ -288,6 +294,18 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin): self.assertEqual(fwr.extra['portRange'], port_range) self.assertEqual(fwr.extra['description'], description) + def test_ex_create_route(self): + route_name = 'lcdemoroute' + dest_range = '192.168.25.0/24' + priority = 1000 + route = self.driver.ex_create_route(route_name, dest_range) + self.assertTrue(isinstance(route, GCERoute)) + self.assertEqual(route.name, route_name) + self.assertEqual(route.priority, priority) + self.assertTrue("tag1" in route.tags) + self.assertTrue(route.extra['nextHopInstance'].endswith('libcloud-100')) + self.assertEqual(route.dest_range, dest_range) + def test_ex_create_network(self): network_name = 'lcnetwork' cidr = '10.11.0.0/16' @@ -573,6 +591,11 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin): destroyed = fwr.destroy() self.assertTrue(destroyed) + def test_ex_destroy_route(self): + route = self.driver.ex_get_route('lcdemoroute') + destroyed = route.destroy() + self.assertTrue(destroyed) + def test_ex_destroy_network(self): network = self.driver.ex_get_network('lcnetwork') destroyed = network.destroy() @@ -675,6 +698,13 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin): self.assertEqual(image.name, name) self.assertEqual(image.extra['description'], description) + def test_ex_get_route(self): + route_name = 'lcdemoroute' + route = self.driver.ex_get_route(route_name) + self.assertEqual(route.name, route_name) + self.assertEqual(route.dest_range, '192.168.25.0/24') + self.assertEqual(route.priority, 1000) + def test_ex_get_network(self): network_name = 'lcnetwork' network = self.driver.ex_get_network(network_name) @@ -861,6 +891,13 @@ class GCEMockHttp(MockHttpTestCase): body = self.fixtures.load('global_images_debian_6_squeeze_v20130926_deprecate.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_routes(self, method, url, body, headers): + if method == 'POST': + body = self.fixtures.load('global_routes_post.json') + else: + body = self.fixtures.load('global_routes.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_networks(self, method, url, body, headers): if method == 'POST': body = self.fixtures.load('global_networks_post.json') @@ -883,6 +920,13 @@ class GCEMockHttp(MockHttpTestCase): 'global_networks_libcloud-demo-europe-network.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_routes_lcdemoroute(self, method, url, body, headers): + if method == 'DELETE': + body = self.fixtures.load('global_routes_lcdemoroute_delete.json') + else: + body = self.fixtures.load('global_routes_lcdemoroute.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_networks_lcnetwork(self, method, url, body, headers): if method == 'DELETE': body = self.fixtures.load('global_networks_lcnetwork_delete.json') @@ -944,12 +988,24 @@ class GCEMockHttp(MockHttpTestCase): 'operations_operation_global_firewalls_post.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_operations_operation_global_routes_lcdemoroute_delete( + self, method, url, body, headers): + body = self.fixtures.load( + 'operations_operation_global_routes_lcdemoroute_delete.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_operations_operation_global_networks_lcnetwork_delete( self, method, url, body, headers): body = self.fixtures.load( 'operations_operation_global_networks_lcnetwork_delete.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_operations_operation_global_routes_post( + self, method, url, body, headers): + body = self.fixtures.load( + 'operations_operation_global_routes_post.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_operations_operation_global_networks_post( self, method, url, body, headers): body = self.fixtures.load(
