nova-powervm/nova_powervm/tests/virt/powervm/test_live_migration.py

395 lines
17 KiB
Python

# Copyright 2015, 2017 IBM Corp.
#
# 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.
#
from __future__ import absolute_import
import fixtures
import mock
from nova import exception
from nova import objects
from nova.objects import migrate_data as mig_obj
from nova import test
from nova.tests.unit import fake_network
from nova_powervm.tests.virt import powervm
from nova_powervm.tests.virt.powervm import fixtures as fx
from nova_powervm.virt.powervm import live_migration as lpm
class TestLPM(test.NoDBTestCase):
def setUp(self):
super(TestLPM, self).setUp()
self.flags(disk_driver='localdisk', group='powervm')
self.drv_fix = self.useFixture(fx.PowerVMComputeDriver())
self.drv = self.drv_fix.drv
self.apt = self.drv.adapter
self.inst = objects.Instance(**powervm.TEST_INSTANCE)
self.network_infos = fake_network.fake_get_instance_nw_info(self, 1)
self.inst.info_cache = objects.InstanceInfoCache(
network_info=self.network_infos)
self.mig_data = mig_obj.PowerVMLiveMigrateData()
self.mig_data.host_mig_data = {}
self.mig_data.dest_ip = '1'
self.mig_data.dest_user_id = 'neo'
self.mig_data.dest_sys_name = 'a'
self.mig_data.public_key = 'PublicKey'
self.mig_data.dest_proc_compat = 'a,b,c'
self.mig_data.vol_data = {}
self.mig_data.vea_vlan_mappings = {}
self.lpmsrc = lpm.LiveMigrationSrc(self.drv, self.inst, self.mig_data)
self.lpmdst = lpm.LiveMigrationDest(self.drv, self.inst)
self.add_key = self.useFixture(fixtures.MockPatch(
'pypowervm.tasks.management_console.add_authorized_key')).mock
self.get_key = self.useFixture(fixtures.MockPatch(
'pypowervm.tasks.management_console.get_public_key')).mock
self.get_key.return_value = 'PublicKey'
# Short path to the host's migration_data
self.host_mig_data = self.drv.host_wrapper.migration_data
@mock.patch('pypowervm.tasks.storage.ScrubOrphanStorageForLpar',
autospec=True)
@mock.patch('nova_powervm.virt.powervm.media.ConfigDrivePowerVM',
autospec=True)
@mock.patch('nova_powervm.virt.powervm.vm.get_instance_wrapper',
autospec=True)
@mock.patch('pypowervm.tasks.vterm.close_vterm', autospec=True)
def test_lpm_source(self, mock_vterm_close, mock_get_wrap,
mock_cd, mock_scrub):
self.host_mig_data['active_migrations_supported'] = 4
self.host_mig_data['active_migrations_in_progress'] = 2
with mock.patch.object(
self.lpmsrc, '_check_migration_ready', return_value=None):
# Test the bad path first, then patch in values to make succeed
mock_wrap = mock.Mock(id=123)
mock_get_wrap.return_value = mock_wrap
self.assertRaises(exception.MigrationPreCheckError,
self.lpmsrc.check_source, 'context',
'block_device_info', [])
# Patch the proc compat fields, to get further
pm = mock.PropertyMock(return_value='b')
type(mock_wrap).proc_compat_mode = pm
self.assertRaises(exception.MigrationPreCheckError,
self.lpmsrc.check_source, 'context',
'block_device_info', [])
pm = mock.PropertyMock(return_value='Not_Migrating')
type(mock_wrap).migration_state = pm
# Get a volume driver.
mock_vol_drv = mock.MagicMock()
# Finally, good path.
self.lpmsrc.check_source('context', 'block_device_info',
[mock_vol_drv])
# Ensure we built a scrubber.
mock_scrub.assert_called_with(mock.ANY, 123)
# Ensure we added the subtasks to remove the vopts.
mock_cd.return_value.dlt_vopt.assert_called_once_with(
mock.ANY, stg_ftsk=mock_scrub.return_value,
remove_mappings=False)
# And ensure the scrubber was executed
mock_scrub.return_value.execute.assert_called_once_with()
mock_vol_drv.pre_live_migration_on_source.assert_called_once_with(
{})
# Ensure migration counts are validated
self.host_mig_data['active_migrations_in_progress'] = 4
self.assertRaises(exception.MigrationPreCheckError,
self.lpmsrc.check_source, 'context',
'block_device_info', [])
# Ensure the vterm was closed
mock_vterm_close.assert_called_once_with(
self.apt, mock_wrap.uuid)
@mock.patch('pypowervm.tasks.storage.ScrubOrphanStorageForLpar',
autospec=True)
@mock.patch('nova_powervm.virt.powervm.media.ConfigDrivePowerVM',
autospec=True)
@mock.patch('nova_powervm.virt.powervm.vm.get_instance_wrapper',
autospec=True)
@mock.patch('pypowervm.tasks.vterm.close_vterm', autospec=True)
def test_lpm_source_for_cold_migration(self, mock_vterm_close,
mock_get_wrap,
mock_cd, mock_scrub):
self.host_mig_data['active_migrations_supported'] = 4
self.host_mig_data['active_migrations_in_progress'] = 2
# Test the bad path first, then patch in values to make succeed
mock_wrap = mock.Mock(id=123)
mock_get_wrap.return_value = mock_wrap
self.assertRaises(exception.MigrationPreCheckError,
self.lpmsrc.check_source, 'context',
'block_device_info', [])
# Patch the proc compat fields, to get further
pm = mock.PropertyMock(return_value='b')
type(mock_wrap).proc_compat_mode = pm
self.assertRaises(exception.MigrationPreCheckError,
self.lpmsrc.check_source, 'context',
'block_device_info', [])
pm = mock.PropertyMock(return_value='Not_Migrating')
type(mock_wrap).migration_state = pm
# Get a volume driver.
mock_vol_drv = mock.MagicMock()
# Finally, good path.
self.lpmsrc.check_source('context', 'block_device_info',
[mock_vol_drv])
# Ensure we built a scrubber.
mock_scrub.assert_called_with(mock.ANY, 123)
# Ensure we added the subtasks to remove the vopts.
mock_cd.return_value.dlt_vopt.assert_called_once_with(
mock.ANY, stg_ftsk=mock_scrub.return_value,
remove_mappings=False)
# And ensure the scrubber was executed
mock_scrub.return_value.execute.assert_called_once_with()
mock_vol_drv.pre_live_migration_on_source.assert_called_once_with(
{})
# Ensure migration counts are validated
self.host_mig_data['active_migrations_in_progress'] = 4
self.assertRaises(exception.MigrationPreCheckError,
self.lpmsrc.check_source, 'context',
'block_device_info', [])
# Ensure the vterm was closed
mock_vterm_close.assert_called_once_with(
self.apt, mock_wrap.uuid)
def test_lpm_dest(self):
src_compute_info = {'stats': {'memory_region_size': 1}}
dst_compute_info = {'stats': {'memory_region_size': 1}}
self.host_mig_data['active_migrations_supported'] = 4
self.host_mig_data['active_migrations_in_progress'] = 2
with mock.patch.object(self.drv.host_wrapper, 'refresh') as mock_rfh:
self.lpmdst.check_destination(
'context', src_compute_info, dst_compute_info)
mock_rfh.assert_called_once_with()
# Ensure migration counts are validated
self.host_mig_data['active_migrations_in_progress'] = 4
self.assertRaises(exception.MigrationPreCheckError,
self.lpmdst.check_destination, 'context',
src_compute_info, dst_compute_info)
# Repair the stat
self.host_mig_data['active_migrations_in_progress'] = 2
# Ensure diff memory sizes raises an exception
dst_compute_info['stats']['memory_region_size'] = 2
self.assertRaises(exception.MigrationPreCheckError,
self.lpmdst.check_destination, 'context',
src_compute_info, dst_compute_info)
@mock.patch('pypowervm.tasks.storage.ComprehensiveScrub', autospec=True)
@mock.patch('nova_powervm.virt.powervm.vif.'
'pre_live_migrate_at_destination', autospec=True)
def test_pre_live_mig(self, mock_vif_pre, mock_scrub):
vol_drv = mock.MagicMock()
network_infos = [{'type': 'pvm_sea'}]
def update_vea_mapping(adapter, host_uuid, instance, network_info,
vea_vlan_mappings):
# Make sure what comes in is None, but that we change it.
self.assertEqual(vea_vlan_mappings, {})
vea_vlan_mappings['test'] = 'resp'
mock_vif_pre.side_effect = update_vea_mapping
resp = self.lpmdst.pre_live_migration(
'context', 'block_device_info', network_infos, 'disk_info',
self.mig_data, [vol_drv])
# Make sure the pre_live_migrate_at_destination was invoked for the vif
mock_vif_pre.assert_called_once_with(
self.drv.adapter, self.drv.host_uuid, self.inst, network_infos[0],
mock.ANY)
self.assertEqual({'test': 'resp'}, self.mig_data.vea_vlan_mappings)
# Make sure we get something back, and that the volume driver was
# invoked.
self.assertIsNotNone(resp)
vol_drv.pre_live_migration_on_destination.assert_called_once_with(
self.mig_data.vol_data)
self.assertEqual(1, mock_scrub.call_count)
self.add_key.assert_called_once_with(self.apt, 'PublicKey')
vol_drv.reset_mock()
raising_vol_drv = mock.Mock()
raising_vol_drv.pre_live_migration_on_destination.side_effect = (
Exception('foo'))
self.assertRaises(
exception.MigrationPreCheckError, self.lpmdst.pre_live_migration,
'context', 'block_device_info', network_infos, 'disk_info',
self.mig_data, [vol_drv, raising_vol_drv])
vol_drv.pre_live_migration_on_destination.assert_called_once_with({})
(raising_vol_drv.pre_live_migration_on_destination.
assert_called_once_with({}))
def test_src_cleanup(self):
vol_drv = mock.Mock()
self.lpmdst.cleanup_volume(vol_drv)
# Ensure the volume driver is not called
self.assertEqual(0, vol_drv.cleanup_volume_at_destination.call_count)
def test_src_cleanup_valid(self):
vol_drv = mock.Mock()
self.lpmdst.pre_live_vol_data = {'vscsi-vol-id': 'fake_udid'}
self.lpmdst.cleanup_volume(vol_drv)
# Ensure the volume driver was called to clean up the volume.
vol_drv.cleanup_volume_at_destination.assert_called_once()
@mock.patch('pypowervm.tasks.migration.migrate_lpar', autospec=True)
@mock.patch('nova_powervm.virt.powervm.live_migration.LiveMigrationSrc.'
'_convert_nl_io_mappings', autospec=True)
@mock.patch('nova_powervm.virt.powervm.vif.pre_live_migrate_at_source',
autospec=True)
def test_live_migration(self, mock_vif_pre_lpm, mock_convert_mappings,
mock_migr):
mock_trunk = mock.MagicMock()
mock_vif_pre_lpm.return_value = [mock_trunk]
mock_convert_mappings.return_value = ['AABBCCDDEEFF/5']
self.lpmsrc.lpar_w = mock.Mock()
self.lpmsrc.live_migration('context', self.mig_data)
mock_migr.assert_called_once_with(
self.lpmsrc.lpar_w, 'a', sdn_override=True, tgt_mgmt_svr='1',
tgt_mgmt_usr='neo', validate_only=False,
virtual_fc_mappings=None, virtual_scsi_mappings=None,
vlan_check_override=True, vlan_mappings=['AABBCCDDEEFF/5'])
# Network assertions
mock_vif_pre_lpm.assert_called_once_with(
self.drv.adapter, self.drv.host_uuid, self.inst, mock.ANY)
mock_trunk.delete.assert_called_once()
# Test that we raise errors received during migration
mock_migr.side_effect = ValueError()
self.assertRaises(ValueError, self.lpmsrc.live_migration, 'context',
self.mig_data)
mock_migr.assert_called_with(
self.lpmsrc.lpar_w, 'a', sdn_override=True, tgt_mgmt_svr='1',
tgt_mgmt_usr='neo', validate_only=False,
virtual_fc_mappings=None, virtual_scsi_mappings=None,
vlan_mappings=['AABBCCDDEEFF/5'], vlan_check_override=True)
def test_convert_nl_io_mappings(self):
# Test simple None case
self.assertIsNone(self.lpmsrc._convert_nl_io_mappings(None))
# Do some mappings
test_mappings = {'aa:bb:cc:dd:ee:ff': 5, 'aa:bb:cc:dd:ee:ee': 126}
expected = ['AABBCCDDEEFF/5', 'AABBCCDDEEEE/126']
self.assertEqual(
set(expected),
set(self.lpmsrc._convert_nl_io_mappings(test_mappings)))
@mock.patch('pypowervm.tasks.migration.migrate_recover', autospec=True)
def test_rollback(self, mock_migr):
self.lpmsrc.lpar_w = mock.Mock()
# Test no need to rollback
self.lpmsrc.lpar_w.migration_state = 'Not_Migrating'
self.lpmsrc.rollback_live_migration('context')
self.assertTrue(self.lpmsrc.lpar_w.refresh.called)
self.assertFalse(mock_migr.called)
# Test calling the rollback
self.lpmsrc.lpar_w.reset_mock()
self.lpmsrc.lpar_w.migration_state = 'Pretend its Migrating'
self.lpmsrc.rollback_live_migration('context')
self.assertTrue(self.lpmsrc.lpar_w.refresh.called)
mock_migr.assert_called_once_with(self.lpmsrc.lpar_w, force=True)
# Test exception from rollback
mock_migr.reset_mock()
self.lpmsrc.lpar_w.reset_mock()
mock_migr.side_effect = ValueError()
self.lpmsrc.rollback_live_migration('context')
self.assertTrue(self.lpmsrc.lpar_w.refresh.called)
mock_migr.assert_called_once_with(self.lpmsrc.lpar_w, force=True)
def test_check_migration_ready(self):
lpar_w, host_w = mock.Mock(), mock.Mock()
lpar_w.can_lpm.return_value = (True, None)
self.lpmsrc._check_migration_ready(lpar_w, host_w)
lpar_w.can_lpm.assert_called_once_with(host_w, migr_data={})
lpar_w.can_lpm.return_value = (False, 'This is the reason message.')
self.assertRaises(exception.MigrationPreCheckError,
self.lpmsrc._check_migration_ready, lpar_w, host_w)
@mock.patch('pypowervm.tasks.migration.migrate_abort', autospec=True)
def test_migration_abort(self, mock_mig_abort):
self.lpmsrc.lpar_w = mock.Mock()
self.lpmsrc.migration_abort()
mock_mig_abort.assert_called_once_with(self.lpmsrc.lpar_w)
@mock.patch('pypowervm.tasks.migration.migrate_recover', autospec=True)
def test_migration_recover(self, mock_mig_recover):
self.lpmsrc.lpar_w = mock.Mock()
self.lpmsrc.migration_recover()
mock_mig_recover.assert_called_once_with(
self.lpmsrc.lpar_w, force=True)
@mock.patch('nova_powervm.virt.powervm.vif.post_live_migrate_at_source',
autospec=True)
def test_post_live_migration_at_source(self, mock_vif_post_lpm_at_source):
network_infos = [{'devname': 'tap-dev1', 'address': 'mac-addr1',
'network': {'bridge': 'br-int'}, 'id': 'vif_id_1'},
{'devname': 'tap-dev2', 'address': 'mac-addr2',
'network': {'bridge': 'br-int'}, 'id': 'vif_id_2'}]
self.lpmsrc.post_live_migration_at_source(network_infos)
# Assertions
for network_info in network_infos:
mock_vif_post_lpm_at_source.assert_any_call(mock.ANY, mock.ANY,
mock.ANY, network_info)
@mock.patch('nova_powervm.virt.powervm.tasks.storage.SaveBDM.execute',
autospec=True)
def test_post_live_migration_at_dest(self, mock_save_bdm):
bdm1, bdm2, vol_drv1, vol_drv2 = [mock.Mock()] * 4
vals = [(bdm1, vol_drv1), (bdm2, vol_drv2)]
self.lpmdst.pre_live_vol_data = {'vscsi-vol-id': 'fake_udid',
'vscsi-vol-id2': 'fake_udid2'}
self.lpmdst.post_live_migration_at_destination('network_infos', vals)
# Assertions
for bdm, vol_drv in vals:
vol_drv.post_live_migration_at_destination.assert_called_with(
mock.ANY)
self.assertEqual(len(vals), mock_save_bdm.call_count)