It was suggested that we replace TestCase with NoDBTestCase [1]. This does two things. First it prevents the test case setup from running the database schema migrations unnecessarily which should improve performance. Second it will cause the test to fail if the code does touch the DB when the developer is claiming that it shouldn't. [1] https://review.openstack.org/#/c/409404/46/nova/tests/unit/virt/powervm/test_media.py Change-Id: I8bcc9cbdcadd9fe89a4450ca405387899baa9fc3
315 lines
14 KiB
Python
315 lines
14 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')
|
|
@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')
|
|
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)
|
|
|
|
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')
|
|
@mock.patch('nova_powervm.virt.powervm.vif.'
|
|
'pre_live_migrate_at_destination')
|
|
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')
|
|
@mock.patch('nova_powervm.virt.powervm.live_migration.LiveMigrationSrc.'
|
|
'_convert_nl_io_mappings')
|
|
@mock.patch('nova_powervm.virt.powervm.vif.pre_live_migrate_at_source')
|
|
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')
|
|
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')
|
|
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)
|
|
|
|
@mock.patch('nova_powervm.virt.powervm.vif.post_live_migrate_at_source')
|
|
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)
|