From 2d6de863eabc0c082515e8cc90587744c27be7b7 Mon Sep 17 00:00:00 2001 From: Roeland Kuipers Date: Fri, 3 Oct 2014 19:15:26 +0200 Subject: [PATCH] CLOUDSTACK: ex_list_nics, ex_add_nic_to_vm, ex_remove_nic_from_vm + tests --- libcloud/compute/drivers/cloudstack.py | 190 ++++++++++++++---- .../addNicToVirtualMachine_default.json | 1 + .../fixtures/cloudstack/listNics_default.json | 1 + .../queryAsyncJobResult_addnictovm.json | 37 ++++ .../queryAsyncJobResult_removenic.json | 37 ++++ .../removeNicFromVirtualMachine_default.json | 1 + libcloud/test/compute/test_cloudstack.py | 40 ++++ 7 files changed, 271 insertions(+), 36 deletions(-) create mode 100644 libcloud/test/compute/fixtures/cloudstack/addNicToVirtualMachine_default.json create mode 100644 libcloud/test/compute/fixtures/cloudstack/listNics_default.json create mode 100644 libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_addnictovm.json create mode 100644 libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_removenic.json create mode 100644 libcloud/test/compute/fixtures/cloudstack/removeNicFromVirtualMachine_default.json diff --git a/libcloud/compute/drivers/cloudstack.py b/libcloud/compute/drivers/cloudstack.py index 0129e0772d..2723a2128f 100644 --- a/libcloud/compute/drivers/cloudstack.py +++ b/libcloud/compute/drivers/cloudstack.py @@ -5,7 +5,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -181,6 +181,10 @@ 'project_id': { 'key_name': 'projectid', 'transform_func': str + }, + 'nics:': { + 'key_name': 'nic', + 'transform_func': list } }, 'volume': { @@ -302,6 +306,12 @@ 'vpcavailable': {'key_name': 'vpcavailable', 'transform_func': int}, 'vpclimit': {'key_name': 'vpclimit', 'transform_func': int}, 'vpctotal': {'key_name': 'vpctotal', 'transform_func': int} + }, + 'nic': { + 'secondary_ip': { + 'key_name': 'secondaryip', + 'transform_func': list + } } } @@ -413,7 +423,6 @@ class CloudStackFirewallRule(object): def __init__(self, id, address, cidr_list, protocol, icmp_code=None, icmp_type=None, start_port=None, end_port=None): - """ A Firewall rule. @@ -468,7 +477,6 @@ class CloudStackEgressFirewallRule(object): def __init__(self, id, network_id, cidr_list, protocol, icmp_code=None, icmp_type=None, start_port=None, end_port=None): - """ A egress firewall rule. @@ -654,6 +662,35 @@ def __repr__(self): self.driver.name)) +class CloudStackNic(object): + """ + Class representing a CloudStack Network Interface. + """ + + def __init__(self, id, network_id, net_mask, gateway, ip_address, + is_default, mac_address, driver, extra=None): + self.id = id + self.network_id = network_id + self.net_mask = net_mask + self.gateway = gateway + self.ip_address = ip_address + self.is_default = is_default + self.mac_address = mac_address + self.driver = driver + self.extra = extra or {} + + def __repr__(self): + return (('') + % (self.id, self.network_id, self.net_mask, + self.gateway, self.ip_address, self.is_default, + self.mac_address, self.driver.name)) + + def __eq__(self, other): + return self.__class__ is other.__class__ and self.id == other.id + + class CloudStackVPC(object): """ Class representing a CloudStack VPC. @@ -1123,13 +1160,13 @@ def ex_list_networks(self): extra['tags'] = self._get_resource_tags(net['tags']) networks.append(CloudStackNetwork( - net['displaytext'], - net['name'], - net['networkofferingid'], - net['id'], - net['zoneid'], - self, - extra=extra)) + net['displaytext'], + net['name'], + net['networkofferingid'], + net['id'], + net['zoneid'], + self, + extra=extra)) return networks @@ -1147,13 +1184,13 @@ def ex_list_network_offerings(self): for netoffer in netoffers: networkofferings.append(CloudStackNetworkOffering( - netoffer['name'], - netoffer['displaytext'], - netoffer['guestiptype'], - netoffer['id'], - netoffer['serviceofferingid'], - netoffer['forvpc'], - self)) + netoffer['name'], + netoffer['displaytext'], + netoffer['guestiptype'], + netoffer['id'], + netoffer['serviceofferingid'], + netoffer['forvpc'], + self)) return networkofferings @@ -1170,10 +1207,10 @@ def ex_create_network(self, display_text, name, network_offering, :param name: the name of the network :type name: ``str`` - :param network_offering: the network offering id + :param network_offering: NetworkOffering object :type network_offering: :class:'CloudStackNetworkOffering` - :param location: Zone + :param location: Zone object :type location: :class:`NodeLocation` :param gateway: Optional, the Gateway of this network @@ -1281,10 +1318,10 @@ def ex_list_vpc_offerings(self): for vpcoffer in vpcoffers: vpcofferings.append(CloudStackVPCOffering( - vpcoffer['name'], - vpcoffer['displaytext'], - vpcoffer['id'], - self)) + vpcoffer['name'], + vpcoffer['displaytext'], + vpcoffer['id'], + self)) return vpcofferings @@ -1301,15 +1338,14 @@ def ex_list_vpcs(self): networks = [] for vpc in vpcs: - networks.append(CloudStackVPC( - vpc['displaytext'], - vpc['name'], - vpc['vpcofferingid'], - vpc['id'], - vpc['zoneid'], - vpc['cidr'], - self)) + vpc['displaytext'], + vpc['name'], + vpc['vpcofferingid'], + vpc['id'], + vpc['zoneid'], + vpc['cidr'], + self)) return networks @@ -1406,11 +1442,11 @@ def ex_list_projects(self): extra['tags'] = self._get_resource_tags(proj['tags']) projects.append(CloudStackProject( - id=proj['id'], - name=proj['name'], - display_text=proj['displaytext'], - driver=self, - extra=extra)) + id=proj['id'], + name=proj['name'], + display_text=proj['displaytext'], + driver=self, + extra=extra)) return projects @@ -2695,6 +2731,88 @@ def ex_list_os_types(self): ostypes = self._sync_request('listOsTypes') return ostypes['ostype'] + def ex_list_nics(self, node): + """ + List the available networks + + :param vm: Node Object + :type vm: :class:`CloudStackNode + + :rtype ``list`` of :class:`CloudStackNic` + """ + + res = self._sync_request(command='listNics', + params={'virtualmachineid': node.id}, + method='GET') + items = res.get('nic', []) + + nics = [] + extra_map = RESOURCE_EXTRA_ATTRIBUTES_MAP['nic'] + for item in items: + extra = self._get_extra_dict(item, extra_map) + + nics.append(CloudStackNic( + id=item['id'], + network_id=item['networkid'], + net_mask=item['netmask'], + gateway=item['gateway'], + ip_address=item['ipaddress'], + is_default=item['isdefault'], + mac_address=item['macaddress'], + driver=self, + extra=extra)) + + return nics + + def ex_add_nic_to_node(self, node, network, ip_address=None): + """ + Add an extra Nic to a VM + + :param network: NetworkOffering object + :type network: :class:'CloudStackNetwork` + + :param node: Node Object + :type node: :class:'CloudStackNode` + + :param ip_address: Optional, specific IP for this Nic + :type ip_address: ``str`` + + + :rtype: ``bool`` + """ + + args = { + 'virtualmachineid': node.id, + 'networkid': network.id + } + + if ip_address is not None: + args['ipaddress'] = ip_address + + self._async_request(command='addNicToVirtualMachine', + params=args) + return True + + def ex_remove_nic_from_node(self, nic, node): + + """ + Remove Nic from a VM + + :param nic: Nic object + :type nic: :class:'CloudStackNetwork` + + :param node: Node Object + :type node: :class:'CloudStackNode` + + :rtype: ``bool`` + """ + + self._async_request(command='removeNicFromVirtualMachine', + params={'nicid': nic.id, + 'virtualmachineid': node.id}) + + return True + def _to_snapshot(self, data): """ Create snapshot object from data diff --git a/libcloud/test/compute/fixtures/cloudstack/addNicToVirtualMachine_default.json b/libcloud/test/compute/fixtures/cloudstack/addNicToVirtualMachine_default.json new file mode 100644 index 0000000000..3816acb953 --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/addNicToVirtualMachine_default.json @@ -0,0 +1 @@ +{ "addnictovirtualmachineresponse" : {"jobid":"addnictovm"} } \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/cloudstack/listNics_default.json b/libcloud/test/compute/fixtures/cloudstack/listNics_default.json new file mode 100644 index 0000000000..4c8b18e750 --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/listNics_default.json @@ -0,0 +1 @@ +{ "listnicsresponse" : { "count":1 ,"nic" : [ {"id":"15418e74-25e8-42d3-9bd7-eb55e57825fe","networkid":"de45b0ed-b5ae-4374-ac7c-aff3fb2aefa2","netmask":"255.255.255.0","gateway":"10.1.1.1","ipaddress":"10.1.1.136","isdefault":true,"macaddress":"02:00:00:b9:01:1a"} ] } } \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_addnictovm.json b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_addnictovm.json new file mode 100644 index 0000000000..12b01cd2aa --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_addnictovm.json @@ -0,0 +1,37 @@ +{ + "queryasyncjobresultresponse" : { + "accountid" : "02c9bf08-6f36-44b1-a57f-df0708f90de4", "userid" : "6ef2b921-4ecf-4651-8188-f9868db73e73", "cmd" : "org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd", "jobstatus" : 1, "jobprocstatus" : 0, "jobresultcode" : 0, "jobresulttype" : "object", "jobresult" : { + "virtualmachine" : { + "id" : "903897b7-9241-4f93-bd08-3453c36a3c99", "name" : "test", "account" : "rkuipers_admin", "domainid" : "4b6e626c-9d50-4480-bf77-daae632c7ffd", "domain" : "rkuipers", "created" : "2014-10-03T17:24:37+0200", "state" : "Stopped", "haenable" : false, "zoneid" : "2", "zonename" : "BETA-SBP-DC-1", "guestosid" : "278699da-edfc-11e2-a249-005056ba4c5e", "securitygroup" : [], "nic" : [{ + "id": "15418e74-25e8-42d3-9bd7-eb55e57825fe", + "networkid": "de45b0ed-b5ae-4374-ac7c-aff3fb2aefa2", + "networkname": "rkuipers-default", + "netmask": "255.255.255.0", + "gateway": "10.1.1.1", + "ipaddress": "10.1.1.136", + "isolationuri": "lswitch:d3eaa05c-4392-4918-93ab-c4a979a7988a", + "broadcasturi": "lswitch:d3eaa05c-4392-4918-93ab-c4a979a7988a", + "traffictype": "Guest", + "type": "Isolated", + "isdefault": true, + "macaddress": "02:00:00:b9:01:1a" + }, { + "id": "f71e48da-fe40-458d-9033-ea27a3a92de1", + "networkid": "f59ee08a-66b5-40c6-baa7-392394c6cea9", + "networkname": "node_cellar_network", + "netmask": "255.255.255.0", + "gateway": "10.1.1.1", + "ipaddress": "10.1.1.253", + "isolationuri": "lswitch:bad1ca9f-b039-4ad1-b5fc-1f3147fad7fa", + "broadcasturi": "lswitch:bad1ca9f-b039-4ad1-b5fc-1f3147fad7fa", + "traffictype": "Guest", + "type": "Isolated", + "isdefault": false, + "macaddress": "02:00:4f:84:00:1d" + } + ], "hypervisor" : "XenServer", "tags" : [], "affinitygroup" : [], "displayvm" : true, "isdynamicallyscalable" : false + } + }, "created" : "2014-10-03T18:30:59+0200", "jobid" : "e521e748-1271-4587-8433-2d094704bfe9" + } +} + diff --git a/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_removenic.json b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_removenic.json new file mode 100644 index 0000000000..3f0d556b6f --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/queryAsyncJobResult_removenic.json @@ -0,0 +1,37 @@ +{ + "queryasyncjobresultresponse" : { + "accountid" : "02c9bf08-6f36-44b1-a57f-df0708f90de4", "userid" : "6ef2b921-4ecf-4651-8188-f9868db73e73", "cmd" : "org.apache.cloudstack.api.command.user.vm.RemoveNicFromVMCmd", "jobstatus" : 1, "jobprocstatus" : 0, "jobresultcode" : 0, "jobresulttype" : "object", "jobresult" : { + "virtualmachine" : { + "id" : "903897b7-9241-4f93-bd08-3453c36a3c99", "name" : "test", "account" : "rkuipers_admin", "domainid" : "4b6e626c-9d50-4480-bf77-daae632c7ffd", "domain" : "rkuipers", "created" : "2014-10-03T17:24:37+0200", "state" : "Stopped", "haenable" : false, "zoneid" : "2", "zonename" : "BETA-SBP-DC-1", "guestosid" : "278699da-edfc-11e2-a249-005056ba4c5e", "securitygroup" : [], "nic" : [{ + "id": "15418e74-25e8-42d3-9bd7-eb55e57825fe", + "networkid": "de45b0ed-b5ae-4374-ac7c-aff3fb2aefa2", + "networkname": "rkuipers-default", + "netmask": "255.255.255.0", + "gateway": "10.1.1.1", + "ipaddress": "10.1.1.136", + "isolationuri": "lswitch:d3eaa05c-4392-4918-93ab-c4a979a7988a", + "broadcasturi": "lswitch:d3eaa05c-4392-4918-93ab-c4a979a7988a", + "traffictype": "Guest", + "type": "Isolated", + "isdefault": true, + "macaddress": "02:00:00:b9:01:1a" + }, { + "id": "f71e48da-fe40-458d-9033-ea27a3a92de1", + "networkid": "f59ee08a-66b5-40c6-baa7-392394c6cea9", + "networkname": "node_cellar_network", + "netmask": "255.255.255.0", + "gateway": "10.1.1.1", + "ipaddress": "10.1.1.253", + "isolationuri": "lswitch:bad1ca9f-b039-4ad1-b5fc-1f3147fad7fa", + "broadcasturi": "lswitch:bad1ca9f-b039-4ad1-b5fc-1f3147fad7fa", + "traffictype": "Guest", + "type": "Isolated", + "isdefault": false, + "macaddress": "02:00:4f:84:00:1d" + } + ], "hypervisor" : "XenServer", "tags" : [], "affinitygroup" : [], "displayvm" : true, "isdynamicallyscalable" : false + } + }, "created" : "2014-10-03T18:40:30+0200", "jobid" : "23fc6bfc-e354-41c8-bf8b-c0db8227feb4" + } +} + diff --git a/libcloud/test/compute/fixtures/cloudstack/removeNicFromVirtualMachine_default.json b/libcloud/test/compute/fixtures/cloudstack/removeNicFromVirtualMachine_default.json new file mode 100644 index 0000000000..ccada15482 --- /dev/null +++ b/libcloud/test/compute/fixtures/cloudstack/removeNicFromVirtualMachine_default.json @@ -0,0 +1 @@ +{ "removenicfromvirtualmachineresponse" : {"jobid":"removenic"} } \ No newline at end of file diff --git a/libcloud/test/compute/test_cloudstack.py b/libcloud/test/compute/test_cloudstack.py index cd72b2b2c3..a3985982a0 100644 --- a/libcloud/test/compute/test_cloudstack.py +++ b/libcloud/test/compute/test_cloudstack.py @@ -295,6 +295,46 @@ def test_ex_delete_network(self): result = self.driver.ex_delete_network(network=network) self.assertTrue(result) + def test_ex_list_nics(self): + _, fixture = CloudStackMockHttp()._load_fixture( + 'listNics_default.json') + + fixture_nic = fixture['listnicsresponse']['nic'] + vm = self.driver.list_nodes()[0] + nics = self.driver.ex_list_nics(vm) + + for i, nic in enumerate(nics): + self.assertEqual(nic.id, fixture_nic[i]['id']) + self.assertEqual(nic.network_id, + fixture_nic[i]['networkid']) + self.assertEqual(nic.net_mask, + fixture_nic[i]['netmask']) + self.assertEqual(nic.gateway, + fixture_nic[i]['gateway']) + self.assertEqual(nic.ip_address, + fixture_nic[i]['ipaddress']) + self.assertEqual(nic.is_default, + fixture_nic[i]['isdefault']) + self.assertEqual(nic.mac_address, + fixture_nic[i]['macaddress']) + + def test_ex_add_nic_to_node(self): + + vm = self.driver.list_nodes()[0] + network = self.driver.ex_list_networks()[0] + ip = "10.1.4.123" + + result = self.driver.ex_add_nic_to_node(node=vm, network=network, ip_address=ip) + self.assertTrue(result) + + def test_ex_remove_nic_from_node(self): + + vm = self.driver.list_nodes()[0] + nic = self.driver.ex_list_nics(node=vm)[0] + + result = self.driver.ex_remove_nic_from_node(node=vm, nic=nic) + self.assertTrue(result) + def test_ex_list_vpc_offerings(self): _, fixture = CloudStackMockHttp()._load_fixture( 'listVPCOfferings_default.json')