diff --git libcloud/compute/drivers/vcloud.py libcloud/compute/drivers/vcloud.py index a58ee91..55063de 100644 --- libcloud/compute/drivers/vcloud.py +++ libcloud/compute/drivers/vcloud.py @@ -74,16 +74,32 @@ def get_url_path(url): class Vdc: """Virtual datacenter (vDC) representation""" - def __init__(self, id, name, driver): + def __init__(self, id, name, driver, allocation_model=None, cpu=None, memory=None, storage=None): self.id = id self.name = name self.driver = driver + self.allocation_model = allocation_model + self.cpu = cpu + self.memory = memory + self.storage = storage def __repr__(self): return (('') % (self.id, self.name, self.driver.name)) +class Capacity: + """Represents CPU, Memory or Storage capacity of vDC.""" + def __init__(self, limit, used, units): + self.limit = limit + self.used = used + self.units = units + + def __repr__(self): + return (('') + % (self.limit, self.used, self.units)) + + class InstantiateVAppXML(object): def __init__(self, name, template, net_href, cpus, memory, password=None, row=None, group=None): @@ -338,12 +354,17 @@ class VCloudNodeDriver(NodeDriver): self.connection.check_org() # make sure the org is set. # pylint: disable-msg=E1101 res = self.connection.request(self.org) self._vdcs = [ - Vdc(i.get('href'), i.get('name'), self) + self._to_vdc( + self.connection.request(get_url_path(i.get('href'))).object + ) for i in res.object.findall(fixxpath(res.object, "Link")) if i.get('type') == 'application/vnd.vmware.vcloud.vdc+xml' ] return self._vdcs + def _to_vdc(self, vdc_elm): + return Vdc(vdc_elm.get('href'), vdc_elm.get('name'), self) + def _get_vdc(self, vdc_name): vdc = None if not vdc_name: @@ -442,8 +463,8 @@ class VCloudNodeDriver(NodeDriver): raise Exception("Canceled status returned by task %s." % task_href) if (time.time() - start_time >= timeout): - raise Exception("Timeout while waiting for task %s." - % task_href) + raise Exception("Timeout (%s sec) while waiting for task %s." + % (timeout, task_href)) time.sleep(5) res = self.connection.request(get_url_path(task_href)) status = res.object.get('status') @@ -482,8 +503,25 @@ class VCloudNodeDriver(NodeDriver): return res.status in [httplib.ACCEPTED, httplib.NO_CONTENT] def list_nodes(self): + return self.ex_list_nodes() + + def ex_list_nodes(self, vdcs=None): + """ + List all nodes across all vDCs. Using ex_vdcs you can specify which vDCs + should be queried. + + @param vdcs: None, vDC or a list of vDCs to query. If None all vDCs will + be queried. + @type vdcs: L{Vdc} + + @rtype: C{list} of L{Node} objects + """ + if not vdcs: + vdcs = self.vdcs + if not getattr(vdcs, '__iter__', False): + vdcs = [vdcs] nodes = [] - for vdc in self.vdcs: + for vdc in vdcs: res = self.connection.request(get_url_path(vdc.id)) elms = res.object.findall(fixxpath( res.object, "ResourceEntities/ResourceEntity") @@ -577,10 +615,10 @@ class VCloudNodeDriver(NodeDriver): res = self._get_catalogitem(cat_item) res_ents = res.findall(fixxpath(res, 'Entity')) images += [ - self._to_image(i) - for i in res_ents - if i.get('type') == - 'application/vnd.vmware.vcloud.vAppTemplate+xml' + self._to_image(i) + for i in res_ents + if i.get('type') == + 'application/vnd.vmware.vcloud.vAppTemplate+xml' ] def idfun(image): @@ -953,6 +991,62 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): res = self.connection.request(get_url_path(node.id)) return self._to_node(res.object) + def ex_power_off_node(self, node): + """ + Powers on all VMs under specified node. VMs need to be This operation + is allowed only when the vApp/VM is powered on. + + @param node: The node to be powered off + @type node: L{Node} + + @rtype: L{Node} + """ + return self._perform_power_operation(node, 'powerOff') + + def ex_power_on_node(self, node): + """ + Powers on all VMs under specified node. This operation is allowed + only when the vApp/VM is powered off or suspended. + + @param node: The node to be powered on + @type node: L{Node} + + @rtype: L{Node} + """ + return self._perform_power_operation(node, 'powerOn') + + def ex_shutdown_node(self, node): + """ + Shutdowns all VMs under specified node. This operation is allowed only + when the vApp/VM is powered on. + + @param node: The node to be shut down + @type node: L{Node} + + @rtype: L{Node} + """ + return self._perform_power_operation(node, 'shutdown') + + def ex_suspend_node(self, node): + """ + Suspends all VMs under specified node. This operation is allowed only + when the vApp/VM is powered on. + + @param node: The node to be suspended + @type node: L{Node} + + @rtype: L{Node} + """ + return self._perform_power_operation(node, 'suspend') + + def _perform_power_operation(self, node, operation): + res = self.connection.request( + '%s/power/action/%s' % (get_url_path(node.id), operation), + method='POST') + self._wait_for_task_completion(res.object.get('href')) + res = self.connection.request(get_url_path(node.id)) + return self._to_node(res.object) + def create_node(self, **kwargs): """Creates and returns node. If the source image is: - vApp template - a new vApp is instantiated from template @@ -1529,11 +1623,38 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): public_ips.extend(vm['public_ips']) private_ips.extend(vm['private_ips']) + # Find vDC + vdc_id = next(link.get('href') for link in node_elm.findall(fixxpath(node_elm, 'Link')) + if link.get('type') == 'application/vnd.vmware.vcloud.vdc+xml') + vdc = next(vdc for vdc in self.vdcs if vdc.id == vdc_id) + node = Node(id=node_elm.get('href'), name=node_elm.get('name'), state=self.NODE_STATE_MAP[node_elm.get('status')], public_ips=public_ips, private_ips=private_ips, driver=self.connection.driver, - extra={'vms': vms}) + extra={'vdc': vdc.name, 'vms': vms}) return node + + def _to_vdc(self, vdc_elm): + + def get_capacity_values(capacity_elm): + if capacity_elm is None: + return None + limit = int(capacity_elm.findtext(fixxpath(capacity_elm, 'Limit'))) + used = int(capacity_elm.findtext(fixxpath(capacity_elm, 'Used'))) + units = capacity_elm.findtext(fixxpath(capacity_elm, 'Units')) + return Capacity(limit, used, units) + + cpu = get_capacity_values(vdc_elm.find(fixxpath(vdc_elm, 'ComputeCapacity/Cpu'))) + memory = get_capacity_values(vdc_elm.find(fixxpath(vdc_elm, 'ComputeCapacity/Memory'))) + storage = get_capacity_values(vdc_elm.find(fixxpath(vdc_elm, 'StorageCapacity'))) + + return Vdc(id=vdc_elm.get('href'), + name=vdc_elm.get('name'), + driver=self, + allocation_model=vdc_elm.findtext(fixxpath(vdc_elm, 'AllocationModel')), + cpu=cpu, + memory=memory, + storage=storage) diff --git libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_all.xml libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_all.xml new file mode 100644 index 0000000..5427533 --- /dev/null +++ libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_all.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml deleted file mode 100644 index 5472234..0000000 --- libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml deleted file mode 100644 index 568b8d5..0000000 --- libcloud/test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml index e31f340..96e14fc 100644 --- libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml +++ libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml @@ -1,4 +1,4 @@ - + @@ -9,27 +9,27 @@ - AllocationVApp + AllocationPool MB - 0 - 0 - 126976 + 5120000 + 5120000 + 1984512 0 MHz - 0 - 0 - 6000 + 130000 + 160000 + 0 0 MB - 0 - 0 - 4255 + 527360 + 527360 + 130752 0 diff --git libcloud/test/compute/test_vcloud.py libcloud/test/compute/test_vcloud.py index 29b8cf8..a8deb9a 100644 --- libcloud/test/compute/test_vcloud.py +++ libcloud/test/compute/test_vcloud.py @@ -132,7 +132,8 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): self.assertEqual(node.state, NodeState.RUNNING) self.assertEqual(node.public_ips, ['65.41.67.2']) self.assertEqual(node.private_ips, ['65.41.67.2']) - self.assertEqual(node.extra, {'vms': [{ + self.assertEqual(node.extra, {'vdc': 'MyVdc', + 'vms': [{ 'id': 'https://vm-vcloud/api/vApp/vm-dd75d1d3-5b7b-48f0-aff3-69622ab7e045', 'name': 'testVm', 'state': NodeState.RUNNING, @@ -145,7 +146,8 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): self.assertEqual(node.state, NodeState.RUNNING) self.assertEqual(node.public_ips, ['192.168.0.103']) self.assertEqual(node.private_ips, ['192.168.0.100']) - self.assertEqual(node.extra, {'vms': [{ + self.assertEqual(node.extra, {'vdc': 'MyVdc', + 'vms': [{ 'id': 'https://vm-vcloud/api/vApp/vm-dd75d1d3-5b7b-48f0-aff3-69622ab7e046', 'name': 'testVm2', 'state': NodeState.RUNNING, @@ -218,6 +220,30 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): def test_ex_set_vm_memory(self): self.driver.ex_set_vm_memory('https://test/api/vApp/vm-test', 1024) + def test_vdcs(self): + vdcs = self.driver.vdcs + self.assertEqual(len(vdcs), 1) + self.assertEqual(vdcs[0].id, 'https://vm-vcloud/api/vdc/3d9ae28c-1de9-4307-8107-9356ff8ba6d0') + self.assertEqual(vdcs[0].name, 'MyVdc') + self.assertEqual(vdcs[0].allocation_model, 'AllocationPool') + self.assertEqual(vdcs[0].storage.limit, 5120000) + self.assertEqual(vdcs[0].storage.used, 1984512) + self.assertEqual(vdcs[0].storage.units, 'MB') + self.assertEqual(vdcs[0].cpu.limit, 160000) + self.assertEqual(vdcs[0].cpu.used, 0) + self.assertEqual(vdcs[0].cpu.units, 'MHz') + self.assertEqual(vdcs[0].memory.limit, 527360) + self.assertEqual(vdcs[0].memory.used, 130752) + self.assertEqual(vdcs[0].memory.units, 'MB') + + def test_ex_list_nodes(self): + self.assertEqual(len(self.driver.ex_list_nodes()), len(self.driver.list_nodes())) + + def test_ex_power_off(self): + node = Node('https://vm-vcloud/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6b', 'testNode', NodeState.RUNNING, [], [], self.driver) + self.driver.ex_power_off_node(node) + + class TerremarkMockHttp(MockHttp): @@ -316,8 +342,7 @@ class VCloud_1_5_MockHttp(MockHttp): return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] def _api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn(self, method, url, body, headers): - body = self.fixtures.load('api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml') - return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + return self._api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6b_power_action_all(method, url, body, headers) # Clone def _api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_cloneVApp(self, method, url, body, headers): @@ -356,8 +381,7 @@ class VCloud_1_5_MockHttp(MockHttp): return status, body, headers, httplib.responses[status] def _api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset(self, method, url, body, headers): - body = self.fixtures.load('api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml') - return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + return self._api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6b_power_action_all(method, url, body, headers) def _api_task_b034df55_fe81_4798_bc81_1f0fd0ead450(self, method, url, body, headers): body = self.fixtures.load('api_task_b034df55_fe81_4798_bc81_1f0fd0ead450.xml') @@ -432,5 +456,13 @@ class VCloud_1_5_MockHttp(MockHttp): status = httplib.ACCEPTED return status, body, headers, httplib.responses[status] + def _api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6b_power_action_powerOff(self, method, url, body, headers): + return self._api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6b_power_action_all(method, url, body, headers) + + def _api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6b_power_action_all(self, method, url, body, headers): + assert method == 'POST' + body = self.fixtures.load('api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_all.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + if __name__ == '__main__': sys.exit(unittest.main())