compute-hyperv/compute_hyperv/tests/unit/cluster/test_clusterops.py

349 lines
16 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 ddt
import mock
from nova.compute import power_state
from nova.compute import task_states
from nova.compute import vm_states
from nova import objects
from os_win import exceptions as os_win_exc
from compute_hyperv.nova.cluster import clusterops
from compute_hyperv.tests import fake_instance
from compute_hyperv.tests.unit import test_base
@ddt.ddt
class ClusterOpsTestCase(test_base.HyperVBaseTestCase):
"""Unit tests for the Hyper-V ClusterOps class."""
_FAKE_INSTANCE_NAME = 'fake_instance_name'
@mock.patch('nova.network.API')
def setUp(self, mock_network_api):
super(ClusterOpsTestCase, self).setUp()
self.context = 'fake_context'
self.clusterops = clusterops.ClusterOps()
self.clusterops._context = self.context
self.clusterops._clustutils = mock.MagicMock()
self.clusterops._vmutils = mock.MagicMock()
self.clusterops._network_api = mock.MagicMock()
self.clusterops._vmops = mock.MagicMock()
self.clusterops._serial_console_ops = mock.MagicMock()
self._clustutils = self.clusterops._clustutils
def test_get_instance_host(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
self.clusterops.get_instance_host(mock_instance)
self.clusterops._clustutils.get_vm_host.assert_called_once_with(
mock_instance.name)
def test_add_to_cluster(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
self.clusterops.add_to_cluster(mock_instance)
mock_add_vm = self.clusterops._clustutils.add_vm_to_cluster
mock_add_vm.assert_called_once_with(mock_instance.name)
self.assertEqual(mock_instance.uuid,
self.clusterops._instance_map[mock_instance.name])
@mock.patch.object(clusterops, 'LOG')
def test_add_to_cluster_exception(self, mock_LOG):
mock_instance = fake_instance.fake_instance_obj(self.context)
mock_add_vm = self.clusterops._clustutils.add_vm_to_cluster
mock_add_vm.side_effect = os_win_exc.HyperVClusterException
self.clusterops.add_to_cluster(mock_instance)
self.assertTrue(mock_LOG.exception.called)
def test_remove_from_cluster(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
self.clusterops.remove_from_cluster(mock_instance)
self.clusterops._clustutils.vm_exists.assert_called_once_with(
mock_instance.name)
self.clusterops._clustutils.delete.assert_called_once_with(
mock_instance.name)
self.assertIsNone(self.clusterops._instance_map.get(
mock_instance.name))
@mock.patch.object(clusterops, 'LOG')
def test_remove_from_cluster_exception(self, mock_LOG):
mock_instance = fake_instance.fake_instance_obj(self.context)
mock_delete = self.clusterops._clustutils.delete
mock_delete.side_effect = os_win_exc.HyperVClusterException
self.clusterops.remove_from_cluster(mock_instance)
self.assertTrue(mock_LOG.exception.called)
def test_post_migration(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
self.clusterops.post_migration(mock_instance)
self.assertEqual(
self.clusterops._instance_map[mock_instance.name],
mock_instance.uuid)
@mock.patch('nova.utils.spawn_n')
def test_start_failover_listener_daemon(self, mock_spawn):
self.clusterops.start_failover_listener_daemon()
spawn_args = mock_spawn.call_args_list[0][0]
self.assertEqual(
self._clustutils.get_vm_owner_change_listener.return_value,
spawn_args[0])
cbk = spawn_args[1]
cbk()
mock_spawn.assert_called_with(self.clusterops._failover_migrate)
@mock.patch('nova.utils.spawn_n')
@mock.patch.object(clusterops.ClusterOps, '_failover_migrate')
@mock.patch.object(clusterops.ClusterOps, '_get_nova_instances')
def test_reclaim_failovered_instances(self, mock_get_instances,
mock_failover_migrate,
mock_spawn):
self.clusterops._this_node = 'fake_node'
mock_instance1 = mock.MagicMock(host='other_host')
mock_instance2 = mock.MagicMock(host=self.clusterops._this_node)
mock_get_instances.return_value = [mock_instance1, mock_instance2]
self.clusterops.reclaim_failovered_instances()
self.clusterops._vmops.list_instance_uuids.assert_called_once_with()
mock_get_instances.assert_called_once_with(
['id', 'uuid', 'name', 'host'],
self.clusterops._vmops.list_instance_uuids.return_value)
mock_spawn.assert_called_once_with(
mock_failover_migrate,
mock_instance1.name, mock_instance1.host,
self.clusterops._this_node)
@mock.patch.object(clusterops, 'LOG')
@mock.patch.object(clusterops.ClusterOps, '_get_instance_by_name')
def test_failover_migrate_no_instance(self, mock_get_instance_by_name,
mock_LOG):
mock_get_instance_by_name.return_value = None
self.clusterops._failover_migrate(mock.sentinel.instance_name,
mock.sentinel.old_host,
mock.sentinel.new_host)
mock_LOG.debug.assert_called_once_with(
'Instance %s does not exist in nova. Skipping.',
mock.sentinel.instance_name)
self.assertFalse(
self.clusterops._network_api.get_instance_nw_info.called)
@mock.patch.object(clusterops, 'LOG')
@mock.patch.object(clusterops.ClusterOps, '_get_instance_by_name')
def test_failover_migrate_migrating(self, mock_get_instance_by_name,
mock_LOG):
instance = mock_get_instance_by_name.return_value
instance.task_state = task_states.MIGRATING
self.clusterops._failover_migrate(mock.sentinel.instance_name,
mock.sentinel.old_host,
mock.sentinel.new_host)
mock_LOG.debug.assert_called_once_with(
'Instance %s is live migrating.', mock.sentinel.instance_name)
@mock.patch.object(clusterops.ClusterOps, '_get_instance_by_name')
def test_failover_migrate_same_host(self, mock_get_instance_by_name):
instance = mock_get_instance_by_name.return_value
instance.host = self.clusterops._this_node
self.clusterops._failover_migrate(mock.sentinel.instance_name,
mock.sentinel.old_host,
mock.sentinel.new_host)
self.assertFalse(
self.clusterops._network_api.get_instance_nw_info.called)
@mock.patch.object(clusterops.ClusterOps, '_get_instance_by_name')
def test_failover_migrate_at_source_node(self, mock_get_instance_by_name):
instance = mock_get_instance_by_name.return_value
old_host = 'old_host'
self.clusterops._this_node = old_host
self.clusterops._failover_migrate(mock.sentinel.instance_name,
old_host, mock.sentinel.new_host)
self.clusterops._vmops.unplug_vifs.assert_called_once_with(instance,
self.clusterops._network_api.get_instance_nw_info.return_value)
@mock.patch.object(clusterops, 'LOG')
@mock.patch.object(clusterops.ClusterOps, '_get_instance_by_name')
def test_failover_migrate_not_this_node(self, mock_get_instance_by_name,
mock_LOG):
self.clusterops._this_node = 'new_host'
self.clusterops._failover_migrate(mock.sentinel.instance_name,
None, 'host')
mock_LOG.debug.assert_called_once_with(
'Instance %s did not failover to this node.',
mock.sentinel.instance_name)
@mock.patch.object(clusterops.ClusterOps, '_failover_migrate_networks')
@mock.patch.object(clusterops.ClusterOps, '_nova_failover_server')
@mock.patch.object(clusterops.ClusterOps, '_get_instance_by_name')
def test_failover_migrate_this_node(self, mock_get_instance_by_name,
mock_nova_failover_server,
mock_failover_migrate_networks):
instance = mock_get_instance_by_name.return_value
old_host = 'old_host'
new_host = 'new_host'
self.clusterops._this_node = new_host
self.clusterops._failover_migrate(mock.sentinel.instance_name,
old_host, new_host)
mock_get_instance_by_name.assert_called_once_with(
mock.sentinel.instance_name)
get_inst_nw_info = self.clusterops._network_api.get_instance_nw_info
get_inst_nw_info.assert_called_once_with(self.clusterops._context,
instance)
mock_nova_failover_server.assert_called_once_with(instance, new_host)
mock_failover_migrate_networks.assert_called_once_with(
instance, old_host)
self.clusterops._vmops.plug_vifs.assert_called_once_with(
instance, get_inst_nw_info.return_value)
c_handler = self.clusterops._serial_console_ops.start_console_handler
c_handler.assert_called_once_with(mock.sentinel.instance_name)
def test_failover_migrate_networks(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
fake_source = mock.MagicMock()
fake_migration = {'source_compute': fake_source,
'dest_compute': self.clusterops._this_node}
self.clusterops._failover_migrate_networks(mock_instance,
fake_source)
mock_network_api = self.clusterops._network_api
calls = [mock.call(self.clusterops._context, mock_instance,
self.clusterops._this_node),
mock.call(self.clusterops._context, mock_instance,
self.clusterops._this_node),
mock.call(self.clusterops._context, mock_instance,
self.clusterops._this_node),
mock.call(self.clusterops._context, mock_instance,
fake_source, teardown=True)]
mock_network_api.setup_networks_on_host.assert_has_calls(calls)
mock_network_api.migrate_instance_start.assert_called_once_with(
self.clusterops._context, mock_instance, fake_migration)
mock_network_api.migrate_instance_finish.assert_called_once_with(
self.clusterops._context, mock_instance, fake_migration)
@mock.patch.object(objects.Instance, 'get_by_uuid')
def test_get_instance_by_name(self, mock_get_by_uuid):
mock_instance = fake_instance.fake_instance_obj(self.context)
mock_get_by_uuid.return_value = mock_instance
self.clusterops._instance_map[mock_instance.name] = mock_instance.uuid
ret = self.clusterops._get_instance_by_name(mock_instance.name)
self.assertEqual(ret, mock_instance)
@mock.patch.object(objects.Instance, 'get_by_uuid')
def test_get_instance_by_name_not_in_cache(self, mock_get_by_uuid):
mock_instance = fake_instance.fake_instance_obj(self.context)
self.clusterops._vmutils.get_instance_uuid.return_value = (
mock_instance.uuid)
mock_get_by_uuid.return_value = mock_instance
ret = self.clusterops._get_instance_by_name(mock_instance.name)
self.assertEqual(ret, mock_instance)
self.assertEqual(mock_instance.uuid,
self.clusterops._instance_map[mock_instance.name])
@mock.patch.object(objects.Instance, 'get_by_uuid')
def test_get_instance_by_name_not_update_map(self, mock_get_by_uuid):
mock_instance = fake_instance.fake_instance_obj(self.context)
self.clusterops._vmutils.get_instance_uuid.side_effect = (
os_win_exc.HyperVVMNotFoundException(vm_name=mock_instance.name))
self.clusterops._update_instance_map = mock.MagicMock()
self.clusterops._instance_map = mock.MagicMock()
self.clusterops._instance_map.get.side_effect = [None,
mock_instance.uuid]
mock_get_by_uuid.return_value = mock_instance
ret = self.clusterops._get_instance_by_name(mock_instance.name)
self.assertEqual(ret, mock_instance)
self.clusterops._update_instance_map.assert_called_with()
@mock.patch.object(clusterops.ClusterOps, '_get_nova_instances')
def test_update_instance_map(self, mock_get_instances):
mock_instance = mock.MagicMock(uuid=mock.sentinel.uuid)
mock_instance.configure_mock(name=mock.sentinel.name)
mock_get_instances.return_value = [mock_instance]
self.clusterops._update_instance_map()
self.assertEqual(mock.sentinel.uuid,
self.clusterops._instance_map[mock.sentinel.name])
@ddt.data({'instance_uuids': None},
{'instance_uuids': []},
{'instance_uuids': mock.sentinel.uuid})
@ddt.unpack
@mock.patch.object(clusterops.objects.InstanceList, 'get_by_filters')
def test_get_nova_instances(self, mock_get_by_filters, instance_uuids):
instances = self.clusterops._get_nova_instances(
instance_uuids=instance_uuids)
self.assertEqual(mock_get_by_filters.return_value, instances)
expected_attrs = ['id', 'uuid', 'name']
expected_filters = {'deleted': False}
if instance_uuids is not None:
expected_filters['uuid'] = instance_uuids
mock_get_by_filters.assert_called_once_with(
self.clusterops._context, expected_filters,
expected_attrs=expected_attrs)
@mock.patch.object(clusterops.block_device, 'DriverVolumeBlockDevice')
@mock.patch.object(clusterops.objects.BlockDeviceMappingList,
'get_by_instance_uuid')
def test_get_instance_block_device_mappings(self, mock_get_by_uuid,
mock_DriverVBD):
mock_get_by_uuid.return_value = [mock.sentinel.bdm]
mock_instance = mock.MagicMock()
bdms = self.clusterops._get_instance_block_device_mappings(
mock_instance)
self.assertEqual([mock_DriverVBD.return_value], bdms)
mock_get_by_uuid.assert_called_once_with(self.clusterops._context,
mock_instance.uuid)
mock_DriverVBD.assert_called_once_with(mock.sentinel.bdm)
def test_nova_failover_server(self):
mock_instance = mock.MagicMock(vm_state=vm_states.ERROR,
power_state=power_state.NOSTATE)
self.clusterops._nova_failover_server(mock_instance,
mock.sentinel.host)
self.assertEqual(vm_states.ACTIVE, mock_instance.vm_state)
self.assertEqual(power_state.RUNNING, mock_instance.power_state)
self.assertEqual(mock.sentinel.host, mock_instance.host)
self.assertEqual(mock.sentinel.host, mock_instance.node)
mock_instance.save.assert_called_once_with(expected_task_state=[None])