class TestVmmClient(unittest.TestCase): def setUp(self): self.vmm_client = VmmClient("fake_hostname", "fake_username", "fake_password") def test_init_set_host_defaults(self): vmm_client = VmmClient("fake_hostname", "fake_username", "fake_password") self.assertEqual(vmm_client.address, "fake_hostname") self.assertEqual(vmm_client.username, "fake_username") self.assertEqual(vmm_client.password, "fake_password") self.assertEqual(vmm_client.host_address, "fake_hostname") self.assertEqual(vmm_client.host_username, "fake_username") self.assertEqual(vmm_client.host_password, "fake_password") self.assertEqual(vmm_client.library_server_username, "fake_username") self.assertEqual(vmm_client.library_server_password, "fake_password") self.assertIsNone(vmm_client.library_server_share_path) self.assertIsNone(vmm_client.library_server_address) def test_init_set_host_override(self): vmm_client = VmmClient( "fake_hostname", "fake_username", "fake_password", host_address="fake_host_hostname", host_username="******", host_password="******", library_server_address="fake_library_server_hostname", library_server_username="******", library_server_password="******") self.assertEqual(vmm_client.address, "fake_hostname") self.assertEqual(vmm_client.username, "fake_username") self.assertEqual(vmm_client.password, "fake_password") self.assertEqual(vmm_client.host_address, "fake_host_hostname") self.assertEqual(vmm_client.host_username, "fake_host_username") self.assertEqual(vmm_client.host_password, "fake_host_password") self.assertEqual(vmm_client.library_server_address, "fake_library_server_hostname") self.assertEqual(vmm_client.library_server_username, "fake_library_server_username") self.assertEqual(vmm_client.library_server_password, "fake_library_server_password") self.assertIsNone(vmm_client.library_server_share_path) def test_init_library_server(self): vmm_client = VmmClient("fake_hostname", "fake_user", "fake_password", library_server_address="fake_library_server", library_server_share_path="fake_library_path") self.assertEqual(vmm_client.library_server_username, "fake_user") self.assertEqual(vmm_client.library_server_password, "fake_password") self.assertEqual(vmm_client.library_server_address, "fake_library_server") self.assertEqual(vmm_client.library_server_share_path, "fake_library_path") def test_is_nutanix_cvm_false(self): vm = {"name": "NOT-A-CVM"} self.assertFalse(VmmClient.is_nutanix_cvm(vm)) def test_is_nutanix_cvm_true(self): vm = {"name": "NTNX-12345678-A-CVM"} self.assertTrue(VmmClient.is_nutanix_cvm(vm)) def test_get_clusters(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.get_clusters() m_ps_client.execute.assert_called_once_with("Get-VmmHypervCluster", json_params="{}") def test_get_clusters_cluster_name_cluster_type(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.get_clusters(cluster_name="Fake Cluster", cluster_type="HyperV") m_ps_client.execute.assert_called_once_with( "Get-VmmHypervCluster", json_params="{\"name\": \"Fake Cluster\", \"type\": \"HyperV\"}") def test_get_library_shares(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.get_library_shares() m_ps_client.execute.assert_called_once_with("Get-VmmLibraryShares") def test_get_vms(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.get_vms(cluster_name="Fake Cluster") m_ps_client.execute.assert_called_once_with( "Get-VmmVM", cluster_name="Fake Cluster", json_params="[]", num_retries=10) def test_get_vms_matching_ids(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.get_vms(cluster_name="Fake Cluster", vm_input_list=[{ "id": "0" }, { "id": "1" }]) m_ps_client.execute.assert_called_once_with( "Get-VmmVM", cluster_name="Fake Cluster", json_params="[{\"id\": \"0\"}, {\"id\": \"1\"}]", num_retries=10) def test_refresh_vms(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.refresh_vms(cluster_name="Fake Cluster") m_ps_client.execute.assert_called_once_with( "Read-VmmVM", cluster_name="Fake Cluster", json_params="[]") def test_refresh_vms_matching_ids(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.refresh_vms(cluster_name="Fake Cluster", vm_input_list=[{ "id": "0" }, { "id": "1" }]) m_ps_client.execute.assert_called_once_with( "Read-VmmVM", cluster_name="Fake Cluster", json_params="[{\"id\": \"0\"}, {\"id\": \"1\"}]") def test_get_nodes(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.get_nodes(cluster_name="Fake Cluster") m_ps_client.execute.assert_called_once_with( "Get-VmmHypervClusterNode", cluster_name="Fake Cluster", json_params="[]", num_retries=10) def test_get_nodes_matching_ids(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.get_nodes(cluster_name="Fake Cluster", nodes=[{ "id": "0" }, { "id": "1" }]) m_ps_client.execute.assert_called_once_with( "Get-VmmHypervClusterNode", cluster_name="Fake Cluster", json_params="[{\"id\": \"0\"}, {\"id\": \"1\"}]", num_retries=10) def test_nodes_power_state(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.nodes_power_state(cluster_name="Fake Cluster") m_ps_client.execute_async.assert_called_once_with( "Set-VmmHypervClusterNodeShutdown", cluster_name="Fake Cluster", json_params="[]") def test_nodes_power_state_matching_ids(self): self.vmm_client.host_username = "******" self.vmm_client.host_password = "******" with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.nodes_power_state(cluster_name="Fake Cluster", nodes=[{ "id": "0" }, { "id": "1" }]) m_ps_client.execute_async.assert_called_once_with( "Set-VmmHypervClusterNodeShutdown", cluster_name="Fake Cluster", json_params="[{\"id\": \"0\", " "\"password\": \"fake_host_password\", " "\"username\": \"fake_host_username\"}, " "{\"id\": \"1\", " "\"password\": \"fake_host_password\", " "\"username\": \"fake_host_username\"}]") def test_vms_set_power_state_for_vms(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.vms_set_power_state_for_vms( cluster_name="Fake Cluster", task_req_list=[{ "vm_id": "0", "power_state": "off" }, { "vm_id": "1", "power_state": "off" }]) m_ps_client.execute.assert_called_once_with( "Set-VmmVMPowerState", cluster_name="Fake Cluster", json_params="[{\"power_state\": \"off\", \"vm_id\": \"0\"}, " "{\"power_state\": \"off\", \"vm_id\": \"1\"}]") def test_vms_set_possible_owners_for_vms(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.vms_set_possible_owners_for_vms( cluster_name="Fake Cluster", task_req_list=[{ "vm_id": "0", "possible_owners": ["0", "1"] }, { "vm_id": "1", "possible_owners": ["0", "1"] }]) m_ps_client.execute.assert_called_once_with( "Set-VmmVMPossibleOwners", cluster_name="Fake Cluster", json_params= "[{\"possible_owners\": [\"0\", \"1\"], \"vm_id\": \"0\"}, " "{\"possible_owners\": [\"0\", \"1\"], \"vm_id\": \"1\"}]") def test_vms_set_snapshot(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.vms_set_snapshot(cluster_name="Fake Cluster", task_req_list=[{ "vm_id": "0", "name": "snapshot_0", "description": "Snapshot 0" }, { "vm_id": "1", "name": "snapshot_1", "description": "Snapshot 1" }]) m_ps_client.execute.assert_called_once_with( "Set-VmmVMSnapshot", cluster_name="Fake Cluster", json_params="[{\"description\": \"Snapshot 0\", " "\"name\": \"snapshot_0\", \"vm_id\": \"0\"}, " "{\"description\": \"Snapshot 1\", " "\"name\": \"snapshot_1\", \"vm_id\": \"1\"}]") def test_vm_get_job_status_vmm_tasks(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.vm_get_job_status(task_id_list=[{ "task_type": "vmm", "task_id": "0" }, { "task_type": "vmm", "task_id": "1" }]) m_ps_client.execute.assert_called_once_with( "Get-Task", json_params="[{\"task_id\": \"0\", \"task_type\": \"vmm\"}, " "{\"task_id\": \"1\", \"task_type\": \"vmm\"}]", num_retries=10) def test_vm_get_job_status_ps_tasks(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: mock_ps_cmd_0 = mock.Mock() mock_ps_cmd_1 = mock.Mock() m_ps_client.poll.side_effect = [mock_ps_cmd_0, mock_ps_cmd_1] self.vmm_client.vm_get_job_status(task_id_list=[{ "task_type": "ps", "task_id": "0" }, { "task_type": "ps", "task_id": "1" }]) m_ps_client.poll.assert_has_calls([mock.call("0"), mock.call("1")]) mock_ps_cmd_0.as_ps_task.assert_called_once_with() mock_ps_cmd_1.as_ps_task.assert_called_once_with() def test_vm_get_job_status_unknown_task_type(self): with self.assertRaises(ValueError) as ar: self.vmm_client.vm_get_job_status(task_id_list=[{ "task_type": "arduous", "task_id": "0" }]) self.assertEqual("Unknown task type 'arduous'", str(ar.exception)) def test_vm_stop_job_vmm_tasks(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.vm_stop_job(task_id_list=[{ "task_type": "vmm", "task_id": "0" }, { "task_type": "vmm", "task_id": "1" }]) m_ps_client.execute.assert_called_once_with( "Stop-Task", json_params="[{\"task_id\": \"0\", \"task_type\": \"vmm\"}, " "{\"task_id\": \"1\", \"task_type\": \"vmm\"}]") def test_vm_stop_job_ps_tasks(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: mock_ps_cmd_0 = mock.Mock() mock_ps_cmd_1 = mock.Mock() m_ps_client.poll.side_effect = [mock_ps_cmd_0, mock_ps_cmd_1] self.vmm_client.vm_stop_job(task_id_list=[{ "task_type": "ps", "task_id": "0" }, { "task_type": "ps", "task_id": "1" }]) m_ps_client.poll.assert_has_calls([mock.call("0"), mock.call("1")]) mock_ps_cmd_0.terminate.assert_called_once_with() mock_ps_cmd_1.terminate.assert_called_once_with() mock_ps_cmd_0.as_ps_task.assert_called_once_with() mock_ps_cmd_1.as_ps_task.assert_called_once_with() def test_vm_stop_job_unknown_task_type(self): with self.assertRaises(ValueError) as ar: self.vmm_client.vm_stop_job(task_id_list=[{ "task_type": "arduous", "task_id": "0" }]) self.assertEqual("Unknown task type 'arduous'", str(ar.exception)) def test_vms_delete_default(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.vms_delete(cluster_name="Fake Cluster", vm_ids=["0", "1"]) m_ps_client.execute.assert_called_once_with( "Remove-VmmVM", cluster_name="Fake Cluster", json_params="{\"force_delete\": false, \"vm_ids\": [\"0\", \"1\"]}" ) def test_vms_delete_force_delete_true(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.vms_delete(cluster_name="Fake Cluster", vm_ids=["0", "1"], force_delete=True) m_ps_client.execute.assert_called_once_with( "Remove-VmmVM", cluster_name="Fake Cluster", json_params="{\"force_delete\": true, \"vm_ids\": [\"0\", \"1\"]}") def test_create_vm_template(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.create_vm_template("fake_cluster", "fake_vm_template", "host_id_0", "/fake/goldimages/path", "/fake/datastore/path", "fake_network") m_ps_client.execute.assert_called_once_with( "Install-VmmVMTemplate", cluster_name="fake_cluster", json_params=json.dumps( { "vm_name": "fake_vm_template", "vm_host_id": "host_id_0", "goldimage_disk_path": "/fake/goldimages/path", "vm_datastore_path": "/fake/datastore/path", "vmm_network_name": "fake_network", "vcpus": 1, "ram_mb": 1024 }, sort_keys=True)) def test_create_vm(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.create_vm("fake_cluster", "fake_vm_template", [{ "vm_name": "fake_vm_0", "node_id": "0" }, { "vm_name": "fake_vm_1", "node_id": "1" }], "/fake/datastore/path", None, None) m_ps_client.execute.assert_called_once_with( "New-VmmVM", cluster_name="fake_cluster", json_params=json.dumps( { "vm_template_name": "fake_vm_template", "vm_host_map": [{ "vm_name": "fake_vm_0", "node_id": "0" }, { "vm_name": "fake_vm_1", "node_id": "1" }], "vm_datastore_path": "/fake/datastore/path", "data_disks": None, "differencing_disks_path": None }, sort_keys=True)) def test_clone_vm(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.clone_vm("fake_cluster", "fake_vm_id_0", "fake_new_cloned_vm", "/fake/datastore/path") m_ps_client.execute.assert_called_once_with( "New-VmmVMClone", cluster_name="fake_cluster", json_params=json.dumps( { "base_vm_id": "fake_vm_id_0", "vm_name": "fake_new_cloned_vm", "vm_datastore_path": "/fake/datastore/path" }, sort_keys=True)) def test_upload_image(self): self.vmm_client.library_server_share_path = "/fake/library/share/path" self.vmm_client.library_server_address = "fake_library_server" with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.upload_image( ["/fake/goldimage/path/0", "/fake/goldimage/path/1"], "/fake/goldimage/target/directory", "fake_disk_name") m_ps_client.execute_async.assert_called_once_with( "Install-VmmDiskImage", overwriteFiles=False, json_params=json.dumps( { "vmm_library_server_share": "/fake/library/share/path", "vmm_library_server_user": "******", "vmm_library_server_password": "******", "vmm_library_server": "fake_library_server", "goldimage_disk_list": ["/fake/goldimage/path/0", "/fake/goldimage/path/1"], "goldimage_target_dir": "/fake/goldimage/target/directory", "disk_name": "fake_disk_name", "transfer_type": None }, sort_keys=True)) def test_convert_to_template(self): self.vmm_client.library_server_share_path = "/fake/library/share/path" with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.convert_to_template(cluster_name="Fake Cluster", target_dir="fake_target_dir", template_name="fake_template") m_ps_client.execute.assert_called_once_with( "ConvertTo-Template", cluster_name="Fake Cluster", json_params="{\"target_dir\": \"fake_target_dir\", " "\"template_name\": \"fake_template\", " "\"vmm_library_server_share\": \"/fake/library/share/path\"}") def test_migrate_vm(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.migrate_vm("fake_cluster", [{ "vm_name": "fake_vm_0", "node_id": "0" }, { "vm_name": "fake_vm_1", "node_id": "1" }], "/fake/datastore/path") m_ps_client.execute.assert_called_once_with( "Move-VmmVM", cluster_name="fake_cluster", json_params=json.dumps( { "vm_host_map": [{ "vm_name": "fake_vm_0", "node_id": "0" }, { "vm_name": "fake_vm_1", "node_id": "1" }], "vm_datastore_path": "/fake/datastore/path", }, sort_keys=True)) def test_migrate_vm_datastore(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.migrate_vm_datastore( "fake_cluster", [{ "vm_id": "fake_vm_id_0", "datastore_name": "fake_datastore_0" }, { "vm_id": "fake_vm_id_1", "datastore_name": "fake_datastore_1" }]) m_ps_client.execute.assert_called_once_with( "Move-VmmVMDatastore", cluster_name="fake_cluster", json_params=json.dumps( { "vm_datastore_map": [{ "vm_id": "fake_vm_id_0", "datastore_name": "fake_datastore_0" }, { "vm_id": "fake_vm_id_1", "datastore_name": "fake_datastore_1" }], }, sort_keys=True)) def test_clean_vmm(self): self.vmm_client.library_server_share_path = "/fake/library/share/path" with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.clean_vmm("fake_cluster", "fake_target_dir", "fake_datastore_path", "fake_vm_") m_ps_client.execute_async.assert_called_once_with( "Remove-ClusterVmmObjects", cluster_name="fake_cluster", json_params=json.dumps( { "vmm_library_server_share": "/fake/library/share/path", "target_dir": "fake_target_dir", "vm_datastore_path": "fake_datastore_path", "vm_name_prefix": "fake_vm_" }, sort_keys=True)) def test_clean_library_server(self): self.vmm_client.library_server_share_path = "/fake/library/share/path" self.vmm_client.library_server_address = "fake_library_server" with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.clean_library_server("/fake/target/directory", "fake_vm_") m_ps_client.execute_async.assert_called_once_with( "Remove-VmmDiskImages", json_params=json.dumps( { "vmm_library_server_share": "/fake/library/share/path", "vmm_library_server_user": "******", "vmm_library_server_password": "******", "vmm_library_server": "fake_library_server", "target_dir": "/fake/target/directory", "vm_name_prefix": "fake_vm_" }, sort_keys=True)) def test_update_library(self): with mock.patch.object(self.vmm_client, "ps_client") as m_ps_client: self.vmm_client.update_library("/fake/goldimages/path") m_ps_client.execute_async.assert_called_once_with( "Update-Library", json_params="{\"goldimage_disk_path\": \"/fake/goldimages/path\"}", num_retries=3)
def update_cluster_virtual_ip(cluster_pb): """ Updates 'prism_host' to correspond to the cluster virtual IP. The 'prism_host' field is set for management and clustering software as appropriate for the target hypervisor. Returns: True if 'prism_host' was updated, else False. Raises: CurieException<kInvalidParameter>: 'cluster_pb' is a Nutanix cluster but does not have a Virtual IP. """ if not cluster_pb.cluster_software_info.HasField("nutanix_info"): return False prism_proto = None ntnx_proto = cluster_pb.cluster_software_info.nutanix_info prism_user = ntnx_proto.decrypt_field("prism_user") prism_password = ntnx_proto.decrypt_field("prism_password") c_uuid = ntnx_proto.cluster_uuid if cluster_pb.cluster_management_server_info.HasField("prism_info"): prism_proto = cluster_pb.cluster_management_server_info.prism_info client = NutanixRestApiClient.from_proto(prism_proto, timeout_secs=10) cluster_json = client.clusters_get(cluster_id=c_uuid) else: cvm_addresses = [] if cluster_pb.cluster_management_server_info.HasField("vmm_info"): vmm_info = cluster_pb.cluster_management_server_info.vmm_info vmm_client = VmmClient(address=vmm_info.vmm_server, username=vmm_info.vmm_user, password=vmm_info.vmm_password) with vmm_client: vms = vmm_client.get_vms(cluster_name=vmm_info.vmm_cluster_name) for vm in vms: if VmmClient.is_nutanix_cvm(vm): if VmmClient.is_powered_on(vm): log.debug("Found CVM '%s' with IPs: %s", vm["name"], vm["ips"]) cvm_addresses.extend(vm["ips"]) else: log.debug("Skipping CVM '%s' because it is not powered on.", vm["name"]) else: node_ids = [node.id for node in cluster_pb.cluster_nodes] # NB: We currently have an asymmetrical input for Prism credentials # depending on whether they're considered as management software or # clustering software. In the latter case, which is when 'nutanix_info' # is set but not 'prism_info', the user is not asked for a Prism host. # In this case, we discover CVMs via vCenter, attempt to connect to Prism # on each in sequence until successful, and then query the virtual IP. mgmt_info = cluster_pb.cluster_management_server_info.vcenter_info with VsphereVcenter.from_proto(mgmt_info) as vcenter: vim_dc = vcenter.lookup_datacenter(mgmt_info.vcenter_datacenter_name) vim_cluster = vcenter.lookup_cluster(vim_dc, mgmt_info.vcenter_cluster_name) for vim_cvm in (vm for vm in vcenter.lookup_vms(vim_cluster) if vcenter.vim_vm_is_nutanix_cvm(vm)): vim_host = get_optional_vim_attr(vim_cvm.runtime, "host") if vim_host: if vim_host.name in node_ids: cvm_address = vcenter.get_vim_vm_ip_address(vim_cvm) if cvm_address: log.debug("Found CVM '%s' with address '%s'" % (vim_cvm.name, cvm_address)) cvm_addresses.append(cvm_address) else: log.debug("Skipping CVM '%s'; Host '%s' is not in the " "metadata" % (vim_cvm.name, vim_host.name)) # We run Nutanix API only against powered on CVMs. if not cvm_addresses: raise CurieTestException( cause="No Nutanix CVMs found.", impact="The cluster virtual IP address can not be discovered.", corrective_action="Please verify that the cluster contains Nutanix " "CVMs, and that they are powered on.", ) for cvm_address in cvm_addresses: client = NutanixRestApiClient(cvm_address, prism_user, prism_password) try: cluster_json = client.clusters_get(cluster_id=c_uuid, max_retries=3) except BaseException: log.warning("Unable to query CVM with IP '%s'", cvm_address, exc_info=True) else: break else: raise CurieTestException( cause="Failed to query Prism on any Nutanix CVM.", impact="The cluster virtual IP address can not be discovered.", corrective_action="Please verify that the Nutanix CVMs on the " "cluster are powered on, and that the network " "connectivity to the CVMs is correct.", ) if "clusterExternalIPAddress" in cluster_json: cluster_name = cluster_json.get("name") cluster_vip = cluster_json["clusterExternalIPAddress"] elif "entities" in cluster_json: cluster_data = cluster_json["entities"][0] cluster_name = cluster_data.get("name") cluster_vip = cluster_data["clusterExternalIPAddress"] else: raise CurieException( CurieError.kInvalidParameter, "Unrecognized response from NutanixRestApiClient.clusters_get") if not cluster_vip: raise CurieException( CurieError.kInvalidParameter, "Cluster '%s' does not appear to be configured with a virtual IP " "(received '%s')" % (cluster_name, cluster_vip)) else: log.debug("Identified Nutanix cluster virtual IP address: '%s'", cluster_vip) ntnx_proto.prism_host = cluster_vip if prism_proto: prism_proto.prism_host = cluster_vip return True