Repository: libcloud Updated Branches: refs/heads/trunk 93c38981d -> 33ca3b2e6
Add support for network creation on Cloudstack advanced zones Closes #316 Signed-off-by: Tomaz Muraus <to...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/33ca3b2e Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/33ca3b2e Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/33ca3b2e Branch: refs/heads/trunk Commit: 33ca3b2e6209aa5347b3f820b35f06327a98ac5d Parents: 93c3898 Author: Roeland Kuipers <rkuip...@schubergphilis.com> Authored: Tue Jun 10 16:58:31 2014 +0200 Committer: Tomaz Muraus <to...@apache.org> Committed: Thu Jun 12 20:31:44 2014 +0200 ---------------------------------------------------------------------- .gitignore | 3 + CHANGES.rst | 6 + libcloud/compute/drivers/cloudstack.py | 175 +++++++++++++++++++ .../cloudstack/createNetwork_default.json | 1 + .../cloudstack/deleteNetwork_default.json | 1 + .../listNetworkOfferings_default.json | 1 + .../queryAsyncJobResult_deleteNetwork.json | 1 + libcloud/test/compute/test_cloudstack.py | 57 ++++++ 8 files changed, 245 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/.gitignore ---------------------------------------------------------------------- diff --git a/.gitignore b/.gitignore index ce19293..dc61a25 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ dist/*apache-libcloud* dist/*apache_libcloud* _build/ apache_libcloud.egg-info/ +.project +.pydevproject +.settings http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index 250430d..8a4e152 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -217,6 +217,12 @@ Compute (GITHUB-312) [LIBCLOUD-575, Zak Estrada] +- Add support for network management for advanced zones + (ex_list_network_offerings, ex_create_network, ex_delete_network) in the + CloudStack driver. + (GITHUB-316) + [Roeland Kuipers] + Storage ~~~~~~~ http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/libcloud/compute/drivers/cloudstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/cloudstack.py b/libcloud/compute/drivers/cloudstack.py index 2b69d84..921b68e 100644 --- a/libcloud/compute/drivers/cloudstack.py +++ b/libcloud/compute/drivers/cloudstack.py @@ -99,6 +99,22 @@ RESOURCE_EXTRA_ATTRIBUTES_MAP = { 'can_use_for_deploy': { 'key_name': 'canusefordeploy', 'transform_func': str + }, + 'gateway': { + 'key_name': 'gateway', + 'transform_func': str + }, + 'netmask': { + 'key_name': 'netmask', + 'transform_func': str + }, + 'vpc_id': { + 'key_name': 'vpcid', + 'transform_func': str + }, + 'project_id': { + 'key_name': 'projectid', + 'transform_func': str } }, 'node': { @@ -453,6 +469,31 @@ class CloudStackNetwork(object): self.networkofferingid, self.zoneid, self.driver.name)) +class CloudStackNetworkOffering(object): + """ + Class representing a CloudStack Network Offering. + """ + + def __init__(self, name, display_text, guest_ip_type, id, + service_offering_id, for_vpc, driver, extra=None): + self.display_text = display_text + self.name = name + self.guest_ip_type = guest_ip_type + self.id = id + self.service_offering_id = service_offering_id + self.for_vpc = for_vpc + self.driver = driver + self.extra = extra or {} + + def __repr__(self): + return (('<CloudStackNetworkOffering: id=%s, name=%s, ' + 'display_text=%s, guest_ip_type=%s, service_offering_id=%s, ' + 'for_vpc=%s, driver%s>') + % (self.id, self.name, self.display_text, + self.guest_ip_type, self.service_offering_id, self.for_vpc, + self.driver.name)) + + class CloudStackProject(object): """ Class representing a CloudStack Project. @@ -880,6 +921,140 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver): return networks + def ex_list_network_offerings(self): + """ + List the available network offerings + + :rtype ``list`` of :class:`CloudStackNetworkOffering` + """ + res = self._sync_request(command='listNetworkOfferings', + method='GET') + netoffers = res.get('networkoffering', []) + + networkofferings = [] + + for netoffer in netoffers: + networkofferings.append(CloudStackNetworkOffering( + netoffer['name'], + netoffer['displaytext'], + netoffer['guestiptype'], + netoffer['id'], + netoffer['serviceofferingid'], + netoffer['forvpc'], + self)) + + return networkofferings + + def ex_create_network(self, display_text, name, network_offering, + location, gateway=None, netmask=None, + network_domain=None, vpc_id=None, project_id=None): + """ + + Creates a Network, only available in advanced zones. + + :param display_text: the display text of the network + :type display_text: ``str`` + + :param name: the name of the network + :type name: ``str`` + + :param network_offering: the network offering id + :type network_offering: :class:'CloudStackNetworkOffering` + + :param location: Zone + :type location: :class:`NodeLocation` + + :param gateway: Optional, the Gateway of this network + :type gateway: ``str`` + + :param netmask: Optional, the netmask of this network + :type netmask: ``str`` + + :param network_domain: Optional, the DNS domain of the network + :type network_domain: ``str`` + + :param vpc_id: Optional, the VPC id the network belongs to + :type vpc_id: ``str`` + + :param project_id: Optional, the project id the networks belongs to + :type project_id: ``str`` + + :rtype: :class:`CloudStackNetwork` + + """ + + extra_map = RESOURCE_EXTRA_ATTRIBUTES_MAP['network'] + + args = { + 'displaytext': display_text, + 'name': name, + 'networkofferingid': network_offering.id, + 'zoneid': location.id, + } + + if gateway is not None: + args['gateway'] = gateway + + if netmask is not None: + args['netmask'] = netmask + + if network_domain is not None: + args['networkdomain'] = network_domain + + if vpc_id is not None: + args['vpcid'] = vpc_id + + if project_id is not None: + args['projectid'] = project_id + + """ Cloudstack allows for duplicate network names, + this should be handled in the code leveraging libcloud + As there could be use cases for duplicate names. + e.g. management from ROOT level""" + + # for net in self.ex_list_networks(): + # if name == net.name: + # raise LibcloudError('This network name already exists') + + result = self._sync_request(command='createNetwork', + params=args, + method='GET') + + result = result['network'] + extra = self._get_extra_dict(result, extra_map) + + network = CloudStackNetwork(display_text, + name, + network_offering.id, + result['id'], + location.id, + self, + extra=extra) + + return network + + def ex_delete_network(self, network, force=None): + """ + + Deletes a Network, only available in advanced zones. + + :param network: The network + :type network: :class: 'CloudStackNetwork' + + :param force: Force deletion of the network? + :type force: ``bool`` + + :rtype: ``bool`` + + """ + + args = {'id': network.id, 'forced': force} + + self._async_request(command='deleteNetwork', + params=args, + method='GET') + return True + def ex_list_projects(self): """ List the available projects http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/libcloud/test/compute/fixtures/cloudstack/createNetwork_default.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/cloudstack/createNetwork_default.json b/libcloud/test/compute/fixtures/cloudstack/createNetwork_default.json new file mode 100644 index 0000000..9b2d904 --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/createNetwork_default.json @@ -0,0 +1 @@ +{ "createnetworkresponse" : { "network" : {"id":"a804d341-996e-4d9a-b2b0-226c648dc6e3","name":"test","displaytext":"test","broadcastdomaintype":"Lswitch","traffictype":"Guest","gateway":"10.1.1.1","netmask":"255.255.255.0","cidr":"10.1.1.0/24","zoneid":"2","zonename":"BETA-SBP-DC-1","networkofferingid":"c348cabe-0208-49e0-91ad-32b88c55fd8c","networkofferingname":"SourceNatNiciraNvpNetwork","networkofferingdisplaytext":"Offering for a Nicira Nvp isolated network with SourceNat","networkofferingconservemode":true,"networkofferingavailability":"Optional","issystem":false,"state":"Allocated","related":"a804d341-996e-4d9a-b2b0-226c648dc6e3","dns1":"8.8.8.8","dns2":"8.8.8.4","type":"Isolated","acltype":"Account","account":"rkuipers_admin","projectid":"d5f1209d-3a28-4dfb-8cc1-884e5d5e1d56","domainid":"4b6e626c-9d50-4480-bf77-daae632c7ffd","domain":"rkuipers","service":[{"name":"Firewall","capability":[{"name":"SupportedProtocols","value":"tcp,udp,icmp","canchooseservicecapability":false}, {"name":"SupportedTrafficDirection","value":"ingress, egress","canchooseservicecapability":false},{"name":"MultipleIps","value":"true","canchooseservicecapability":false},{"name":"SupportedEgressProtocols","value":"tcp,udp,icmp, all","canchooseservicecapability":false},{"name":"TrafficStatistics","value":"per public ip","canchooseservicecapability":false}]},{"name":"StaticNat"},{"name":"Lb","capability":[{"name":"LbSchemes","value":"Public","canchooseservicecapability":false},{"name":"SupportedStickinessMethods","value":"[{\"methodname\":\"LbCookie\",\"paramlist\":[{\"paramname\":\"cookie-name\",\"required\":false,\"isflag\":false,\"description\":\" \"},{\"paramname\":\"mode\",\"required\":false,\"isflag\":false,\"description\":\" \"},{\"paramname\":\"nocache\",\"required\":false,\"isflag\":true,\"description\":\" \"},{\"paramname\":\"indirect\",\"required\":false,\"isflag\":true,\"description\":\" \"},{\"paramname\":\"postonly\",\"required\":false,\"isflag\":true,\"description\":\" \"},{\"paramname\":\"domain\",\"required\":false,\"isflag\":false,\"description\":\" \"}],\"description\":\"This is loadbalancer cookie based stickiness method.\"},{\"methodname\":\"AppCookie\",\"paramlist\":[{\"paramname\":\"cookie-name\",\"required\":false,\"isflag\":false,\"description\":\" \"},{\"paramname\":\"length\",\"required\":false,\"isflag\":false,\"description\":\" \"},{\"paramname\":\"holdtime\",\"required\":false,\"isflag\":false,\"description\":\" \"},{\"paramname\":\"request-learn\",\"required\":false,\"isflag\":true,\"description\":\" \"},{\"paramname\":\"prefix\",\"required\":false,\"isflag\":true,\"description\":\" \"},{\"paramname\":\"mode\",\"required\":false,\"isflag\":false,\"description\":\" \"}],\"description\":\"This is App session based sticky method. Define session stickiness on an existing application cookie. It can be used only for a specific http traffic\"},{\"methodname\":\"SourceBased\",\"paramlist\":[{\"paramname\":\"tablesize\",\"required\":false, \"isflag\":false,\"description\":\" \"},{\"paramname\":\"expire\",\"required\":false,\"isflag\":false,\"description\":\" \"}],\"description\":\"This is source based Stickiness method, it can be used for any type of protocol.\"}]","canchooseservicecapability":false},{"name":"SupportedLBIsolation","value":"dedicated","canchooseservicecapability":false},{"name":"SupportedLbAlgorithms","value":"roundrobin,leastconn,source","canchooseservicecapability":false},{"name":"SupportedProtocols","value":"tcp, udp","canchooseservicecapability":false}]},{"name":"SourceNat","capability":[{"name":"RedundantRouter","value":"true","canchooseservicecapability":false},{"name":"SupportedSourceNatTypes","value":"peraccount","canchooseservicecapability":false}]},{"name":"Dns","capability":[{"name":"AllowDnsSuffixModification","value":"true","canchooseservicecapability":false}]},{"name":"Connectivity"},{"name":"Vpn","capability":[{"name":"SupportedVpnTypes","value":"pptp,l2tp,ipsec","canchooseservicecapabil ity":false},{"name":"VpnTypes","value":"removeaccessvpn","canchooseservicecapability":false}]},{"name":"Dhcp","capability":[{"name":"DhcpAccrossMultipleSubnets","value":"true","canchooseservicecapability":false}]},{"name":"UserData"},{"name":"PortForwarding"}],"networkdomain":"rkuipers.local","physicalnetworkid":"e48527a6-6882-4c5f-bce9-c02ecd5ef8c1","restartrequired":false,"specifyipranges":false,"vpcid":"22e8388c-21bf-4b84-8f20-e92a7f550898","canusefordeploy":true,"ispersistent":false,"tags":[],"displaynetwork":true} } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/libcloud/test/compute/fixtures/cloudstack/deleteNetwork_default.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/cloudstack/deleteNetwork_default.json b/libcloud/test/compute/fixtures/cloudstack/deleteNetwork_default.json new file mode 100644 index 0000000..ad161b2 --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/deleteNetwork_default.json @@ -0,0 +1 @@ +{ "deletenetworkresponse" : {"jobid":"deleteNetwork"} } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/libcloud/test/compute/fixtures/cloudstack/listNetworkOfferings_default.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/cloudstack/listNetworkOfferings_default.json b/libcloud/test/compute/fixtures/cloudstack/listNetworkOfferings_default.json new file mode 100644 index 0000000..35d8bcc --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/listNetworkOfferings_default.json @@ -0,0 +1 @@ +{ "listnetworkofferingsresponse" : { "count":2 ,"networkoffering" : [ {"id":"c348cabe-0208-49e0-91ad-32b88c55fd8c","name":"SourceNatNiciraNvpNetwork","displaytext":"Offering for a Nicira Nvp isolated network with SourceNat","tags":"BETA-SBP-DC-1-pSTT","traffictype":"Guest","isdefault":true,"specifyvlan":false,"conservemode":true,"specifyipranges":false,"availability":"Optional","networkrate":-1,"state":"Enabled","guestiptype":"Isolated","serviceofferingid":"01f93707-3a35-44a6-84e9-ea767287a6b2","service":[{"name":"Firewall","provider":[{"name":"VirtualRouter"}]},{"name":"StaticNat","provider":[{"name":"VirtualRouter"}],"capability":[{"name":"ElasticIp","value":"false","canchooseservicecapability":false},{"name":"AssociatePublicIP","value":"false","canchooseservicecapability":false}]},{"name":"Lb","provider":[{"name":"VirtualRouter"}],"capability":[{"name":"SupportedLBIsolation","value":"dedicated","canchooseservicecapability":false},{"name":"ElasticLb","value":"false","canchooseser vicecapability":false},{"name":"InlineMode","value":"false","canchooseservicecapability":false}]},{"name":"SourceNat","provider":[{"name":"VirtualRouter"}],"capability":[{"name":"SupportedSourceNatTypes","value":"peraccount","canchooseservicecapability":false},{"name":"RedundantRouter","value":"false","canchooseservicecapability":false}]},{"name":"Dns","provider":[{"name":"VirtualRouter"}]},{"name":"Connectivity","provider":[{"name":"NiciraNvp"}]},{"name":"Vpn","provider":[{"name":"VirtualRouter"}]},{"name":"Dhcp","provider":[{"name":"VirtualRouter"}]},{"name":"UserData","provider":[{"name":"VirtualRouter"}]},{"name":"PortForwarding","provider":[{"name":"VirtualRouter"}]}],"forvpc":false,"ispersistent":false,"egressdefaultpolicy":false}, {"id":"7c09e208-2af5-43d6-9f0b-53868ef788ea","name":"OAT offering for OAT purposes","displaytext":"OAT offering for OAT purposes","tags":"BETA-SBP-DC-1-pSTT","traffictype":"Guest","isdefault":false,"specifyvlan":false,"conservemode":true,"specifyipr anges":false,"availability":"Optional","networkrate":-1,"state":"Enabled","guestiptype":"Isolated","serviceofferingid":"01f93707-3a35-44a6-84e9-ea767287a6b2","service":[{"name":"Firewall","provider":[{"name":"VirtualRouter"}]},{"name":"StaticNat","provider":[{"name":"VirtualRouter"}],"capability":[{"name":"ElasticIp","value":"false","canchooseservicecapability":false},{"name":"AssociatePublicIP","value":"false","canchooseservicecapability":false}]},{"name":"Lb","provider":[{"name":"VirtualRouter"}],"capability":[{"name":"SupportedLBIsolation","value":"dedicated","canchooseservicecapability":false},{"name":"ElasticLb","value":"false","canchooseservicecapability":false},{"name":"InlineMode","value":"false","canchooseservicecapability":false}]},{"name":"SourceNat","provider":[{"name":"VirtualRouter"}],"capability":[{"name":"SupportedSourceNatTypes","value":"peraccount","canchooseservicecapability":false},{"name":"RedundantRouter","value":"false","canchooseservicecapability":false}]},{" name":"Dns","provider":[{"name":"VirtualRouter"}]},{"name":"Connectivity","provider":[{"name":"NiciraNvp"}]},{"name":"Vpn","provider":[{"name":"VirtualRouter"}]},{"name":"Dhcp","provider":[{"name":"VirtualRouter"}]},{"name":"UserData","provider":[{"name":"VirtualRouter"}]},{"name":"PortForwarding","provider":[{"name":"VirtualRouter"}]}],"forvpc":false,"ispersistent":false,"egressdefaultpolicy":true,"maxconnections":4096} ] } } http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_deleteNetwork.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_deleteNetwork.json b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_deleteNetwork.json new file mode 100644 index 0000000..19494c3 --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_deleteNetwork.json @@ -0,0 +1 @@ +{ "queryasyncjobresultresponse" : {"accountid":"02c9bf08-6f36-44b1-a57f-df0708f90de4","userid":"6ef2b921-4ecf-4651-8188-f9868db73e73","cmd":"org.apache.cloudstack.api.command.user.network.DeleteNetworkCmd","jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"success":true},"created":"2014-06-11T10:09:00+0200","jobid":"65789636-d2c8-484c-9d13-47ad3de384ed"} } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/33ca3b2e/libcloud/test/compute/test_cloudstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_cloudstack.py b/libcloud/test/compute/test_cloudstack.py index b90c905..091d98b 100644 --- a/libcloud/test/compute/test_cloudstack.py +++ b/libcloud/test/compute/test_cloudstack.py @@ -196,6 +196,63 @@ class CloudStackCommonTestCase(TestCaseMixin): fixture_networks[i]['networkofferingid']) self.assertEqual(network.zoneid, fixture_networks[i]['zoneid']) + def test_ex_list_network_offerings(self): + _, fixture = CloudStackMockHttp()._load_fixture( + 'listNetworkOfferings_default.json') + fixture_networkoffers = \ + fixture['listnetworkofferingsresponse']['networkoffering'] + + networkoffers = self.driver.ex_list_network_offerings() + + for i, networkoffer in enumerate(networkoffers): + self.assertEqual(networkoffer.id, fixture_networkoffers[i]['id']) + self.assertEqual(networkoffer.name, + fixture_networkoffers[i]['name']) + self.assertEqual(networkoffer.display_text, + fixture_networkoffers[i]['displaytext']) + self.assertEqual(networkoffer.for_vpc, + fixture_networkoffers[i]['forvpc']) + self.assertEqual(networkoffer.guest_ip_type, + fixture_networkoffers[i]['guestiptype']) + self.assertEqual(networkoffer.service_offering_id, + fixture_networkoffers[i]['serviceofferingid']) + + def test_ex_create_network(self): + _, fixture = CloudStackMockHttp()._load_fixture( + 'createNetwork_default.json') + + fixture_network = fixture['createnetworkresponse']['network'] + + netoffer = self.driver.ex_list_network_offerings()[0] + location = self.driver.list_locations()[0] + network = self.driver.ex_create_network(display_text='test', + name='test', + network_offering=netoffer, + location=location, + gateway='10.1.1.1', + netmask='255.255.255.0', + network_domain='cloud.local', + vpc_id="2", + project_id="2") + + self.assertEqual(network.name, fixture_network['name']) + self.assertEqual(network.displaytext, fixture_network['displaytext']) + self.assertEqual(network.id, fixture_network['id']) + self.assertEqual(network.extra['gateway'], fixture_network['gateway']) + self.assertEqual(network.extra['netmask'], fixture_network['netmask']) + self.assertEqual(network.networkofferingid, + fixture_network['networkofferingid']) + self.assertEqual(network.extra['vpc_id'], fixture_network['vpcid']) + self.assertEqual(network.extra['project_id'], + fixture_network['projectid']) + + def test_ex_delete_network(self): + + network = self.driver.ex_list_networks()[0] + + result = self.driver.ex_delete_network(network=network) + self.assertTrue(result) + def test_ex_list_projects(self): _, fixture = CloudStackMockHttp()._load_fixture( 'listProjects_default.json')