# Copyright 2016 Cloudbase Solutions Srl # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ctypes import ddt import mock from six.moves import queue from os_win import constants from os_win import exceptions from os_win.tests.unit import test_base from os_win.utils.compute import clusterutils from os_win.utils.winapi import constants as w_const from os_win.utils.winapi.libs import clusapi as clusapi_def from os_win.utils.winapi import wintypes @ddt.ddt class ClusterUtilsTestCase(test_base.OsWinBaseTestCase): """Unit tests for the Hyper-V ClusterUtilsBase class.""" _autospec_classes = [ clusterutils._clusapi_utils.ClusApiUtils, ] _FAKE_RES_NAME = "fake_res_name" _FAKE_HOST = "fake_host" _FAKE_PREV_HOST = "fake_prev_host" _FAKE_VM_NAME = 'instance-00000001' _FAKE_RESOURCEGROUP_NAME = 'Virtual Machine %s' % _FAKE_VM_NAME def setUp(self): super(ClusterUtilsTestCase, self).setUp() self._clusterutils = clusterutils.ClusterUtils() self._clusterutils._conn_cluster = mock.MagicMock() self._clusterutils._cluster = mock.MagicMock() self._clusapi = self._clusterutils._clusapi_utils def test_init_hyperv_conn(self): fake_cluster_name = "fake_cluster" mock_cluster = mock.MagicMock() mock_cluster.path_.return_value = r"\\%s\root" % fake_cluster_name mock_conn = mock.MagicMock() mock_conn.MSCluster_Cluster.return_value = [mock_cluster] self._clusterutils._get_wmi_conn = mock.MagicMock() self._clusterutils._get_wmi_conn.return_value = mock_conn self._clusterutils._init_hyperv_conn("fake_host") def test_init_hyperv_conn_exception(self): self._clusterutils._get_wmi_conn = mock.MagicMock() self._clusterutils._get_wmi_conn.side_effect = AttributeError self.assertRaises(exceptions.HyperVClusterException, self._clusterutils._init_hyperv_conn, "fake_host") @mock.patch.object(clusterutils.ClusterUtils, '_get_cluster_nodes') def test_check_cluster_state_not_enough_nodes(self, mock_get_nodes): self.assertRaises(exceptions.HyperVClusterException, self._clusterutils.check_cluster_state) def test_get_node_name(self): self._clusterutils._this_node = mock.sentinel.fake_node_name self.assertEqual(mock.sentinel.fake_node_name, self._clusterutils.get_node_name()) def test_get_cluster_nodes(self): fake_node1 = mock.MagicMock(Dependent=mock.sentinel.cluster_node1) fake_node2 = mock.MagicMock(Dependent=mock.sentinel.cluster_node2) node_list = [fake_node1, fake_node2] expected = [mock.sentinel.cluster_node1, mock.sentinel.cluster_node2] fake_class = self._clusterutils._conn_cluster.MSCluster_ClusterToNode fake_class.return_value = node_list self.assertEqual(expected, self._clusterutils._get_cluster_nodes()) def test_get_vm_groups(self): vm_gr1 = mock.MagicMock(GroupType=self._clusterutils._VM_GROUP_TYPE) vm_gr2 = mock.MagicMock() vm_gr3 = mock.MagicMock(GroupType=self._clusterutils._VM_GROUP_TYPE) fake_assoc1 = mock.MagicMock(PartComponent=vm_gr1) fake_assoc2 = mock.MagicMock(PartComponent=vm_gr2) fake_assoc3 = mock.MagicMock(PartComponent=vm_gr3) assoc_list = [fake_assoc1, fake_assoc2, fake_assoc3] fake_conn = self._clusterutils._conn_cluster fake_conn.MSCluster_ClusterToResourceGroup.return_value = assoc_list res = list(self._clusterutils._get_vm_groups()) self.assertIn(vm_gr1, res) self.assertNotIn(vm_gr2, res) self.assertIn(vm_gr3, res) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm_group') def test_lookup_vm_group_check(self, mock_lookup_vm_group): mock_lookup_vm_group.return_value = mock.sentinel.fake_vm ret = self._clusterutils._lookup_vm_group_check( self._FAKE_VM_NAME) self.assertEqual(mock.sentinel.fake_vm, ret) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm_group') def test_lookup_vm_group_check_no_vm(self, mock_lookup_vm_group): mock_lookup_vm_group.return_value = None self.assertRaises(exceptions.HyperVVMNotFoundException, self._clusterutils._lookup_vm_group_check, self._FAKE_VM_NAME) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_res') def test_lookup_vm_group(self, mock_lookup_res): self._clusterutils._lookup_vm_group(self._FAKE_VM_NAME) mock_lookup_res.assert_called_once_with( self._clusterutils._conn_cluster.MSCluster_ResourceGroup, self._FAKE_VM_NAME) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm') def test_lookup_vm_check(self, mock_lookup_vm): mock_lookup_vm.return_value = mock.sentinel.fake_vm ret = self._clusterutils._lookup_vm_check( self._FAKE_VM_NAME) self.assertEqual(mock.sentinel.fake_vm, ret) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm') def test_lookup_vm_check_no_vm(self, mock_lookup_vm): mock_lookup_vm.return_value = None self.assertRaises(exceptions.HyperVVMNotFoundException, self._clusterutils._lookup_vm_check, self._FAKE_VM_NAME) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_res') def test_lookup_vm(self, mock_lookup_res): self._clusterutils._lookup_vm(self._FAKE_VM_NAME) mock_lookup_res.assert_called_once_with( self._clusterutils._conn_cluster.MSCluster_Resource, self._clusterutils._VM_BASE_NAME % self._FAKE_VM_NAME) def test_lookup_res_no_res(self): res_list = [] resource_source = mock.MagicMock() resource_source.return_value = res_list self.assertIsNone( self._clusterutils._lookup_res(resource_source, self._FAKE_RES_NAME)) resource_source.assert_called_once_with( Name=self._FAKE_RES_NAME) def test_lookup_res_duplicate_res(self): res_list = [mock.sentinel.r1, mock.sentinel.r1] resource_source = mock.MagicMock() resource_source.return_value = res_list self.assertRaises(exceptions.HyperVClusterException, self._clusterutils._lookup_res, resource_source, self._FAKE_RES_NAME) resource_source.assert_called_once_with( Name=self._FAKE_RES_NAME) def test_lookup_res(self): res_list = [mock.sentinel.r1] resource_source = mock.MagicMock() resource_source.return_value = res_list self.assertEqual( mock.sentinel.r1, self._clusterutils._lookup_res(resource_source, self._FAKE_RES_NAME)) resource_source.assert_called_once_with( Name=self._FAKE_RES_NAME) @mock.patch.object(clusterutils.ClusterUtils, '_get_cluster_nodes') def test_get_cluster_node_names(self, mock_get_cluster_nodes): cluster_nodes = [mock.Mock(Name='node1'), mock.Mock(Name='node2')] mock_get_cluster_nodes.return_value = cluster_nodes ret = self._clusterutils.get_cluster_node_names() self.assertItemsEqual(['node1', 'node2'], ret) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm_group_check') def test_get_vm_host(self, mock_lookup_vm_group_check): owner_node = "fake_owner_node" vm = mock.Mock(OwnerNode=owner_node) mock_lookup_vm_group_check.return_value = vm self.assertEqual( owner_node, self._clusterutils.get_vm_host(self._FAKE_VM_NAME)) @mock.patch.object(clusterutils.ClusterUtils, '_get_vm_groups') def test_list_instances(self, mock_get_vm_groups): mock_get_vm_groups.return_value = [mock.Mock(Name='vm1'), mock.Mock(Name='vm2')] ret = self._clusterutils.list_instances() self.assertItemsEqual(['vm1', 'vm2'], ret) @mock.patch.object(clusterutils.ClusterUtils, '_get_vm_groups') def test_list_instance_uuids(self, mock_get_vm_groups): mock_get_vm_groups.return_value = [mock.Mock(Id='uuid1'), mock.Mock(Id='uuid2')] ret = self._clusterutils.list_instance_uuids() self.assertItemsEqual(['uuid1', 'uuid2'], ret) @ddt.data(True, False) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm_group_check') def test_add_vm_to_cluster(self, auto_failback, mock_lookup_vm_group_check): self._clusterutils._cluster.AddVirtualMachine = mock.MagicMock() vm_group = mock.Mock() mock_lookup_vm_group_check.return_value = vm_group self._clusterutils.add_vm_to_cluster( self._FAKE_VM_NAME, mock.sentinel.max_failover_count, mock.sentinel.failover_period, auto_failback) self.assertEqual(mock.sentinel.max_failover_count, vm_group.FailoverThreshold) self.assertEqual(mock.sentinel.failover_period, vm_group.FailoverPeriod) self.assertTrue(vm_group.PersistentState) self.assertEqual(vm_group.AutoFailbackType, int(auto_failback)) self.assertEqual(vm_group.FailbackWindowStart, self._clusterutils._FAILBACK_WINDOW_MIN) self.assertEqual(vm_group.FailbackWindowEnd, self._clusterutils._FAILBACK_WINDOW_MAX) vm_group.put.assert_called_once_with() @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm_check') def test_bring_online(self, mock_lookup_vm_check): vm = mock.MagicMock() mock_lookup_vm_check.return_value = vm self._clusterutils.bring_online(self._FAKE_VM_NAME) vm.BringOnline.assert_called_once_with() @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm') def test_take_offline(self, mock_lookup_vm): vm = mock.MagicMock() mock_lookup_vm.return_value = vm self._clusterutils.take_offline(self._FAKE_VM_NAME) vm.TakeOffline.assert_called_once_with() @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm_group') def test_delete(self, mock_lookup_vm_group): vm = mock.MagicMock() mock_lookup_vm_group.return_value = vm self._clusterutils.delete(self._FAKE_VM_NAME) vm.DestroyGroup.assert_called_once_with( self._clusterutils._DESTROY_GROUP) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm') def test_vm_exists_true(self, mock_lookup_vm): vm = mock.MagicMock() mock_lookup_vm.return_value = vm self.assertTrue(self._clusterutils.vm_exists(self._FAKE_VM_NAME)) @mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm') def test_vm_exists_false(self, mock_lookup_vm): mock_lookup_vm.return_value = None self.assertFalse(self._clusterutils.vm_exists(self._FAKE_VM_NAME)) @mock.patch.object(clusterutils.ClusterUtils, '_migrate_vm') def test_live_migrate_vm(self, mock_migrate_vm): self._clusterutils.live_migrate_vm(self._FAKE_VM_NAME, self._FAKE_HOST, mock.sentinel.timeout) mock_migrate_vm.assert_called_once_with( self._FAKE_VM_NAME, self._FAKE_HOST, self._clusterutils._LIVE_MIGRATION_TYPE, constants.CLUSTER_GROUP_ONLINE, mock.sentinel.timeout) @mock.patch.object(wintypes, 'DWORD') @mock.patch.object(clusterutils.ClusterUtils, '_wait_for_cluster_group_migration') @mock.patch.object(clusterutils.ClusterUtils, '_validate_migration') @mock.patch.object(clusterutils, '_ClusterGroupStateChangeListener') @ddt.data(None, exceptions.ClusterException) def test_migrate_vm(self, wait_unexpected_exc, mock_listener_cls, mock_validate_migr, mock_wait_group, mock_dword): mock_wait_group.side_effect = wait_unexpected_exc migrate_args = (self._FAKE_VM_NAME, self._FAKE_HOST, self._clusterutils._LIVE_MIGRATION_TYPE, constants.CLUSTER_GROUP_ONLINE, mock.sentinel.timeout) if wait_unexpected_exc: self.assertRaises(wait_unexpected_exc, self._clusterutils._migrate_vm, *migrate_args) else: self._clusterutils._migrate_vm(*migrate_args) mock_dword.assert_called_once_with( self._clusterutils._LIVE_MIGRATION_TYPE) self._clusapi.get_property_list_entry.assert_has_calls( [mock.call(prop_name, w_const.CLUSPROP_SYNTAX_LIST_VALUE_DWORD, mock_dword.return_value) for prop_name in (w_const.CLUS_RESTYPE_NAME_VM, w_const.CLUS_RESTYPE_NAME_VM_CONFIG)]) expected_prop_entries = [ self._clusapi.get_property_list_entry.return_value] * 2 self._clusapi.get_property_list.assert_called_once_with( expected_prop_entries) expected_migrate_flags = ( w_const.CLUSAPI_GROUP_MOVE_RETURN_TO_SOURCE_NODE_ON_ERROR | w_const.CLUSAPI_GROUP_MOVE_QUEUE_ENABLED | w_const.CLUSAPI_GROUP_MOVE_HIGH_PRIORITY_START) exp_clus_h = self._clusapi.open_cluster.return_value exp_clus_node_h = self._clusapi.open_cluster_node.return_value exp_clus_group_h = self._clusapi.open_cluster_group.return_value self._clusapi.open_cluster.assert_called_once_with() self._clusapi.open_cluster_group.assert_called_once_with( exp_clus_h, self._FAKE_VM_NAME) self._clusapi.open_cluster_node.assert_called_once_with( exp_clus_h, self._FAKE_HOST) self._clusapi.move_cluster_group.assert_called_once_with( exp_clus_group_h, exp_clus_node_h, expected_migrate_flags, self._clusapi.get_property_list.return_value) mock_listener_cls.assert_called_once_with(exp_clus_h, self._FAKE_VM_NAME) mock_listener = mock_listener_cls.return_value mock_wait_group.assert_called_once_with( mock_listener.__enter__.return_value, self._FAKE_VM_NAME, exp_clus_group_h, constants.CLUSTER_GROUP_ONLINE, mock.sentinel.timeout) if not wait_unexpected_exc: mock_validate_migr.assert_called_once_with( exp_clus_group_h, self._FAKE_VM_NAME, constants.CLUSTER_GROUP_ONLINE, self._FAKE_HOST) self._clusapi.close_cluster_group.assert_called_once_with( exp_clus_group_h) self._clusapi.close_cluster_node.assert_called_once_with( exp_clus_node_h) self._clusapi.close_cluster.assert_called_once_with(exp_clus_h) @mock.patch.object(clusterutils.ClusterUtils, '_cancel_cluster_group_migration') @mock.patch.object(clusterutils.ClusterUtils, '_wait_for_cluster_group_migration') @mock.patch.object(clusterutils.ClusterUtils, '_validate_migration') @mock.patch.object(clusterutils, '_ClusterGroupStateChangeListener') @ddt.data(True, False) def test_migrate_vm_timeout(self, finished_after_cancel, mock_listener_cls, mock_validate_migr, mock_wait_group, mock_cancel_migr): timeout_exc = exceptions.ClusterGroupMigrationTimeOut( group_name=self._FAKE_VM_NAME, time_elapsed=10) mock_wait_group.side_effect = timeout_exc mock_listener = mock_listener_cls.return_value.__enter__.return_value mock_validate_migr.side_effect = ( (None, ) if finished_after_cancel else exceptions.ClusterGroupMigrationFailed( group_name=self._FAKE_VM_NAME, expected_state=mock.sentinel.expected_state, expected_node=self._FAKE_HOST, group_state=mock.sentinel.expected_state, owner_node=mock.sentinel.other_host)) migrate_args = (self._FAKE_VM_NAME, self._FAKE_HOST, self._clusterutils._LIVE_MIGRATION_TYPE, mock.sentinel.exp_state, mock.sentinel.timeout) if finished_after_cancel: self._clusterutils._migrate_vm(*migrate_args) else: self.assertRaises(exceptions.ClusterGroupMigrationTimeOut, self._clusterutils._migrate_vm, *migrate_args) exp_clus_group_h = self._clusapi.open_cluster_group.return_value mock_cancel_migr.assert_called_once_with( mock_listener, self._FAKE_VM_NAME, exp_clus_group_h, mock.sentinel.exp_state, mock.sentinel.timeout) mock_validate_migr.assert_called_once_with(exp_clus_group_h, self._FAKE_VM_NAME, mock.sentinel.exp_state, self._FAKE_HOST) @ddt.data({}, {'expected_state': constants.CLUSTER_GROUP_OFFLINE, 'is_valid': False}, {'expected_node': 'some_other_node', 'is_valid': False}) @ddt.unpack def test_validate_migration( self, expected_node=_FAKE_HOST, expected_state=constants.CLUSTER_GROUP_ONLINE, is_valid=True): group_state = dict(owner_node=self._FAKE_HOST.upper(), state=constants.CLUSTER_GROUP_ONLINE) self._clusapi.get_cluster_group_state.return_value = group_state if is_valid: self._clusterutils._validate_migration(mock.sentinel.group_handle, self._FAKE_VM_NAME, expected_state, expected_node) else: self.assertRaises(exceptions.ClusterGroupMigrationFailed, self._clusterutils._validate_migration, mock.sentinel.group_handle, self._FAKE_VM_NAME, expected_state, expected_node) self._clusapi.get_cluster_group_state.assert_called_once_with( mock.sentinel.group_handle) @mock.patch.object(clusterutils.ClusterUtils, '_cancel_cluster_group_migration') @mock.patch.object(clusterutils, '_ClusterGroupStateChangeListener') def test_cancel_cluster_group_migration_public(self, mock_listener_cls, mock_cancel_migr): exp_clus_h = self._clusapi.open_cluster.return_value exp_clus_group_h = self._clusapi.open_cluster_group.return_value mock_listener = mock_listener_cls.return_value mock_listener.__enter__.return_value = mock_listener self._clusterutils.cancel_cluster_group_migration( mock.sentinel.group_name, mock.sentinel.expected_state, mock.sentinel.timeout) self._clusapi.open_cluster.assert_called_once_with() self._clusapi.open_cluster_group.assert_called_once_with( exp_clus_h, mock.sentinel.group_name) mock_listener.__enter__.assert_called_once_with() mock_listener_cls.assert_called_once_with(exp_clus_h, mock.sentinel.group_name) mock_cancel_migr.assert_called_once_with( mock_listener, mock.sentinel.group_name, exp_clus_group_h, mock.sentinel.expected_state, mock.sentinel.timeout) self._clusapi.close_cluster.assert_called_once_with(exp_clus_h) self._clusapi.close_cluster_group.assert_called_once_with( exp_clus_group_h) @mock.patch.object(clusterutils.ClusterUtils, '_get_cluster_group_state') @mock.patch.object(clusterutils.ClusterUtils, '_is_migration_pending') @mock.patch.object(clusterutils.ClusterUtils, '_wait_for_cluster_group_migration') @ddt.data({}, {'cancel_exception': test_base.TestingException()}, {'cancel_exception': exceptions.Win32Exception( error_code=w_const.INVALID_HANDLE_VALUE, func_name=mock.sentinel.func_name, error_message=mock.sentinel.error_message)}, {'cancel_exception': exceptions.Win32Exception( error_code=w_const.ERROR_INVALID_STATE, func_name=mock.sentinel.func_name, error_message=mock.sentinel.error_message), 'invalid_state_for_cancel': True}, {'cancel_exception': exceptions.Win32Exception( error_code=w_const.ERROR_INVALID_STATE, func_name=mock.sentinel.func_name, error_message=mock.sentinel.error_message), 'invalid_state_for_cancel': True, 'cancel_still_pending': True}, {'cancel_still_pending': True}, {'cancel_still_pending': True, 'cancel_wait_exception': test_base.TestingException()}) @ddt.unpack def test_cancel_cluster_group_migration(self, mock_wait_migr, mock_is_migr_pending, mock_get_gr_state, cancel_still_pending=False, cancel_exception=None, invalid_state_for_cancel=False, cancel_wait_exception=None): expected_exception = None if cancel_wait_exception: expected_exception = exceptions.JobTerminateFailed() if (cancel_exception and (not invalid_state_for_cancel or cancel_still_pending)): expected_exception = cancel_exception mock_is_migr_pending.return_value = cancel_still_pending mock_get_gr_state.return_value = dict( state=mock.sentinel.state, status_info=mock.sentinel.status_info) self._clusapi.cancel_cluster_group_operation.side_effect = ( cancel_exception or (not cancel_still_pending, )) mock_wait_migr.side_effect = cancel_wait_exception cancel_args = (mock.sentinel.listener, mock.sentinel.group_name, mock.sentinel.group_handle, mock.sentinel.expected_state, mock.sentinel.timeout) if expected_exception: self.assertRaises( expected_exception.__class__, self._clusterutils._cancel_cluster_group_migration, *cancel_args) else: self._clusterutils._cancel_cluster_group_migration( *cancel_args) self._clusapi.cancel_cluster_group_operation.assert_called_once_with( mock.sentinel.group_handle) if isinstance(cancel_exception, exceptions.Win32Exception): mock_get_gr_state.assert_called_once_with( mock.sentinel.group_handle) mock_is_migr_pending.assert_called_once_with( mock.sentinel.state, mock.sentinel.status_info, mock.sentinel.expected_state) if cancel_still_pending and not cancel_exception: mock_wait_migr.assert_called_once_with( mock.sentinel.listener, mock.sentinel.group_name, mock.sentinel.group_handle, mock.sentinel.expected_state, timeout=mock.sentinel.timeout) def test_is_migration_pending(self): self.assertTrue( self._clusterutils._is_migration_pending( group_state=constants.CLUSTER_GROUP_OFFLINE, group_status_info=0, expected_state=constants.CLUSTER_GROUP_ONLINE)) self.assertTrue( self._clusterutils._is_migration_pending( group_state=constants.CLUSTER_GROUP_ONLINE, group_status_info=w_const. CLUSGRP_STATUS_WAITING_IN_QUEUE_FOR_MOVE | 1, # noqa expected_state=constants.CLUSTER_GROUP_ONLINE)) self.assertFalse( self._clusterutils._is_migration_pending( group_state=constants.CLUSTER_GROUP_OFFLINE, group_status_info=0, expected_state=constants.CLUSTER_GROUP_OFFLINE)) @mock.patch.object(clusterutils.ClusterUtils, '_is_migration_pending') @mock.patch.object(clusterutils.ClusterUtils, '_get_cluster_group_state') @mock.patch.object(clusterutils, 'time') def test_wait_for_clus_group_migr_timeout(self, mock_time, mock_get_gr_state, mock_is_migr_pending): exp_wait_iterations = 3 mock_listener = mock.Mock() mock_time.time.side_effect = range(exp_wait_iterations + 2) timeout = 10 state_info = dict(state=mock.sentinel.current_state, status_info=mock.sentinel.status_info) events = [dict(status_info=mock.sentinel.migr_queued), dict(state=mock.sentinel.pending_state), queue.Empty] mock_get_gr_state.return_value = state_info mock_is_migr_pending.return_value = True mock_listener.get.side_effect = events self.assertRaises( exceptions.ClusterGroupMigrationTimeOut, self._clusterutils._wait_for_cluster_group_migration, mock_listener, mock.sentinel.group_name, mock.sentinel.group_handle, mock.sentinel.expected_state, timeout=timeout) mock_get_gr_state.assert_called_once_with(mock.sentinel.group_handle) exp_wait_times = [timeout - elapsed - 1 for elapsed in range(exp_wait_iterations)] mock_listener.get.assert_has_calls( [mock.call(wait_time) for wait_time in exp_wait_times]) mock_is_migr_pending.assert_has_calls( [mock.call(mock.sentinel.current_state, mock.sentinel.status_info, mock.sentinel.expected_state), mock.call(mock.sentinel.current_state, mock.sentinel.migr_queued, mock.sentinel.expected_state), mock.call(mock.sentinel.pending_state, mock.sentinel.migr_queued, mock.sentinel.expected_state)]) @mock.patch.object(clusterutils.ClusterUtils, '_is_migration_pending') @mock.patch.object(clusterutils.ClusterUtils, '_get_cluster_group_state') def test_wait_for_clus_group_migr_success(self, mock_get_gr_state, mock_is_migr_pending): mock_listener = mock.Mock() state_info = dict(state=mock.sentinel.current_state, status_info=mock.sentinel.status_info) mock_get_gr_state.return_value = state_info mock_is_migr_pending.side_effect = [True, False] mock_listener.get.return_value = {} self._clusterutils._wait_for_cluster_group_migration( mock_listener, mock.sentinel.group_name, mock.sentinel.group_handle, mock.sentinel.expected_state, timeout=None) mock_listener.get.assert_called_once_with(None) @mock.patch.object(clusterutils.ClusterUtils, '_get_cluster_group_state') @mock.patch.object(clusterutils.ClusterUtils, '_is_migration_queued') def test_get_cluster_group_state_info(self, mock_is_migr_queued, mock_get_gr_state): exp_clus_h = self._clusapi.open_cluster.return_value exp_clus_group_h = self._clusapi.open_cluster_group.return_value mock_get_gr_state.return_value = dict( state=mock.sentinel.state, status_info=mock.sentinel.status_info, owner_node=mock.sentinel.owner_node) sts_info = self._clusterutils.get_cluster_group_state_info( mock.sentinel.group_name) exp_sts_info = dict(state=mock.sentinel.state, owner_node=mock.sentinel.owner_node, migration_queued=mock_is_migr_queued.return_value) self.assertEqual(exp_sts_info, sts_info) self._clusapi.open_cluster.assert_called_once_with() self._clusapi.open_cluster_group.assert_called_once_with( exp_clus_h, mock.sentinel.group_name) mock_get_gr_state.assert_called_once_with(exp_clus_group_h) mock_is_migr_queued.assert_called_once_with(mock.sentinel.status_info) self._clusapi.close_cluster.assert_called_once_with(exp_clus_h) self._clusapi.close_cluster_group.assert_called_once_with( exp_clus_group_h) @mock.patch('ctypes.byref') def test_get_cluster_group_state(self, mock_byref): mock_byref.side_effect = lambda x: ('byref', x) state_info = dict(state=mock.sentinel.state, owner_node=mock.sentinel.owner_node) self._clusapi.get_cluster_group_state.return_value = state_info self._clusapi.cluster_group_control.return_value = ( mock.sentinel.buff, mock.sentinel.buff_sz) self._clusapi.get_cluster_group_status_info.return_value = ( mock.sentinel.status_info) exp_state_info = state_info.copy() exp_state_info['status_info'] = mock.sentinel.status_info ret_val = self._clusterutils._get_cluster_group_state( mock.sentinel.group_handle) self.assertEqual(exp_state_info, ret_val) self._clusapi.get_cluster_group_state.assert_called_once_with( mock.sentinel.group_handle) self._clusapi.cluster_group_control.assert_called_once_with( mock.sentinel.group_handle, w_const.CLUSCTL_GROUP_GET_RO_COMMON_PROPERTIES) self._clusapi.get_cluster_group_status_info.assert_called_once_with( mock_byref(mock.sentinel.buff), mock.sentinel.buff_sz) @mock.patch.object(clusterutils, 'tpool') @mock.patch.object(clusterutils, 'patcher') def test_monitor_vm_failover_no_vm(self, mock_patcher, mock_tpool): mock_watcher = mock.MagicMock() fake_prev = mock.MagicMock(OwnerNode=self._FAKE_PREV_HOST) fake_wmi_object = mock.MagicMock(OwnerNode=self._FAKE_HOST, Name='Virtual Machine', previous=fake_prev) mock_tpool.execute.return_value = fake_wmi_object fake_callback = mock.MagicMock() self._clusterutils._monitor_vm_failover(mock_watcher, fake_callback, mock.sentinel.event_timeout_ms) mock_tpool.execute.assert_called_once_with( mock_watcher, mock.sentinel.event_timeout_ms) fake_callback.assert_not_called() @mock.patch.object(clusterutils, 'tpool') @mock.patch.object(clusterutils, 'patcher') def test_monitor_vm_failover(self, mock_patcher, mock_tpool): mock_watcher = mock.MagicMock() fake_prev = mock.MagicMock(OwnerNode=self._FAKE_PREV_HOST) fake_wmi_object = mock.MagicMock(OwnerNode=self._FAKE_HOST, Name=self._FAKE_RESOURCEGROUP_NAME, previous=fake_prev) mock_tpool.execute.return_value = fake_wmi_object fake_callback = mock.MagicMock() self._clusterutils._monitor_vm_failover(mock_watcher, fake_callback) mock_tpool.execute.assert_called_once_with( mock_watcher, self._clusterutils._WMI_EVENT_TIMEOUT_MS) fake_callback.assert_called_once_with(self._FAKE_VM_NAME, self._FAKE_PREV_HOST, self._FAKE_HOST) @mock.patch.object(clusterutils.ClusterUtils, '_get_failover_watcher') @mock.patch.object(clusterutils.ClusterUtils, '_monitor_vm_failover') @mock.patch.object(clusterutils, 'time') def test_get_vm_owner_change_listener(self, mock_time, mock_monitor, mock_get_watcher): mock_monitor.side_effect = [None, exceptions.OSWinException, KeyboardInterrupt] listener = self._clusterutils.get_vm_owner_change_listener() self.assertRaises(KeyboardInterrupt, listener, mock.sentinel.callback) mock_monitor.assert_has_calls( [mock.call(mock_get_watcher.return_value, mock.sentinel.callback, constants.DEFAULT_WMI_EVENT_TIMEOUT_MS)] * 3) mock_time.sleep.assert_called_once_with( constants.DEFAULT_WMI_EVENT_TIMEOUT_MS / 1000) class ClusterEventListenerTestCase(test_base.OsWinBaseTestCase): @mock.patch.object(clusterutils._ClusterEventListener, '_setup') def setUp(self, mock_setup): super(ClusterEventListenerTestCase, self).setUp() self._listener = clusterutils._ClusterEventListener( mock.sentinel.cluster_handle, mock.sentinel.notif_filters_list) self._listener._clusapi_utils = mock.Mock() self._clusapi = self._listener._clusapi_utils def test_get_notif_key_dw(self): fake_notif_key = 1 notif_key_dw = self._listener._get_notif_key_dw(fake_notif_key) self.assertIsInstance(notif_key_dw, ctypes.c_ulong) self.assertEqual(fake_notif_key, notif_key_dw.value) self.assertEqual(notif_key_dw, self._listener._get_notif_key_dw(fake_notif_key)) @mock.patch.object(clusterutils._ClusterEventListener, '_get_notif_key_dw') def test_add_filter(self, mock_get_notif_key): mock_get_notif_key.side_effect = ( mock.sentinel.notif_key_dw, mock.sentinel.notif_key_dw_2) self._clusapi.create_cluster_notify_port_v2.return_value = ( mock.sentinel.notif_port_h) self._listener._add_filter(mock.sentinel.filter, mock.sentinel.notif_key) self._listener._add_filter(mock.sentinel.filter_2, mock.sentinel.notif_key_2) self.assertEqual(mock.sentinel.notif_port_h, self._listener._notif_port_h) mock_get_notif_key.assert_has_calls( [mock.call(mock.sentinel.notif_key), mock.call(mock.sentinel.notif_key_2)]) self._clusapi.create_cluster_notify_port_v2.assert_has_calls( [mock.call(mock.sentinel.cluster_handle, mock.sentinel.filter, None, mock.sentinel.notif_key_dw), mock.call(mock.sentinel.cluster_handle, mock.sentinel.filter_2, mock.sentinel.notif_port_h, mock.sentinel.notif_key_dw_2)]) @mock.patch.object(clusterutils._ClusterEventListener, '_add_filter') @mock.patch.object(clusapi_def, 'NOTIFY_FILTER_AND_TYPE') def test_setup_notif_port(self, mock_filter_struct_cls, mock_add_filter): notif_filter = dict(object_type=mock.sentinel.object_type, filter_flags=mock.sentinel.filter_flags, notif_key=mock.sentinel.notif_key) self._listener._notif_filters_list = [notif_filter] self._listener._setup_notif_port() mock_filter_struct_cls.assert_called_once_with( dwObjectType=mock.sentinel.object_type, FilterFlags=mock.sentinel.filter_flags) mock_add_filter.assert_called_once_with( mock_filter_struct_cls.return_value, mock.sentinel.notif_key) def test_signal_stopped(self): self._listener._running = True self._listener._signal_stopped() self.assertFalse(self._listener._running) self.assertIsNone(self._listener._event_queue.get(block=False)) @mock.patch.object(clusterutils._ClusterEventListener, '_signal_stopped') def test_stop(self, mock_signal_stopped): self._listener._notif_port_h = mock.sentinel.notif_port_h self._listener.stop() mock_signal_stopped.assert_called_once_with() self._clusapi.close_cluster_notify_port.assert_called_once_with( mock.sentinel.notif_port_h) @mock.patch.object(clusterutils._ClusterEventListener, '_process_event') def test_listen(self, mock_process_event): events = [mock.sentinel.ignored_event, mock.sentinel.retrieved_event] self._clusapi.get_cluster_notify_v2.side_effect = events self._listener._running = True self._listener._notif_port_h = mock.sentinel.notif_port_h def fake_process_event(event): if event == mock.sentinel.ignored_event: return self._listener._running = False return mock.sentinel.processed_event mock_process_event.side_effect = fake_process_event self._listener._listen() processed_event = self._listener._event_queue.get(block=False) self.assertEqual(mock.sentinel.processed_event, processed_event) self.assertTrue(self._listener._event_queue.empty()) self._clusapi.get_cluster_notify_v2.assert_any_call( mock.sentinel.notif_port_h, timeout_ms=-1) def test_listen_exception(self): self._listener._running = True self._clusapi.get_cluster_notify_v2.side_effect = ( test_base.TestingException) self._listener._listen() self.assertFalse(self._listener._running) def test_get_event(self): self._listener._running = True self._listener._event_queue = mock.Mock() event = self._listener.get(timeout=mock.sentinel.timeout) self.assertEqual(self._listener._event_queue.get.return_value, event) self._listener._event_queue.get.assert_called_once_with( timeout=mock.sentinel.timeout) def test_get_event_listener_stopped(self): self.assertRaises(exceptions.OSWinException, self._listener.get, timeout=1) def fake_get(block=True, timeout=0): self._listener._running = False return None self._listener._running = True self._listener._event_queue = mock.Mock(get=fake_get) self.assertRaises(exceptions.OSWinException, self._listener.get, timeout=1) @mock.patch.object(clusterutils._ClusterEventListener, '_ensure_listener_running') @mock.patch.object(clusterutils._ClusterEventListener, 'stop') def test_context_manager(self, mock_stop, mock_ensure_running): with self._listener as l: self.assertIs(self._listener, l) mock_ensure_running.assert_called_once_with() mock_stop.assert_called_once_with() class ClusterGroupStateChangeListenerTestCase(test_base.OsWinBaseTestCase): _FAKE_GROUP_NAME = 'fake_group_name' @mock.patch.object(clusterutils._ClusterEventListener, '_setup') def setUp(self, mock_setup): super(ClusterGroupStateChangeListenerTestCase, self).setUp() self._listener = clusterutils._ClusterGroupStateChangeListener( mock.sentinel.cluster_handle, self._FAKE_GROUP_NAME) self._listener._clusapi_utils = mock.Mock() self._clusapi = self._listener._clusapi_utils def _get_fake_event(self, **kwargs): event = dict(cluster_object_name=self._FAKE_GROUP_NAME.upper(), object_type=mock.sentinel.object_type, filter_flags=mock.sentinel.filter_flags, buff=mock.sentinel.buff, buff_sz=mock.sentinel.buff_sz) event.update(**kwargs) return event def _get_exp_processed_event(self, event, **kwargs): preserved_keys = ['cluster_object_name', 'object_type', 'filter_flags', 'notif_key'] exp_proc_evt = {key: event[key] for key in preserved_keys} exp_proc_evt.update(**kwargs) return exp_proc_evt @mock.patch('ctypes.byref') def test_process_event_dropped(self, mock_byref): event = self._get_fake_event(cluster_object_name='other_group_name') self.assertIsNone(self._listener._process_event(event)) event = self._get_fake_event(notif_key=2) self.assertIsNone(self._listener._process_event(event)) notif_key = self._listener._NOTIF_KEY_GROUP_COMMON_PROP self._clusapi.get_cluster_group_status_info.side_effect = ( exceptions.ClusterPropertyListEntryNotFound( property_name='fake_prop_name')) event = self._get_fake_event(notif_key=notif_key) self.assertIsNone(self._listener._process_event(event)) def test_process_state_change_event(self): fake_state = constants.CLUSTER_GROUP_ONLINE event_buff = ctypes.c_ulong(fake_state) notif_key = self._listener._NOTIF_KEY_GROUP_STATE event = self._get_fake_event(notif_key=notif_key, buff=ctypes.byref(event_buff), buff_sz=ctypes.sizeof(event_buff)) exp_proc_evt = self._get_exp_processed_event( event, state=fake_state) proc_evt = self._listener._process_event(event) self.assertEqual(exp_proc_evt, proc_evt) @mock.patch('ctypes.byref') def test_process_status_info_change_event(self, mock_byref): self._clusapi.get_cluster_group_status_info.return_value = ( mock.sentinel.status_info) mock_byref.side_effect = lambda x: ('byref', x) notif_key = self._listener._NOTIF_KEY_GROUP_COMMON_PROP event = self._get_fake_event(notif_key=notif_key) exp_proc_evt = self._get_exp_processed_event( event, status_info=mock.sentinel.status_info) proc_evt = self._listener._process_event(event) self.assertEqual(exp_proc_evt, proc_evt) self._clusapi.get_cluster_group_status_info.assert_called_once_with( mock_byref(mock.sentinel.buff), mock.sentinel.buff_sz)