Exemplo n.º 1
0
    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
Exemplo n.º 3
0
 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")
Exemplo n.º 5
0
    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")
Exemplo n.º 6
0
    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))
Exemplo n.º 7
0
    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))
Exemplo n.º 8
0
    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))
Exemplo n.º 10
0
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")
Exemplo n.º 12
0
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
Exemplo n.º 13
0
 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")
Exemplo n.º 15
0
    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))
Exemplo n.º 16
0
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)
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
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
Exemplo n.º 21
0
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
Exemplo n.º 22
0
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)
Exemplo n.º 23
0
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")
Exemplo n.º 26
0
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
Exemplo n.º 27
0
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)
Exemplo n.º 29
0
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()
Exemplo n.º 32
0
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
Exemplo n.º 33
0
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)
Exemplo n.º 34
0
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")