241 lines
10 KiB
Python
241 lines
10 KiB
Python
# Copyright 2015 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.
|
|
#
|
|
|
|
import mock
|
|
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova import test
|
|
|
|
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.TestCase):
|
|
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.lpmsrc = lpm.LiveMigrationSrc(self.drv, self.inst, {})
|
|
self.lpmdst = lpm.LiveMigrationDest(self.drv, self.inst)
|
|
|
|
@mock.patch('pypowervm.tasks.storage.ScrubOrphanStorageForLpar')
|
|
@mock.patch('nova_powervm.virt.powervm.media.ConfigDrivePowerVM')
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_instance_wrapper')
|
|
@mock.patch('pypowervm.tasks.vterm.close_vterm')
|
|
@mock.patch('pypowervm.wrappers.managed_system.System.migration_data',
|
|
new_callable=mock.PropertyMock, name='MigDataProp')
|
|
def test_lpm_source(self, mock_migrdata, mock_vterm_close, mock_get_wrap,
|
|
mock_cd, mock_scrub):
|
|
migr_data = {'active_migrations_supported': 4,
|
|
'active_migrations_in_progress': 2}
|
|
mock_migrdata.return_value = migr_data
|
|
|
|
with mock.patch.object(
|
|
self.lpmsrc, '_check_migration_ready', return_value=None):
|
|
|
|
# Test the bad path first, then patch in values to make succeed
|
|
self.lpmsrc.dest_data = {'dest_proc_compat': 'a,b,c'}
|
|
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(
|
|
{'public_key': None})
|
|
|
|
# Ensure migration counts are validated
|
|
migr_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.wrappers.managed_system.System.migration_data',
|
|
new_callable=mock.PropertyMock, name='MigDataProp')
|
|
def test_lpm_dest(self, mock_migrdata):
|
|
src_compute_info = {'stats': {'memory_region_size': 1}}
|
|
dst_compute_info = {'stats': {'memory_region_size': 1}}
|
|
|
|
migr_data = {'active_migrations_supported': 4,
|
|
'active_migrations_in_progress': 2}
|
|
mock_migrdata.return_value = migr_data
|
|
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()
|
|
self.assertEqual(2, mock_migrdata.call_count)
|
|
|
|
# Ensure migration counts are validated
|
|
migr_data['active_migrations_in_progress'] = 4
|
|
self.assertRaises(exception.MigrationPreCheckError,
|
|
self.lpmdst.check_destination, 'context',
|
|
src_compute_info, dst_compute_info)
|
|
# Repair the stat
|
|
migr_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')
|
|
def test_pre_live_mig(self, mock_scrub):
|
|
mock_vol_drv = mock.MagicMock()
|
|
resp = self.lpmdst.pre_live_migration(
|
|
'context', 'block_device_info', 'network_info', 'disk_info',
|
|
{}, [mock_vol_drv])
|
|
|
|
# Make sure we get something back, and that the volume driver was
|
|
# invoked.
|
|
self.assertIsNotNone(resp)
|
|
mock_vol_drv.pre_live_migration_on_destination.assert_called_once_with(
|
|
{}, {})
|
|
self.assertEqual(1, mock_scrub.call_count)
|
|
# Ensure we save the data for later use.
|
|
self.assertIsNotNone(getattr(self.lpmdst, 'pre_live_data', None))
|
|
|
|
@mock.patch('pypowervm.tasks.management_console.add_authorized_key')
|
|
def test_pre_live_mig2(self, mock_add_key):
|
|
vol_drv = mock.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_info', 'disk_info',
|
|
{'migrate_data': {'public_key': 'abc123'}},
|
|
[vol_drv, raising_vol_drv])
|
|
mock_add_key.assert_called_once_with(self.apt, 'abc123')
|
|
vol_drv.pre_live_migration_on_destination.assert_called_once_with(
|
|
{'public_key': 'abc123'}, {})
|
|
(raising_vol_drv.pre_live_migration_on_destination.
|
|
assert_called_once_with({'public_key': 'abc123'}, {}))
|
|
|
|
def test_src_cleanup(self):
|
|
vol_drv = mock.Mock()
|
|
self.lpmdst.pre_live_data = {}
|
|
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_with({})
|
|
|
|
@mock.patch('pypowervm.tasks.migration.migrate_lpar')
|
|
def test_live_migration(self, mock_migr):
|
|
|
|
self.lpmsrc.lpar_w = mock.Mock()
|
|
self.lpmsrc.dest_data = dict(
|
|
dest_sys_name='a', dest_ip='1', dest_user_id='neo')
|
|
self.lpmsrc.live_migration('context', {})
|
|
mock_migr.called_once_with('context')
|
|
|
|
# Test that we raise errors received during migration
|
|
mock_migr.side_effect = ValueError()
|
|
self.assertRaises(ValueError, self.lpmsrc.live_migration, 'context',
|
|
{})
|
|
mock_migr.called_once_with('context')
|
|
|
|
def test_post_live_mig_src(self):
|
|
self.lpmsrc.post_live_migration_at_source('network_info')
|
|
|
|
def test_post_live_mig_dest(self):
|
|
self.lpmdst.post_live_migration_at_destination('network_info', [])
|
|
|
|
@mock.patch('pypowervm.tasks.migration.migrate_recover')
|
|
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=None)
|
|
|
|
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')
|
|
def test_migration_abort(self, mock_mig_abort):
|
|
self.lpmsrc.lpar_w = mock.Mock()
|
|
self.lpmsrc.migration_abort()
|
|
mock_mig_abort.called_once_with(self.lpmsrc.lpar_w)
|
|
|
|
@mock.patch('pypowervm.tasks.migration.migrate_recover')
|
|
def test_migration_recover(self, mock_mig_recover):
|
|
self.lpmsrc.lpar_w = mock.Mock()
|
|
self.lpmsrc.migration_recover()
|
|
mock_mig_recover.called_once_with(self.lpmsrc.lpar_w, force=True)
|