PyMI subscribes to event providers, setting a callback that will be invoked when an event arrives. When called, the event watcher will block until an event is retrieved. A threading.Event is used internally by PyMI for the event waiting workflow. The issue is that the threading module will be monkey patched by eventlet. The event callback is performed from a different thread, so attempting to set the event for which the event watcher waits will some times raise the "error: cannot switch to a different thread" greenlet exception, thus breaking the event listener. This issue can be fixed at the os-win level, without PyMI having to know about eventlet monkey patching. We can make sure that PyMI uses the unpatched threading module, also ensuring that the blocking calls are run in a different thread, so that we don't block the calling thread. eventlet.tpool can be used to easily achieve this. Closes-Bug: #1568824 Change-Id: I9be6c1a1b144bdf565f25517c52439a5fb10cb51
334 lines
14 KiB
Python
334 lines
14 KiB
Python
# 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 mock
|
|
|
|
from os_win import exceptions
|
|
from os_win.tests import test_base
|
|
from os_win.utils.compute import clusterutils
|
|
|
|
|
|
class ClusterUtilsTestCase(test_base.OsWinBaseTestCase):
|
|
"""Unit tests for the Hyper-V ClusterUtilsBase class."""
|
|
|
|
_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()
|
|
|
|
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)
|
|
|
|
@mock.patch.object(clusterutils.ClusterUtils,
|
|
'_lookup_vm_group_check')
|
|
def test_add_vm_to_cluster(self, 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)
|
|
|
|
self.assertTrue(vm_group.PersistentState)
|
|
self.assertEqual(vm_group.AutoFailbackType,
|
|
self._clusterutils._FAILBACK_TRUE)
|
|
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_migrate_vm.assert_called_once_with(
|
|
self._FAKE_VM_NAME, self._FAKE_HOST,
|
|
self._clusterutils._LIVE_MIGRATION_TYPE)
|
|
|
|
@mock.patch.object(clusterutils.ClusterUtils, '_lookup_vm_group_check')
|
|
def test_migrate_vm(self, mock_lookup_vm_group_check):
|
|
vm_group = mock.MagicMock()
|
|
mock_lookup_vm_group_check.return_value = vm_group
|
|
|
|
self._clusterutils._migrate_vm(
|
|
self._FAKE_VM_NAME, self._FAKE_HOST,
|
|
self._clusterutils._LIVE_MIGRATION_TYPE)
|
|
|
|
vm_group.MoveToNewNodeParams.assert_called_once_with(
|
|
self._clusterutils._IGNORE_LOCKED,
|
|
self._FAKE_HOST,
|
|
[self._clusterutils._LIVE_MIGRATION_TYPE])
|
|
|
|
@mock.patch.object(clusterutils, 'tpool')
|
|
def test_monitor_vm_failover_no_vm(self, mock_tpool):
|
|
self._clusterutils._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(fake_callback)
|
|
|
|
mock_tpool.execute.assert_called_once_with(
|
|
self._clusterutils._watcher,
|
|
self._clusterutils._WMI_EVENT_TIMEOUT_MS)
|
|
fake_callback.assert_not_called()
|
|
|
|
@mock.patch.object(clusterutils, 'tpool')
|
|
def test_monitor_vm_failover(self, mock_tpool):
|
|
self._clusterutils._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(fake_callback)
|
|
|
|
mock_tpool.execute.assert_called_once_with(
|
|
self._clusterutils._watcher,
|
|
self._clusterutils._WMI_EVENT_TIMEOUT_MS)
|
|
fake_callback.assert_called_once_with(self._FAKE_VM_NAME,
|
|
self._FAKE_PREV_HOST,
|
|
self._FAKE_HOST)
|