def test_update_fail_will_suicide(self, sleep_mock, connect_mock, update_mock, update_hosts_mock): killed = threading.Event() def suicide(): killed.set() threading.current_thread().stop() update_cache = MagicMock() update_cache.side_effect = vim.fault.HostConnectFault client = VimClient("esx.local", "root", "password", auto_sync=True, min_interval=1, errback=lambda: suicide()) client.update_cache = update_cache killed.wait(1) client.disconnect(wait=True) # update_cache will be called 5 times before it kill itself assert_that(update_cache.call_count, is_(5)) assert_that(killed.is_set(), is_(True))
def test_update_fail_without_looping(self, connect_mock, update_mock): client = VimClient(auto_sync=True, min_interval=1) client.connect_userpwd("esx.local", "root", "password") update_mock.side_effect = vim.fault.HostConnectFault time.sleep(0.5) client.disconnect() assert_that(update_mock.call_count, less_than(5)) # no crazy loop
def test_update_fail_without_looping(self, connect_mock, update_mock): client = VimClient("esx.local", "root", "password", auto_sync=True, min_interval=1) update_mock.side_effect = vim.fault.HostConnectFault time.sleep(0.5) client.disconnect(wait=True) assert_that(update_mock.call_count, less_than(4)) # no crazy loop
def test_update_host_cache_in_thread(self, disconnect_mock, connect_mock, spec_mock, update_mock, prop_collector_mock): vm = vim.VirtualMachine("moid", None) vm.kind = "enter" vm.changeSet = {} update = MagicMock() update.filterSet = [MagicMock()] update.filterSet[0].objectSet = [MagicMock()] update.filterSet[0].objectSet[0] = vm # Mock the Vim APIs. prop_collector_mock.WaitForUpdatesEx = MagicMock() prop_collector_mock.WaitForUpdatesEx.return_value = update # Create VimClient. vim_client = VimClient(min_interval=0.1, auto_sync=True) vim_client.connect_userpwd("esx.local", "root", "password") # Verify that the update mock is called a few times. retry = 0 while update_mock.call_count < 5 and retry < 10: time.sleep(0.2) retry += 1 assert_that(retry, is_not(10), "VimClient.update_mock is not called repeatedly") # Disconnect the client and stop the thread. vim_client.disconnect() assert_that(disconnect_mock.called, is_(True)) assert_that(update_mock.call_count, is_not(0), "VimClient.update_mock is not called")
def test_update_host_cache_in_thread(self, disconnect_mock, connect_mock, spec_mock, update_mock, prop_collector_mock): vm = vim.VirtualMachine("moid", None) vm.kind = "enter" vm.changeSet = {} update = MagicMock() update.filterSet = [MagicMock()] update.filterSet[0].objectSet = [MagicMock()] update.filterSet[0].objectSet[0] = vm # Mock the Vim APIs. prop_collector_mock.WaitForUpdatesEx = MagicMock() prop_collector_mock.WaitForUpdatesEx.return_value = update # Create VimClient. vim_client = VimClient(min_interval=0.1, auto_sync=True) vim_client.connect_userpwd("esx.local", "root", "password") # Verify that the update mock is called a few times. retry = 0 while update_mock.call_count < 5 and retry < 10: time.sleep(0.2) retry += 1 assert_that(retry, is_not(10), "VimClient.update_mock is not called repeatedly") # Disconnect the client and stop the thread. vim_client.disconnect() assert_that(disconnect_mock.called, is_(True)) assert_that(update_mock.call_count, is_not(0), "VimClient.update_mock is not called")
def test_update_host_cache_in_thread(self, disconnect_mock, connect_mock, spec_mock, update_mock, update_host_mock, query_spec_mock, perf_manager_mock, prop_collector_mock): # Test Values. counter = MagicMock() counter.groupInfo.key = "mem" counter.nameInfo.key = "consumed" counter.key = 65613 n = 5 statValues = ','.join([str(x) for x in range(1, n + 1)]) statAverage = sum(range(1, n + 1)) / len(range(1, n + 1)) stat = MagicMock() stat.value = [MagicMock()] stat.value[0].id.counterId = 65613 stat.value[0].value = statValues # Mock the Vim APIs. pc_return_mock = MagicMock({'WaitForUpdatesEx.return_value': {}}) summarize_stats = {'QueryPerf.return_value': [stat]} pm_return_mock = MagicMock(perfCounter=[counter], **summarize_stats) # Tie the mocked APIs with VimClient. prop_collector_mock.return_value = pc_return_mock perf_manager_mock.return_value = pm_return_mock # Create VimClient. vim_client = VimClient("esx.local", "root", "password", min_interval=0.1, auto_sync=True, stats_interval=0.2) # Verify that the update mock is called a few times. retry = 0 while update_mock.call_count < 5 and retry < 10: time.sleep(0.2) retry += 1 assert_that(retry, is_not(10), "VimClient.update_mock is not " "called repeatedly") # Disconnect the client and stop the thread. vim_client.disconnect(wait=True) assert_that(disconnect_mock.called, is_(True)) # Verify that update_host_mock is called atleast once and is called # less number of times than update_mock. assert_that(update_host_mock.call_count, is_not(0), "VimClient.update_host_mock is not called repeatedly") assert_that(update_host_mock.call_count, less_than(update_mock.call_count)) host_stats = update_host_mock.call_args_list for host in host_stats: assert_that(host[0][0]['mem.consumed'], equal_to(statAverage))
def test_update_host_cache_in_thread(self, disconnect_mock, connect_mock, spec_mock, update_mock, update_host_mock, query_spec_mock, perf_manager_mock, prop_collector_mock): # Test Values. counter = MagicMock() counter.groupInfo.key = "mem" counter.nameInfo.key = "consumed" counter.key = 65613 n = 5 statValues = ','.join([str(x) for x in range(1, n+1)]) statAverage = sum(range(1, n+1)) / len(range(1, n+1)) stat = MagicMock() stat.value = [MagicMock()] stat.value[0].id.counterId = 65613 stat.value[0].value = statValues # Mock the Vim APIs. pc_return_mock = MagicMock({'WaitForUpdatesEx.return_value': {}}) summarize_stats = {'QueryPerf.return_value': [stat]} pm_return_mock = MagicMock(perfCounter=[counter], **summarize_stats) # Tie the mocked APIs with VimClient. prop_collector_mock.return_value = pc_return_mock perf_manager_mock.return_value = pm_return_mock # Create VimClient. vim_client = VimClient("esx.local", "root", "password", min_interval=0.1, auto_sync=True, stats_interval=0.2) # Verify that the update mock is called a few times. retry = 0 while update_mock.call_count < 5 and retry < 10: time.sleep(0.2) retry += 1 assert_that(retry, is_not(10), "VimClient.update_mock is not " "called repeatedly") # Disconnect the client and stop the thread. vim_client.disconnect(wait=True) assert_that(disconnect_mock.called, is_(True)) # Verify that update_host_mock is called atleast once and is called # less number of times than update_mock. assert_that(update_host_mock.call_count, is_not(0), "VimClient.update_host_mock is not called repeatedly") assert_that(update_host_mock.call_count, less_than(update_mock.call_count)) host_stats = update_host_mock.call_args_list for host in host_stats: assert_that(host[0][0]['mem.consumed'], equal_to(statAverage))
def test_poll_update_in_thread(self, disconnect_mock, connect_mock, spec_mock, update_mock): vim_client = VimClient(min_interval=0, auto_sync=True) vim_client.connect_userpwd("esx.local", "root", "password") vim_client._property_collector.WaitForUpdatesEx.return_value = {} assert_that(update_mock.called, is_(True)) retry = 0 while update_mock.call_count < 5 and retry < 10: time.sleep(0.2) retry += 1 assert_that(retry, is_not(10), "VimClient._poll_updates is not called repeatedly") vim_client.disconnect() assert_that(disconnect_mock.called, is_(True))
def test_poll_update_in_thread(self, disconnect_mock, connect_mock, spec_mock, update_mock): vim_client = VimClient(min_interval=0, auto_sync=True) vim_client.connect_userpwd("esx.local", "root", "password") vim_client._property_collector.WaitForUpdatesEx.return_value = {} assert_that(update_mock.called, is_(True)) retry = 0 while update_mock.call_count < 5 and retry < 10: time.sleep(0.2) retry += 1 assert_that(retry, is_not(10), "VimClient._poll_updates is not called repeatedly") vim_client.disconnect() assert_that(disconnect_mock.called, is_(True))
class TestVmManager(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self._logger = logging.getLogger(__name__) self.vim_client = VimClient() self.vim_client.connect_userpwd(self.host, "root", self.pwd) self.vm_manager = VmManager(self.vim_client, None) for vm in self.vim_client._get_vms(): vm.Destroy() def tearDown(self): self.vim_client.disconnect() @patch('os.path.exists', return_value=True) def test_mks_ticket(self, _exists): vm_id = self._vm_id() flavor = Flavor("vm", [ QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB) ]) datastore = self.vim_client.get_all_datastores()[0].name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) try: self.vm_manager.create_vm(vm_id, spec) self.vm_manager.power_on_vm(vm_id) ticket = self.vm_manager.get_mks_ticket(vm_id) assert_that(ticket.cfg_file, not_none()) assert_that(ticket.ticket, not_none()) finally: self.vm_manager.power_off_vm(vm_id) self.vm_manager.delete_vm(vm_id) def _vm_id(self): vm_id = strftime("%Y-%m-%d-%H%M%S-", localtime()) vm_id += str(random.randint(1, 10000)) return vm_id def _test_port(self): return 5907
class TestEsxDiskManager(unittest.TestCase): @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.vim_client.wait_for_task = MagicMock() self.disk_manager = EsxDiskManager(self.vim_client, []) self.disk_manager._vmdk_mkdir = MagicMock() self.disk_manager._vmdk_rmdir = MagicMock() def tearDown(self): self.vim_client.disconnect(wait=True) def test_create_spec(self): """Test that we create a valid disk spec.""" capacity = 2 spec = self.disk_manager._create_spec(capacity) assert_that(spec.capacityKb, equal_to(capacity * (1024 ** 2))) assert_that(spec.adapterType, equal_to(DEFAULT_DISK_ADAPTER_TYPE)) def test_invalid_datastore_path(self): """Test that we propagate InvalidDatastorePath.""" self.vim_client.wait_for_task.side_effect = \ vim.fault.InvalidDatastorePath self.assertRaises(DiskPathException, self.disk_manager.create_disk, "ds1", "foo", 101) def test_disk_not_found(self): """Test that we propagate FileNotFound.""" self.vim_client.wait_for_task.side_effect = vim.fault.FileNotFound self.assertRaises(DiskFileException, self.disk_manager.delete_disk, "ds1", "bar") def test_general_fault(self): """Test general Exception propagation.""" self.vim_client.wait_for_task.side_effect = vim.fault.TaskInProgress self.assertRaises(vim.fault.TaskInProgress, self.disk_manager.move_disk, "ds1", "biz", "ds1", "baz")
class TestVmManager(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self._logger = logging.getLogger(__name__) self.vim_client = VimClient() self.vim_client.connect_userpwd(self.host, "root", self.pwd) self.vm_manager = VmManager(self.vim_client, None) for vm in self.vim_client._get_vms(): vm.Destroy() def tearDown(self): self.vim_client.disconnect() @patch('os.path.exists', return_value=True) def test_mks_ticket(self, _exists): vm_id = self._vm_id() flavor = Flavor("vm", [QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB)]) datastore = self.vim_client.get_all_datastores()[0].name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) try: self.vm_manager.create_vm(vm_id, spec) self.vm_manager.power_on_vm(vm_id) ticket = self.vm_manager.get_mks_ticket(vm_id) assert_that(ticket.cfg_file, not_none()) assert_that(ticket.ticket, not_none()) finally: self.vm_manager.power_off_vm(vm_id) self.vm_manager.delete_vm(vm_id) def _vm_id(self): vm_id = strftime("%Y-%m-%d-%H%M%S-", localtime()) vm_id += str(random.randint(1, 10000)) return vm_id def _test_port(self): return 5907
def vim_delete_vm(self, vm_id): """ Delete a VM using the vim client """ try: vim_client = VimClient() vim_client.connect_ticket(self.server, self._get_vim_ticket()) vim_vm = vim_client.get_vm(vm_id) if vim_vm.runtime.powerState != 'poweredOff': try: vim_task = vim_vm.PowerOff() vim_client.wait_for_task(vim_task) except: logger.info("Cannot power off vm", exc_info=True) vim_task = vim_vm.Destroy() vim_client.wait_for_task(vim_task) finally: if vim_client: vim_client.disconnect()
class TestEsxDiskManager(unittest.TestCase): @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.vim_client.wait_for_task = MagicMock() self.disk_manager = EsxDiskManager(self.vim_client, []) self.disk_manager._vmdk_mkdir = MagicMock() self.disk_manager._vmdk_rmdir = MagicMock() def tearDown(self): self.vim_client.disconnect(wait=True) def test_create_spec(self): """Test that we create a valid disk spec.""" capacity = 2 spec = self.disk_manager._create_spec(capacity) assert_that(spec.capacityKb, equal_to(capacity * (1024**2))) assert_that(spec.adapterType, equal_to(DEFAULT_DISK_ADAPTER_TYPE)) def test_invalid_datastore_path(self): """Test that we propagate InvalidDatastorePath.""" self.vim_client.wait_for_task.side_effect = \ vim.fault.InvalidDatastorePath self.assertRaises(DiskPathException, self.disk_manager.create_disk, "ds1", "foo", 101) def test_disk_not_found(self): """Test that we propagate FileNotFound.""" self.vim_client.wait_for_task.side_effect = vim.fault.FileNotFound self.assertRaises(DiskFileException, self.disk_manager.delete_disk, "ds1", "bar") def test_general_fault(self): """Test general Exception propagation.""" self.vim_client.wait_for_task.side_effect = vim.fault.TaskInProgress self.assertRaises(vim.fault.TaskInProgress, self.disk_manager.move_disk, "ds1", "biz", "ds1", "baz")
def test_update_fail_will_suicide(self, sleep_mock, connect_mock, update_mock): killed = threading.Event() def suicide(): killed.set() threading.current_thread().stop() poll_updates = MagicMock() poll_updates.side_effect = vim.fault.HostConnectFault client = VimClient(auto_sync=True, min_interval=1, errback=lambda: suicide()) client.connect_userpwd("esx.local", "root", "password") client._vim_cache.poll_updates = poll_updates killed.wait(1) client.disconnect() # poll_updates will be called 5 times before it kill itself assert_that(poll_updates.call_count, is_(5)) assert_that(killed.is_set(), is_(True))
class TestVimClient(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self.vim_client = VimClient(auto_sync=True) self.vim_client.connect_userpwd(self.host, "root", self.pwd) self._logger = logging.getLogger(__name__) def tearDown(self): self.vim_client.disconnect() def test_memory_usage(self): used_memory = self.vim_client.memory_usage_mb assert_that(used_memory > 0, is_(True)) def test_total_memory(self): total_memory = self.vim_client.total_vmusable_memory_mb assert_that(total_memory > 0, is_(True)) def test_total_cpus(self): num_cpus = self.vim_client.num_physical_cpus assert_that(num_cpus > 0, is_(True)) def _create_test_vm(self, suffix="host-integ"): # Create VM vm_id = "vm_%s-%s-%s" % (time.strftime("%Y-%m-%d-%H%M%S", time.localtime()), str(random.randint(100000, 1000000)), suffix) datastore = self.vim_client.get_all_datastores()[0].name disk_path = "[%s] %s/disk.vmdk" % (datastore, vm_id) create_spec = self.get_create_spec(datastore, vm_id, disk_path) self.vim_client.create_vm(vm_id, create_spec) vm = self.vim_client.get_vm(vm_id) return (vm_id, vm, datastore, disk_path) def test_get_cached_vm(self): vm_id, vm, datastore, disk_path = self._create_test_vm("vm-cache-test") # Verify VM is in cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].power_state, is_(VmPowerState.STOPPED)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Make sure get_vm_in_cache works vm_from_cache = self.vim_client.get_vm_in_cache(vm_id) assert_that(vm_from_cache.name, is_(vm_id)) self.assertRaises(VmNotFoundException, self.vim_client.get_vm_in_cache, "missing") # Add disk disk2_path = "[%s] %s/disk2.vmdk" % (datastore, vm_id) update_spec = self.get_update_spec(vm, disk2_path) task = vm.ReconfigVM_Task(update_spec.get_spec()) self.vim_client.wait_for_task(task) # For the ReconfigVM task to remove disk, the hostd could update # task status to success before updating VM status. Thus when # wait_for_task returns, the vm_cache is possible to be still in old # state, though eventually it converges to consistent state. It only # happens in this task AFAIK. It should be fine for this task, because # rarely there is other operation that depends on this task. self._wait_vm_has_disk(vm_id, 2) # Verify disk added vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms[0].disks), is_(2)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path, disk2_path)) # Remove disk vm = self.vim_client.get_vm(vm_id) remove_spec = self.get_remove_spec(vm, disk2_path) task = vm.ReconfigVM_Task(remove_spec.get_spec()) self.vim_client.wait_for_task(task) # Same as before when disk is added self._wait_vm_has_disk(vm_id, 1) # Verify disk removed vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(len(found_vms[0].disks), is_(1), "disk2 in " + str(found_vms[0].disks)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path)) # Power on vm task = vm.PowerOn() self.vim_client.wait_for_task(task) # Wait until it disappears from the cache self._wait_vm_power_status(vm_id, VmPowerState.STARTED) # Verify VM state in cache is updated vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].power_state, is_(VmPowerState.STARTED)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Destroy VM task = vm.PowerOff() self.vim_client.wait_for_task(task) task = vm.Destroy() self.vim_client.wait_for_task(task) # Verify VM is deleted from cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(0)) def test_no_datastore_update(self): """ Test datastore update is no longer triggered on VM creates/deletes """ class UpdateListener(object): def __init__(self): self._ds_update_count = 0 def datastores_updated(self): self._ds_update_count += 1 listener = UpdateListener() self.vim_client.add_update_listener(listener) # listener always gets updated once on add assert_that(listener._ds_update_count, is_(1)) mock_apply = MagicMock( wraps=self.vim_client._vim_cache._update_ds_cache) self.vim_client._vim_cache._update_ds_cache = mock_apply _, vm, _, _ = self._create_test_vm("ds-update-test") task = vm.Destroy() self.vim_client.wait_for_task(task) # expect to get a datastore property update (unfortunately) ... for _ in xrange(50): if mock_apply.call_count > 0: break time.sleep(0.1) # ... but that additional datastore updated notifications are sent out # as a result assert_that(listener._ds_update_count, is_(1)) def get_create_spec(self, datastore, vm_id, disk_path): create_spec = EsxVmConfigSpec(None) create_spec.init_for_create(vm_id, datastore, 64, 2) create_spec._cfg_spec.files = vim.vm.FileInfo(vmPathName="[%s] /" % datastore) controller = vim.vm.device.VirtualLsiLogicController( key=1, sharedBus=vim.vm.device.VirtualSCSIController.Sharing.noSharing, busNumber=2, unitNumber=-1) create_spec._add_device(controller) backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent) disk = vim.vm.device.VirtualDisk( controllerKey=1, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) create_spec._create_device(disk) return create_spec def get_update_spec(self, vm_info, disk_path): update_spec = EsxVmConfigSpec(None) update_spec.init_for_update() backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent) controller = update_spec._find_scsi_controller(vm_info.config) disk = vim.vm.device.VirtualDisk( controllerKey=controller.key, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) update_spec._create_device(disk) return update_spec def get_remove_spec(self, vm_info, disk_path): remove_spec = EsxVmConfigSpec(None) remove_spec.init_for_update() devices = remove_spec._get_devices_by_type(vm_info.config, vim.vm.device.VirtualDisk) found_device = None for device in devices: if device.backing.fileName.endswith(disk_path): found_device = device remove_spec._remove_device(found_device) return remove_spec def test_clone_ticket(self): ticket = self.vim_client.get_vim_ticket() vim_client2 = VimClient() vim_client2.connect_ticket(self.host, ticket) vim_client2.host_system() def _wait_vm_has_disk(self, vm_id, disk_num): """Wait until the vm has disk number of the vm becomes disk_num """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if len(vm_in_cache.disks) == disk_num: self._logger.info("VmCache disk number synced in %.2f second" % (time.time() - now)) break time.sleep(0.1) def _wait_vm_power_status(self, vm_id, power_state): """Wait until the vm has power_state """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if vm_in_cache.power_state == power_state: self._logger.info("VmCache power_state synced in %.2f second" % (time.time() - now)) break time.sleep(0.1)
class TestEsxVmConfig(unittest.TestCase): @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) with patch("host.hypervisor.esx.vm_config.GetEnv"): self.vm_config = EsxVmConfig(self.vim_client) def tearDown(self): self.vim_client.disconnect(wait=True) def dummy_devices(self): return [ vim.vm.device.VirtualFloppy(key=10), vim.vm.device.VirtualPCIController(key=100), DEFAULT_DISK_CONTROLLER_CLASS(key=1000), vim.vm.device.VirtualSoundCard(key=10000), ] def test_vm_create_spec(self): datastore = "ds1" vm_id = str(uuid.uuid4()) metadata = { "configuration": {"guestOS": "otherLinuxGuest"}, "parameters": [{"name": "key1"}, {"name": "key2"}] } env = { "key1": "value1", "keyUnexpected": "valueNotSet", } spec = self.vm_config.create_spec(vm_id, datastore, 512, 1, metadata, env) assert_that(spec.memoryMB, equal_to(512)) assert_that(spec.numCPUs, equal_to(1)) assert_that(spec.name, equal_to(vm_id)) assert_that(spec.guestId, equal_to("otherLinuxGuest")) expected_metadata = {'guestOS': 'otherLinuxGuest', 'key1': 'value1'} assert_that(spec._metadata, equal_to(expected_metadata)) def test_create_nic_spec(self): net_name = "VM_network" cspec = self.vm_config.update_spec() spec = self.vm_config.add_nic(cspec, net_name) backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo assert_that(spec.deviceChange[0].device.backing.__class__, equal_to(backing)) assert_that(spec.deviceChange[0].device.backing.deviceName, equal_to(net_name)) def test_find_disk_controller(self): devices = self.dummy_devices() device_type = DEFAULT_DISK_CONTROLLER_CLASS disk_controller = self.vm_config.find_device(devices, device_type) assert_that(disk_controller.key, equal_to(1000)) def test_find_nic_controller(self): devices = self.dummy_devices() device_type = vim.vm.device.VirtualPCIController disk_controller = self.vm_config.find_device(devices, device_type) assert_that(disk_controller.key, equal_to(100)) def test_find_virtual_disk(self): spec = vim.vm.ConfigSpec() vm_config = self.vm_config devices = self.dummy_devices() for device in devices: vm_config.add_device(spec, device) cfg_info = FakeConfigInfo() device_type = vim.vm.device.VirtualDisk datastore = "ds1" filename = "folder/foo" path = vmdk_path(datastore, filename) find_disk = vm_config.disk_matcher(datastore, filename) disk = vm_config.find_device(devices, device_type, matcher=find_disk) assert_that(disk, equal_to(None)) vm_config.add_scsi_disk(cfg_info, spec, datastore, "nope") self.assertRaises(DeviceNotFoundException, vm_config.get_device, devices, device_type, matcher=find_disk) vm_config.add_scsi_disk(cfg_info, spec, datastore, filename) device_changes = spec.deviceChange device_list = [] for device_change in device_changes: device_list.append(device_change.device) disk = vm_config.find_device(device_list, device_type, matcher=find_disk) assert_that(disk.backing.fileName, equal_to(path)) def _create_spec_for_disk_test(self, datastore, vm_id): spec = vim.vm.ConfigSpec() devices = self.dummy_devices() for device in devices: self.vm_config.add_device(spec, device) vm_path_name = '[%s] %s/%s' % (datastore, vm_id[0:2], vm_id) spec.files = vim.vm.FileInfo(vmPathName=vm_path_name) spec.name = vm_id return spec def test_create_empty_disk(self): vm_id = str(uuid.uuid4()) datastore = "ds1" spec = self._create_spec_for_disk_test(datastore, vm_id) size_mb = 100 disk_id = str(uuid.uuid4()) self.vm_config.create_empty_disk(spec, datastore, disk_id, size_mb) devs = [change.device for change in spec.deviceChange] device_type = vim.vm.device.VirtualDisk disks = self.vm_config.find_devices(devs, device_type) assert_that(len(disks), equal_to(1)) # verify that uuid to be set on disk to be added matches the # of the disk (modulo some formatting differences) assert_that(disks[0].backing.uuid, equal_to(uuid_to_vmdk_uuid(disk_id))) def test_create_child_disk(self): vm_id = str(uuid.uuid4()) datastore = "ds1" spec = self._create_spec_for_disk_test(datastore, vm_id) disk_id = str(uuid.uuid4()) parent_id = str(uuid.uuid4()) self.vm_config.create_child_disk(spec, datastore, disk_id, parent_id) devs = [change.device for change in spec.deviceChange] device_type = vim.vm.device.VirtualDisk disks = self.vm_config.find_devices(devs, device_type) assert_that(len(disks), equal_to(1)) # verify that disk to be added does not request a specifc uuid assert_that(disks[0].backing.uuid, equal_to(None)) def _get_config_info_with_iso(self, iso_path): devices = self.dummy_devices() cfg_info = FakeConfigInfo() cfg_info.hardware.device = devices cdrom = vim.vm.device.VirtualCdrom() cdrom.key = 1234 cdrom.controllerKey = 100 cdrom.unitNumber = 1 iso_backing = vim.vm.device.VirtualCdrom.IsoBackingInfo() iso_backing.fileName = iso_path cdrom.backing = iso_backing conInfo = vim.vm.device.VirtualDevice.ConnectInfo() conInfo.allowGuestControl = True conInfo.connected = True conInfo.startConnected = True cdrom.connectable = conInfo cfg_info.hardware.device.append(cdrom) return cfg_info def _get_config_info_without_connected(self, is_iso_backing): devices = self.dummy_devices() cfg_info = FakeConfigInfo() cfg_info.hardware.device = devices cdrom = vim.vm.device.VirtualCdrom() cdrom.key = 1234 cdrom.controllerKey = 100 cdrom.unitNumber = 1 if is_iso_backing: iso_backing = vim.vm.device.VirtualCdrom.IsoBackingInfo() cdrom.backing = iso_backing conInfo = vim.vm.device.VirtualDevice.ConnectInfo() conInfo.allowGuestControl = True conInfo.connected = False conInfo.startConnected = True cdrom.connectable = conInfo cfg_info.hardware.device.append(cdrom) return cfg_info def test_add_iso_cdrom(self): virtual_ide_controller = vim.vm.device.VirtualIDEController() cfgOption = vim.vm.ConfigOption() cfgOption.defaultDevice.append(virtual_ide_controller) self.vm_config._cfg_opts = cfgOption # fake iso ds path fake_iso_ds_path = '[ds] vms/fa/fake/fake.iso' # test if no virtual cdrom attached to the VM cfg_info = FakeConfigInfo() cspec = self.vm_config.update_spec() result = self.vm_config.add_iso_cdrom( cspec, fake_iso_ds_path, cfg_info) assert_that(result.__class__, equal_to(bool)) assert_that(result, equal_to(True)) dev = cspec.deviceChange[0].device assert_that(len(cspec.deviceChange), equal_to(1)) assert_that(dev.connectable.connected, equal_to(True)) assert_that(dev.connectable.startConnected, equal_to(True)) assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) # test if virtual cdrom exist and ISO already attached to the VM cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_with_iso(fake_iso_ds_path) result = self.vm_config.add_iso_cdrom( cspec, fake_iso_ds_path, cfg_info) assert_that(result.__class__, equal_to(bool)) assert_that(result, equal_to(False)) # test if virtual cdrom exist and it's iso_backing # and ISO is not attached to the VM cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_without_connected(is_iso_backing=True) result = self.vm_config.add_iso_cdrom( cspec, fake_iso_ds_path, cfg_info) assert_that(result.__class__, equal_to(bool)) assert_that(result, equal_to(True)) dev = cspec.deviceChange[0].device assert_that(len(cspec.deviceChange), equal_to(1)) assert_that(dev.connectable.connected, equal_to(True)) assert_that(dev.connectable.startConnected, equal_to(True)) assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) # test if virtual cdrom exist and it's _not_ iso_backing # and ISO is not attached to the VM cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_without_connected( is_iso_backing=False) self.assertRaises(TypeError, self.vm_config.add_iso_cdrom, cspec, fake_iso_ds_path, cfg_info) def test_disconnect_iso(self): # on vm config with no cdrom devices cfg_info = FakeConfigInfo() cspec = self.vm_config.update_spec() self.assertRaises(DeviceNotFoundException, self.vm_config.disconnect_iso_cdrom, cspec, cfg_info) assert_that(len(cspec.deviceChange), equal_to(0)) # on vm config with no a fake cdrom device fake_iso_ds_path = '[ds] vms/fa/fake/fake.iso' cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_with_iso(fake_iso_ds_path) iso_path = self.vm_config.disconnect_iso_cdrom(cspec, cfg_info) assert_that(len(cspec.deviceChange), equal_to(1)) dev = cspec.deviceChange[0].device assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) assert_that(dev.backing.fileName, equal_to(fake_iso_ds_path)) assert_that(iso_path, equal_to(fake_iso_ds_path)) assert_that(dev.connectable.connected, equal_to(False)) assert_that(dev.connectable.startConnected, equal_to(False)) def test_remove_iso_cdrom_device(self): fake_iso_ds_path = '[ds] vms/fa/fake/fake.iso' cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_with_iso(fake_iso_ds_path) self.vm_config.remove_iso_cdrom(cspec, cfg_info) assert_that(len(cspec.deviceChange), equal_to(1)) assert_that(cspec.deviceChange[0].operation, equal_to('remove')) dev = cspec.deviceChange[0].device assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) assert_that(dev.backing.fileName, equal_to(fake_iso_ds_path)) def test_update_spec(self): cfg_info = FakeConfigInfo() spec = self.vm_config.update_spec() assert_that(len(spec.deviceChange), equal_to(0)) net_name = "VM_Network" self.vm_config.add_nic(spec, net_name) assert_that(len(spec.deviceChange), equal_to(1)) self.vm_config.add_scsi_disk(cfg_info, spec, "ds1", "foo") # One for the controller and one for the disk itself. assert_that(len(spec.deviceChange), equal_to(3)) def test_path_conversion_invalid(self): self.assertRaises(IndexError, datastore_to_os_path, "invalid_ds_path") @parameterized.expand([ ('[foo] a/b/c.vmdk', '/vmfs/volumes/foo/a/b/c.vmdk'), ('[foo] c.vmdk', '/vmfs/volumes/foo/c.vmdk'), ('[foo]a', '/vmfs/volumes/foo/a'), ('/vmfs/volumes/foo/bar.vmdk', '/vmfs/volumes/foo/bar.vmdk'), ('[]/vmfs/volumes/foo/bar.vmdk', '/vmfs/volumes/foo/bar.vmdk'), ('[] /vmfs/volumes/foo/bar.vmdk', '/vmfs/volumes/foo/bar.vmdk') ]) def test_path_conversion(self, ds_path, expected_os_path): path = datastore_to_os_path(ds_path) assert_that(path, equal_to(expected_os_path)) @parameterized.expand([ (['[foo] images/a/b/c.vmdk'], True, False, False), (['[foo] vms/a/b/c.vmdk'], False, True, False), (['[foo] images/a/b/c.vmdk', '[foo] vms/a.vmdk'], False, True, False), (['[foo] disks/a/b/c.vmdk'], False, False, True), (['[foo] images/a/c.vmdk', '[foo] disks/a.vmdk'], False, False, True), ([], False, False, False) ]) def test_is_what_disk(self, disk_files, image, ephemeral, persistent): assert_that(is_image(disk_files), equal_to(image)) assert_that(is_ephemeral_disk(disk_files), equal_to(ephemeral)) assert_that(is_persistent_disk(disk_files), equal_to(persistent)) @parameterized.expand([ ("ds1", "image_id", "/vmfs/volumes/ds1/images/im/image_id/image_id.manifest"), ("123 456", "image_id", "/vmfs/volumes/123 456/images/im/image_id/image_id.manifest"), ]) def test_os_image_manifest_path(self, datastore, image_id, expected): assert_that(os_image_manifest_path(datastore, image_id), equal_to(expected)) def test_vmdk_uuid_conversion(self): for id in ['01234567-89ab-cedf-0123-456789abcdef', '01 23456 789ABCEDF0123456789ABCDEF', '01 23 45 67 89 ab ce df-01 23 45 67 89 ab cd ef', '0123456789abcedf0123456789abcdef']: vmdk_uuid = uuid_to_vmdk_uuid(id) assert_that( vmdk_uuid, equal_to('01 23 45 67 89 ab ce df-01 23 45 67 89 ab cd ef')) for id in ['', '01234567-89ab-cedf-0123-456789abcd', '01 23456 789abcedf0123456789abcdefabcd']: self.assertRaises(ValueError, uuid_to_vmdk_uuid, id)
class TestVmManager(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self._logger = logging.getLogger(__name__) self.vim_client = VimClient(self.host, "root", self.pwd) self.vm_manager = EsxVmManager(self.vim_client, []) for vm in self.vim_client.get_vms(): vm.Destroy() def tearDown(self): self.vim_client.disconnect(wait=True) @patch("os.path.exists", return_value=True) def test_vnc_ports(self, _exists): vm_id = self._vm_id() port = self._test_port() flavor = Flavor("vm", [QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB)]) datastore = self.vim_client.get_datastore().name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) self.vm_manager.set_vnc_port(spec, port) try: self.vm_manager.create_vm(vm_id, spec) expected = self.vm_manager.get_vnc_port(vm_id) assert_that(expected, equal_to(port)) ports = self.vm_manager.get_occupied_vnc_ports() assert_that(ports, contains(port)) finally: self.vm_manager.delete_vm(vm_id) @patch("os.path.exists", return_value=True) def test_mks_ticket(self, _exists): vm_id = self._vm_id() flavor = Flavor("vm", [QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB)]) datastore = self.vim_client.get_datastore().name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) try: self.vm_manager.create_vm(vm_id, spec) self.vm_manager.power_on_vm(vm_id) ticket = self.vm_manager.get_mks_ticket(vm_id) assert_that(ticket.cfg_file, not_none()) assert_that(ticket.ticket, not_none()) finally: self.vm_manager.power_off_vm(vm_id) self.vm_manager.delete_vm(vm_id) @patch("os.path.exists", return_value=True) def test_vminfo(self, _exists): self._test_vminfo({}) self._test_vminfo({"project": "p1"}) self._test_vminfo({"tenant": "t1"}) self._test_vminfo({"project": "p1", "tenant": "t1"}) def _test_vminfo(self, vminfo): vm_id = self._vm_id() flavor = Flavor("vm", [QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB)]) datastore = self.vim_client.get_datastore().name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) self.vm_manager.set_vminfo(spec, vminfo) try: self.vm_manager.create_vm(vm_id, spec) got_metadata = self.vm_manager.get_vminfo(vm_id) assert_that(got_metadata, equal_to(vminfo)) finally: self.vm_manager.delete_vm(vm_id) def _vm_id(self): vm_id = strftime("%Y-%m-%d-%H%M%S-", localtime()) vm_id += str(random.randint(1, 10000)) return vm_id def _test_port(self): return 5907
class TestEsxImageManager(unittest.TestCase): """Image Manager tests.""" # We can use even more unit test coverage of the image manager here @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.ds_manager = MagicMock() services.register(ServiceName.AGENT_CONFIG, MagicMock()) self.image_manager = EsxImageManager(self.vim_client, self.ds_manager) def tearDown(self): self.vim_client.disconnect(wait=True) @patch("os.path.isdir", return_value=False) @patch("os.makedirs", side_effect=OSError) def test_make_image_dir(self, _makedirs, _isdir): self.assertRaises( OSError, self.image_manager._make_image_dir, "ds", "fake_iid") _isdir.assert_called_once_with("/vmfs/volumes/ds/images/fa/fake_iid") self.assertEqual( _makedirs.call_count, EsxImageManager.NUM_MAKEDIRS_ATTEMPTS) for i in range(0, EsxImageManager.NUM_MAKEDIRS_ATTEMPTS): self.assertEqual(_makedirs.call_args_list[i][0], ("/vmfs/volumes/ds/images/fa/fake_iid",)) @patch( "host.hypervisor.esx.image_manager.EsxImageManager.reap_tmp_images") def test_periodic_reaper(self, mock_reap): """ Test that the we invoke the image reaper periodically """ image_manager = EsxImageManager(self.vim_client, self.ds_manager) image_manager.monitor_for_cleanup(reap_interval=0.1) self.assertFalse(image_manager._image_reaper is None) retry = 0 while mock_reap.call_count < 2 and retry < 10: time.sleep(0.1) retry += 1 image_manager.cleanup() assert_that(mock_reap.call_count, greater_than(1)) assert_that(retry, is_not(10), "reaper cleanup not called repeatedly") @patch("uuid.uuid4", return_value="fake_id") @patch("host.hypervisor.esx.vm_config.os_datastore_path") def test_reap_tmp_images(self, _os_datastore_path, _uuid): """ Test that stray images are found and deleted by the reaper """ def _fake_ds_folder(datastore, folder): return "%s__%s" % (datastore, folder) ds = MagicMock() ds.id = "dsid" ds.type = DatastoreType.EXT3 # In a random transient directory, set up a directory to act as the # tmp images folder and to contain a stray image folder with a file. tmpdir = file_util.mkdtemp(delete=True) tmp_images_folder = _fake_ds_folder(ds.id, TMP_IMAGE_FOLDER_NAME) tmp_images_dir = os.path.join(tmpdir, tmp_images_folder) tmp_image_dir = os.path.join(tmp_images_dir, "stray_image") os.mkdir(tmp_images_dir) os.mkdir(tmp_image_dir) (fd, path) = tempfile.mkstemp(prefix='strayimage_', dir=tmp_image_dir) self.assertTrue(os.path.exists(path)) def _fake_os_datastore_path(datastore, folder): return os.path.join(tmpdir, _fake_ds_folder(datastore, folder)) _os_datastore_path.side_effect = _fake_os_datastore_path ds_manager = MagicMock() ds_manager.get_datastores.return_value = [ds] image_manager = EsxImageManager(self.vim_client, ds_manager) image_manager.reap_tmp_images() # verify stray image is deleted self.assertFalse(os.path.exists(path)) @patch("os.path.isdir") @patch("os.makedirs") def test_vmdk_mkdir_eexist(self, _makedirs, _isdir): eexist = OSError() eexist.errno = errno.EEXIST _makedirs.side_effect = eexist _isdir.side_effect = (False, # dest image dir missing True) # dest image dir is created self.image_manager._make_image_dir("ds", "fake_iid") _isdir.assert_called("/vmfs/volumes/ds/images/fa/fake_iid") @patch("pysdk.task.WaitForTask") @patch("uuid.uuid4", return_value="fake_id") @patch("os.path.exists") @patch("os.makedirs") @patch("shutil.copy") @patch("shutil.rmtree") @patch("shutil.move") @patch.object(EsxImageManager, "_manage_disk") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_check_image_repair", return_value=False) @patch.object(EsxImageManager, "check_and_validate_image", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_copy_image(self, _flock, _create_image_timestamp, check_image, _check_image_repair, _get_ds_type, _manage_disk, _mv_dir, _rmtree, _copy, _makedirs, _exists, _uuid, _wait_for_task): _exists.side_effect = (True, # dest image vmdk missing True) # source meta file present self.image_manager.copy_image("ds1", "foo", "ds2", "bar") os_path_prefix1 = '/vmfs/volumes/ds1/images' os_path_prefix2 = '/vmfs/volumes/ds2/images' os_tmp_path_prefix = '/vmfs/volumes/ds2/tmp_images' _copy.assert_called_once_with( '%s/fo/foo/foo.%s' % (os_path_prefix1, METADATA_FILE_EXT), '/vmfs/volumes/ds2/tmp_images/fake_id') ds_path_prefix1 = '[] ' + os_path_prefix1 ds_tmp_path_prefix = '[] ' + os_tmp_path_prefix expected_tmp_disk_ds_path = '%s/fake_id/%s.vmdk' % (ds_tmp_path_prefix, 'bar') _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='%s/fo/foo/foo.vmdk' % ds_path_prefix1, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) _mv_dir.assert_called_once_with('/vmfs/volumes/ds2/tmp_images/fake_id', '%s/ba/bar' % os_path_prefix2) _create_image_timestamp.assert_called_once_with( "/vmfs/volumes/ds2/tmp_images/fake_id") @patch("pysdk.task.WaitForTask") @patch("uuid.uuid4", return_value="fake_id") @patch("os.path.exists") @patch("os.makedirs") @patch("shutil.copy") @patch.object(EsxImageManager, "_manage_disk") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "check_image", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_create_tmp_image(self, _flock, _create_image_timestamp, check_image, _get_ds_type, _manage_disk, _copy, _makedirs, _exists, _uuid, _wait_for_task): # Common case is the same as the one covered by test_copy_image. # Check that things work when the src metadata file doesn't exist. _exists.side_effect = (False, True) ds_path_prefix1 = '[] /vmfs/volumes/ds1/images' expected_tmp_disk_ds_path = \ "[] /vmfs/volumes/ds2/tmp_images/fake_id/bar.vmdk" self.image_manager._create_tmp_image("ds1", "foo", "ds2", "bar") _flock.assert_called_once_with("/vmfs/volumes/ds2/tmp_images/fake_id", DatastoreType.EXT3) # Verify that we don't copy the metadata file. self.assertFalse(_copy.called) # Verify that we copy the disk correctly _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='%s/fo/foo/foo.vmdk' % ds_path_prefix1, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) # check that we return an IO error if the copy of metadata fails. _copy.side_effect = IOError _exists.side_effect = (True, True) _manage_disk.reset_mock() _flock.reset_mock() self.assertRaises(IOError, self.image_manager._create_tmp_image, "ds1", "foo", "ds2", "bar") self.assertFalse(_manage_disk.called) _flock.assert_called_once_with("/vmfs/volumes/ds2/tmp_images/fake_id", DatastoreType.EXT3) _create_image_timestamp.assert_called_once_with( "/vmfs/volumes/ds2/tmp_images/fake_id") @patch("os.makedirs") @patch("shutil.rmtree") @patch("shutil.move") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_check_image_repair", return_value=True) @patch("host.hypervisor.esx.image_manager.FileBackedLock") @raises(DiskAlreadyExistException) def test_move_image(self, _flock, check_image, _get_ds_type, _mv_dir, _rmtree, _makedirs): # Common case is covered in test_copy_image. # check that if destination image directory exists we don't call move # and just bail after removing the tmp dir _rmtree.reset_mock() _mv_dir.reset_mock() expected_tmp_disk_folder = '/vmfs/volumes/ds2/tmp_images/bar' expected_rm_calls = [call(expected_tmp_disk_folder)] self.image_manager._move_image("foo", "ds1", expected_tmp_disk_folder) self.assertEqual(expected_rm_calls, _rmtree.call_args_list) _makedirs.assert_called_once_with('/vmfs/volumes/ds1/images/fo') _flock.assert_called_once_with('/vmfs/volumes/ds1/images/fo/foo', DatastoreType.EXT3, 3) @parameterized.expand([ (True, ), (False, ) ]) @patch("os.path.exists") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch.object(EsxImageManager, "_delete_renamed_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_validate_existing_image(self, create, _flock, _delete_renamed_timestamp_file, _create_timestamp_file, _get_ds_type, _path_exists): self._create_image_timestamp_file = create _path_exists.side_effect = self._local_os_path_exists _disk_folder = '/vmfs/volumes/ds1/images/fo/foo' self.image_manager._check_image_repair("foo", "ds1") if create: _create_timestamp_file.assert_called_once_with(_disk_folder) _delete_renamed_timestamp_file.assert_called_once() else: assert not _create_timestamp_file.called assert not _delete_renamed_timestamp_file.called def _local_os_path_exists(self, pathname): if not self._create_image_timestamp_file: return True if pathname.endswith(EsxImageManager.IMAGE_TIMESTAMP_FILE_NAME): return False else: return True @patch.object(EsxImageManager, "_clean_gc_dir") @patch.object(EsxImageManager, "_gc_image_dir") @patch.object(EsxImageManager, "_lock_data_disk") @patch.object(EsxImageManager, "create_image_tombstone") @patch.object(EsxImageManager, "check_image_dir") def test_delete(self, check_image_dir, create_image_tombstone, lock_data_disk, gc_image_dir, clean_gc_dir): # Test successful delete check_image_dir.return_value = True self.image_manager.delete_image("ds1", "foo", 0, False) check_image_dir.assert_called_with("foo", "ds1") create_image_tombstone.assert_called_with("ds1", "foo") # Test successful delete with force option self.image_manager.delete_image("ds1", "foo", 0, True) check_image_dir.assert_called_with("foo", "ds1") create_image_tombstone.assert_called_with("ds1", "foo") lock_data_disk.assert_called_with("ds1", "foo") gc_image_dir.assert_called_with("ds1", "foo") clean_gc_dir.assert_called() # Test image not found check_image_dir.return_value = False self.assertRaises(ImageNotFoundException, self.image_manager.delete_image, "ds1", "foo", 0, False) @patch("host.hypervisor.esx.image_manager.os_vmdk_path") @patch("host.hypervisor.esx.image_manager.os_datastore_path") def test_gc_image_dir(self, dst_path, src_path): """ Test that we move the directory correctly to the GC location """ src_dir = file_util.mkdtemp(delete=True) dst_dir = file_util.mkdtemp(delete=True) src_path.return_value = os.path.join(src_dir, "test.vmdk") dst_path.return_value = dst_dir self.image_manager._gc_image_dir("ds1", "foo") uuid_dir = os.path.join(dst_dir, os.listdir(dst_dir)[0]) # Verify the src directory has been moved into the garbage dir. self.assertEqual(os.listdir(uuid_dir), [os.path.basename(src_dir)]) src_path.assert_called_once_with("ds1", "foo", IMAGE_FOLDER_NAME) dst_path.assert_called_once_with("ds1", GC_IMAGE_FOLDER) def test_image_path(self): image_path = "/vmfs/volumes/ds/images/tt/ttylinux/ttylinux.vmdk" ds = self.image_manager.get_datastore_id_from_path(image_path) image = self.image_manager.get_image_id_from_path(image_path) self.assertEqual(ds, "ds") self.assertEqual(image, "ttylinux") @patch("host.hypervisor.esx.image_manager.os_vmdk_flat_path") @patch("host.hypervisor.esx.image_manager.os.remove") def test_lock_data_disk(self, mock_rm, vmdk_flat_path): """ Test acquisition of the lock on the flat file. """ vmdk_flat_path.return_value = "fake_f_name" self.assertTrue(self.image_manager._lock_data_disk("ds1", "foo")) vmdk_flat_path.assert_called_once_with("ds1", "foo") mock_rm.side_effect = OSError self.assertFalse(self.image_manager._lock_data_disk("ds1", "foo")) @parameterized.expand([ ("CLOUD", "EAGER", ImageType.CLOUD, ImageReplication.EAGER), ("MANAGEMENT", "EAGER", ImageType.MANAGEMENT, ImageReplication.EAGER), ("CLOUD", "ON_DEMAND", ImageType.CLOUD, ImageReplication.ON_DEMAND), ("MANAGEMENT", "ON_DEMAND", ImageType.MANAGEMENT, ImageReplication.ON_DEMAND), ]) def test_image_type(self, type, replication, expected_type, expected_replication): self.ds_manager.image_datastores.return_value = "ds1" with patch("host.hypervisor.esx.image_manager.os_image_manifest_path" "") as manifest_path: tmpdir = file_util.mkdtemp(delete=True) tmpfile = os.path.join(tmpdir, "ds1.manifest") manifest_path.return_value = tmpfile with open(tmpfile, 'w+') as f: f.write('{"imageType":"%s","imageReplication":"%s"}' % ( type, replication)) type, replication = self.image_manager.get_image_manifest( "image_id") self.assertEqual(type, expected_type) self.assertEqual(replication, expected_replication) @patch.object(EsxImageManager, "_move_image") @patch.object(EsxImageManager, "check_image_dir", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file_from_ids") @patch("os.path.exists") def test_create_image(self, _exists, _create_timestamp, check_image_dir, move_image): # Happy path verify move is called with the right args. _exists.side_effect = ([True]) self.image_manager.create_image("ds1", "foo", "img_1") check_image_dir.assert_called_once_with("img_1", "ds1") move_image.assert_called_once_with('img_1', 'ds1', '/vmfs/volumes/ds1/foo') _create_timestamp.assert_called_once_with("ds1", "img_1") # Verify error if tmp image doesn't exist _exists.side_effect = ([False]) move_image.reset_mock() self.assertRaises(ImageNotFoundException, self.image_manager.create_image, "ds1", "foo", "img_1") self.assertFalse(move_image.called) # Verify error if destination image already exists. _exists.side_effect = ([True]) move_image.reset_mock() check_image_dir.return_value = True self.assertRaises(DiskAlreadyExistException, self.image_manager.create_image, "ds1", "foo", "img_1") self.assertFalse(move_image.called) @patch.object(EsxImageManager, "create_image") @patch.object(EsxImageManager, "_manage_disk") @patch("os.path.exists", return_value=True) def test_create_image_with_vm_disk(self, _exists, _manage_disk, _create_image): vm_disk_path = "/vmfs/volumes/dsname/vms/ab/cd.vmdk" self.image_manager.create_image_with_vm_disk( "ds1", "foo", "img_1", vm_disk_path) # Verify that we copy the disk correctly expected_tmp_disk_ds_path = \ "[] /vmfs/volumes/ds1/foo/img_1.vmdk" _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='[] %s' % vm_disk_path, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) _create_image.assert_called_once_with("ds1", "foo", "img_1") @patch("shutil.rmtree") @patch("os.path.exists") def test_delete_tmp_dir(self, _exists, _rmtree): self.image_manager.delete_tmp_dir("ds1", "foo") _exists.assert_called_once("/vmfs/volumes/ds1/foo") _rmtree.assert_called_once("/vmfs/volumes/ds1/foo") _exists.reset_mock() _exists.return_value = False _rmtree.reset_mock() self.assertRaises(DirectoryNotFound, self.image_manager.delete_tmp_dir, "ds1", "foo") _exists.assert_called_once("/vmfs/volumes/ds1/foo") self.assertFalse(_rmtree.called)
class TestEsxVmManager(unittest.TestCase): @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.vim_client.wait_for_task = MagicMock() self.patcher = patch("host.hypervisor.esx.vm_config.GetEnv") self.patcher.start() self.vm_manager = EsxVmManager(self.vim_client, MagicMock()) def tearDown(self): self.patcher.stop() self.vim_client.disconnect(wait=True) def test_power_vm_not_found(self): """Test that we propagate VmNotFound.""" self.vim_client.find_by_inventory_path = MagicMock(return_value=None) self.assertRaises(VmNotFoundException, self.vm_manager.power_on_vm, "ENOENT") def test_power_vm_illegal_state(self): """Test InvalidPowerState propagation.""" vm_mock = MagicMock(name="vm_mock") self.vm_manager.vim_client.get_vm = vm_mock self.vim_client.wait_for_task.side_effect = \ vim.fault.InvalidPowerState() self.assertRaises(VmPowerStateException, self.vm_manager.power_on_vm, "foo") def test_power_vm_error(self): """Test general Exception propagation.""" vm_mock = MagicMock(name="vm_mock") self.vm_manager.vim_client.get_vm = vm_mock self.vim_client.wait_for_task.side_effect = vim.fault.TaskInProgress self.assertRaises(vim.fault.TaskInProgress, self.vm_manager.power_on_vm, "foo") def test_add_nic(self): """Test add nic""" # 3 cases for add_nic: # * caller passes in network_id = None # * caller passes in the correct network_id and hostd # returns the right thing from get_network. def _get_device(devices, controller_type): f = MagicMock("get_device_foo") f.key = 1 return f self.vm_manager.vm_config.get_device = _get_device spec = self.vm_manager.vm_config.update_spec() # Caller passes none self.vm_manager.add_nic(spec, None) # Caller passes some network_id self.vm_manager.add_nic(spec, "Private Vlan") def test_create_vm_already_exist(self): """Test VM creation fails if VM is found""" vim_mock = MagicMock() self.vm_manager.vim_client = vim_mock vim_mock.find_vm = MagicMock(return_value="existing_vm") mock_spec = MagicMock() self.assertRaises(VmAlreadyExistException, self.vm_manager.create_vm, "existing_vm_name", mock_spec) def test_create_vm(self): """Test VM creation""" vim_mock = MagicMock() self.vm_manager.vim_client = vim_mock vm_folder_mock = MagicMock() vim_mock.vm_folder = vm_folder_mock root_res_pool_mock = PropertyMock(return_value="fake_rp") type(vim_mock).root_resource_pool = root_res_pool_mock vim_mock.get_vm_in_cache = MagicMock(return_value=None) vm_folder_mock.CreateVm.return_value = "fake-task" mock_spec = MagicMock() mock_spec.files.vmPathName = "[] /vmfs/volumes/ds/vms" self.vm_manager.create_vm("fake_vm_id", mock_spec) vim_mock.get_vm_in_cache.assert_called_once_with("fake_vm_id") vm_folder_mock.CreateVm.assert_called_once_with( mock_spec, 'fake_rp', None) vim_mock.wait_for_task.assert_called_once_with("fake-task") @staticmethod def _validate_spec_extra_config(spec, config, expected): """Validates the config entries against the created config spec when expected=True, returns True iff all the entries in config are found in the config spec's extraConfig when expected=False, returns True iff all the entries in config are not found in the config spec's extraConfig """ for k, v in config.items(): in_spec = any((x.key == k and x.value == v) for x in spec.extraConfig) if in_spec is not expected: return False return True def _create_vm_spec(self, metadata, env): """Test VM spec creation""" flavor = Flavor("default", [ QuotaLineItem("vm.memory", "256", Unit.MB), QuotaLineItem("vm.cpu", "1", Unit.COUNT), ]) create_spec_mock = MagicMock( wraps=self.vm_manager.vm_config.create_spec) self.vm_manager.vm_config.create_spec = create_spec_mock spec = self.vm_manager.create_vm_spec( "vm_id", "ds1", flavor, metadata, env) create_spec_mock.assert_called_once_with( "vm_id", "ds1", 256, 1, metadata, env) return spec def test_create_vm_spec(self): metadata = { "configuration": {}, "parameters": [ {"name": "bios.bootOrder"} ] } extra_config_metadata = {} non_extra_config_metadata = {"scsi0.virtualDev": "lsisas1068", "bogus": "1"} metadata["configuration"].update(extra_config_metadata) metadata["configuration"].update(non_extra_config_metadata) env = {"disallowed_key": "x", "bios.bootOrder": "x"} spec = self._create_vm_spec(metadata, env) expected_extra_config = extra_config_metadata.copy() expected_extra_config["bios.bootOrder"] = "x" self.assertTrue(TestEsxVmManager._validate_spec_extra_config( spec, config=expected_extra_config, expected=True)) self.assertTrue(TestEsxVmManager._validate_spec_extra_config( spec, config=non_extra_config_metadata, expected=False)) assert_that(spec.flags.diskUuidEnabled, equal_to(True)) def test_customize_vm_with_metadata(self): metadata = { "configuration": { "annotation": "fake_annotation", "serial0.fileType": "network", "serial0.yieldOnMsrRead": "TRUE", "serial0.network.endPoint": "server" }, "parameters": [ {"name": "serial0.fileName"}, {"name": "serial0.vspc"} ] } env = { "serial0.fileName": "vSPC.py", "serial0.vspc": "telnet://1.2.3.4:17000", } spec = self._create_vm_spec(metadata, env) self.vm_manager.customize_vm(spec) assert_that(spec.annotation, equal_to("fake_annotation")) backing = spec.deviceChange[0].device.backing assert_that( backing, instance_of(vim.vm.device.VirtualSerialPort.URIBackingInfo)) assert_that(backing.serviceURI, equal_to('vSPC.py')) assert_that(backing.proxyURI, equal_to('telnet://1.2.3.4:17000')) assert_that(backing.direction, equal_to('server')) @staticmethod def _summarize_controllers_in_spec(cfg_spec, base_type, expected_type): num_scsi_adapters_matching_expected_type = 0 num_scsi_adapters_not_matching_expected_type = 0 for dev_change in cfg_spec.deviceChange: dev = dev_change.device if isinstance(dev, expected_type): num_scsi_adapters_matching_expected_type += 1 elif (isinstance(dev, base_type) and not isinstance(dev, expected_type)): num_scsi_adapters_not_matching_expected_type += 1 return (num_scsi_adapters_matching_expected_type, num_scsi_adapters_not_matching_expected_type) @parameterized.expand([ ("lsilogic", vim.vm.device.VirtualLsiLogicController), ("lsisas1068", vim.vm.device.VirtualLsiLogicSASController), ("pvscsi", vim.vm.device.ParaVirtualSCSIController), ("buslogic", vim.vm.device.VirtualBusLogicController) ]) def test_customize_disk_adapter_type(self, ctlr_type_value, expected_ctlr_type): metadata = { "configuration": {"scsi0.virtualDev": ctlr_type_value} } spec = self._create_vm_spec(metadata, {}) ds = "fake_ds" disk_id = str(uuid.uuid4()) parent_disk_id = str(uuid.uuid4()) capacity_mb = 1024 self.vm_manager.create_child_disk(spec, ds, disk_id, parent_disk_id) self.vm_manager.create_empty_disk(spec, ds, disk_id, capacity_mb) # check that we only create one controller of desired type to attach # to both disks summary = TestEsxVmManager._summarize_controllers_in_spec( spec, vim.vm.device.VirtualSCSIController, expected_ctlr_type) assert_that(summary, equal_to((1, 0))) @parameterized.expand([ ("vmxnet", vim.vm.device.VirtualVmxnet), ("vmxnet2", vim.vm.device.VirtualVmxnet2), ("vmxnet3", vim.vm.device.VirtualVmxnet3), ("vlance", vim.vm.device.VirtualPCNet32), ("e1000", vim.vm.device.VirtualE1000), ("e1000e", vim.vm.device.VirtualE1000e), ]) @patch.object(VimClient, "get_network") def test_customize_nic_adapter_type(self, ctlr_type_value, expected_ctlr_type, mock_get_network): metadata = { "configuration": {"ethernet0.virtualDev": ctlr_type_value} } spec = self._create_vm_spec(metadata, {}) fake_network = MagicMock() fake_network.name = "fake_network_name" mock_get_network.return_value = fake_network self.vm_manager.add_nic(spec, "fake_network_id") summary = TestEsxVmManager._summarize_controllers_in_spec( spec, vim.vm.device.VirtualEthernetCard, expected_ctlr_type) assert_that(summary, equal_to((1, 0))) @parameterized.expand([ ('a.txt', 'Stray file: a.txt'), ('b.vmdk', 'Stray disk (possible data leak): b.vmdk') ]) @patch.object(os.path, "isdir", return_value=True) @patch.object(os.path, "islink", return_value=False) @patch.object(shutil, "rmtree") def test_ensure_directory_cleanup( self, stray_file, expected, rmtree, islink, isdir): """Test cleanup of stray vm directory""" self.vm_manager._logger = MagicMock() with patch.object(os, "listdir", return_value=[stray_file]): self.vm_manager._ensure_directory_cleanup("/vmfs/volumes/fake/vm_vm_foo") rmtree.assert_called_once_with("/vmfs/volumes/fake/vm_vm_foo") self.vm_manager._logger.info.assert_called_once_with(expected) self.vm_manager._logger.warning.assert_called_once_with( "Force delete vm directory /vmfs/volumes/fake/vm_vm_foo") def test_delete_vm(self): """Test deleting a VM""" runtime = MagicMock() runtime.powerState = "poweredOff" vm = MagicMock() vm.runtime = runtime self.vm_manager.vim_client.get_vm = MagicMock(return_value=vm) self.vm_manager.vm_config.get_devices = MagicMock(return_value=[]) self.vm_manager.get_vm_path = MagicMock() self.vm_manager.get_vm_path.return_value = "[fake] vm_foo/xxyy.vmx" self.vm_manager.get_vm_datastore = MagicMock() self.vm_manager.get_vm_datastore.return_value = "fake" self.vm_manager._ensure_directory_cleanup = MagicMock() self.vm_manager.delete_vm("vm_foo") self.vm_manager._ensure_directory_cleanup.assert_called_once_with( "/vmfs/volumes/fake/vm_vm_foo") @parameterized.expand([ ("poweredOn"), ("suspended") ]) def test_delete_vm_wrong_state(self, state): runtime = MagicMock() runtime.powerState = state vm = MagicMock() vm.runtime = runtime self.vm_manager.vim_client.get_vm = MagicMock(return_value=vm) self.assertRaises(VmPowerStateException, self.vm_manager.delete_vm, "vm_foo") def test_add_vm_disk(self): """Test adding VM disk""" self.vm_manager.vim_client.get_vm = MagicMock() self.vm_manager.vm_config.get_devices = MagicMock(return_value=[ DEFAULT_DISK_CONTROLLER_CLASS(key=1000) ]) info = FakeConfigInfo() spec = self.vm_manager.vm_config.update_spec() self.vm_manager.add_disk(spec, "ds1", "vm_foo", info) def test_used_memory(self): self.vm_manager.vim_client.get_vms_in_cache = MagicMock(return_value=[ VmCache(memory_mb=1024), VmCache(), VmCache(memory_mb=2048) ]) memory = self.vm_manager.get_used_memory_mb() self.assertEqual(memory, 2048 + 1024) def atest_remove_vm_disk(self): """Test removing VM disk""" datastore = "ds1" disk_id = "foo" self.vm_manager.vim_client.get_vm = MagicMock() self.vm_manager.vm_config.get_devices = MagicMock(return_value=[ vim.vm.device.VirtualLsiLogicController(key=1000), self.vm_manager.vm_config.create_disk_spec(datastore, disk_id) ]) info = FakeConfigInfo() self.vm_manager.remove_disk("vm_foo", datastore, disk_id, info) def btest_remove_vm_disk_enoent(self): """Test removing VM disk that isn't attached""" self.vm_manager.vim_client.get_vm = MagicMock() self.vm_manager.vm_config.get_devices = MagicMock(return_value=[ self.vm_manager.vm_config.create_disk_spec("ds1", "foo") ]) self.assertRaises(vim.fault.DeviceNotFound, self.vm_manager.remove_disk, "vm_foo", "ds1", "bar") def test_check_ip_v4(self): """Test to check ipv4 validation""" self.assertTrue(NetUtil.is_ipv4_address("1.2.3.4")) self.assertFalse(NetUtil.is_ipv4_address( "FE80:0000:0000:0000:0202:B3FF:FE1E:8329")) self.assertFalse(NetUtil.is_ipv4_address("InvalidAddress")) def test_check_prefix_len_to_netmask_conversion(self): """Check the conversion from prefix length to netmask""" self.assertEqual(NetUtil.prefix_len_to_mask(32), "255.255.255.255") self.assertEqual(NetUtil.prefix_len_to_mask(0), "0.0.0.0") self.assertRaises(ValueError, NetUtil.prefix_len_to_mask, 33) self.assertEqual(NetUtil.prefix_len_to_mask(23), "255.255.254.0") self.assertEqual(NetUtil.prefix_len_to_mask(6), "252.0.0.0") self.assertEqual(NetUtil.prefix_len_to_mask(32), "255.255.255.255") def test_get_vm_network_guest_info(self): """ Tests the guest vm network info, without the vmx returned info. Test 1: Only mac address info available. Test 2: Only mac + ipv4 address available. Test 3: Only mac + ipv6 address available. Test 4: Only mac + ipv6, ipv4 address available. Test 5: No mac or ipv4 address available """ sample_mac_address = "00:0c:29:00:00:01" sample_ip_address = "127.0.0.2" sample_prefix_length = 24 sample_netmask = "255.255.255.0" sample_ipv6_address = "FE80:0000:0000:0000:0202:B3FF:FE1E:8329" sample_network = "VM Network" def _get_v4_address(): ip_address = MagicMock(name="ipv4address") ip_address.ipAddress = sample_ip_address ip_address.prefixLength = sample_prefix_length return ip_address def _get_v6_address(): ip_address = MagicMock(name="ipv6address") ip_address.ipAddress = sample_ipv6_address ip_address.prefixLength = sample_prefix_length return ip_address def _guest_info_1(): """ Only have the mac address. """ net = MagicMock(name="guest_info_1") net.macAddress = sample_mac_address net.connected = True net.network = None return net def _guest_info_2(): """ Have mac and ipv4 address """ net = MagicMock(name="guest_info_2") net.macAddress = sample_mac_address net.ipConfig.ipAddress = [_get_v4_address()] net.network = sample_network net.connected = False return net def _guest_info_3(): """ Have mac and ipv6 address """ net = MagicMock(name="guest_info_3") net.macAddress = sample_mac_address net.ipConfig.ipAddress = [_get_v6_address()] net.connected = False net.network = sample_network return net def _guest_info_4(): """ Have a mac and an ipv4 and an ipv6 address """ net = MagicMock(name="guest_info_4") net.macAddress = sample_mac_address net.network = None net.ipConfig.ipAddress = [_get_v6_address(), _get_v4_address()] net.connected = True return net def _get_vm_no_net_info(vm_id): """ Return empty guest_info """ f = MagicMock(name="get_vm") f.config.uuid = str(uuid.uuid4()) g = MagicMock(name="guest_info") f.guest = g g.net = [] return f def _get_vm(vm_id): """ Return a mocked up guest info object """ f = MagicMock(name="get_vm") g = MagicMock(name="guest_info") f.guest = g net = _guest_info() g.net = [net] return f def _get_vm_vim_guest_info(vm_id): """ Return a real Vim object with reasonable values to validate python typing """ f = MagicMock(name="get_vm") f.config.uuid = str(uuid.uuid4()) g = MagicMock(name="guest_info") f.guest = g net = vim.vm.GuestInfo.NicInfo() ip_config_info = vim.net.IpConfigInfo() net.ipConfig = ip_config_info net.macAddress = sample_mac_address net.network = sample_network net.connected = True ipAddress = vim.net.IpConfigInfo.IpAddress() ipAddress.ipAddress = sample_ip_address ipAddress.prefixLength = sample_prefix_length ip_config_info.ipAddress.append(ipAddress) g.net = [net] return f # Test 1 _guest_info = _guest_info_1 self.vm_manager.vim_client.get_vm = _get_vm self.vm_manager._get_mac_network_mapping = MagicMock(return_value={}) network_info = self.vm_manager.get_vm_network("vm_foo1") expected_1 = VmNetworkInfo(mac_address=sample_mac_address, is_connected=ConnectedStatus.CONNECTED) self.assertEqual(network_info, [expected_1]) # Test 2 _guest_info = _guest_info_2 network_info = self.vm_manager.get_vm_network("vm_foo2") ip_address = Ipv4Address(ip_address=sample_ip_address, netmask=sample_netmask) expected_2 = VmNetworkInfo(mac_address=sample_mac_address, ip_address=ip_address, network=sample_network, is_connected=ConnectedStatus.DISCONNECTED) self.assertEqual(network_info, [expected_2]) # Test 3 _guest_info = _guest_info_3 network_info = self.vm_manager.get_vm_network("vm_foo3") expected_3 = VmNetworkInfo(mac_address=sample_mac_address, network=sample_network, is_connected=ConnectedStatus.DISCONNECTED) self.assertEqual(network_info, [expected_3]) # Test 4 _guest_info = _guest_info_4 network_info = self.vm_manager.get_vm_network("vm_foo4") expected_4 = VmNetworkInfo(mac_address=sample_mac_address, ip_address=ip_address, is_connected=ConnectedStatus.CONNECTED) self.assertEqual(network_info, [expected_4]) # Test 5 self.vm_manager.vim_client.get_vm = _get_vm_no_net_info network_info = self.vm_manager.get_vm_network("vm_foo5") self.assertEqual(network_info, []) # Test 6 self.vm_manager.vim_client.get_vm = _get_vm_vim_guest_info network_info = self.vm_manager.get_vm_network("vm_foo5") expected_6 = VmNetworkInfo(mac_address=sample_mac_address, ip_address=ip_address, network=sample_network, is_connected=ConnectedStatus.CONNECTED) self.assertEqual(network_info, [expected_6]) def test_get_linked_clone_image_path(self): image_path = self.vm_manager.get_linked_clone_image_path # VM not found vm = MagicMock(return_value=None) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # disks is None vm = MagicMock(return_value=VmCache(disks=None)) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # disks is an empty list vm = MagicMock(return_value=VmCache(disks=[])) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # no image disk vm = MagicMock(return_value=VmCache(disks=["a", "b", "c"])) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # image found image = "[ds1] image_ttylinux/ttylinux.vmdk" vm = MagicMock(return_value=VmCache(disks=["a", "b", image])) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(datastore_to_os_path(image))) def test_set_vnc_port(self): flavor = Flavor("default", [ QuotaLineItem("vm.memory", "256", Unit.MB), QuotaLineItem("vm.cpu", "1", Unit.COUNT), ]) spec = self.vm_manager.create_vm_spec( "vm_id", "ds1", flavor) self.vm_manager.set_vnc_port(spec, 5901) options = [o for o in spec.extraConfig if o.key == 'RemoteDisplay.vnc.enabled'] assert_that(options[0].value, equal_to('True')) options = [o for o in spec.extraConfig if o.key == 'RemoteDisplay.vnc.port'] assert_that(options[0].value, equal_to(5901)) @patch.object(VimClient, "get_vm") def test_get_vnc_port(self, get_vm): vm_mock = MagicMock() vm_mock.config.extraConfig = [ vim.OptionValue(key="RemoteDisplay.vnc.port", value="5901") ] get_vm.return_value = vm_mock port = self.vm_manager.get_vnc_port("id") assert_that(port, equal_to(5901)) def test_get_resources(self): """ Test that get_resources excludes VMs/disks if it can't find their corresponding datastore UUIDs. """ self.vm_manager.vim_client.get_vms_in_cache = MagicMock(return_value=[ VmCache(path="vm_path_a", disks=["disk_a", "disk_b", "disk_c"]), VmCache(path="vm_path_b", disks=["disk_b", "disk_c", "disk_d"]), VmCache(path="vm_path_c", disks=["disk_c", "disk_d", "disk_e"]), ]) def normalize(name): if name == "vm_path_b" or name == "disk_b": raise DatastoreNotFoundException() return name def mock_get_name(path): return path def mock_get_state(power_state): return State.STOPPED self.vm_manager._ds_manager.normalize.side_effect = normalize self.vm_manager._get_datastore_name_from_ds_path = mock_get_name self.vm_manager._power_state_to_resource_state = mock_get_state # vm_path_b and disk_b are not included in the get_resources response. resources = self.vm_manager.get_resources() assert_that(len(resources), equal_to(2)) assert_that(len(resources[0].disks), equal_to(2)) assert_that(len(resources[1].disks), equal_to(3)) @patch.object(VimClient, "get_vms") def test_get_occupied_vnc_ports(self, get_vms): get_vms.return_value = [self._create_vm_mock(5900), self._create_vm_mock(5901)] ports = self.vm_manager.get_occupied_vnc_ports() assert_that(ports, contains_inanyorder(5900, 5901)) def _create_vm_mock(self, vnc_port): vm = MagicMock() vm.config.extraConfig = [] vm.config.extraConfig.append( vim.OptionValue(key="RemoteDisplay.vnc.port", value=str(vnc_port))) vm.config.extraConfig.append( vim.OptionValue(key="RemoteDisplay.vnc.enabled", value="True")) return vm
class TestVmManager(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self._logger = logging.getLogger(__name__) self.vim_client = VimClient(self.host, "root", self.pwd) self.vm_manager = EsxVmManager(self.vim_client, []) for vm in self.vim_client.get_vms(): vm.Destroy() def tearDown(self): self.vim_client.disconnect(wait=True) @patch('os.path.exists', return_value=True) def test_vnc_ports(self, _exists): vm_id = self._vm_id() port = self._test_port() flavor = Flavor("vm", [QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB)]) datastore = self.vim_client.get_datastore().name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) self.vm_manager.set_vnc_port(spec, port) try: self.vm_manager.create_vm(vm_id, spec) expected = self.vm_manager.get_vnc_port(vm_id) assert_that(expected, equal_to(port)) ports = self.vm_manager.get_occupied_vnc_ports() assert_that(ports, contains(port)) finally: self.vm_manager.delete_vm(vm_id) @patch('os.path.exists', return_value=True) def test_mks_ticket(self, _exists): vm_id = self._vm_id() flavor = Flavor("vm", [QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB)]) datastore = self.vim_client.get_datastore().name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) try: self.vm_manager.create_vm(vm_id, spec) self.vm_manager.power_on_vm(vm_id) ticket = self.vm_manager.get_mks_ticket(vm_id) assert_that(ticket.cfg_file, not_none()) assert_that(ticket.ticket, not_none()) finally: self.vm_manager.power_off_vm(vm_id) self.vm_manager.delete_vm(vm_id) @patch('os.path.exists', return_value=True) def test_vminfo(self, _exists): self._test_vminfo({}) self._test_vminfo({"project": "p1"}) self._test_vminfo({"tenant": "t1"}) self._test_vminfo({"project": "p1", "tenant": "t1"}) def _test_vminfo(self, vminfo): vm_id = self._vm_id() flavor = Flavor("vm", [QuotaLineItem("vm.cpu", 1, Unit.COUNT), QuotaLineItem("vm.memory", 8, Unit.MB)]) datastore = self.vim_client.get_datastore().name spec = self.vm_manager.create_vm_spec(vm_id, datastore, flavor) self.vm_manager.set_vminfo(spec, vminfo) try: self.vm_manager.create_vm(vm_id, spec) got_metadata = self.vm_manager.get_vminfo(vm_id) assert_that(got_metadata, equal_to(vminfo)) finally: self.vm_manager.delete_vm(vm_id) def _vm_id(self): vm_id = strftime("%Y-%m-%d-%H%M%S-", localtime()) vm_id += str(random.randint(1, 10000)) return vm_id def _test_port(self): return 5907
class TestHttpTransfer(unittest.TestCase): """Http Transferer tests.""" @patch.object(VimClient, "acquire_credentials") @patch("pysdk.connect.Connect") def setUp(self, connect, creds): self.host_uuid = str(uuid.uuid4()) VimClient.host_uuid = self.host_uuid self.image_datastores = ["image_ds", "alt_image_ds"] creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.patcher = patch("host.hypervisor.esx.vm_config.GetEnv") self.patcher.start() services.register(ServiceName.AGENT_CONFIG, MagicMock()) self.http_transferer = HttpNfcTransferer(self.vim_client, self.image_datastores) def tearDown(self): self.vim_client.disconnect(wait=True) self.patcher.stop() @parameterized.expand([ (True,), (False,) ]) @patch("host.hypervisor.esx.http_disk_transfer.VimClient") @patch("host.hypervisor.esx.http_disk_transfer.DirectClient") def test_get_remote_connections(self, get_svc_ticket_success, _client_cls, _vim_client_cls): host = "mock_host" port = 8835 get_service_ticket_mock = MagicMock() if get_svc_ticket_success: get_service_ticket_mock.result = ServiceTicketResultCode.OK else: get_service_ticket_mock.result = ServiceTicketResultCode.NOT_FOUND instance = _client_cls.return_value instance.get_service_ticket.return_value = get_service_ticket_mock if get_svc_ticket_success: (agent_conn, vim_conn) = self.http_transferer._get_remote_connections( host, port) _client_cls.assert_called_once_with( "Host", Host.Client, host, port) instance.connect.assert_called_once_with() request = ServiceTicketRequest(service_type=ServiceType.VIM) instance.get_service_ticket.assert_called_once_with(request) _vim_client_cls.assert_called_once_with( host=host, ticket=get_service_ticket_mock.vim_ticket, auto_sync=False) self.assertEqual(agent_conn, instance) self.assertEqual(vim_conn, _vim_client_cls.return_value) else: self.assertRaises( ValueError, self.http_transferer._get_remote_connections, host, port) @parameterized.expand([ (None, "http://*/ha-nfc/x.vmdk", "http://actual_host/ha-nfc/x.vmdk"), (None, "https://*/foo", "https://actual_host/foo"), (None, "http://*:1234/foo", "http://*****:*****@patch("uuid.uuid4", return_value="fake_id") @patch("pyVmomi.vim.vm.VmImportSpec") def test_create_import_vm_spec(self, mock_vm_imp_spec_cls, mock_uuid): image_id = "fake_image_id" destination_datastore = "fake_datastore" create_spec_mock = MagicMock() create_empty_disk_mock = MagicMock() xferer = self.http_transferer xferer._vm_config.create_spec_for_import = create_spec_mock xferer._vm_manager.create_empty_disk = create_empty_disk_mock spec = xferer._create_import_vm_spec(image_id, destination_datastore) expected_vm_id = "h2h_fake_id" create_spec_mock.assert_called_once_with( vm_id=expected_vm_id, image_id=image_id, datastore=destination_datastore, memory=32, cpus=1) create_empty_disk_mock.assert_called_once_with( create_spec_mock.return_value, destination_datastore, None, size_mb=1) mock_vm_imp_spec_cls.assert_called_once_with( configSpec=create_empty_disk_mock.return_value) self.assertEqual(spec, mock_vm_imp_spec_cls.return_value) def test_get_url_from_import_vm(self): host = "mock_host" lease_mock = MagicMock() url_mock = MagicMock() import_spec = MagicMock() rp_mock = MagicMock() folder_mock = MagicMock() vim_client_mock = MagicMock() vim_client_mock.host = host xferer = self.http_transferer xferer._wait_for_lease = MagicMock() xferer._get_disk_url_from_lease = MagicMock(return_value=url_mock) xferer._ensure_host_in_url = MagicMock(return_value=url_mock) vim_client_mock.root_resource_pool = rp_mock vim_client_mock.vm_folder = folder_mock rp_mock.ImportVApp.return_value = lease_mock lease, url = xferer._get_url_from_import_vm(vim_client_mock, import_spec) rp_mock.ImportVApp.assert_called_once_with( import_spec, folder_mock) xferer._wait_for_lease.assert_called_once_with( lease_mock) xferer._get_disk_url_from_lease.assert_called_once_with(lease_mock) xferer._ensure_host_in_url.assert_called_once_with(url_mock, host) self.assertEqual(lease, lease_mock) self.assertEqual(url, url_mock) @patch("os.path.exists") @patch("__builtin__.open") @patch("os.unlink") def test_send_image_to_host(self, mock_unlink, mock_open, mock_exists): host = "mock_host" port = 8835 image_id = "fake_image_id" image_datastore = "fake_image_ds" destination_datastore = "fake_destination_image_ds" read_lease_mock = MagicMock() from_url_mock = MagicMock() write_lease_mock = MagicMock() to_url_mock = MagicMock() import_spec_mock = MagicMock() xferer = self.http_transferer file_contents = ["fake_metadata", "fake_manifest"] def fake_read(): return file_contents.pop() mock_open().__enter__().read.side_effect = fake_read mock_exists.return_value = True xferer._get_image_stream_from_shadow_vm = MagicMock( return_value=(read_lease_mock, from_url_mock)) xferer.download_file = MagicMock() vim_conn_mock = MagicMock() agent_conn_mock = MagicMock() xferer._get_remote_connections = MagicMock( return_value=(agent_conn_mock, vim_conn_mock)) xferer._create_import_vm_spec = MagicMock( return_value=import_spec_mock) xferer._get_url_from_import_vm = MagicMock( return_value=(write_lease_mock, to_url_mock)) xferer.upload_file = MagicMock() receive_image_resp_mock = MagicMock() receive_image_resp_mock.result = ReceiveImageResultCode.OK agent_conn_mock.receive_image.return_value = receive_image_resp_mock self.http_transferer.send_image_to_host( image_id, image_datastore, None, destination_datastore, host, port) assert_that(agent_conn_mock.receive_image.call_args_list, has_length(1)) request = agent_conn_mock.receive_image.call_args_list[0][0][0] assert_that(request.metadata, equal_to("fake_metadata")) assert_that(request.manifest, equal_to("fake_manifest")) xferer._get_image_stream_from_shadow_vm.assert_called_once_with( image_id, image_datastore) shadow_vm_id = "shadow_%s" % self.host_uuid expected_tmp_file = "/vmfs/volumes/%s/%s_transfer.vmdk" % ( self.image_datastores[0], shadow_vm_id) xferer.download_file.assert_called_once_with( from_url_mock, expected_tmp_file) read_lease_mock.Complete.assert_called_once_with() xferer._create_import_vm_spec.assert_called_once_with( image_id, destination_datastore) xferer._get_url_from_import_vm.assert_called_once_with( vim_conn_mock, import_spec_mock) xferer.upload_file.assert_called_once_with( expected_tmp_file, to_url_mock) write_lease_mock.Complete.assert_called_once_with() mock_unlink.assert_called_once_with(expected_tmp_file)
class TestHttpTransfer(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] self.agent_port = config["host_remote_test"].get("agent_port", 8835) if self.host is None or self.pwd is None: raise SkipTest() self.image_datastore = config["host_remote_test"].get( "image_datastore", "datastore1") self._logger = logging.getLogger(__name__) self.vim_client = VimClient(self.host, "root", self.pwd) self.http_transferer = HttpNfcTransferer(self.vim_client, [self.image_datastore], self.host) with tempfile.NamedTemporaryFile(delete=False) as source_file: with open(source_file.name, 'wb') as f: f.write(os.urandom(1024 * 100)) self.random_file = source_file.name self.remote_files_to_delete = [] def _cleanup_remote_files(self): file_manager = self.vim_client._content.fileManager for ds_path in self.remote_files_to_delete: try: delete_task = file_manager.DeleteFile(ds_path, None) task.WaitForTask(delete_task) except: pass def tearDown(self): os.unlink(self.random_file) self._cleanup_remote_files() self.vim_client.disconnect(wait=True) def _remote_ds_path(self, ds, relpath): return '[%s] %s' % (ds, relpath) def _datastore_path_url(self, datastore, relpath): quoted_dc_name = 'ha%252ddatacenter' url = 'https://%s/folder/%s?dcPath=%s&dsName=%s' % ( self.host, relpath, quoted_dc_name, datastore) return url def test_download_missing_file(self): url = self._datastore_path_url(self.image_datastore, "_missing_file_.bin") ticket = self.http_transferer._get_cgi_ticket(self.host, self.agent_port, url, http_op=HttpOp.GET) with tempfile.NamedTemporaryFile(delete=True) as local_file: self.assertRaises(TransferException, self.http_transferer.download_file, url, local_file.name, ticket=ticket) def test_upload_file_bad_destination(self): url = self._datastore_path_url("_missing__datastore_", "random.bin") ticket = self.http_transferer._get_cgi_ticket(self.host, self.agent_port, url, http_op=HttpOp.PUT) self.assertRaises(TransferException, self.http_transferer.upload_file, self.random_file, url, ticket=ticket) def test_raw_file_transfer_roundtrip(self): relpath = "_test_http_xfer_random.bin" url = self._datastore_path_url(self.image_datastore, relpath) ticket = self.http_transferer._get_cgi_ticket(self.host, self.agent_port, url, http_op=HttpOp.PUT) self.http_transferer.upload_file(self.random_file, url, ticket=ticket) self.remote_files_to_delete.append( self._remote_ds_path(self.image_datastore, relpath)) ticket = self.http_transferer._get_cgi_ticket(self.host, self.agent_port, url, http_op=HttpOp.GET) with tempfile.NamedTemporaryFile(delete=True) as downloaded_file: self.http_transferer.download_file(url, downloaded_file.name, ticket=ticket) # check that file uploaded and immediately downloaded back is # identical to the source file used. assert_that( filecmp.cmp(self.random_file, downloaded_file.name, shallow=False), is_(True)) @patch('os.path.exists', return_value=True) def test_get_streamoptimized_image_stream(self, _exists): image_id = "ttylinux" lease, url = self.http_transferer._get_image_stream_from_shadow_vm( image_id, self.image_datastore) try: with tempfile.NamedTemporaryFile(delete=True) as downloaded_file: # see if we can download without errors self.http_transferer.download_file(url, downloaded_file.name) # check that the first part of the file looks like that from a # stream-optimized disk with open(downloaded_file.name, 'rb') as f: data = f.read(65536) assert_that(len(data), is_(65536)) regex = re.compile("streamOptimized", re.IGNORECASE | re.MULTILINE) matches = regex.findall(data) assert_that(matches, not (empty())) finally: lease.Complete()
class ImageScannerTestCase(unittest.TestCase): DATASTORE_ID = "DS01" BASE_TEMP_DIR = "image_scanner" @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): # Create VM manager creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.vim_client.wait_for_task = MagicMock() self.patcher = patch("host.hypervisor.esx.vm_config.GetEnv") self.patcher.start() self.vm_manager = EsxVmManager(self.vim_client, MagicMock()) # Set up test files self.base_dir = os.path.dirname(__file__) self.test_dir = os.path.join(self.base_dir, "../../test_files") self.image_manager = EsxImageManager(MagicMock(), MagicMock()) self.image_scanner = DatastoreImageScanner(self.image_manager, self.vm_manager, self.DATASTORE_ID) self.image_scanner._task_runner = MagicMock() self.image_scanner._task_runner.is_stopped.return_value = False self.write_count = 0 def tearDown(self): self.patcher.stop() self.vim_client.disconnect(wait=True) def test_vm_scan(self): self.image_scanner.vm_scan_rate = 60000 dictionary = self.vm_manager.\ _collect_active_images(self.image_scanner, self.test_dir) assert_that(len(dictionary) is 1) assert_that(dictionary["92e62599-6689-4a8f-ba2a-633914b5048e"] == "/vmfs/volumes/555ca9f8-9f24fa2c-41c1-0025b5414043/" "images/92/92e62599-6689-4a8f-ba2a-633914b5048e/92e" "62599-6689-4a8f-ba2a-633914b5048e.vmdk") def test_vm_scan_bad_root(self): self.image_scanner.vm_scan_rate = 60000 bad_dir = os.path.join(self.base_dir, "test_files", "vms", "test", "bad", "bad.vmdk") dictionary = self.vm_manager.\ _collect_active_images(self.image_scanner, bad_dir) assert_that(len(dictionary) is 0) def test_vm_scan_bad_vmdk(self): self.image_scanner.vm_scan_rate = 60000 bad_dir = os.path.join(self.base_dir, "test_files", "vms", "test", "bad") dictionary = self.vm_manager.\ _collect_active_images(self.image_scanner, bad_dir) assert_that(len(dictionary) is 0) @patch("host.hypervisor.image_scanner.waste_time") def test_vm_scan_rate(self, waste_time): waste_time.side_effect = self.fake_waste_time # fake activation self.image_scanner.vm_scan_rate = 30 dictionary = self.vm_manager.\ _collect_active_images(self.image_scanner, self.test_dir) assert_that(len(dictionary) is 1) assert_that(dictionary["92e62599-6689-4a8f-ba2a-633914b5048e"] == "/vmfs/volumes/555ca9f8-9f24fa2c-41c1-0025b5414043/" "images/92/92e62599-6689-4a8f-ba2a-633914b5048e/92e" "62599-6689-4a8f-ba2a-633914b5048e.vmdk") def fake_waste_time(self, seconds): assert_that((seconds > 1.0) is True)
class TestEsxImageManager(unittest.TestCase): """Image Manager tests.""" # We can use even more unit test coverage of the image manager here @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.ds_manager = MagicMock() services.register(ServiceName.AGENT_CONFIG, MagicMock()) self.image_manager = EsxImageManager(self.vim_client, self.ds_manager) def tearDown(self): self.vim_client.disconnect(wait=True) @patch("os.path.isdir", return_value=False) @patch("os.makedirs", side_effect=OSError) def test_make_image_dir(self, _makedirs, _isdir): self.assertRaises( OSError, self.image_manager._make_image_dir, "ds", "fake_iid") _isdir.assert_called_once_with("/vmfs/volumes/ds/images/fa/fake_iid") self.assertEqual( _makedirs.call_count, EsxImageManager.NUM_MAKEDIRS_ATTEMPTS) for i in range(0, EsxImageManager.NUM_MAKEDIRS_ATTEMPTS): self.assertEqual(_makedirs.call_args_list[i][0], ("/vmfs/volumes/ds/images/fa/fake_iid",)) @patch( "host.hypervisor.esx.image_manager.EsxImageManager.reap_tmp_images") def test_periodic_reaper(self, mock_reap): """ Test that the we invoke the image reaper periodically """ image_manager = EsxImageManager(self.vim_client, self.ds_manager) image_manager.monitor_for_cleanup(reap_interval=0.1) self.assertFalse(image_manager._image_reaper is None) retry = 0 while mock_reap.call_count < 2 and retry < 10: time.sleep(0.1) retry += 1 image_manager.cleanup() assert_that(mock_reap.call_count, greater_than(1)) assert_that(retry, is_not(10), "reaper cleanup not called repeatedly") @parameterized.expand([ (True, ), (False, ) ]) @patch("uuid.uuid4", return_value="fake_id") @patch("host.hypervisor.esx.vm_config.os_datastore_path") def test_reap_tmp_images(self, _allow_grace_period, _os_datastore_path, _uuid): """ Test that stray images are found and deleted by the reaper """ def _fake_ds_folder(datastore, folder): return "%s__%s" % (datastore, folder) ds = MagicMock() ds.id = "dsid" ds.type = DatastoreType.EXT3 # In a random transient directory, set up a directory to act as the # tmp images folder and to contain a stray image folder with a file. tmpdir = file_util.mkdtemp(delete=True) tmp_images_folder = _fake_ds_folder(ds.id, TMP_IMAGE_FOLDER_NAME) tmp_images_dir = os.path.join(tmpdir, tmp_images_folder) tmp_image_dir = os.path.join(tmp_images_dir, "stray_image") os.mkdir(tmp_images_dir) os.mkdir(tmp_image_dir) (fd, path) = tempfile.mkstemp(prefix='strayimage_', dir=tmp_image_dir) self.assertTrue(os.path.exists(path)) def _fake_os_datastore_path(datastore, folder): return os.path.join(tmpdir, _fake_ds_folder(datastore, folder)) _os_datastore_path.side_effect = _fake_os_datastore_path ds_manager = MagicMock() ds_manager.get_datastores.return_value = [ds] image_manager = EsxImageManager(self.vim_client, ds_manager) if not _allow_grace_period: image_manager.REAP_TMP_IMAGES_GRACE_PERIOD = 0.0 time.sleep(0.1) image_manager.reap_tmp_images() if _allow_grace_period: # verify stray image is not deleted due to grace period self.assertTrue(os.path.exists(path)) else: # verify stray image is deleted self.assertFalse(os.path.exists(path)) @patch("os.path.isdir") @patch("os.makedirs") def test_vmdk_mkdir_eexist(self, _makedirs, _isdir): eexist = OSError() eexist.errno = errno.EEXIST _makedirs.side_effect = eexist _isdir.side_effect = (False, # dest image dir missing True) # dest image dir is created self.image_manager._make_image_dir("ds", "fake_iid") _isdir.assert_called("/vmfs/volumes/ds/images/fa/fake_iid") @patch("pysdk.task.WaitForTask") @patch("uuid.uuid4", return_value="fake_id") @patch("os.path.exists") @patch("os.makedirs") @patch("shutil.copy") @patch("shutil.rmtree") @patch("shutil.move") @patch.object(EsxImageManager, "_manage_disk") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_check_image_repair", return_value=False) @patch.object(EsxImageManager, "check_and_validate_image", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_copy_image(self, _flock, _create_image_timestamp, check_image, _check_image_repair, _get_ds_type, _manage_disk, _mv_dir, _rmtree, _copy, _makedirs, _exists, _uuid, _wait_for_task): _exists.side_effect = (True, # dest image vmdk missing True, # source meta file present True) # source manifest file present self.image_manager.copy_image("ds1", "foo", "ds2", "bar") os_path_prefix1 = '/vmfs/volumes/ds1/images' os_path_prefix2 = '/vmfs/volumes/ds2/images' os_tmp_path_prefix = '/vmfs/volumes/ds2/tmp_images' assert_that(_copy.call_count, equal_to(2)) _copy.assert_has_calls([ call('%s/fo/foo/foo.%s' % (os_path_prefix1, METADATA_FILE_EXT), '/vmfs/volumes/ds2/tmp_images/fake_id/bar.%s' % METADATA_FILE_EXT), call('%s/fo/foo/foo.%s' % (os_path_prefix1, MANIFEST_FILE_EXT), '/vmfs/volumes/ds2/tmp_images/fake_id/bar.%s' % MANIFEST_FILE_EXT), ]) ds_path_prefix1 = '[] ' + os_path_prefix1 ds_tmp_path_prefix = '[] ' + os_tmp_path_prefix expected_tmp_disk_ds_path = '%s/fake_id/%s.vmdk' % (ds_tmp_path_prefix, 'bar') _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='%s/fo/foo/foo.vmdk' % ds_path_prefix1, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) _mv_dir.assert_called_once_with('/vmfs/volumes/ds2/tmp_images/fake_id', '%s/ba/bar' % os_path_prefix2) _create_image_timestamp.assert_called_once_with( "/vmfs/volumes/ds2/tmp_images/fake_id") @patch("pysdk.task.WaitForTask") @patch("uuid.uuid4", return_value="fake_id") @patch("os.path.exists") @patch("os.makedirs") @patch("shutil.copy") @patch.object(EsxImageManager, "_manage_disk") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "check_image", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_create_tmp_image(self, _flock, _create_image_timestamp, check_image, _get_ds_type, _manage_disk, _copy, _makedirs, _exists, _uuid, _wait_for_task): # Common case is the same as the one covered by test_copy_image. # Check that things work when the src metadata file doesn't exist. _exists.side_effect = (False, False, True) ds_path_prefix1 = '[] /vmfs/volumes/ds1/images' expected_tmp_disk_ds_path = \ "[] /vmfs/volumes/ds2/tmp_images/fake_id/bar.vmdk" self.image_manager._create_tmp_image("ds1", "foo", "ds2", "bar") _flock.assert_called_once_with("/vmfs/volumes/ds2/tmp_images/fake_id", DatastoreType.EXT3) # Verify that we don't copy the metadata file. self.assertFalse(_copy.called) # Verify that we copy the disk correctly _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='%s/fo/foo/foo.vmdk' % ds_path_prefix1, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) # check that we return an IO error if the copy of metadata fails. _copy.side_effect = IOError _exists.side_effect = (True, True) _manage_disk.reset_mock() _flock.reset_mock() self.assertRaises(IOError, self.image_manager._create_tmp_image, "ds1", "foo", "ds2", "bar") self.assertFalse(_manage_disk.called) _flock.assert_called_once_with("/vmfs/volumes/ds2/tmp_images/fake_id", DatastoreType.EXT3) _create_image_timestamp.assert_called_once_with( "/vmfs/volumes/ds2/tmp_images/fake_id") @patch("os.makedirs") @patch("shutil.rmtree") @patch("shutil.move") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_check_image_repair", return_value=True) @patch("host.hypervisor.esx.image_manager.FileBackedLock") @raises(DiskAlreadyExistException) def test_move_image(self, _flock, check_image, _get_ds_type, _mv_dir, _rmtree, _makedirs): # Common case is covered in test_copy_image. # check that if destination image directory exists we don't call move # and just bail after removing the tmp dir _rmtree.reset_mock() _mv_dir.reset_mock() expected_tmp_disk_folder = '/vmfs/volumes/ds2/tmp_images/bar' expected_rm_calls = [call(expected_tmp_disk_folder)] self.image_manager._move_image("foo", "ds1", expected_tmp_disk_folder) self.assertEqual(expected_rm_calls, _rmtree.call_args_list) _makedirs.assert_called_once_with('/vmfs/volumes/ds1/images/fo') _flock.assert_called_once_with('/vmfs/volumes/ds1/images/fo/foo', DatastoreType.EXT3, 3) @parameterized.expand([ (True, ), (False, ) ]) @patch("os.path.exists") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch.object(EsxImageManager, "_delete_renamed_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_validate_existing_image(self, create, _flock, _delete_renamed_timestamp_file, _create_timestamp_file, _get_ds_type, _path_exists): self._create_image_timestamp_file = create _path_exists.side_effect = self._local_os_path_exists _disk_folder = '/vmfs/volumes/ds1/images/fo/foo' self.image_manager._check_image_repair("foo", "ds1") if create: _create_timestamp_file.assert_called_once_with(_disk_folder) _delete_renamed_timestamp_file.assert_called_once() else: assert not _create_timestamp_file.called assert not _delete_renamed_timestamp_file.called def _local_os_path_exists(self, pathname): if not self._create_image_timestamp_file: return True if pathname.endswith(EsxImageManager.IMAGE_TIMESTAMP_FILE_NAME): return False else: return True @patch.object(EsxImageManager, "_clean_gc_dir") @patch.object(EsxImageManager, "_gc_image_dir") @patch.object(EsxImageManager, "_lock_data_disk") @patch.object(EsxImageManager, "create_image_tombstone") @patch.object(EsxImageManager, "check_image_dir") def test_delete(self, check_image_dir, create_image_tombstone, lock_data_disk, gc_image_dir, clean_gc_dir): # Test successful delete check_image_dir.return_value = True self.image_manager.delete_image("ds1", "foo", 0, False) check_image_dir.assert_called_with("foo", "ds1") create_image_tombstone.assert_called_with("ds1", "foo") # Test successful delete with force option self.image_manager.delete_image("ds1", "foo", 0, True) check_image_dir.assert_called_with("foo", "ds1") create_image_tombstone.assert_called_with("ds1", "foo") lock_data_disk.assert_called_with("ds1", "foo") gc_image_dir.assert_called_with("ds1", "foo") clean_gc_dir.assert_called() # Test image not found check_image_dir.return_value = False self.assertRaises(ImageNotFoundException, self.image_manager.delete_image, "ds1", "foo", 0, False) @patch("host.hypervisor.esx.image_manager.os_vmdk_path") @patch("host.hypervisor.esx.image_manager.os_datastore_path") def test_gc_image_dir(self, dst_path, src_path): """ Test that we move the directory correctly to the GC location """ src_dir = file_util.mkdtemp(delete=True) dst_dir = file_util.mkdtemp(delete=True) src_path.return_value = os.path.join(src_dir, "test.vmdk") dst_path.return_value = dst_dir self.image_manager._gc_image_dir("ds1", "foo") uuid_dir = os.path.join(dst_dir, os.listdir(dst_dir)[0]) # Verify the src directory has been moved into the garbage dir. self.assertEqual(os.listdir(uuid_dir), [os.path.basename(src_dir)]) src_path.assert_called_once_with("ds1", "foo", IMAGE_FOLDER_NAME) dst_path.assert_called_once_with("ds1", GC_IMAGE_FOLDER) def test_image_path(self): image_path = "/vmfs/volumes/ds/images/tt/ttylinux/ttylinux.vmdk" ds = self.image_manager.get_datastore_id_from_path(image_path) image = self.image_manager.get_image_id_from_path(image_path) self.assertEqual(ds, "ds") self.assertEqual(image, "ttylinux") @patch("host.hypervisor.esx.image_manager.os_vmdk_flat_path") @patch("host.hypervisor.esx.image_manager.os.remove") def test_lock_data_disk(self, mock_rm, vmdk_flat_path): """ Test acquisition of the lock on the flat file. """ vmdk_flat_path.return_value = "fake_f_name" self.assertTrue(self.image_manager._lock_data_disk("ds1", "foo")) vmdk_flat_path.assert_called_once_with("ds1", "foo") mock_rm.side_effect = OSError self.assertFalse(self.image_manager._lock_data_disk("ds1", "foo")) @parameterized.expand([ ("CLOUD", "EAGER", ImageType.CLOUD, ImageReplication.EAGER), ("MANAGEMENT", "EAGER", ImageType.MANAGEMENT, ImageReplication.EAGER), ("CLOUD", "ON_DEMAND", ImageType.CLOUD, ImageReplication.ON_DEMAND), ("MANAGEMENT", "ON_DEMAND", ImageType.MANAGEMENT, ImageReplication.ON_DEMAND), ]) def test_image_type(self, type, replication, expected_type, expected_replication): self.ds_manager.image_datastores.return_value = ["ds1", "ds2"] with patch("host.hypervisor.esx.image_manager.os_image_manifest_path" "") as manifest_path: tmpdir = file_util.mkdtemp(delete=True) tmpfile = os.path.join(tmpdir, "ds1.manifest") manifest_path.return_value = tmpfile with open(tmpfile, 'w+') as f: f.write('{"imageType":"%s","imageReplication":"%s"}' % ( type, replication)) type, replication = self.image_manager.get_image_manifest( "image_id") self.assertEqual(type, expected_type) self.assertEqual(replication, expected_replication) def test_image_type_not_exist(self): self.ds_manager.image_datastores.return_value = ["ds1", "ds2"] type, replication = self.image_manager.get_image_manifest( "image_id") self.assertEqual(type, None) self.assertEqual(replication, None) @patch.object(EsxImageManager, "_move_image") @patch.object(EsxImageManager, "check_image_dir", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file_from_ids") @patch("os.path.exists") def test_create_image(self, _exists, _create_timestamp, check_image_dir, move_image): # Happy path verify move is called with the right args. _exists.side_effect = ([True]) self.image_manager.create_image("ds1", "foo", "img_1") check_image_dir.assert_called_once_with("img_1", "ds1") move_image.assert_called_once_with('img_1', 'ds1', '/vmfs/volumes/ds1/foo') _create_timestamp.assert_called_once_with("ds1", "img_1") # Verify error if tmp image doesn't exist _exists.side_effect = ([False]) move_image.reset_mock() self.assertRaises(ImageNotFoundException, self.image_manager.create_image, "ds1", "foo", "img_1") self.assertFalse(move_image.called) # Verify error if destination image already exists. _exists.side_effect = ([True]) move_image.reset_mock() check_image_dir.return_value = True self.assertRaises(DiskAlreadyExistException, self.image_manager.create_image, "ds1", "foo", "img_1") self.assertFalse(move_image.called) @patch.object(EsxImageManager, "create_image") @patch.object(EsxImageManager, "_manage_disk") @patch("os.path.exists", return_value=True) def test_create_image_with_vm_disk(self, _exists, _manage_disk, _create_image): vm_disk_path = "/vmfs/volumes/dsname/vms/ab/cd.vmdk" self.image_manager.create_image_with_vm_disk( "ds1", "foo", "img_1", vm_disk_path) # Verify that we copy the disk correctly expected_tmp_disk_ds_path = \ "[] /vmfs/volumes/ds1/foo/img_1.vmdk" _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='[] %s' % vm_disk_path, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) _create_image.assert_called_once_with("ds1", "foo", "img_1") @patch("shutil.rmtree") @patch("os.path.exists") def test_delete_tmp_dir(self, _exists, _rmtree): self.image_manager.delete_tmp_dir("ds1", "foo") _exists.assert_called_once("/vmfs/volumes/ds1/foo") _rmtree.assert_called_once("/vmfs/volumes/ds1/foo") _exists.reset_mock() _exists.return_value = False _rmtree.reset_mock() self.assertRaises(DirectoryNotFound, self.image_manager.delete_tmp_dir, "ds1", "foo") _exists.assert_called_once("/vmfs/volumes/ds1/foo") self.assertFalse(_rmtree.called) def test_image_size(self): self.ds_manager.image_datastores.return_value = ["ds1", "ds2"] with patch("host.hypervisor.esx.image_manager.os_vmdk_flat_path" "") as image_path: tmpdir = file_util.mkdtemp(delete=True) image_path.return_value = tmpdir size = self.image_manager.image_size("image_id") self.assertTrue(size > 0) def test_image_size_not_exist(self): self.ds_manager.image_datastores.return_value = ["ds1", "ds2"] self.assertRaises(NoSuchResourceException, self.image_manager.image_size, "image_id")
class TestRemoteAgent(unittest.TestCase, AgentCommonTests): def shortDescription(self): return None def get_service_instance(self): # create a connection to hostd. request = ServiceTicketRequest(ServiceType.VIM) response = self.host_client.get_service_ticket(request) self.assertEqual(response.result, ServiceTicketResultCode.OK) hostd_port = 443 vim_namespace = "vim25/5.0" stub = SoapStubAdapter(self.server, hostd_port, vim_namespace) si = vim.ServiceInstance("ServiceInstance", stub) si.RetrieveContent().sessionManager.CloneSession(response.vim_ticket) connect.SetSi(si) return si def connect_client(self, service, cls, server): """ Utility method to connect to a remote agent """ max_sleep_time = 32 sleep_time = 0.1 while sleep_time < max_sleep_time: try: client = DirectClient(service, cls, server, 8835) client.connect() return client except TTransport.TTransportException: time.sleep(sleep_time) sleep_time *= 2 self.fail("Cannot connect to agent %s" % server) def create_client(self): return self.connect_client("Host", Host.Client, self.server) def client_connections(self): self.host_client = self.create_client() self.control_client = self.connect_client("AgentControl", AgentControl.Client, self.server) def provision_hosts(self, mem_overcommit=2.0, datastores=None, used_for_vms=True, image_ds=None, host_id=None, deployment_id="test-deployment"): """ Provisions the agents on the remote hosts """ if datastores is None: datastores = self.get_all_datastores() image_datastore = self.get_image_datastore() elif image_ds: image_datastore = image_ds else: image_datastore = datastores[0] req = ProvisionRequest() req.datastores = datastores req.address = ServerAddress(host=self.server, port=8835) req.memory_overcommit = mem_overcommit req.image_datastore_info = ImageDatastore( name=image_datastore, used_for_vms=used_for_vms) req.image_datastores = set([req.image_datastore_info]) req.management_only = True if host_id: req.host_id = host_id else: req.host_id = self.host_id if deployment_id: req.deployment_id = deployment_id else: req.deployment_id = self.deployment_id res = self.control_client.provision(req) # This will trigger a restart if the agent config changes, which # will happen the first time provision_hosts is called. self.assertEqual(res.result, ProvisionResultCode.OK) # Wait for up to 60 seconds for the agent to reboot. count = 0 while count < 60: try: res = self.control_client.get_agent_status() if res.status == AgentStatusCode.OK: # Agent is up return except: logger.exception("Can't connect to agent") count += 1 time.sleep(1) # Reconnect the clients self._close_agent_connections() self.client_connections() self.fail("Cannot connect to agent %s after provisioning" % self.server) return host_id def setUp(self): from testconfig import config if "agent_remote_test" not in config: raise SkipTest() # Set the default datastore name self._datastores = None if "datastores" in config["agent_remote_test"]: datastores = config["agent_remote_test"]["datastores"] self._datastores = [d.strip() for d in datastores.split(",")] else: self.fail("datastores not provided for test setUp") # Optionally update the specification of a remote iso file. The file # needs to exist on the remote esx server for this test to succeed. self._remote_iso_file = None self._second_remote_iso_file = None if ("iso_file" in config["agent_remote_test"]): self._remote_iso_file = config["agent_remote_test"]["iso_file"] if ("second_iso_file" in config["agent_remote_test"]): self._second_remote_iso_file = config["agent_remote_test"]["second_iso_file"] server = config["agent_remote_test"]["server"] self.server = server self.generation = int(time.time()) # Connect to server and configure vim_client self.client_connections() self.vim_client = VimClient() self.vim_client.connect_ticket(self.server, self._get_vim_ticket()) connect.SetSi(self.vim_client._si) # Set host mode to normal self.set_host_mode(HostMode.NORMAL) # The first time setup is called the agent will restart. self.provision_hosts() # Reconnect to account for the restart self.client_connections() self.clear() @classmethod def setUpClass(cls): cls.host_id = str(uuid.uuid4()) cls.deployment_id = "test-deployment" def _close_agent_connections(self): self.host_client.close() self.control_client.close() def tearDown(self): self._close_agent_connections() self.vim_client.disconnect() def vim_delete_vm(self, vm_id): """ Delete a VM using the vim client """ try: vim_client = VimClient() vim_client.connect_ticket(self.server, self._get_vim_ticket()) vim_vm = vim_client.get_vm(vm_id) if vim_vm.runtime.powerState != 'poweredOff': try: vim_task = vim_vm.PowerOff() vim_client.wait_for_task(vim_task) except: logger.info("Cannot power off vm", exc_info=True) vim_task = vim_vm.Destroy() vim_client.wait_for_task(vim_task) finally: if vim_client: vim_client.disconnect() def clear(self): """Remove all the VMs, disks and images """ request = GetResourcesRequest() response = rpc_call(self.host_client.get_resources, request) assert_that(response.result, is_(GetResourcesResultCode.OK)) for resource in response.resources: delete_request = Host.DeleteVmRequest(vm_id=resource.vm.id, force=True) response = rpc_call(self.host_client.delete_vm, delete_request) if response.result == DeleteVmResultCode.VM_NOT_POWERED_OFF: poweroff_request = Host.PowerVmOpRequest(vm_id=resource.vm.id, op=Host.PowerVmOp.OFF) response = rpc_call(self.host_client.power_vm_op, poweroff_request) assert_that(response.result, is_(PowerVmOpResultCode.OK)) response = rpc_call(self.host_client.delete_vm, delete_request) if response.result != DeleteVmResultCode.OK: logger.info("Cannot delete vm %s trying vim_client" % resource.vm.id) self.vim_delete_vm(resource.vm.id) self.clean_images() def clean_images(self): """ Clean up images if there are any """ datastore = self._find_configured_datastore_in_host_config() request = Host.GetImagesRequest(datastore.id) response = self.host_client.get_images(request) if response.result == GetImagesResultCode.OK: for image_id in response.image_ids: if image_id == "ttylinux": continue # To be removed when we remove ttylinux. logging.info("Cleaning up stray image %s " % image_id) self._delete_image(Image(image_id, datastore)) else: logger.warning("Failed to obtain the list of images to cleanup") def test_send_image_to_host(self): image_id = new_id() + "_test_xfer_image" image_id_2 = "%s_xfered" % image_id dst_image, _ = self._create_test_image(image_id) datastore = self._find_configured_datastore_in_host_config() transfer_image_request = TransferImageRequest( source_image_id=image_id, source_datastore_id=datastore.id, destination_host=ServerAddress(host="localhost", port=8835), destination_datastore_id=datastore.id, destination_image_id=image_id_2) res = self.host_client.transfer_image(transfer_image_request) self.assertEqual(res.result, TransferImageResultCode.OK) # clean up images created in test self._delete_image(dst_image) xfered_image = Image(image_id_2, datastore) self._delete_image(xfered_image) def test_host_config_after_provision(self): """ Test if the agent returns the correct HostConfig after being provisioned """ host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) self.assertEqual(res.result, GetConfigResultCode.OK) hostConfig = res.hostConfig datastores = [ds.name for ds in hostConfig.datastores] containsDs = [ds for ds in self.get_all_datastores() if ds in datastores] self.assertEqual(containsDs, self.get_all_datastores()) networks = [net.id for net in hostConfig.networks] self.assertEqual(networks, self.vim_client.get_networks()) self.assertEqual(hostConfig.address, ServerAddress(host=self.server, port=8835)) self.assertTrue(hostConfig.management_only) # get_host_config reports datastore id for image datastore even if it # was provisioned with a datastore name. image_datastore_name = self.get_image_datastore() image_datastore_id = None for ds in hostConfig.datastores: if ds.name == image_datastore_name: image_datastore_id = ds.id self.assertEqual(list(hostConfig.image_datastore_ids)[0], image_datastore_id) def _generate_new_iso_ds_path(self): if (self._remote_iso_file.lower().rfind(".iso") != len(self._remote_iso_file) - 4): raise ValueError() return "%s-%s.iso" % (self._remote_iso_file[:-4], str(uuid.uuid4())) def _make_new_iso_copy(self, file_manager, new_iso_path): copy_task = file_manager.CopyFile(self._remote_iso_file, None, new_iso_path, None) task.WaitForTask(copy_task) def test_attach_cdrom(self): """ Tests attach iso code path. 1. Attach an iso to a non existent VM. Check correct error 2. Attach a non existent iso file to a valid VM. Check correct error 3. Attach a real iso if specified to a VM. Verify it succeeds. Test should pass the iso path as [datastore_name]/path/to/iso.iso """ if not self._remote_iso_file: raise SkipTest("ISO file on server not provided") si = self.get_service_instance() file_manager = si.RetrieveContent().fileManager iso_path = self._generate_new_iso_ds_path() iso_path_2 = self._generate_new_iso_ds_path() vm_wrapper = VmWrapper(self.host_client) image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), "default", False, True, image=image, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), ] # Create disk and VM. reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # Verify the result when the VM is not found. fake_id = str(uuid.uuid4()) vm_wrapper.attach_iso(fake_id, "/tmp/foo.iso", Host.AttachISOResultCode.VM_NOT_FOUND) # Verify the result when the the iso doesn't exist. vm_wrapper.attach_iso(vm_id, "/tmp/foo.iso", Host.AttachISOResultCode.SYSTEM_ERROR) self._make_new_iso_copy(file_manager, iso_path) self._make_new_iso_copy(file_manager, iso_path_2) # Doing enough attaches will indirectly verify that we do not grow the # device list on reattach. for i in xrange(3): # verify attach works vm_wrapper.attach_iso(vm_id, iso_path) # verify re-attach to another iso works vm_wrapper.attach_iso(vm_id, iso_path_2) vm_wrapper.power(Host.PowerVmOp.ON) # Verify reattach fails when vm is powered on. vm_wrapper.attach_iso(vm_id, iso_path, Host.AttachISOResultCode.ISO_ATTACHED_ERROR) vm_wrapper.power(Host.PowerVmOp.OFF) vm_wrapper.detach_iso(vm_id, True) vm_wrapper.attach_iso(vm_id, iso_path) vm_wrapper.detach_iso(vm_id, True) self.clear() def test_detach_cdrom_failure(self): """ Tests failures of detach iso from VM. """ vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # no prior attach of iso vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.ISO_NOT_ATTACHED) # nonexistent VM id fake_id = str(uuid.uuid4()) vm_wrapper.detach_iso(fake_id, True, Host.DetachISOResultCode.VM_NOT_FOUND) # Attaching nonexistent iso path still should succeed as long # as a valid datastore path format is used random = str(uuid.uuid4()) vm_wrapper.attach_iso(vm_id, "[] /tmp/%s_nonexistent_.iso" % random, Host.AttachISOResultCode.OK) # Not supporting detach without delete yet. vm_wrapper.detach_iso(vm_id, False, Host.DetachISOResultCode.SYSTEM_ERROR) # But detach a non-exist iso should work. vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.delete(request=vm_wrapper.delete_request()) def test_detach_cdrom(self): """ Tests detach iso from VM. Verify Detaching a real iso from a VM. """ if not self._remote_iso_file: raise SkipTest("ISO file on server not provided") si = self.get_service_instance() file_manager = si.RetrieveContent().fileManager iso_path = self._generate_new_iso_ds_path() self._make_new_iso_copy(file_manager, iso_path) iso_path_2 = self._generate_new_iso_ds_path() self._make_new_iso_copy(file_manager, iso_path_2) vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id vm_wrapper.attach_iso(vm_id, iso_path, Host.AttachISOResultCode.OK) vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.attach_iso(vm_id, iso_path_2, Host.AttachISOResultCode.OK) # verify detach works when powered on vm_wrapper.power(Host.PowerVmOp.ON) vm_wrapper.detach_iso(vm_id, True, Host.DetachISOResultCode.OK) vm_wrapper.power(Host.PowerVmOp.OFF) vm_wrapper.delete(request=vm_wrapper.delete_request()) def test_remote_boostrap(self): """ Tests boostrapping of an agent against a real host """ # We need to be able to read the config from the host to set it # correctly. # https://www.pivotaltracker.com/story/show/83243144 raise SkipTest() req = self._update_agent_config() # Try connecting to the client in a loop. # Back off on failure to connect to agent max_sleep_time = 32 sleep_time = 0.1 while sleep_time < max_sleep_time: try: self.host_client.connect() break except TTransport.TTransportException: time.sleep(sleep_time) sleep_time *= 2 self._validate_post_boostrap_config(req) def test_get_nfc_ticket_with_ds_id(self): datastores = self.vim_client.get_all_datastores() image_datastore = [ds for ds in datastores if ds.name == self.get_image_datastore()][0] request = ServiceTicketRequest(service_type=ServiceType.NFC, datastore_name=image_datastore.id) response = self.host_client.get_service_ticket(request) assert_that(response.result, is_(ServiceTicketResultCode.OK)) ticket = response.ticket assert_that(ticket, not_none()) assert_that(ticket.port, is_(902)) assert_that(ticket.service_type, is_("nfc")) assert_that(ticket.session_id, not_none()) assert_that(ticket.ssl_thumbprint, not_none()) def test_persist_mode(self): # Enter maintenance self.set_host_mode(HostMode.MAINTENANCE) # Restart agent by provisioning with different configuration self.provision_hosts(mem_overcommit=2.1) self.client_connections() # Check mode. It should still be MAINTENANCE. response = self.host_client.get_host_mode(GetHostModeRequest()) assert_that(response.result, equal_to(GetHostModeResultCode.OK)) assert_that(response.mode, equal_to(HostMode.MAINTENANCE)) def _create_test_image(self, name): """ Create an test image for tests to use on a datastore """ datastore = self._find_configured_datastore_in_host_config() # ttylinux is the default image that is copied to datastore # when agent starts src_image = Image("ttylinux", datastore) dst_image = Image(name, datastore) # Copy image request = Host.CopyImageRequest(src_image, dst_image) response = self.host_client.copy_image(request) assert_that(response.result, is_in([CopyImageResultCode.OK, CopyImageResultCode.DESTINATION_ALREADY_EXIST])) return dst_image, datastore def _get_vim_ticket(self): request = ServiceTicketRequest(ServiceType.VIM) response = self.host_client.get_service_ticket(request) assert_that(response.result, is_(ServiceTicketResultCode.OK)) return response.vim_ticket def test_create_vm_with_ephemeral_disks_concurrent(self): concurrency = 5 atmoic_lock = threading.Lock() results = {"count": 0} def _thread(): self._test_create_vm_with_ephemeral_disks("ttylinux", concurrent=True, new_client=True) with atmoic_lock: results["count"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() assert_that(results["count"], is_(concurrency)) def test_concurrent_copy_image(self): concurrency = 3 atomic_lock = threading.Lock() results = {"ok": 0, "existed": 0} datastore = self._find_configured_datastore_in_host_config() new_image_id = "concurrent-copy-%s" % str(uuid.uuid4()) src_image = Image("ttylinux", datastore) dst_image = Image(new_image_id, datastore) # verify destination_id is not in datastore request = Host.GetImagesRequest(datastore.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item("ttylinux")) assert_that(response.image_ids, not(has_item(new_image_id))) image_number = len(response.image_ids) def _thread(): client = self.create_client() request = Host.CopyImageRequest(src_image, dst_image) response = client.copy_image(request) ok = response.result == CopyImageResultCode.OK existed = response.result == CopyImageResultCode.\ DESTINATION_ALREADY_EXIST # Verify destination_id is in datastore request = Host.GetImagesRequest(datastore.id) response = client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item("ttylinux")) assert_that(response.image_ids, has_item(new_image_id)) assert_that(response.image_ids, has_length(image_number + 1)) with atomic_lock: if ok: results["ok"] += 1 if existed: results["existed"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() # Clean destination image self._delete_image(dst_image) # Only one copy is successful, all others return # DESTINATION_ALREADY_EXIST assert_that(results["ok"], is_(1)) assert_that(results["existed"], is_(concurrency - 1)) def test_force_delete_vm(self): vm_wrapper = VmWrapper(self.host_client) # create a vm without disk reservation = vm_wrapper.place_and_reserve().reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # create 2 disks disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR) ] for disk in disks: reservation = vm_wrapper.place_and_reserve(disk=disk).reservation vm_wrapper.create_disk(disk, reservation, validate=True) # attach disks disk_ids = [disk.id for disk in disks] vm_wrapper.attach_disks(vm_id, disk_ids) # delete vm fails without force vm_wrapper.delete(request=vm_wrapper.delete_request(), expect=Host.DeleteVmResultCode.OPERATION_NOT_ALLOWED) # delete vm with force succeeds vm_wrapper.delete(request=vm_wrapper.delete_request(force=True)) for disk_id in disk_ids: vm_wrapper.get_disk(disk_id, expect_found=False) def test_disk_uuids(self): # Create a vm without a root disk and blank disk then attach another # persistent disk. Then verify that only the uuids of the # ephemeral and persistent disks are updated to match their cloud ids. vm_wrapper = VmWrapper(self.host_client) disk_id_root = new_id() disk_id_ephemeral = new_id() disk_id_persistent = new_id() image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(disk_id_root, self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(disk_id_ephemeral, self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), ] reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_id = vm_wrapper.create(request=request).vm.id # create one persistent disk disk = Disk(disk_id_persistent, self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR) reservation = vm_wrapper.place_and_reserve(disk=disk).reservation vm_wrapper.create_disk(disk, reservation, validate=True) vm_wrapper.attach_disks(vm_id, [disk_id_persistent]) vim_vm = self.vim_client.get_vm(vm_id) disk_uuid_map = dict([(dev.backing.uuid, dev.backing.fileName) for dev in vim_vm.config.hardware.device if isinstance(dev, vim.vm.device.VirtualDisk)]) # Assert that the UUID assigned to the ephemeral and persistent disks # matches their ids for disk_id in (disk_id_ephemeral, disk_id_persistent): self.assertTrue(disk_id in disk_uuid_map and disk_id in disk_uuid_map[disk_id]) # Assert that no such assignment is done for link-clone root disk. self.assertFalse(disk_id_root in disk_uuid_map) vm_wrapper.detach_disks(vm_id, [disk_id_persistent]) vm_wrapper.delete(request=vm_wrapper.delete_request()) vm_wrapper.delete_disks([disk_id_persistent], validate=True) def test_place_on_multiple_datastores(self): """ Test placement can actually place vm to datastores without image. """ host_datastores = self.vim_client.get_all_datastores() image_datastore = self._find_configured_datastore_in_host_config() dest_datastore = None for ds in host_datastores: if ds.id != image_datastore.id: dest_datastore = ds break if not dest_datastore: raise SkipTest() # Test only 2 datastores, with one image datastore and another # datastore. self.provision_hosts(datastores=[image_datastore.name, dest_datastore.name], used_for_vms=False) self.client_connections() concurrency = 3 atmoic_lock = threading.Lock() results = {"count": 0} # Only copy image to datastore[0] new_image_id = str(uuid.uuid4()) datastore = Datastore(id=image_datastore.name) src_image = Image("ttylinux", datastore) dst_image = Image(new_image_id, datastore) request = Host.CopyImageRequest(src_image, dst_image) response = self.host_client.copy_image(request) assert_that(response.result, is_(CopyImageResultCode.OK)) def _thread(): self._test_create_vm_with_ephemeral_disks(new_image_id, concurrent=True, new_client=True) with atmoic_lock: results["count"] += 1 threads = [] for i in range(concurrency): thread = threading.Thread(target=_thread) threads.append(thread) thread.start() for thread in threads: thread.join() # Make sure the new image is copied to both datastores, and clean # them up. for ds in (image_datastore, dest_datastore): image = Image(datastore=Datastore(id=ds.name), id=new_image_id) self._delete_image(image) assert_that(results["count"], is_(concurrency)) def test_place_on_datastore_tag(self): host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) self.assertEqual(res.result, GetConfigResultCode.OK) datastores = res.hostConfig.datastores for datastore in datastores: tag = self._type_to_tag(datastore.type) if not tag: continue vm_wrapper = VmWrapper(self.host_client) # Test place disks with only datastore constraint disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], []) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints) # Test place disks with datastore and datastore tag constraint disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], [tag]) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints, expect=PlaceResultCode.OK) # Test place disks with the wrong datastore tag for other_tag in self._other_tags(tag): disk = Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, capacity_gb=0) resource_constraints = self._create_constraints([datastore.id], [other_tag]) vm_wrapper.place(vm_disks=[disk], vm_constraints=resource_constraints, expect=PlaceResultCode.NO_SUCH_RESOURCE) def test_provision_without_datastores(self): """ Test that the host uses all the datastores when it gets provisioned without any datastores specified. """ # provision the host without datastores datastores = self.get_all_datastores() self.provision_hosts(datastores=[], image_ds=datastores[0]) # verify that the host configuration contains all the datastores. req = Host.GetConfigRequest() res = self.create_client().get_host_config(req) self.assertEqual(len(res.hostConfig.datastores), len(self.vim_client.get_all_datastores())) def _manage_disk(self, op, **kwargs): task = op(self.vim_client._content.virtualDiskManager, **kwargs) self.vim_client.wait_for_task(task) def _gen_vd_spec(self): spec = vim.VirtualDiskManager.VirtualDiskSpec() spec.disk_type = str(vim.VirtualDiskManager.VirtualDiskType.thin) spec.adapterType = str(vim.VirtualDiskManager.VirtualDiskAdapterType.lsiLogic) return spec def test_finalize_image(self): """ Integration test for atomic image create """ img_id = "test-create-image" tmp_img_id = "-tmp-" + img_id tmp_image, ds = self._create_test_image(tmp_img_id) tmp_image_path = datastore_path(ds.name, "image_" + tmp_img_id) src_vmdk = vmdk_path(ds.id, tmp_img_id, IMAGE_FOLDER_NAME_PREFIX) dst_vmdk = "%s/%s.vmdk" % (tmp_image_path, img_id) try: self._manage_disk( vim.VirtualDiskManager.MoveVirtualDisk_Task, sourceName=src_vmdk, destName=dst_vmdk, force=True) except: logger.error("Error moving vmdk %s" % src_vmdk, exc_info=True) self._manage_disk( vim.VirtualDiskManager.DeleteVirtualDisk_Task, name=src_vmdk) raise dst_image = Image(img_id, ds) req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.OK) request = Host.GetImagesRequest(ds.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item(img_id)) # Issue another create call and it should fail as the source doesn't # exist. req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.IMAGE_NOT_FOUND) # Verify that we fail if the destination already exists. tmp_image, ds = self._create_test_image(tmp_img_id) req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.DESTINATION_ALREADY_EXIST) # cleanup self._delete_image(dst_image) def test_start_image_scanner(self): """ Test image scanner. Make sure the idle images are reported correctly. """ datastore = self._find_configured_datastore_in_host_config() image_id_1 = new_id() dst_image_1, _ = self._create_test_image(image_id_1) image_id_2 = new_id() dst_image_2, _ = self._create_test_image(image_id_2) disk_image_2 = DiskImage(image_id_2, CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=disk_image_2, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=2, flavor_info=self.DEFAULT_DISK_FLAVOR) ] vm_wrapper = VmWrapper(self.host_client) reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_wrapper.create(request=request) logger.info("Image scan, Vm id: %s" % vm_wrapper.id) # Start image scanner start_scan_request = StartImageScanRequest() start_scan_request.datastore_id = datastore.id start_scan_request.scan_rate = 600 start_scan_request.timeout = 30 start_scan_response = self.host_client.start_image_scan(start_scan_request) self.assertEqual(start_scan_response.result, StartImageOperationResultCode.OK) self._get_and_check_inactive_images(datastore.id, image_id_1, True) get_inactive_images_response = self._get_and_check_inactive_images(datastore.id, image_id_2, False) # Start image sweeper start_sweep_request = StartImageSweepRequest() start_sweep_request.datastore_id = datastore.id start_sweep_request.image_descs = get_inactive_images_response.image_descs start_sweep_request.sweep_rate = 600 start_sweep_request.timeout = 30 start_sweep_request.grace_period = 0 start_sweep_response = self.host_client.start_image_sweep(start_sweep_request) self.assertEqual(start_sweep_response.result, StartImageOperationResultCode.OK) self._get_and_check_deleted_images(datastore.id, image_id_1, True) # cleanup vm_wrapper.delete() self._delete_image(dst_image_1, DeleteDirectoryResultCode.DIRECTORY_NOT_FOUND) self._delete_image(dst_image_2) def _get_and_check_inactive_images(self, datastore_id, image_id, found): get_inactive_images_request = GetInactiveImagesRequest() get_inactive_images_request.datastore_id = datastore_id for counter in range(1, 30): time.sleep(1) get_inactive_images_response = self.host_client.get_inactive_images(get_inactive_images_request) if get_inactive_images_response.result is GetMonitoredImagesResultCode.OK: break self.assertEqual(get_inactive_images_response.result, GetMonitoredImagesResultCode.OK) image_descriptors = get_inactive_images_response.image_descs image_found = False logger.info("Image Descriptors: %s" % image_descriptors) logger.info("Target Image Id: %s" % image_id) for image_descriptor in image_descriptors: if image_descriptor.image_id == image_id: image_found = True self.assertEqual(image_found, found) return get_inactive_images_response def _get_and_check_deleted_images(self, datastore_id, image_id, found): get_deleted_images_request = GetInactiveImagesRequest() get_deleted_images_request.datastore_id = datastore_id for counter in range(1, 30): time.sleep(1) get_inactive_deleted_response = self.host_client.get_deleted_images(get_deleted_images_request) if get_inactive_deleted_response.result is GetMonitoredImagesResultCode.OK: break self.assertEqual(get_inactive_deleted_response.result, GetMonitoredImagesResultCode.OK) image_descriptors = get_inactive_deleted_response.image_descs image_found = False logger.info("Image Descriptors: %s" % image_descriptors) logger.info("Target Image Id: %s" % image_id) for image_descriptor in image_descriptors: if image_descriptor.image_id == image_id: image_found = True self.assertEqual(image_found, found) return get_inactive_deleted_response def _type_to_tag(self, type): type_to_tag = { DatastoreType.NFS_3: NFS_TAG, DatastoreType.NFS_41: NFS_TAG, DatastoreType.SHARED_VMFS: SHARED_VMFS_TAG, DatastoreType.LOCAL_VMFS: LOCAL_VMFS_TAG, } if type in type_to_tag: return type_to_tag[type] else: return None def _other_tags(self, tag): tags = [NFS_TAG, SHARED_VMFS_TAG, LOCAL_VMFS_TAG] tags.remove(tag) return tags def _create_constraints(self, datastores, tags): constraints = [] for datastore in datastores: constraints.append(ResourceConstraint( type=ResourceConstraintType.DATASTORE, values=[datastore])) for tag in tags: constraints.append(ResourceConstraint( type=ResourceConstraintType.DATASTORE_TAG, values=[tag])) return constraints def test_create_image_from_vm(self): """ Integration test for creating an image from a VM """ img_id = "test-new-im-from-vm-%s" % new_id() tmp_img_id = "-tmp-" + img_id tmp_image, ds = self._create_test_image(tmp_img_id) tmp_image_path = datastore_path(ds.id, "image_" + tmp_img_id) src_vmdk = vmdk_path(ds.id, tmp_img_id, IMAGE_FOLDER_NAME_PREFIX) vm_wrapper = VmWrapper(self.host_client) try: self._manage_disk( vim.VirtualDiskManager.DeleteVirtualDisk_Task, name=src_vmdk) except: logger.error( "Error deleting vmdk when setting up tmp image %s" % src_vmdk, exc_info=True) raise dst_image = Image(img_id, ds) image = DiskImage("ttylinux", CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=1, flavor_info=self.DEFAULT_DISK_FLAVOR), Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, True, True, capacity_gb=2, flavor_info=self.DEFAULT_DISK_FLAVOR) ] reservation = vm_wrapper.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper.create_request(res_id=reservation) vm_wrapper.create(request=request) # VM in wrong state vm_wrapper.power(Host.PowerVmOp.ON, Host.PowerVmOpResultCode.OK) time.sleep(10) vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.INVALID_VM_POWER_STATE) vm_wrapper.power(Host.PowerVmOp.OFF, Host.PowerVmOpResultCode.OK) time.sleep(10) # Happy case vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.OK) request = Host.GetImagesRequest(ds.id) response = self.host_client.get_images(request) assert_that(response.result, is_(GetImagesResultCode.OK)) assert_that(response.image_ids, has_item(img_id)) # Issue another create call and it should fail as the source doesn't # exist. req = FinalizeImageRequest(image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path) response = self.host_client.finalize_image(req) self.assertEqual(response.result, FinalizeImageResultCode.IMAGE_NOT_FOUND) # Verify that we fail if the destination already exists. tmp_image, ds = self._create_test_image(tmp_img_id) vm_wrapper.create_image_from_vm( image_id=tmp_img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.IMAGE_ALREADY_EXIST) vm_wrapper.delete() # VM to create image from is gone. vm_wrapper.create_image_from_vm( image_id=img_id, datastore=ds.id, tmp_image_path=tmp_image_path, expect=Host.CreateImageFromVmResultCode.VM_NOT_FOUND) # Create a VM using the new image created vm_wrapper2 = VmWrapper(self.host_client) image = DiskImage(img_id, CloneType.COPY_ON_WRITE) disks = [ Disk(new_id(), self.DEFAULT_DISK_FLAVOR.name, False, True, image=image, capacity_gb=0, flavor_info=self.DEFAULT_DISK_FLAVOR), ] reservation = vm_wrapper2.place_and_reserve(vm_disks=disks).reservation request = vm_wrapper2.create_request(res_id=reservation) vm_wrapper2.create(request=request) vm_wrapper2.power(Host.PowerVmOp.ON, Host.PowerVmOpResultCode.OK) vm_wrapper2.power(Host.PowerVmOp.OFF, Host.PowerVmOpResultCode.OK) vm_wrapper2.delete() # cleanup self._delete_image(dst_image) def test_delete_tmp_image(self): """ Integration test for deleting temp image directory """ img_id = "test-delete-tmp-image" tmp_iamge, ds = self._create_test_image(img_id) tmp_image_path = "image_" + img_id req = DeleteDirectoryRequest(datastore=ds.id, directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.OK) req = DeleteDirectoryRequest(datastore=ds.id, directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.DIRECTORY_NOT_FOUND) req = DeleteDirectoryRequest(datastore="foo_bar", directory_path=tmp_image_path) res = self.host_client.delete_directory(req) self.assertEqual(res.result, DeleteDirectoryResultCode.DATASTORE_NOT_FOUND) def _get_agent_id(self): host_config_request = Host.GetConfigRequest() res = self.host_client.get_host_config(host_config_request) return res.hostConfig.agent_id
class TestVimClient(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self.vim_client = VimClient(auto_sync=True) self.vim_client.connect_userpwd(self.host, "root", self.pwd) self._logger = logging.getLogger(__name__) def tearDown(self): self.vim_client.disconnect() def test_memory_usage(self): used_memory = self.vim_client.memory_usage_mb assert_that(used_memory > 0, is_(True)) def test_total_memory(self): total_memory = self.vim_client.total_vmusable_memory_mb assert_that(total_memory > 0, is_(True)) def test_total_cpus(self): num_cpus = self.vim_client.num_physical_cpus assert_that(num_cpus > 0, is_(True)) def _create_test_vm(self, suffix="host-integ"): # Create VM vm_id = "vm_%s-%s-%s" % ( time.strftime("%Y-%m-%d-%H%M%S", time.localtime()), str(random.randint(100000, 1000000)), suffix) datastore = self.vim_client.get_all_datastores()[0].name disk_path = "[%s] %s/disk.vmdk" % (datastore, vm_id) create_spec = self.get_create_spec(datastore, vm_id, disk_path) self.vim_client.create_vm(vm_id, create_spec) vm = self.vim_client.get_vm(vm_id) return (vm_id, vm, datastore, disk_path) def test_get_cached_vm(self): vm_id, vm, datastore, disk_path = self._create_test_vm("vm-cache-test") # Verify VM is in cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].power_state, is_(VmPowerState.STOPPED)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Make sure get_vm_in_cache works vm_from_cache = self.vim_client.get_vm_in_cache(vm_id) assert_that(vm_from_cache.name, is_(vm_id)) self.assertRaises(VmNotFoundException, self.vim_client.get_vm_in_cache, "missing") # Add disk disk2_path = "[%s] %s/disk2.vmdk" % (datastore, vm_id) update_spec = self.get_update_spec(vm, disk2_path) task = vm.ReconfigVM_Task(update_spec.get_spec()) self.vim_client.wait_for_task(task) # For the ReconfigVM task to remove disk, the hostd could update # task status to success before updating VM status. Thus when # wait_for_task returns, the vm_cache is possible to be still in old # state, though eventually it converges to consistent state. It only # happens in this task AFAIK. It should be fine for this task, because # rarely there is other operation that depends on this task. self._wait_vm_has_disk(vm_id, 2) # Verify disk added vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms[0].disks), is_(2)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path, disk2_path)) # Remove disk vm = self.vim_client.get_vm(vm_id) remove_spec = self.get_remove_spec(vm, disk2_path) task = vm.ReconfigVM_Task(remove_spec.get_spec()) self.vim_client.wait_for_task(task) # Same as before when disk is added self._wait_vm_has_disk(vm_id, 1) # Verify disk removed vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(len(found_vms[0].disks), is_(1), "disk2 in " + str(found_vms[0].disks)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path)) # Power on vm task = vm.PowerOn() self.vim_client.wait_for_task(task) # Wait until it disappears from the cache self._wait_vm_power_status(vm_id, VmPowerState.STARTED) # Verify VM state in cache is updated vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].power_state, is_(VmPowerState.STARTED)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Destroy VM task = vm.PowerOff() self.vim_client.wait_for_task(task) task = vm.Destroy() self.vim_client.wait_for_task(task) # Verify VM is deleted from cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(0)) def test_no_datastore_update(self): """ Test datastore update is no longer triggered on VM creates/deletes """ class UpdateListener(object): def __init__(self): self._ds_update_count = 0 def datastores_updated(self): self._ds_update_count += 1 listener = UpdateListener() self.vim_client.add_update_listener(listener) # listener always gets updated once on add assert_that(listener._ds_update_count, is_(1)) mock_apply = MagicMock(wraps=self.vim_client._vim_cache._update_ds_cache) self.vim_client._vim_cache._update_ds_cache = mock_apply _, vm, _, _ = self._create_test_vm("ds-update-test") task = vm.Destroy() self.vim_client.wait_for_task(task) # expect to get a datastore property update (unfortunately) ... for _ in xrange(50): if mock_apply.call_count > 0: break time.sleep(0.1) # ... but that additional datastore updated notifications are sent out # as a result assert_that(listener._ds_update_count, is_(1)) def get_create_spec(self, datastore, vm_id, disk_path): create_spec = EsxVmConfigSpec(None) create_spec.init_for_create(vm_id, datastore, 64, 2) create_spec._cfg_spec.files = vim.vm.FileInfo(vmPathName="[%s] /" % datastore) controller = vim.vm.device.VirtualLsiLogicController( key=1, sharedBus=vim.vm.device.VirtualSCSIController.Sharing.noSharing, busNumber=2, unitNumber=-1) create_spec._add_device(controller) backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent ) disk = vim.vm.device.VirtualDisk( controllerKey=1, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) create_spec._create_device(disk) return create_spec def get_update_spec(self, vm_info, disk_path): update_spec = EsxVmConfigSpec(None) update_spec.init_for_update() backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent ) controller = update_spec._find_scsi_controller(vm_info.config) disk = vim.vm.device.VirtualDisk( controllerKey=controller.key, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) update_spec._create_device(disk) return update_spec def get_remove_spec(self, vm_info, disk_path): remove_spec = EsxVmConfigSpec(None) remove_spec.init_for_update() devices = remove_spec._get_devices_by_type(vm_info.config, vim.vm.device.VirtualDisk) found_device = None for device in devices: if device.backing.fileName.endswith(disk_path): found_device = device remove_spec._remove_device(found_device) return remove_spec def test_clone_ticket(self): ticket = self.vim_client.get_vim_ticket() vim_client2 = VimClient() vim_client2.connect_ticket(self.host, ticket) vim_client2.host_system() def _wait_vm_has_disk(self, vm_id, disk_num): """Wait until the vm has disk number of the vm becomes disk_num """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if len(vm_in_cache.disks) == disk_num: self._logger.info("VmCache disk number synced in %.2f second" % (time.time() - now)) break time.sleep(0.1) def _wait_vm_power_status(self, vm_id, power_state): """Wait until the vm has power_state """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if vm_in_cache.power_state == power_state: self._logger.info("VmCache power_state synced in %.2f second" % (time.time() - now)) break time.sleep(0.1)
class ImageScannerVmTestCase(unittest.TestCase): DATASTORE_ID = "DS01" BASE_TEMP_DIR = "image_scanner" @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): # Create VM manager creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.vim_client.wait_for_task = MagicMock() self.patcher = patch("host.hypervisor.esx.vm_config.GetEnv") self.patcher.start() self.vm_manager = EsxVmManager(self.vim_client, MagicMock()) services.register(ServiceName.AGENT_CONFIG, MagicMock()) # Set up test files self.base_dir = os.path.dirname(__file__) self.test_dir = os.path.join(self.base_dir, "../../test_files") self.image_manager = EsxImageManager(MagicMock(), MagicMock()) self.image_scanner = DatastoreImageScanner(self.image_manager, self.vm_manager, self.DATASTORE_ID) self.write_count = 0 def tearDown(self): self.patcher.stop() self.vim_client.disconnect(wait=True) @patch("host.hypervisor.image_scanner.DatastoreImageScanner.is_stopped", return_value=False) def test_vm_scan(self, is_stopped): self.image_scanner.vm_scan_rate = 60000 dictionary = self.image_scanner._task_runner._scan_vms_for_active_images( self.image_scanner, self.test_dir + "/vm_*") assert_that(len(dictionary) is 1) assert_that(dictionary["92e62599-6689-4a8f-ba2a-633914b5048e"] == "/vmfs/volumes/555ca9f8-9f24fa2c-41c1-0025b5414043/" "image_92e62599-6689-4a8f-ba2a-633914b5048e/92e" "62599-6689-4a8f-ba2a-633914b5048e.vmdk") @patch("host.hypervisor.image_scanner.DatastoreImageScanner.is_stopped", return_value=False) def test_vm_scan_bad_root(self, is_stopped): self.image_scanner.vm_scan_rate = 60000 bad_dir = os.path.join(self.base_dir, "test_files", "vm_bad") dictionary = self.image_scanner._task_runner._scan_vms_for_active_images(self.image_scanner, bad_dir) assert_that(len(dictionary) is 0) @patch("host.hypervisor.image_scanner.DatastoreImageScanner.is_stopped", return_value=False) def test_vm_scan_bad_vmdk(self, is_stopped): self.image_scanner.vm_scan_rate = 60000 bad_dir = os.path.join(self.base_dir, "test_files", "vm_bad") dictionary = self.image_scanner._task_runner._scan_vms_for_active_images(self.image_scanner, bad_dir) assert_that(len(dictionary) is 0) @patch("host.hypervisor.image_scanner.DatastoreImageScanner.is_stopped", return_value=False) @patch("host.hypervisor.image_scanner.waste_time") def test_vm_scan_rate(self, waste_time, is_stopped): waste_time.side_effect = self.fake_waste_time # fake activation self.image_scanner.vm_scan_rate = 30 dictionary = self.image_scanner._task_runner._scan_vms_for_active_images( self.image_scanner, self.test_dir + "/vm_*") assert_that(len(dictionary) is 1) assert_that(dictionary["92e62599-6689-4a8f-ba2a-633914b5048e"] == "/vmfs/volumes/555ca9f8-9f24fa2c-41c1-0025b5414043/" "image_92e62599-6689-4a8f-ba2a-633914b5048e/92e" "62599-6689-4a8f-ba2a-633914b5048e.vmdk") def fake_waste_time(self, seconds): assert_that((seconds > 1.0) is True)
class TestVimClient(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self.vim_client = VimClient(self.host, "root", self.pwd, auto_sync=True) self.vm_config = EsxVmConfig(self.vim_client) self._logger = logging.getLogger(__name__) def tearDown(self): self.vim_client.disconnect(wait=True) def test_memory_usage(self): used_memory = self.vim_client.memory_usage_mb assert_that(used_memory > 0, is_(True)) def test_total_memory(self): total_memory = self.vim_client.total_vmusable_memory_mb assert_that(total_memory > 0, is_(True)) def test_total_cpus(self): num_cpus = self.vim_client.num_physical_cpus assert_that(num_cpus > 0, is_(True)) def _create_test_vm(self, suffix="host-integ"): # Create VM vm_id = "vm_%s-%s-%s" % ( time.strftime("%Y-%m-%d-%H%M%S", time.localtime()), str(random.randint(100000, 1000000)), suffix) datastore = self.vim_client.get_datastore().name disk_path = "[%s] %s/disk.vmdk" % (datastore, vm_id) create_spec = self.get_create_spec(datastore, vm_id, disk_path) folder = self.vim_client.vm_folder resource_pool = self.vim_client.root_resource_pool task = folder.CreateVm(create_spec, resource_pool, None) self.vim_client.wait_for_task(task) vm = self.vim_client.get_vm(vm_id) return (vm_id, vm, datastore, disk_path) def test_get_cached_vm(self): vm_id, vm, datastore, disk_path = self._create_test_vm("vm-cache-test") # Verify VM is in cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].power_state, is_(PowerState.poweredOff)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Make sure get_vm_in_cache works vm_from_cache = self.vim_client.get_vm_in_cache(vm_id) assert_that(vm_from_cache.name, is_(vm_id)) self.assertRaises(VmNotFoundException, self.vim_client.get_vm_in_cache, "missing") # Add disk disk2_path = "[%s] %s/disk2.vmdk" % (datastore, vm_id) update_spec = self.get_update_spec(vm, disk2_path) task = vm.ReconfigVM_Task(update_spec) self.vim_client.wait_for_task(task) # For the ReconfigVM task to remove disk, the hostd could update # task status to success before updating VM status. Thus when # wait_for_task returns, the vm_cache is possible to be still in old # state, though eventually it converges to consistent state. It only # happens in this task AFAIK. It should be fine for this task, because # rarely there is other operation that depends on this task. self._wait_vm_has_disk(vm_id, 2) # Verify disk added vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms[0].disks), is_(2)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path, disk2_path)) # Remove disk vm = self.vim_client.get_vm(vm_id) remove_spec = self.get_remove_spec(vm, disk2_path) task = vm.ReconfigVM_Task(remove_spec) self.vim_client.wait_for_task(task) # Same as before when disk is added self._wait_vm_has_disk(vm_id, 1) # Verify disk removed vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(len(found_vms[0].disks), is_(1), "disk2 in " + str(found_vms[0].disks)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path)) # Power on vm task = vm.PowerOn() self.vim_client.wait_for_task(task) # Wait until it disappears from the cache self._wait_vm_power_status(vm_id, PowerState.poweredOn) # Verify VM state in cache is updated vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].power_state, is_(PowerState.poweredOn)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Destroy VM task = vm.PowerOff() self.vim_client.wait_for_task(task) task = vm.Destroy() self.vim_client.wait_for_task(task) # Verify VM is deleted from cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(0)) def test_no_datastore_update(self): """ Test datastore update is no longer triggered on VM creates/deletes """ class UpdateListener(object): def __init__(self): self._ds_update_count = 0 def datastores_updated(self): self._ds_update_count += 1 def networks_updated(self): pass def virtual_machines_updated(self): pass listener = UpdateListener() self.vim_client.add_update_listener(listener) # listener always gets updated once on add assert_that(listener._ds_update_count, is_(1)) mock_apply = MagicMock(wraps=self.vim_client._apply_ds_update) self.vim_client._apply_ds_update = mock_apply _, vm, _, _ = self._create_test_vm("ds-update-test") task = vm.Destroy() self.vim_client.wait_for_task(task) # expect to get a datastore property update (unfortunately) ... for _ in xrange(50): if mock_apply.call_count > 0: break time.sleep(0.1) # ... but that additional datastore updated notifications are sent out # as a result assert_that(listener._ds_update_count, is_(1)) def get_create_spec(self, datastore, vm_id, disk_path): create_spec = vim.vm.ConfigSpec( name=vm_id, guestId="otherGuest", memoryMB=64, numCPUs=2, files=vim.vm.FileInfo(vmPathName="[%s] /" % datastore), deviceChange=[], ) controller = vim.vm.device.VirtualLsiLogicController( key=1, sharedBus=vim.vm.device.VirtualSCSIController.Sharing.noSharing, busNumber=2, unitNumber=-1) self.vm_config.add_device(create_spec, controller) backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent ) disk = vim.vm.device.VirtualDisk( controllerKey=1, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) self.vm_config.create_device(create_spec, disk) return create_spec def get_update_spec(self, vm_info, disk_path): update_spec = vim.vm.ConfigSpec() backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent ) controller = \ self.vm_config._find_scsi_controller(update_spec, vm_info.config) disk = vim.vm.device.VirtualDisk( controllerKey=controller.key, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) self.vm_config.create_device(update_spec, disk) return update_spec def get_remove_spec(self, vm_info, disk_path): remove_spec = vim.vm.ConfigSpec() devices = self.vm_config.get_devices_from_config(vm_info.config) found_device = None for device in devices: if isinstance(device, vim.vm.device.VirtualDisk) and \ device.backing.fileName.endswith(disk_path): found_device = device self.vm_config.remove_device(remove_spec, found_device) return remove_spec def test_clone_ticket(self): ticket = self.vim_client.acquire_clone_ticket() vim_client2 = VimClient(host=self.host, ticket=ticket) vim_client2.host_system def test_http_ticket(self): datastore = self.vim_client.get_datastore().name filename = "%s.bin" % str(uuid.uuid4()) quoted_dc_name = 'ha%252ddatacenter' url = 'https://%s/folder/%s?dcPath=%s&dsName=%s' % ( self.host, filename, quoted_dc_name, datastore) ticket = self.vim_client.acquire_cgi_ticket(url, HttpOp.PUT) assert_that(ticket, is_not(equal_to(None))) def test_host_stats(self): """ Skip host stats test. This test does not agree with the contract exposed from the implementation. Until the vim_client code be refactor/cleanup, disable this test for now. """ raise SkipTest() self.vim_client.initialize_host_counters() self.vim_client.update_hosts_stats() stats = self.vim_client.get_host_stats() assert_that(has_key('mem.consumed')) assert_that(stats['mem.consumed'], greater_than(0)) assert_that(has_key('rescpu.actav1')) assert_that(stats['rescpu.actav1'], greater_than(0)) def _wait_vm_has_disk(self, vm_id, disk_num): """Wait until the vm has disk number of the vm becomes disk_num """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if len(vm_in_cache.disks) == disk_num: self._logger.info("VmCache disk number synced in %.2f second" % (time.time() - now)) break time.sleep(0.1) def _wait_vm_power_status(self, vm_id, power_state): """Wait until the vm has power_state """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if vm_in_cache.power_state == power_state: self._logger.info("VmCache power_state synced in %.2f second" % (time.time() - now)) break time.sleep(0.1)
class TestHttpTransfer(unittest.TestCase): """Http Transferer tests.""" @patch.object(VimClient, "acquire_credentials") @patch("pysdk.connect.Connect") def setUp(self, connect, creds): self.shadow_vm_id = SHADOW_VM_NAME_PREFIX + str(uuid.uuid1()) self.image_datastores = ["image_ds", "alt_image_ds"] creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.patcher = patch("host.hypervisor.esx.vm_config.GetEnv") self.patcher.start() services.register(ServiceName.AGENT_CONFIG, MagicMock()) self.http_transferer = HttpNfcTransferer(self.vim_client, self.image_datastores) def tearDown(self): self.vim_client.disconnect(wait=True) self.patcher.stop() @parameterized.expand([ (True,), (False,) ]) @patch("host.hypervisor.esx.http_disk_transfer.VimClient") @patch("host.hypervisor.esx.http_disk_transfer.DirectClient") def test_get_remote_connections(self, get_svc_ticket_success, _client_cls, _vim_client_cls): host = "mock_host" port = 8835 get_service_ticket_mock = MagicMock() if get_svc_ticket_success: get_service_ticket_mock.result = ServiceTicketResultCode.OK else: get_service_ticket_mock.result = ServiceTicketResultCode.NOT_FOUND instance = _client_cls.return_value instance.get_service_ticket.return_value = get_service_ticket_mock if get_svc_ticket_success: (agent_conn, vim_conn) = self.http_transferer._get_remote_connections( host, port) _client_cls.assert_called_once_with( "Host", Host.Client, host, port) instance.connect.assert_called_once_with() request = ServiceTicketRequest(service_type=ServiceType.VIM) instance.get_service_ticket.assert_called_once_with(request) _vim_client_cls.assert_called_once_with( host=host, ticket=get_service_ticket_mock.vim_ticket, auto_sync=False) self.assertEqual(agent_conn, instance) self.assertEqual(vim_conn, _vim_client_cls.return_value) else: self.assertRaises( ValueError, self.http_transferer._get_remote_connections, host, port) @parameterized.expand([ (None, "http://*/ha-nfc/x.vmdk", "http://actual_host/ha-nfc/x.vmdk"), (None, "https://*/foo", "https://actual_host/foo"), (None, "http://*:1234/foo", "http://*****:*****@patch("uuid.uuid4", return_value="fake_id") @patch("pyVmomi.vim.vm.VmImportSpec") def test_create_import_vm_spec(self, mock_vm_imp_spec_cls, mock_uuid): image_id = "fake_image_id" destination_datastore = "fake_datastore" create_spec_mock = MagicMock() create_empty_disk_mock = MagicMock() xferer = self.http_transferer xferer._vm_config.create_spec_for_import = create_spec_mock xferer._vm_manager.create_empty_disk = create_empty_disk_mock spec = xferer._create_import_vm_spec(image_id, destination_datastore) expected_vm_id = "fake_id" create_spec_mock.assert_called_once_with( vm_id=expected_vm_id, image_id=image_id, datastore=destination_datastore, memory=32, cpus=1) create_empty_disk_mock.assert_called_once_with( create_spec_mock.return_value, destination_datastore, None, size_mb=1) mock_vm_imp_spec_cls.assert_called_once_with( configSpec=create_empty_disk_mock.return_value) self.assertEqual(spec, mock_vm_imp_spec_cls.return_value) def test_get_url_from_import_vm(self): host = "mock_host" lease_mock = MagicMock() url_mock = MagicMock() import_spec = MagicMock() rp_mock = MagicMock() folder_mock = MagicMock() vim_client_mock = MagicMock() vim_client_mock.host = host xferer = self.http_transferer xferer._wait_for_lease = MagicMock() xferer._get_disk_url_from_lease = MagicMock(return_value=url_mock) xferer._ensure_host_in_url = MagicMock(return_value=url_mock) vim_client_mock.root_resource_pool = rp_mock vim_client_mock.vm_folder = folder_mock rp_mock.ImportVApp.return_value = lease_mock lease, url = xferer._get_url_from_import_vm(vim_client_mock, import_spec) rp_mock.ImportVApp.assert_called_once_with( import_spec, folder_mock) xferer._wait_for_lease.assert_called_once_with( lease_mock) xferer._get_disk_url_from_lease.assert_called_once_with(lease_mock) xferer._ensure_host_in_url.assert_called_once_with(url_mock, host) self.assertEqual(lease, lease_mock) self.assertEqual(url, url_mock) @patch("os.path.exists") @patch("__builtin__.open") @patch("os.unlink") def test_send_image_to_host(self, mock_unlink, mock_open, mock_exists): host = "mock_host" port = 8835 image_id = "fake_image_id" image_datastore = "fake_image_ds" destination_datastore = "fake_destination_image_ds" read_lease_mock = MagicMock() from_url_mock = MagicMock() write_lease_mock = MagicMock() to_url_mock = MagicMock() import_spec_mock = MagicMock() xferer = self.http_transferer file_contents = ["fake_metadata", "fake_manifest"] def fake_read(): return file_contents.pop() mock_open().__enter__().read.side_effect = fake_read mock_exists.return_value = True xferer._create_shadow_vm = MagicMock( return_value=self.shadow_vm_id) xferer._delete_shadow_vm = MagicMock() xferer._get_image_stream_from_shadow_vm = MagicMock( return_value=(read_lease_mock, from_url_mock)) xferer.download_file = MagicMock() vim_conn_mock = MagicMock() agent_conn_mock = MagicMock() xferer._get_remote_connections = MagicMock( return_value=(agent_conn_mock, vim_conn_mock)) xferer._create_import_vm_spec = MagicMock( return_value=import_spec_mock) xferer._get_url_from_import_vm = MagicMock( return_value=(write_lease_mock, to_url_mock)) xferer.upload_file = MagicMock() receive_image_resp_mock = MagicMock() receive_image_resp_mock.result = ReceiveImageResultCode.OK agent_conn_mock.receive_image.return_value = receive_image_resp_mock self.http_transferer.send_image_to_host( image_id, image_datastore, None, destination_datastore, host, port) assert_that(agent_conn_mock.receive_image.call_args_list, has_length(1)) request = agent_conn_mock.receive_image.call_args_list[0][0][0] assert_that(request.metadata, equal_to("fake_metadata")) assert_that(request.manifest, equal_to("fake_manifest")) xferer._get_image_stream_from_shadow_vm.assert_called_once_with( image_id, image_datastore, self.shadow_vm_id) expected_tmp_file = "/vmfs/volumes/%s/%s_transfer.vmdk" % ( self.image_datastores[0], self.shadow_vm_id) xferer.download_file.assert_called_once_with( from_url_mock, expected_tmp_file, read_lease_mock) read_lease_mock.Complete.assert_called_once_with() xferer._create_import_vm_spec.assert_called_once_with( image_id, destination_datastore) xferer._get_url_from_import_vm.assert_called_once_with( vim_conn_mock, import_spec_mock) xferer.upload_file.assert_called_once_with( expected_tmp_file, to_url_mock, write_lease_mock) write_lease_mock.Complete.assert_called_once_with() xferer._delete_shadow_vm.assert_called_once_with( self.shadow_vm_id) mock_unlink.assert_called_once_with(expected_tmp_file)
class TestHttpTransfer(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] self.agent_port = config["host_remote_test"].get("agent_port", 8835) if self.host is None or self.pwd is None: raise SkipTest() self.image_datastore = config["host_remote_test"].get( "image_datastore", "datastore1") self._logger = logging.getLogger(__name__) self.vim_client = VimClient(self.host, "root", self.pwd) self.http_transferer = HttpNfcTransferer(self.vim_client, [self.image_datastore], self.host) with tempfile.NamedTemporaryFile(delete=False) as source_file: with open(source_file.name, 'wb') as f: f.write(os.urandom(1024 * 100)) self.random_file = source_file.name self.remote_files_to_delete = [] def _cleanup_remote_files(self): file_manager = self.vim_client._content.fileManager for ds_path in self.remote_files_to_delete: try: delete_task = file_manager.DeleteFile(ds_path, None) task.WaitForTask(delete_task) except: pass def tearDown(self): os.unlink(self.random_file) self._cleanup_remote_files() self.vim_client.disconnect(wait=True) def _remote_ds_path(self, ds, relpath): return '[%s] %s' % (ds, relpath) def _datastore_path_url(self, datastore, relpath): quoted_dc_name = 'ha%252ddatacenter' url = 'https://%s/folder/%s?dcPath=%s&dsName=%s' % ( self.host, relpath, quoted_dc_name, datastore) return url def test_download_missing_file(self): url = self._datastore_path_url(self.image_datastore, "_missing_file_.bin") ticket = self.http_transferer._get_cgi_ticket( self.host, self.agent_port, url, http_op=HttpOp.GET) with tempfile.NamedTemporaryFile(delete=True) as local_file: self.assertRaises(TransferException, self.http_transferer.download_file, url, local_file.name, MagicMock(), ticket=ticket) def test_upload_file_bad_destination(self): url = self._datastore_path_url("_missing__datastore_", "random.bin") ticket = self.http_transferer._get_cgi_ticket( self.host, self.agent_port, url, http_op=HttpOp.PUT) self.assertRaises( TransferException, self.http_transferer.upload_file, self.random_file, url, MagicMock(), ticket=ticket) def test_raw_file_transfer_roundtrip(self): relpath = "_test_http_xfer_random.bin" url = self._datastore_path_url(self.image_datastore, relpath) ticket = self.http_transferer._get_cgi_ticket( self.host, self.agent_port, url, http_op=HttpOp.PUT) self.http_transferer.upload_file(self.random_file, url, MagicMock(), ticket=ticket) self.remote_files_to_delete.append( self._remote_ds_path(self.image_datastore, relpath)) ticket = self.http_transferer._get_cgi_ticket( self.host, self.agent_port, url, http_op=HttpOp.GET) with tempfile.NamedTemporaryFile(delete=True) as downloaded_file: self.http_transferer.download_file(url, downloaded_file.name, MagicMock(), ticket=ticket) # check that file uploaded and immediately downloaded back is # identical to the source file used. assert_that( filecmp.cmp(self.random_file, downloaded_file.name, shallow=False), is_(True)) @patch('os.path.exists', return_value=True) def test_get_streamoptimized_image_stream(self, _exists): image_id = "ttylinux" shadow_vm_id = self.http_transferer._create_shadow_vm() lease, url = self.http_transferer._get_image_stream_from_shadow_vm( image_id, self.image_datastore, shadow_vm_id) try: with tempfile.NamedTemporaryFile(delete=True) as downloaded_file: # see if we can download without errors self.http_transferer.download_file(url, downloaded_file.name, lease) # check that the first part of the file looks like that from a # stream-optimized disk with open(downloaded_file.name, 'rb') as f: data = f.read(65536) assert_that(len(data), is_(65536)) regex = re.compile("streamOptimized", re.IGNORECASE | re.MULTILINE) matches = regex.findall(data) assert_that(matches, not(empty())) finally: lease.Complete()
class TestEsxVmManager(unittest.TestCase): @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.vim_client.wait_for_task = MagicMock() self.patcher = patch("host.hypervisor.esx.vm_config.GetEnv") self.patcher.start() self.vm_manager = EsxVmManager(self.vim_client, MagicMock()) def tearDown(self): self.patcher.stop() self.vim_client.disconnect(wait=True) def test_power_vm_not_found(self): """Test that we propagate VmNotFound.""" self.vim_client.find_by_inventory_path = MagicMock(return_value=None) self.assertRaises(VmNotFoundException, self.vm_manager.power_on_vm, "ENOENT") def test_power_vm_illegal_state(self): """Test InvalidPowerState propagation.""" vm_mock = MagicMock(name="vm_mock") self.vm_manager.vim_client.get_vm = vm_mock self.vim_client.wait_for_task.side_effect = \ vim.fault.InvalidPowerState() self.assertRaises(VmPowerStateException, self.vm_manager.power_on_vm, "foo") def test_power_vm_error(self): """Test general Exception propagation.""" vm_mock = MagicMock(name="vm_mock") self.vm_manager.vim_client.get_vm = vm_mock self.vim_client.wait_for_task.side_effect = vim.fault.TaskInProgress self.assertRaises(vim.fault.TaskInProgress, self.vm_manager.power_on_vm, "foo") def test_add_nic(self): """Test add nic""" # 3 cases for add_nic: # * caller passes in network_id = None # * caller passes in the correct network_id and hostd # returns the right thing from get_network. def _get_device(devices, controller_type): f = MagicMock("get_device_foo") f.key = 1 return f self.vm_manager.vm_config.get_device = _get_device spec = self.vm_manager.vm_config.update_spec() # Caller passes none self.vm_manager.add_nic(spec, None) # Caller passes some network_id self.vm_manager.add_nic(spec, "Private Vlan") def test_create_vm_already_exist(self): """Test VM creation fails if VM is found""" vim_mock = MagicMock() self.vm_manager.vim_client = vim_mock vim_mock.find_vm = MagicMock(return_value="existing_vm") mock_spec = MagicMock() self.assertRaises(VmAlreadyExistException, self.vm_manager.create_vm, "existing_vm_name", mock_spec) def test_create_vm(self): """Test VM creation""" vim_mock = MagicMock() self.vm_manager.vim_client = vim_mock vm_folder_mock = MagicMock() vim_mock.vm_folder = vm_folder_mock root_res_pool_mock = PropertyMock(return_value="fake_rp") type(vim_mock).root_resource_pool = root_res_pool_mock vim_mock.get_vm_in_cache = MagicMock(return_value=None) vm_folder_mock.CreateVm.return_value = "fake-task" mock_spec = MagicMock() mock_spec.files.vmPathName = "[] /vmfs/volumes/ds/vms" self.vm_manager.create_vm("fake_vm_id", mock_spec) vim_mock.get_vm_in_cache.assert_called_once_with("fake_vm_id") vm_folder_mock.CreateVm.assert_called_once_with( mock_spec, 'fake_rp', None) vim_mock.wait_for_task.assert_called_once_with("fake-task") @staticmethod def _validate_spec_extra_config(spec, config, expected): """Validates the config entries against the created config spec when expected=True, returns True iff all the entries in config are found in the config spec's extraConfig when expected=False, returns True iff all the entries in config are not found in the config spec's extraConfig """ for k, v in config.items(): in_spec = any( (x.key == k and x.value == v) for x in spec.extraConfig) if in_spec is not expected: return False return True def _create_vm_spec(self, metadata, env): """Test VM spec creation""" flavor = Flavor("default", [ QuotaLineItem("vm.memory", "256", Unit.MB), QuotaLineItem("vm.cpu", "1", Unit.COUNT), ]) create_spec_mock = MagicMock( wraps=self.vm_manager.vm_config.create_spec) self.vm_manager.vm_config.create_spec = create_spec_mock spec = self.vm_manager.create_vm_spec("vm_id", "ds1", flavor, metadata, env) create_spec_mock.assert_called_once_with("vm_id", "ds1", 256, 1, metadata, env) return spec def test_create_vm_spec(self): metadata = { "configuration": {}, "parameters": [{ "name": "bios.bootOrder" }] } extra_config_metadata = {} non_extra_config_metadata = { "scsi0.virtualDev": "lsisas1068", "bogus": "1" } metadata["configuration"].update(extra_config_metadata) metadata["configuration"].update(non_extra_config_metadata) env = {"disallowed_key": "x", "bios.bootOrder": "x"} spec = self._create_vm_spec(metadata, env) expected_extra_config = extra_config_metadata.copy() expected_extra_config["bios.bootOrder"] = "x" self.assertTrue( TestEsxVmManager._validate_spec_extra_config( spec, config=expected_extra_config, expected=True)) self.assertTrue( TestEsxVmManager._validate_spec_extra_config( spec, config=non_extra_config_metadata, expected=False)) assert_that(spec.flags.diskUuidEnabled, equal_to(True)) def test_customize_vm_with_metadata(self): metadata = { "configuration": { "annotation": "fake_annotation", "serial0.fileType": "network", "serial0.yieldOnMsrRead": "TRUE", "serial0.network.endPoint": "server" }, "parameters": [{ "name": "serial0.fileName" }, { "name": "serial0.vspc" }] } env = { "serial0.fileName": "vSPC.py", "serial0.vspc": "telnet://1.2.3.4:17000", } spec = self._create_vm_spec(metadata, env) self.vm_manager.customize_vm(spec) assert_that(spec.annotation, equal_to("fake_annotation")) backing = spec.deviceChange[0].device.backing assert_that( backing, instance_of(vim.vm.device.VirtualSerialPort.URIBackingInfo)) assert_that(backing.serviceURI, equal_to('vSPC.py')) assert_that(backing.proxyURI, equal_to('telnet://1.2.3.4:17000')) assert_that(backing.direction, equal_to('server')) @staticmethod def _summarize_controllers_in_spec(cfg_spec, base_type, expected_type): num_scsi_adapters_matching_expected_type = 0 num_scsi_adapters_not_matching_expected_type = 0 for dev_change in cfg_spec.deviceChange: dev = dev_change.device if isinstance(dev, expected_type): num_scsi_adapters_matching_expected_type += 1 elif (isinstance(dev, base_type) and not isinstance(dev, expected_type)): num_scsi_adapters_not_matching_expected_type += 1 return (num_scsi_adapters_matching_expected_type, num_scsi_adapters_not_matching_expected_type) @parameterized.expand([ ("lsilogic", vim.vm.device.VirtualLsiLogicController), ("lsisas1068", vim.vm.device.VirtualLsiLogicSASController), ("pvscsi", vim.vm.device.ParaVirtualSCSIController), ("buslogic", vim.vm.device.VirtualBusLogicController) ]) def test_customize_disk_adapter_type(self, ctlr_type_value, expected_ctlr_type): metadata = {"configuration": {"scsi0.virtualDev": ctlr_type_value}} spec = self._create_vm_spec(metadata, {}) ds = "fake_ds" disk_id = str(uuid.uuid4()) parent_disk_id = str(uuid.uuid4()) capacity_mb = 1024 self.vm_manager.create_child_disk(spec, ds, disk_id, parent_disk_id) self.vm_manager.create_empty_disk(spec, ds, disk_id, capacity_mb) # check that we only create one controller of desired type to attach # to both disks summary = TestEsxVmManager._summarize_controllers_in_spec( spec, vim.vm.device.VirtualSCSIController, expected_ctlr_type) assert_that(summary, equal_to((1, 0))) @parameterized.expand([ ("vmxnet", vim.vm.device.VirtualVmxnet), ("vmxnet2", vim.vm.device.VirtualVmxnet2), ("vmxnet3", vim.vm.device.VirtualVmxnet3), ("vlance", vim.vm.device.VirtualPCNet32), ("e1000", vim.vm.device.VirtualE1000), ("e1000e", vim.vm.device.VirtualE1000e), ]) @patch.object(VimClient, "get_network") def test_customize_nic_adapter_type(self, ctlr_type_value, expected_ctlr_type, mock_get_network): metadata = {"configuration": {"ethernet0.virtualDev": ctlr_type_value}} spec = self._create_vm_spec(metadata, {}) fake_network = MagicMock() fake_network.name = "fake_network_name" mock_get_network.return_value = fake_network self.vm_manager.add_nic(spec, "fake_network_id") summary = TestEsxVmManager._summarize_controllers_in_spec( spec, vim.vm.device.VirtualEthernetCard, expected_ctlr_type) assert_that(summary, equal_to((1, 0))) @parameterized.expand([('a.txt', 'Stray file: a.txt'), ('b.vmdk', 'Stray disk (possible data leak): b.vmdk')]) @patch.object(os.path, "isdir", return_value=True) @patch.object(os.path, "islink", return_value=False) @patch.object(shutil, "rmtree") def test_ensure_directory_cleanup(self, stray_file, expected, rmtree, islink, isdir): """Test cleanup of stray vm directory""" self.vm_manager._logger = MagicMock() with patch.object(os, "listdir", return_value=[stray_file]): self.vm_manager._ensure_directory_cleanup( "/vmfs/volumes/fake/vm_vm_foo") rmtree.assert_called_once_with("/vmfs/volumes/fake/vm_vm_foo") self.vm_manager._logger.info.assert_called_once_with(expected) self.vm_manager._logger.warning.assert_called_once_with( "Force delete vm directory /vmfs/volumes/fake/vm_vm_foo") def test_delete_vm(self): """Test deleting a VM""" runtime = MagicMock() runtime.powerState = "poweredOff" vm = MagicMock() vm.runtime = runtime self.vm_manager.vim_client.get_vm = MagicMock(return_value=vm) self.vm_manager.vm_config.get_devices = MagicMock(return_value=[]) self.vm_manager.get_vm_path = MagicMock() self.vm_manager.get_vm_path.return_value = "[fake] vm_foo/xxyy.vmx" self.vm_manager.get_vm_datastore = MagicMock() self.vm_manager.get_vm_datastore.return_value = "fake" self.vm_manager._ensure_directory_cleanup = MagicMock() self.vm_manager.delete_vm("vm_foo") self.vm_manager._ensure_directory_cleanup.assert_called_once_with( "/vmfs/volumes/fake/vm_vm_foo") @parameterized.expand([("poweredOn"), ("suspended")]) def test_delete_vm_wrong_state(self, state): runtime = MagicMock() runtime.powerState = state vm = MagicMock() vm.runtime = runtime self.vm_manager.vim_client.get_vm = MagicMock(return_value=vm) self.assertRaises(VmPowerStateException, self.vm_manager.delete_vm, "vm_foo") def test_add_vm_disk(self): """Test adding VM disk""" self.vm_manager.vim_client.get_vm = MagicMock() self.vm_manager.vm_config.get_devices = MagicMock( return_value=[DEFAULT_DISK_CONTROLLER_CLASS(key=1000)]) info = FakeConfigInfo() spec = self.vm_manager.vm_config.update_spec() self.vm_manager.add_disk(spec, "ds1", "vm_foo", info) def test_used_memory(self): self.vm_manager.vim_client.get_vms_in_cache = MagicMock(return_value=[ VmCache(memory_mb=1024), VmCache(), VmCache(memory_mb=2048) ]) memory = self.vm_manager.get_used_memory_mb() self.assertEqual(memory, 2048 + 1024) def atest_remove_vm_disk(self): """Test removing VM disk""" datastore = "ds1" disk_id = "foo" self.vm_manager.vim_client.get_vm = MagicMock() self.vm_manager.vm_config.get_devices = MagicMock(return_value=[ vim.vm.device.VirtualLsiLogicController(key=1000), self.vm_manager.vm_config.create_disk_spec(datastore, disk_id) ]) info = FakeConfigInfo() self.vm_manager.remove_disk("vm_foo", datastore, disk_id, info) def btest_remove_vm_disk_enoent(self): """Test removing VM disk that isn't attached""" self.vm_manager.vim_client.get_vm = MagicMock() self.vm_manager.vm_config.get_devices = MagicMock(return_value=[ self.vm_manager.vm_config.create_disk_spec("ds1", "foo") ]) self.assertRaises(vim.fault.DeviceNotFound, self.vm_manager.remove_disk, "vm_foo", "ds1", "bar") def test_check_ip_v4(self): """Test to check ipv4 validation""" self.assertTrue(NetUtil.is_ipv4_address("1.2.3.4")) self.assertFalse( NetUtil.is_ipv4_address("FE80:0000:0000:0000:0202:B3FF:FE1E:8329")) self.assertFalse(NetUtil.is_ipv4_address("InvalidAddress")) def test_check_prefix_len_to_netmask_conversion(self): """Check the conversion from prefix length to netmask""" self.assertEqual(NetUtil.prefix_len_to_mask(32), "255.255.255.255") self.assertEqual(NetUtil.prefix_len_to_mask(0), "0.0.0.0") self.assertRaises(ValueError, NetUtil.prefix_len_to_mask, 33) self.assertEqual(NetUtil.prefix_len_to_mask(23), "255.255.254.0") self.assertEqual(NetUtil.prefix_len_to_mask(6), "252.0.0.0") self.assertEqual(NetUtil.prefix_len_to_mask(32), "255.255.255.255") def test_get_vm_network_guest_info(self): """ Tests the guest vm network info, without the vmx returned info. Test 1: Only mac address info available. Test 2: Only mac + ipv4 address available. Test 3: Only mac + ipv6 address available. Test 4: Only mac + ipv6, ipv4 address available. Test 5: No mac or ipv4 address available """ sample_mac_address = "00:0c:29:00:00:01" sample_ip_address = "127.0.0.2" sample_prefix_length = 24 sample_netmask = "255.255.255.0" sample_ipv6_address = "FE80:0000:0000:0000:0202:B3FF:FE1E:8329" sample_network = "VM Network" def _get_v4_address(): ip_address = MagicMock(name="ipv4address") ip_address.ipAddress = sample_ip_address ip_address.prefixLength = sample_prefix_length return ip_address def _get_v6_address(): ip_address = MagicMock(name="ipv6address") ip_address.ipAddress = sample_ipv6_address ip_address.prefixLength = sample_prefix_length return ip_address def _guest_info_1(): """ Only have the mac address. """ net = MagicMock(name="guest_info_1") net.macAddress = sample_mac_address net.connected = True net.network = None return net def _guest_info_2(): """ Have mac and ipv4 address """ net = MagicMock(name="guest_info_2") net.macAddress = sample_mac_address net.ipConfig.ipAddress = [_get_v4_address()] net.network = sample_network net.connected = False return net def _guest_info_3(): """ Have mac and ipv6 address """ net = MagicMock(name="guest_info_3") net.macAddress = sample_mac_address net.ipConfig.ipAddress = [_get_v6_address()] net.connected = False net.network = sample_network return net def _guest_info_4(): """ Have a mac and an ipv4 and an ipv6 address """ net = MagicMock(name="guest_info_4") net.macAddress = sample_mac_address net.network = None net.ipConfig.ipAddress = [_get_v6_address(), _get_v4_address()] net.connected = True return net def _get_vm_no_net_info(vm_id): """ Return empty guest_info """ f = MagicMock(name="get_vm") f.config.uuid = str(uuid.uuid4()) g = MagicMock(name="guest_info") f.guest = g g.net = [] return f def _get_vm(vm_id): """ Return a mocked up guest info object """ f = MagicMock(name="get_vm") g = MagicMock(name="guest_info") f.guest = g net = _guest_info() g.net = [net] return f def _get_vm_vim_guest_info(vm_id): """ Return a real Vim object with reasonable values to validate python typing """ f = MagicMock(name="get_vm") f.config.uuid = str(uuid.uuid4()) g = MagicMock(name="guest_info") f.guest = g net = vim.vm.GuestInfo.NicInfo() ip_config_info = vim.net.IpConfigInfo() net.ipConfig = ip_config_info net.macAddress = sample_mac_address net.network = sample_network net.connected = True ipAddress = vim.net.IpConfigInfo.IpAddress() ipAddress.ipAddress = sample_ip_address ipAddress.prefixLength = sample_prefix_length ip_config_info.ipAddress.append(ipAddress) g.net = [net] return f # Test 1 _guest_info = _guest_info_1 self.vm_manager.vim_client.get_vm = _get_vm self.vm_manager._get_mac_network_mapping = MagicMock(return_value={}) network_info = self.vm_manager.get_vm_network("vm_foo1") expected_1 = VmNetworkInfo(mac_address=sample_mac_address, is_connected=ConnectedStatus.CONNECTED) self.assertEqual(network_info, [expected_1]) # Test 2 _guest_info = _guest_info_2 network_info = self.vm_manager.get_vm_network("vm_foo2") ip_address = Ipv4Address(ip_address=sample_ip_address, netmask=sample_netmask) expected_2 = VmNetworkInfo(mac_address=sample_mac_address, ip_address=ip_address, network=sample_network, is_connected=ConnectedStatus.DISCONNECTED) self.assertEqual(network_info, [expected_2]) # Test 3 _guest_info = _guest_info_3 network_info = self.vm_manager.get_vm_network("vm_foo3") expected_3 = VmNetworkInfo(mac_address=sample_mac_address, network=sample_network, is_connected=ConnectedStatus.DISCONNECTED) self.assertEqual(network_info, [expected_3]) # Test 4 _guest_info = _guest_info_4 network_info = self.vm_manager.get_vm_network("vm_foo4") expected_4 = VmNetworkInfo(mac_address=sample_mac_address, ip_address=ip_address, is_connected=ConnectedStatus.CONNECTED) self.assertEqual(network_info, [expected_4]) # Test 5 self.vm_manager.vim_client.get_vm = _get_vm_no_net_info network_info = self.vm_manager.get_vm_network("vm_foo5") self.assertEqual(network_info, []) # Test 6 self.vm_manager.vim_client.get_vm = _get_vm_vim_guest_info network_info = self.vm_manager.get_vm_network("vm_foo5") expected_6 = VmNetworkInfo(mac_address=sample_mac_address, ip_address=ip_address, network=sample_network, is_connected=ConnectedStatus.CONNECTED) self.assertEqual(network_info, [expected_6]) def test_get_linked_clone_image_path(self): image_path = self.vm_manager.get_linked_clone_image_path # VM not found vm = MagicMock(return_value=None) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # disks is None vm = MagicMock(return_value=VmCache(disks=None)) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # disks is an empty list vm = MagicMock(return_value=VmCache(disks=[])) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # no image disk vm = MagicMock(return_value=VmCache(disks=["a", "b", "c"])) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(None)) # image found image = "[ds1] image_ttylinux/ttylinux.vmdk" vm = MagicMock(return_value=VmCache(disks=["a", "b", image])) self.vm_manager.vim_client.get_vm_in_cache = vm assert_that(image_path("vm1"), is_(datastore_to_os_path(image))) def test_set_vnc_port(self): flavor = Flavor("default", [ QuotaLineItem("vm.memory", "256", Unit.MB), QuotaLineItem("vm.cpu", "1", Unit.COUNT), ]) spec = self.vm_manager.create_vm_spec("vm_id", "ds1", flavor) self.vm_manager.set_vnc_port(spec, 5901) options = [ o for o in spec.extraConfig if o.key == 'RemoteDisplay.vnc.enabled' ] assert_that(options[0].value, equal_to('True')) options = [ o for o in spec.extraConfig if o.key == 'RemoteDisplay.vnc.port' ] assert_that(options[0].value, equal_to(5901)) @patch.object(VimClient, "get_vm") def test_get_vnc_port(self, get_vm): vm_mock = MagicMock() vm_mock.config.extraConfig = [ vim.OptionValue(key="RemoteDisplay.vnc.port", value="5901") ] get_vm.return_value = vm_mock port = self.vm_manager.get_vnc_port("id") assert_that(port, equal_to(5901)) def test_get_resources(self): """ Test that get_resources excludes VMs/disks if it can't find their corresponding datastore UUIDs. """ self.vm_manager.vim_client.get_vms_in_cache = MagicMock(return_value=[ VmCache(path="vm_path_a", disks=["disk_a", "disk_b", "disk_c"]), VmCache(path="vm_path_b", disks=["disk_b", "disk_c", "disk_d"]), VmCache(path="vm_path_c", disks=["disk_c", "disk_d", "disk_e"]), ]) def normalize(name): if name == "vm_path_b" or name == "disk_b": raise DatastoreNotFoundException() return name def mock_get_name(path): return path def mock_get_state(power_state): return State.STOPPED self.vm_manager._ds_manager.normalize.side_effect = normalize self.vm_manager._get_datastore_name_from_ds_path = mock_get_name self.vm_manager._power_state_to_resource_state = mock_get_state # vm_path_b and disk_b are not included in the get_resources response. resources = self.vm_manager.get_resources() assert_that(len(resources), equal_to(2)) assert_that(len(resources[0].disks), equal_to(2)) assert_that(len(resources[1].disks), equal_to(3)) @patch.object(VimClient, "get_vms") def test_get_occupied_vnc_ports(self, get_vms): get_vms.return_value = [ self._create_vm_mock(5900), self._create_vm_mock(5901) ] ports = self.vm_manager.get_occupied_vnc_ports() assert_that(ports, contains_inanyorder(5900, 5901)) def _create_vm_mock(self, vnc_port): vm = MagicMock() vm.config.extraConfig = [] vm.config.extraConfig.append( vim.OptionValue(key="RemoteDisplay.vnc.port", value=str(vnc_port))) vm.config.extraConfig.append( vim.OptionValue(key="RemoteDisplay.vnc.enabled", value="True")) return vm
class TestVimClient(unittest.TestCase): def setUp(self): if "host_remote_test" not in config: raise SkipTest() self.host = config["host_remote_test"]["server"] self.pwd = config["host_remote_test"]["esx_pwd"] if self.host is None or self.pwd is None: raise SkipTest() self.vim_client = VimClient(self.host, "root", self.pwd, auto_sync=True) self.vm_config = EsxVmConfig(self.vim_client) self._logger = logging.getLogger(__name__) def tearDown(self): self.vim_client.disconnect(wait=True) def test_memory_usage(self): used_memory = self.vim_client.memory_usage_mb assert_that(used_memory > 0, is_(True)) def test_total_memory(self): total_memory = self.vim_client.total_vmusable_memory_mb assert_that(total_memory > 0, is_(True)) def test_total_cpus(self): num_cpus = self.vim_client.num_physical_cpus assert_that(num_cpus > 0, is_(True)) def _create_test_vm(self, suffix="host-integ"): # Create VM vm_id = "%s-%s-%s" % ( time.strftime("%Y-%m-%d-%H%M%S", time.localtime()), str(random.randint(100000, 1000000)), suffix) datastore = self.vim_client.get_datastore().name disk_path = "[%s] %s/disk.vmdk" % (datastore, vm_id) create_spec = self.get_create_spec(datastore, vm_id, disk_path) folder = self.vim_client.vm_folder resource_pool = self.vim_client.root_resource_pool task = folder.CreateVm(create_spec, resource_pool, None) self.vim_client.wait_for_task(task) vm = self.vim_client.get_vm(vm_id) return (vm_id, vm, datastore, disk_path) def test_get_cached_vm(self): vm_id, vm, datastore, disk_path = self._create_test_vm("vm-cache-test") # Verify VM is in cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].power_state, is_(PowerState.poweredOff)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Make sure get_vm_in_cache works vm_from_cache = self.vim_client.get_vm_in_cache(vm_id) assert_that(vm_from_cache.name, is_(vm_id)) self.assertRaises(VmNotFoundException, self.vim_client.get_vm_in_cache, "missing") # Add disk disk2_path = "[%s] %s/disk2.vmdk" % (datastore, vm_id) update_spec = self.get_update_spec(vm, disk2_path) task = vm.ReconfigVM_Task(update_spec) self.vim_client.wait_for_task(task) # For the ReconfigVM task to remove disk, the hostd could update # task status to success before updating VM status. Thus when # wait_for_task returns, the vm_cache is possible to be still in old # state, though eventually it converges to consistent state. It only # happens in this task AFAIK. It should be fine for this task, because # rarely there is other operation that depends on this task. self._wait_vm_has_disk(vm_id, 2) # Verify disk added vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms[0].disks), is_(2)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path, disk2_path)) # Remove disk vm = self.vim_client.get_vm(vm_id) remove_spec = self.get_remove_spec(vm, disk2_path) task = vm.ReconfigVM_Task(remove_spec) self.vim_client.wait_for_task(task) # Same as before when disk is added self._wait_vm_has_disk(vm_id, 1) # Verify disk removed vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(len(found_vms[0].disks), is_(1), "disk2 in " + str(found_vms[0].disks)) assert_that(found_vms[0].disks, contains_inanyorder(disk_path)) # Power on vm task = vm.PowerOn() self.vim_client.wait_for_task(task) # Wait until it disappears from the cache self._wait_vm_power_status(vm_id, PowerState.poweredOn) # Verify VM state in cache is updated vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(1)) assert_that(found_vms[0].power_state, is_(PowerState.poweredOn)) assert_that(found_vms[0].name, is_(vm_id)) assert_that(found_vms[0].memory_mb, is_(64)) assert_that(found_vms[0].path, starts_with("[%s]" % datastore)) assert_that(len(found_vms[0].disks), is_(1)) assert_that(found_vms[0].disks[0], is_(disk_path)) # Destroy VM task = vm.PowerOff() self.vim_client.wait_for_task(task) task = vm.Destroy() self.vim_client.wait_for_task(task) # Verify VM is deleted from cache vms = self.vim_client.get_vms_in_cache() found_vms = [v for v in vms if v.name == vm_id] assert_that(len(found_vms), is_(0)) def test_no_datastore_update(self): """ Test datastore update is no longer triggered on VM creates/deletes """ class UpdateListener(object): def __init__(self): self._ds_update_count = 0 def datastores_updated(self): self._ds_update_count += 1 def networks_updated(self): pass def virtual_machines_updated(self): pass listener = UpdateListener() self.vim_client.add_update_listener(listener) # listener always gets updated once on add assert_that(listener._ds_update_count, is_(1)) mock_apply = MagicMock(wraps=self.vim_client._apply_ds_update) self.vim_client._apply_ds_update = mock_apply _, vm, _, _ = self._create_test_vm("ds-update-test") task = vm.Destroy() self.vim_client.wait_for_task(task) # expect to get a datastore property update (unfortunately) ... for _ in xrange(50): if mock_apply.call_count > 0: break time.sleep(0.1) # ... but that additional datastore updated notifications are sent out # as a result assert_that(listener._ds_update_count, is_(1)) def get_create_spec(self, datastore, vm_id, disk_path): create_spec = vim.vm.ConfigSpec( name=vm_id, guestId="otherGuest", memoryMB=64, numCPUs=2, files=vim.vm.FileInfo(vmPathName="[%s] /" % datastore), deviceChange=[], ) controller = vim.vm.device.VirtualLsiLogicController( key=1, sharedBus=vim.vm.device.VirtualSCSIController.Sharing.noSharing, busNumber=2, unitNumber=-1) self.vm_config.add_device(create_spec, controller) backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent ) disk = vim.vm.device.VirtualDisk( controllerKey=1, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) self.vm_config.create_device(create_spec, disk) return create_spec def get_update_spec(self, vm_info, disk_path): update_spec = vim.vm.ConfigSpec() backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo( fileName=disk_path, diskMode=vim.vm.device.VirtualDiskOption.DiskMode.persistent ) controller = \ self.vm_config._find_scsi_controller(update_spec, vm_info.config) disk = vim.vm.device.VirtualDisk( controllerKey=controller.key, key=-1, unitNumber=-1, backing=backing, capacityInKB=1024, ) self.vm_config.create_device(update_spec, disk) return update_spec def get_remove_spec(self, vm_info, disk_path): remove_spec = vim.vm.ConfigSpec() devices = self.vm_config.get_devices_from_config(vm_info.config) found_device = None for device in devices: if isinstance(device, vim.vm.device.VirtualDisk) and \ device.backing.fileName.endswith(disk_path): found_device = device self.vm_config.remove_device(remove_spec, found_device) return remove_spec def test_clone_ticket(self): ticket = self.vim_client.acquire_clone_ticket() vim_client2 = VimClient(host=self.host, ticket=ticket) vim_client2.host_system def test_http_ticket(self): datastore = self.vim_client.get_datastore().name filename = "%s.bin" % str(uuid.uuid4()) quoted_dc_name = 'ha%252ddatacenter' url = 'https://%s/folder/%s?dcPath=%s&dsName=%s' % ( self.host, filename, quoted_dc_name, datastore) ticket = self.vim_client.acquire_cgi_ticket(url, HttpOp.PUT) assert_that(ticket, is_not(equal_to(None))) def test_host_stats(self): """ Skip host stats test. This test does not agree with the contract exposed from the implementation. Until the vim_client code be refactor/cleanup, disable this test for now. """ raise SkipTest() self.vim_client.initialize_host_counters() self.vim_client.update_hosts_stats() stats = self.vim_client.get_host_stats() assert_that(has_key('mem.consumed')) assert_that(stats['mem.consumed'], greater_than(0)) assert_that(has_key('rescpu.actav1')) assert_that(stats['rescpu.actav1'], greater_than(0)) def _wait_vm_has_disk(self, vm_id, disk_num): """Wait until the vm has disk number of the vm becomes disk_num """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if len(vm_in_cache.disks) == disk_num: self._logger.info("VmCache disk number synced in %.2f second" % (time.time() - now)) break time.sleep(0.1) def _wait_vm_power_status(self, vm_id, power_state): """Wait until the vm has power_state """ now = time.time() for _ in xrange(50): vm_in_cache = self.vim_client.get_vm_in_cache(vm_id) if vm_in_cache.power_state == power_state: self._logger.info("VmCache power_state synced in %.2f second" % (time.time() - now)) break time.sleep(0.1)
class TestEsxVmConfig(unittest.TestCase): @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) with patch("host.hypervisor.esx.vm_config.GetEnv"): self.vm_config = EsxVmConfig(self.vim_client) def tearDown(self): self.vim_client.disconnect(wait=True) def dummy_devices(self): return [ vim.vm.device.VirtualFloppy(key=10), vim.vm.device.VirtualPCIController(key=100), DEFAULT_DISK_CONTROLLER_CLASS(key=1000), vim.vm.device.VirtualSoundCard(key=10000), ] def test_vm_create_spec(self): datastore = "ds1" vm_id = str(uuid.uuid4()) metadata = { "configuration": {"guestOS": "otherLinuxGuest"}, "parameters": [{"name": "key1"}, {"name": "key2"}] } env = { "key1": "value1", "keyUnexpected": "valueNotSet", } spec = self.vm_config.create_spec(vm_id, datastore, 512, 1, metadata, env) assert_that(spec.memoryMB, equal_to(512)) assert_that(spec.numCPUs, equal_to(1)) assert_that(spec.name, equal_to(vm_id)) assert_that(spec.guestId, equal_to("otherLinuxGuest")) expected_metadata = {'guestOS': 'otherLinuxGuest', 'key1': 'value1'} assert_that(spec._metadata, equal_to(expected_metadata)) def test_create_nic_spec(self): net_name = "VM_network" cspec = self.vm_config.update_spec() spec = self.vm_config.add_nic(cspec, net_name) backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo assert_that(spec.deviceChange[0].device.backing.__class__, equal_to(backing)) assert_that(spec.deviceChange[0].device.backing.deviceName, equal_to(net_name)) def test_find_disk_controller(self): devices = self.dummy_devices() device_type = DEFAULT_DISK_CONTROLLER_CLASS disk_controller = self.vm_config.find_device(devices, device_type) assert_that(disk_controller.key, equal_to(1000)) def test_find_nic_controller(self): devices = self.dummy_devices() device_type = vim.vm.device.VirtualPCIController disk_controller = self.vm_config.find_device(devices, device_type) assert_that(disk_controller.key, equal_to(100)) def test_find_virtual_disk(self): spec = vim.vm.ConfigSpec() vm_config = self.vm_config devices = self.dummy_devices() for device in devices: vm_config.add_device(spec, device) cfg_info = FakeConfigInfo() device_type = vim.vm.device.VirtualDisk datastore = "ds1" filename = "folder/foo" path = vmdk_path(datastore, filename) find_disk = vm_config.disk_matcher(datastore, filename) disk = vm_config.find_device(devices, device_type, matcher=find_disk) assert_that(disk, equal_to(None)) vm_config.add_scsi_disk(cfg_info, spec, datastore, "nope") self.assertRaises(DeviceNotFoundException, vm_config.get_device, devices, device_type, matcher=find_disk) vm_config.add_scsi_disk(cfg_info, spec, datastore, filename) device_changes = spec.deviceChange device_list = [] for device_change in device_changes: device_list.append(device_change.device) disk = vm_config.find_device(device_list, device_type, matcher=find_disk) assert_that(disk.backing.fileName, equal_to(path)) def _create_spec_for_disk_test(self, datastore, vm_id): spec = vim.vm.ConfigSpec() devices = self.dummy_devices() for device in devices: self.vm_config.add_device(spec, device) vm_path_name = '[%s] %s/%s' % (datastore, vm_id[0:2], vm_id) spec.files = vim.vm.FileInfo(vmPathName=vm_path_name) spec.name = vm_id return spec def test_create_empty_disk(self): vm_id = str(uuid.uuid4()) datastore = "ds1" spec = self._create_spec_for_disk_test(datastore, vm_id) size_mb = 100 disk_id = str(uuid.uuid4()) self.vm_config.create_empty_disk(spec, datastore, disk_id, size_mb) devs = [change.device for change in spec.deviceChange] device_type = vim.vm.device.VirtualDisk disks = self.vm_config.find_devices(devs, device_type) assert_that(len(disks), equal_to(1)) # verify that uuid to be set on disk to be added matches the # of the disk (modulo some formatting differences) assert_that(disks[0].backing.uuid, equal_to(uuid_to_vmdk_uuid(disk_id))) def test_create_child_disk(self): vm_id = str(uuid.uuid4()) datastore = "ds1" spec = self._create_spec_for_disk_test(datastore, vm_id) disk_id = str(uuid.uuid4()) parent_id = str(uuid.uuid4()) self.vm_config.create_child_disk(spec, datastore, disk_id, parent_id) devs = [change.device for change in spec.deviceChange] device_type = vim.vm.device.VirtualDisk disks = self.vm_config.find_devices(devs, device_type) assert_that(len(disks), equal_to(1)) # verify that disk to be added does not request a specifc uuid assert_that(disks[0].backing.uuid, equal_to(None)) def _get_config_info_with_iso(self, iso_path): devices = self.dummy_devices() cfg_info = FakeConfigInfo() cfg_info.hardware.device = devices cdrom = vim.vm.device.VirtualCdrom() cdrom.key = 1234 cdrom.controllerKey = 100 cdrom.unitNumber = 1 iso_backing = vim.vm.device.VirtualCdrom.IsoBackingInfo() iso_backing.fileName = iso_path cdrom.backing = iso_backing conInfo = vim.vm.device.VirtualDevice.ConnectInfo() conInfo.allowGuestControl = True conInfo.connected = True conInfo.startConnected = True cdrom.connectable = conInfo cfg_info.hardware.device.append(cdrom) return cfg_info def _get_config_info_without_connected(self, is_iso_backing): devices = self.dummy_devices() cfg_info = FakeConfigInfo() cfg_info.hardware.device = devices cdrom = vim.vm.device.VirtualCdrom() cdrom.key = 1234 cdrom.controllerKey = 100 cdrom.unitNumber = 1 if is_iso_backing: iso_backing = vim.vm.device.VirtualCdrom.IsoBackingInfo() cdrom.backing = iso_backing conInfo = vim.vm.device.VirtualDevice.ConnectInfo() conInfo.allowGuestControl = True conInfo.connected = False conInfo.startConnected = True cdrom.connectable = conInfo cfg_info.hardware.device.append(cdrom) return cfg_info def test_add_iso_cdrom(self): virtual_ide_controller = vim.vm.device.VirtualIDEController() cfgOption = vim.vm.ConfigOption() cfgOption.defaultDevice.append(virtual_ide_controller) self.vm_config._cfg_opts = cfgOption # fake iso ds path fake_iso_ds_path = '[ds] vm_fake/fake.iso' # test if no virtual cdrom attached to the VM cfg_info = FakeConfigInfo() cspec = self.vm_config.update_spec() result = self.vm_config.add_iso_cdrom( cspec, fake_iso_ds_path, cfg_info) assert_that(result.__class__, equal_to(bool)) assert_that(result, equal_to(True)) dev = cspec.deviceChange[0].device assert_that(len(cspec.deviceChange), equal_to(1)) assert_that(dev.connectable.connected, equal_to(True)) assert_that(dev.connectable.startConnected, equal_to(True)) assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) # test if virtual cdrom exist and ISO already attached to the VM cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_with_iso(fake_iso_ds_path) result = self.vm_config.add_iso_cdrom( cspec, fake_iso_ds_path, cfg_info) assert_that(result.__class__, equal_to(bool)) assert_that(result, equal_to(False)) # test if virtual cdrom exist and it's iso_backing # and ISO is not attached to the VM cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_without_connected(is_iso_backing=True) result = self.vm_config.add_iso_cdrom( cspec, fake_iso_ds_path, cfg_info) assert_that(result.__class__, equal_to(bool)) assert_that(result, equal_to(True)) dev = cspec.deviceChange[0].device assert_that(len(cspec.deviceChange), equal_to(1)) assert_that(dev.connectable.connected, equal_to(True)) assert_that(dev.connectable.startConnected, equal_to(True)) assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) # test if virtual cdrom exist and it's _not_ iso_backing # and ISO is not attached to the VM cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_without_connected( is_iso_backing=False) self.assertRaises(TypeError, self.vm_config.add_iso_cdrom, cspec, fake_iso_ds_path, cfg_info) def test_disconnect_iso(self): # on vm config with no cdrom devices cfg_info = FakeConfigInfo() cspec = self.vm_config.update_spec() self.assertRaises(DeviceNotFoundException, self.vm_config.disconnect_iso_cdrom, cspec, cfg_info) assert_that(len(cspec.deviceChange), equal_to(0)) # on vm config with no a fake cdrom device fake_iso_ds_path = '[ds] vm_fake/fake.iso' cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_with_iso(fake_iso_ds_path) iso_path = self.vm_config.disconnect_iso_cdrom(cspec, cfg_info) assert_that(len(cspec.deviceChange), equal_to(1)) dev = cspec.deviceChange[0].device assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) assert_that(dev.backing.fileName, equal_to(fake_iso_ds_path)) assert_that(iso_path, equal_to(fake_iso_ds_path)) assert_that(dev.connectable.connected, equal_to(False)) assert_that(dev.connectable.startConnected, equal_to(False)) def test_remove_iso_cdrom_device(self): fake_iso_ds_path = '[ds] vm_fake/fake.iso' cspec = self.vm_config.update_spec() cfg_info = self._get_config_info_with_iso(fake_iso_ds_path) self.vm_config.remove_iso_cdrom(cspec, cfg_info) assert_that(len(cspec.deviceChange), equal_to(1)) assert_that(cspec.deviceChange[0].operation, equal_to('remove')) dev = cspec.deviceChange[0].device assert_that(dev.backing.__class__, equal_to(vim.vm.device.VirtualCdrom.IsoBackingInfo)) assert_that(dev.backing.fileName, equal_to(fake_iso_ds_path)) def test_update_spec(self): cfg_info = FakeConfigInfo() spec = self.vm_config.update_spec() assert_that(len(spec.deviceChange), equal_to(0)) net_name = "VM_Network" self.vm_config.add_nic(spec, net_name) assert_that(len(spec.deviceChange), equal_to(1)) self.vm_config.add_scsi_disk(cfg_info, spec, "ds1", "foo") # One for the controller and one for the disk itself. assert_that(len(spec.deviceChange), equal_to(3)) def test_path_conversion_invalid(self): self.assertRaises(IndexError, datastore_to_os_path, "invalid_ds_path") @parameterized.expand([ ('[foo] a/b/c.vmdk', '/vmfs/volumes/foo/a/b/c.vmdk'), ('[foo] c.vmdk', '/vmfs/volumes/foo/c.vmdk'), ('[foo]a', '/vmfs/volumes/foo/a'), ('/vmfs/volumes/foo/bar.vmdk', '/vmfs/volumes/foo/bar.vmdk'), ('[]/vmfs/volumes/foo/bar.vmdk', '/vmfs/volumes/foo/bar.vmdk'), ('[] /vmfs/volumes/foo/bar.vmdk', '/vmfs/volumes/foo/bar.vmdk') ]) def test_path_conversion(self, ds_path, expected_os_path): path = datastore_to_os_path(ds_path) assert_that(path, equal_to(expected_os_path)) @parameterized.expand([ (['[foo] image_a_b/c.vmdk'], True, False, False), (['[foo] vm_a_b/c.vmdk'], False, True, False), (['[foo] image_a_b/c.vmdk', '[foo] vm/a.vmdk'], False, True, False), (['[foo] disk_a_b/c.vmdk'], False, False, True), (['[foo] image_a/c.vmdk', '[foo] disk/a.vmdk'], False, False, True), ([], False, False, False) ]) def test_is_what_disk(self, disk_files, image, ephemeral, persistent): assert_that(is_image(disk_files), equal_to(image)) assert_that(is_ephemeral_disk(disk_files), equal_to(ephemeral)) assert_that(is_persistent_disk(disk_files), equal_to(persistent)) def test_vmdk_uuid_conversion(self): for id in ['01234567-89ab-cedf-0123-456789abcdef', '01 23456 789ABCEDF0123456789ABCDEF', '01 23 45 67 89 ab ce df-01 23 45 67 89 ab cd ef', '0123456789abcedf0123456789abcdef']: vmdk_uuid = uuid_to_vmdk_uuid(id) assert_that( vmdk_uuid, equal_to('01 23 45 67 89 ab ce df-01 23 45 67 89 ab cd ef')) for id in ['', '01234567-89ab-cedf-0123-456789abcd', '01 23456 789abcedf0123456789abcdefabcd']: self.assertRaises(ValueError, uuid_to_vmdk_uuid, id)
class TestEsxImageManager(unittest.TestCase): """Image Manager tests.""" # We can use even more unit test coverage of the image manager here @patch.object(VimClient, "acquire_credentials") @patch.object(VimClient, "update_cache") @patch("pysdk.connect.Connect") def setUp(self, connect, update, creds): creds.return_value = ["username", "password"] self.vim_client = VimClient(auto_sync=False) self.ds_manager = MagicMock() services.register(ServiceName.AGENT_CONFIG, MagicMock()) self.image_manager = EsxImageManager(self.vim_client, self.ds_manager) def tearDown(self): self.vim_client.disconnect(wait=True) @patch("os.path.isdir", return_value=False) @patch("os.makedirs", side_effect=OSError) def test_make_image_dir(self, _makedirs, _isdir): path = "/vmfs/volumes/ds/image_fake_iid" self.assertRaises( OSError, self.image_manager._make_image_dir, "ds", "fake_iid") _isdir.assert_called_once_with(path) self.assertEqual( _makedirs.call_count, EsxImageManager.NUM_MAKEDIRS_ATTEMPTS) for i in range(0, EsxImageManager.NUM_MAKEDIRS_ATTEMPTS): self.assertEqual(_makedirs.call_args_list[i][0], (path,)) @patch( "host.hypervisor.esx.image_manager.EsxImageManager.reap_tmp_images") def test_periodic_reaper(self, mock_reap): """ Test that the we invoke the image reaper periodically """ image_manager = EsxImageManager(self.vim_client, self.ds_manager) image_manager.monitor_for_cleanup(reap_interval=0.1) self.assertFalse(image_manager._image_reaper is None) retry = 0 while mock_reap.call_count < 2 and retry < 10: time.sleep(0.1) retry += 1 image_manager.cleanup() assert_that(mock_reap.call_count, greater_than(1)) assert_that(retry, is_not(10), "reaper cleanup not called repeatedly") @parameterized.expand([ (True, ), (False, ) ]) @patch("uuid.uuid4", return_value="fake_id") @patch("host.hypervisor.esx.vm_config.os_datastore_root") def test_reap_tmp_images(self, _allow_grace_period, _os_datastore_root, _uuid): """ Test that stray images are found and deleted by the reaper """ ds = MagicMock() ds.id = "dsid" ds.type = DatastoreType.EXT3 # In a random transient directory, set up a directory to act as the # tmp images folder and to contain a stray image folder with a file. tmpdir = file_util.mkdtemp(delete=True) tmp_ds_dir = os.path.join(tmpdir, ds.id) os.mkdir(tmp_ds_dir) tmp_image_dir = os.path.join(tmp_ds_dir, compond_path_join(TMP_IMAGE_FOLDER_NAME_PREFIX, "stray_image")) os.mkdir(tmp_image_dir) (fd, path) = tempfile.mkstemp(prefix='strayimage_', dir=tmp_image_dir) self.assertTrue(os.path.exists(path)) def _fake_os_datastore_root(datastore): return os.path.join(tmpdir, datastore) _os_datastore_root.side_effect = _fake_os_datastore_root ds_manager = MagicMock() ds_manager.get_datastores.return_value = [ds] image_manager = EsxImageManager(self.vim_client, ds_manager) if not _allow_grace_period: image_manager.REAP_TMP_IMAGES_GRACE_PERIOD = 0.0 time.sleep(0.1) image_manager.reap_tmp_images() if _allow_grace_period: # verify stray image is not deleted due to grace period self.assertTrue(os.path.exists(path)) else: # verify stray image is deleted self.assertFalse(os.path.exists(path)) @patch("os.path.isdir") @patch("os.makedirs") def test_vmdk_mkdir_eexist(self, _makedirs, _isdir): eexist = OSError() eexist.errno = errno.EEXIST _makedirs.side_effect = eexist _isdir.side_effect = (False, # dest image dir missing True) # dest image dir is created self.image_manager._make_image_dir("ds", "fake_iid") _isdir.assert_called("/vmfs/volumes/ds/image_fake_iid") @patch("pysdk.task.WaitForTask") @patch("uuid.uuid4", return_value="fake_id") @patch("os.path.exists") @patch("shutil.copy") @patch.object(VimClient, "move_file") @patch.object(EsxImageManager, "_manage_disk") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_check_image_repair", return_value=False) @patch.object(EsxImageManager, "check_and_validate_image", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_copy_image(self, _flock, _create_image_timestamp, check_image, _check_image_repair, _get_ds_type, _manage_disk, _mv_dir, _copy, _exists, _uuid, _wait_for_task): _exists.side_effect = (True, # tmp_dir exists True, # dest image vmdk missing True) # source meta file present self.image_manager.copy_image("ds1", "foo", "ds2", "bar") os_path_prefix1 = '/vmfs/volumes/ds1' os_path_prefix2 = '/vmfs/volumes/ds2' ds_tmp_path_prefix = '[] /vmfs/volumes/ds2' assert_that(_copy.call_count, equal_to(1)) _copy.assert_has_calls([ call('%s/image_foo/foo.%s' % (os_path_prefix1, METADATA_FILE_EXT), '/vmfs/volumes/ds2/tmp_image_fake_id/bar.%s' % METADATA_FILE_EXT), ]) ds_path_prefix1 = '[] ' + os_path_prefix1 expected_tmp_disk_ds_path = '%s/tmp_image_fake_id/bar.vmdk' % (ds_tmp_path_prefix) _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='%s/image_foo/foo.vmdk' % ds_path_prefix1, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) _mv_dir.assert_called_once_with('/vmfs/volumes/ds2/tmp_image_fake_id', '%s/image_bar' % os_path_prefix2) _create_image_timestamp.assert_called_once_with( "/vmfs/volumes/ds2/tmp_image_fake_id") @patch("pysdk.task.WaitForTask") @patch("uuid.uuid4", return_value="fake_id") @patch("os.path.exists") @patch("os.makedirs") @patch("shutil.copy") @patch.object(EsxImageManager, "_manage_disk") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "check_image", return_value=False) @patch.object(EsxImageManager, "_create_image_timestamp_file") def test_create_tmp_image(self, _create_image_timestamp, check_image, _get_ds_type, _manage_disk, _copy, _makedirs, _exists, _uuid, _wait_for_task): # Common case is the same as the one covered by test_copy_image. # Check that things work when the src metadata file doesn't exist. _exists.side_effect = (False, False, True) ds_path_prefix1 = '[] /vmfs/volumes/ds1' expected_tmp_disk_ds_path = \ "[] /vmfs/volumes/ds2/tmp_image_fake_id/bar.vmdk" self.image_manager._copy_to_tmp_image("ds1", "foo", "ds2", "bar") # Verify that we don't copy the metadata file. self.assertFalse(_copy.called) # Verify that we copy the disk correctly _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='%s/image_foo/foo.vmdk' % ds_path_prefix1, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) # check that we return an IO error if the copy of metadata fails. _copy.side_effect = IOError _exists.side_effect = (True, True) _manage_disk.reset_mock() self.assertRaises(IOError, self.image_manager._copy_to_tmp_image, "ds1", "foo", "ds2", "bar") self.assertFalse(_manage_disk.called) _create_image_timestamp.assert_called_once_with( "/vmfs/volumes/ds2/tmp_image_fake_id") @patch.object(VimClient, "delete_file") @patch("os.path.exists", return_value=True) @patch("os.makedirs") @patch("shutil.rmtree") @patch("shutil.move") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_check_image_repair", return_value=True) @patch("host.hypervisor.esx.image_manager.FileBackedLock") @raises(DiskAlreadyExistException) def test_move_image(self, _flock, check_image, _get_ds_type, _mv_dir, _rmtree, _makedirs, _exists, _delete_file): # Common case is covered in test_copy_image. # check that if destination image directory exists we don't call move # and just bail after removing the tmp dir _rmtree.reset_mock() _mv_dir.reset_mock() expected_tmp_disk_folder = '/vmfs/volumes/ds2/tmp_images/bar' expected_rm_calls = [call(expected_tmp_disk_folder)] self.image_manager._move_image("foo", "ds1", expected_tmp_disk_folder) self.assertEqual(expected_rm_calls, _rmtree.call_args_list) _makedirs.assert_called_once_with('/vmfs/volumes/ds1/images/fo') _flock.assert_called_once_with('/vmfs/volumes/ds1/image_foo', DatastoreType.EXT3, 3) _delete_file.assert_called_once_with('/vmfs/volumes/ds1/image_foo') @parameterized.expand([ (True, ), (False, ) ]) @patch("os.path.exists") @patch.object(EsxImageManager, "_get_datastore_type", return_value=DatastoreType.EXT3) @patch.object(EsxImageManager, "_create_image_timestamp_file") @patch.object(EsxImageManager, "_delete_renamed_image_timestamp_file") @patch("host.hypervisor.esx.image_manager.FileBackedLock") def test_validate_existing_image(self, create, _flock, _delete_renamed_timestamp_file, _create_timestamp_file, _get_ds_type, _path_exists): self._create_image_timestamp_file = create _path_exists.side_effect = self._local_os_path_exists _disk_folder = '/vmfs/volumes/ds1/image_foo' self.image_manager._check_image_repair("foo", "ds1") if create: _create_timestamp_file.assert_called_once_with(_disk_folder) _delete_renamed_timestamp_file.assert_called_once() else: assert not _create_timestamp_file.called assert not _delete_renamed_timestamp_file.called def _local_os_path_exists(self, pathname): if not self._create_image_timestamp_file: return True if pathname.endswith(EsxImageManager.IMAGE_TIMESTAMP_FILE_NAME): return False else: return True def test_image_path(self): image_path = "/vmfs/volumes/ds/image_ttylinux/ttylinux.vmdk" ds = self.image_manager.get_datastore_id_from_path(image_path) image = self.image_manager.get_image_id_from_path(image_path) self.assertEqual(ds, "ds") self.assertEqual(image, "ttylinux") @patch.object(EsxImageManager, "_get_datastore_type") def test_create_image(self, _get_ds_type): image_id = "image_id" datastore_id = "ds1" _get_ds_type.side_effect = (DatastoreType.LOCAL_VMFS, DatastoreType.VSAN) tmp_image_path = self.image_manager.create_image(image_id, datastore_id) prefix = "[] /vmfs/volumes/%s/tmp_image_" % datastore_id self.assertTrue(tmp_image_path.startswith(prefix)) tmp_image_path = self.image_manager.create_image(image_id, datastore_id) prefix = "[] /vmfs/volumes/%s/image_%s/tmp_image_" % (datastore_id, image_id) self.assertTrue(tmp_image_path.startswith(prefix)) @patch.object(EsxImageManager, "_move_image") @patch.object(EsxImageManager, "_create_image_timestamp_file_from_ids") @patch("os.path.exists") def test_finalize_image(self, _exists, _create_timestamp, move_image): # Happy path verify move is called with the right args. _exists.side_effect = ([True]) self.image_manager.finalize_image("ds1", "[] /vmfs/volumes/ds1/foo", "img_1") move_image.assert_called_once_with('img_1', 'ds1', '/vmfs/volumes/ds1/foo') _create_timestamp.assert_called_once_with("ds1", "img_1") @patch.object(EsxImageManager, "finalize_image") @patch.object(EsxImageManager, "_manage_disk") @patch("os.path.exists", return_value=True) def test_create_image_with_vm_disk(self, _exists, _manage_disk, _create_image): vm_disk_path = "/vmfs/volumes/dsname/vms/ab/cd.vmdk" self.image_manager.create_image_with_vm_disk( "ds1", "[] /vmfs/volumes/ds1/foo", "img_1", vm_disk_path) # Verify that we copy the disk correctly expected_tmp_disk_ds_path = \ "[] /vmfs/volumes/ds1/foo/img_1.vmdk" _vd_spec = _manage_disk.call_args_list[0][1]['destSpec'] self.assertEqual("thin", _vd_spec.diskType) self.assertEqual("lsiLogic", _vd_spec.adapterType) copy_call = call(vim.VirtualDiskManager.CopyVirtualDisk_Task, sourceName='[] %s' % vm_disk_path, destName=expected_tmp_disk_ds_path, destSpec=_vd_spec) expected_vim_calls = [copy_call] self.assertEqual(expected_vim_calls, _manage_disk.call_args_list) _create_image.assert_called_once_with("ds1", "[] /vmfs/volumes/ds1/foo", "img_1") @patch("shutil.rmtree") @patch("os.path.exists") def test_delete_tmp_dir(self, _exists, _rmtree): self.image_manager.delete_tmp_dir("ds1", "foo") _exists.assert_called_once("/vmfs/volumes/ds1/foo") _rmtree.assert_called_once("/vmfs/volumes/ds1/foo") _exists.reset_mock() _exists.return_value = False _rmtree.reset_mock() self.assertRaises(DirectoryNotFound, self.image_manager.delete_tmp_dir, "ds1", "foo") _exists.assert_called_once("/vmfs/volumes/ds1/foo") self.assertFalse(_rmtree.called) def test_image_size(self): self.ds_manager.image_datastores.return_value = ["ds1", "ds2"] with patch("host.hypervisor.esx.image_manager.os_vmdk_flat_path" "") as image_path: tmpdir = file_util.mkdtemp(delete=True) image_path.return_value = tmpdir size = self.image_manager.image_size("image_id") self.assertTrue(size > 0) def test_image_size_not_exist(self): self.ds_manager.image_datastores.return_value = ["ds1", "ds2"] self.assertRaises(NoSuchResourceException, self.image_manager.image_size, "image_id")