nova/nova/tests/functional/compute/test_live_migration.py
Stephen Finucane 8750c4e173 functional: Enable real policy fixture by default
Enable the policy fixture by default, which should yield more realistic
functional tests. We need to update some tests to use admin APIs where
policy dictates they are necessary. Note that we're currently testing
the legacy policy - not the updated, scoped policy - since the legacy
policy is the default one currently.

Note that we also need to modify the 'SingleCellSimple' fixture in this
change to use the same project ID as the 'OSAPIFixture'.

Change-Id: Ia3dea78f16cb3c7081714c4db36e20d5ee76ed7d
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
2020-09-16 11:31:23 +01:00

219 lines
8.9 KiB
Python

# Copyright 2018 Red Hat, Inc.
#
# 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 oslo_utils.fixture import uuidsentinel as uuids
from nova import exception
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional import integrated_helpers
from nova.tests.unit import fake_notifier
class FakeCinderError(object):
"""Poor man's Mock because we're stubbing out and not mock.patching. Stubs
out attachment_delete. We keep a raise and call count to simulate a single
volume error while being able to assert that we still got called for all
of an instance's volumes.
"""
def __init__(self):
self.raise_count = 0
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
if self.raise_count == 0:
self.raise_count += 1
raise exception.CinderConnectionFailed(reason='Fake Cinder error')
class LiveMigrationCinderFailure(integrated_helpers._IntegratedTestBase):
# Default self.api to the self.admin_api as live migration is admin only
ADMIN_API = True
api_major_version = 'v2.1'
microversion = 'latest'
def setUp(self):
super(LiveMigrationCinderFailure, self).setUp()
fake_notifier.stub_notifier(self)
self.addCleanup(fake_notifier.reset)
# Start a second compute node (the first one was started for us by
# _IntegratedTestBase. set_nodes() is needed to avoid duplicate
# nodenames. See comments in test_bug_1702454.py.
self.compute2 = self.start_service('compute', host='host2')
def test_live_migrate_attachment_delete_fails(self):
server = self.api.post_server({
'server': {
'flavorRef': 1,
'imageRef': '155d900f-4e14-4e4c-a73d-069cbf4541e6',
'name': 'live-migrate-attachment-delete-fail-test',
'networks': 'none',
'block_device_mapping_v2': [
{'boot_index': 0,
'uuid': uuids.broken_volume,
'source_type': 'volume',
'destination_type': 'volume'},
{'boot_index': 1,
'uuid': uuids.working_volume,
'source_type': 'volume',
'destination_type': 'volume'}]}})
server = self._wait_for_state_change(server, 'ACTIVE')
source = server['OS-EXT-SRV-ATTR:host']
if source == self.compute.host:
dest = self.compute2.host
else:
dest = self.compute.host
post = {
'os-migrateLive': {
'host': dest,
'block_migration': False,
}
}
stub_attachment_delete = FakeCinderError()
self.stub_out('nova.volume.cinder.API.attachment_delete',
stub_attachment_delete)
self.api.post_server_action(server['id'], post)
self._wait_for_server_parameter(server,
{'OS-EXT-SRV-ATTR:host': dest,
'status': 'ACTIVE'})
self.assertEqual(2, stub_attachment_delete.call_count)
self.assertEqual(1, stub_attachment_delete.raise_count)
class TestVolAttachmentsDuringLiveMigration(
integrated_helpers._IntegratedTestBase
):
"""Assert the lifecycle of volume attachments during LM rollbacks
"""
# Default self.api to the self.admin_api as live migration is admin only
ADMIN_API = True
microversion = 'latest'
def _setup_compute_service(self):
self._start_compute('src')
self._start_compute('dest')
@mock.patch('nova.virt.fake.FakeDriver.live_migration')
def test_vol_attachments_during_driver_live_mig_failure(self, mock_lm):
"""Assert volume attachments during live migration rollback
* Mock live_migration to always rollback and raise a failure within the
fake virt driver
* Launch a boot from volume instance
* Assert that the volume is attached correctly to the instance
* Live migrate the instance to another host invoking the mocked
live_migration method
* Assert that the instance is still on the source host
* Assert that the original source host volume attachment remains
"""
# Mock out driver.live_migration so that we always rollback
def _fake_live_migration_with_rollback(
context, instance, dest, post_method, recover_method,
block_migration=False, migrate_data=None):
# Just call the recover_method to simulate a rollback
recover_method(context, instance, dest, migrate_data)
# raise test.TestingException here to imitate a virt driver
raise test.TestingException()
mock_lm.side_effect = _fake_live_migration_with_rollback
volume_id = nova_fixtures.CinderFixture.IMAGE_BACKED_VOL
server = self._build_server(
name='test_bfv_live_migration_failure', image_uuid='',
networks='none'
)
server['block_device_mapping_v2'] = [{
'source_type': 'volume',
'destination_type': 'volume',
'boot_index': 0,
'uuid': volume_id
}]
server = self.api.post_server({'server': server})
self._wait_for_state_change(server, 'ACTIVE')
# Fetch the source host for use later
server = self.api.get_server(server['id'])
src_host = server['OS-EXT-SRV-ATTR:host']
# Assert that the volume is connected to the instance
self.assertIn(
volume_id, self.cinder.volume_ids_for_instance(server['id']))
# Assert that we have an active attachment in the fixture
attachments = self.cinder.volume_to_attachment.get(volume_id)
self.assertEqual(1, len(attachments))
# Fetch the attachment_id for use later once we have migrated
src_attachment_id = list(attachments.keys())[0]
# Migrate the instance and wait until the migration errors out thanks
# to our mocked version of live_migration raising TestingException
self._live_migrate(server, 'error', server_expected_state='ERROR')
# Assert that we called the fake live_migration method
mock_lm.assert_called_once()
# Assert that the instance is on the source
server = self.api.get_server(server['id'])
self.assertEqual(src_host, server['OS-EXT-SRV-ATTR:host'])
# Assert that the src attachment is still present
attachments = self.cinder.volume_to_attachment.get(volume_id)
self.assertIn(src_attachment_id, attachments.keys())
self.assertEqual(1, len(attachments))
class LiveMigrationNeutronInteractionsTest(
integrated_helpers._IntegratedTestBase):
# NOTE(artom) We need the admin API to force the host when booting the test
# server.
ADMIN_API = True
microversion = 'latest'
def _setup_compute_service(self):
self._start_compute('src')
self._start_compute('dest')
def test_live_migrate_vifs_from_info_cache(self):
"""Test that bug 1879787 can no longer manifest itself because we get
the network_info from the instance info cache, and not Neutron.
"""
def stub_notify(context, instance, event_suffix,
network_info=None, extra_usage_info=None, fault=None):
vif = network_info[0]
# Make sure we have the correct VIF (the NeutronFixture
# deterministically uses port_2 for networks=auto) and that the
# profile does not contain `migrating_to`, indicating that we did
# not obtain it from the Neutron API.
self.assertEqual(self.neutron.port_2['id'], vif['id'])
self.assertNotIn('migrating_to', vif['profile'])
server = self._create_server(networks='auto',
host=self.computes['src'].host)
with mock.patch.object(self.computes['src'].manager,
'_notify_about_instance_usage',
side_effect=stub_notify) as mock_notify:
self._live_migrate(server, 'completed')
server = self.api.get_server(server['id'])
self.assertEqual('dest', server['OS-EXT-SRV-ATTR:host'])
# We don't care about call arguments here, we just want to be sure
# our stub actually got called.
mock_notify.assert_called()