|
12 | 12 | # License for the specific language governing permissions and limitations
|
13 | 13 | # under the License.
|
14 | 14 |
|
| 15 | +import mock |
15 | 16 | from oslo_utils.fixture import uuidsentinel as uuids
|
16 | 17 |
|
17 | 18 | from nova import exception
|
| 19 | +from nova import test |
18 | 20 | from nova.tests import fixtures as nova_fixtures
|
19 | 21 | from nova.tests.functional import integrated_helpers
|
20 | 22 | from nova.tests.unit import fake_notifier
|
@@ -91,3 +93,94 @@ def test_live_migrate_attachment_delete_fails(self):
|
91 | 93 | 'status': 'ACTIVE'})
|
92 | 94 | self.assertEqual(2, stub_attachment_delete.call_count)
|
93 | 95 | self.assertEqual(1, stub_attachment_delete.raise_count)
|
| 96 | + |
| 97 | + |
| 98 | +class TestVolAttachmentsDuringLiveMigration( |
| 99 | + integrated_helpers._IntegratedTestBase |
| 100 | +): |
| 101 | + """Assert the lifecycle of volume attachments during LM rollbacks |
| 102 | + """ |
| 103 | + |
| 104 | + # Default self.api to the self.admin_api as live migration is admin only |
| 105 | + ADMIN_API = True |
| 106 | + microversion = 'latest' |
| 107 | + |
| 108 | + def setUp(self): |
| 109 | + super().setUp() |
| 110 | + self.cinder = self.useFixture(nova_fixtures.CinderFixture(self)) |
| 111 | + |
| 112 | + def _setup_compute_service(self): |
| 113 | + self._start_compute('src') |
| 114 | + self._start_compute('dest') |
| 115 | + |
| 116 | + @mock.patch('nova.virt.fake.FakeDriver.live_migration') |
| 117 | + def test_vol_attachments_during_driver_live_mig_failure(self, mock_lm): |
| 118 | + """Assert volume attachments during live migration rollback |
| 119 | +
|
| 120 | + * Mock live_migration to always rollback and raise a failure within the |
| 121 | + fake virt driver |
| 122 | + * Launch a boot from volume instance |
| 123 | + * Assert that the volume is attached correctly to the instance |
| 124 | + * Live migrate the instance to another host invoking the mocked |
| 125 | + live_migration method |
| 126 | + * Assert that the instance is still on the source host |
| 127 | + * Assert that the original source host volume attachment remains |
| 128 | + """ |
| 129 | + # Mock out driver.live_migration so that we always rollback |
| 130 | + def _fake_live_migration_with_rollback( |
| 131 | + context, instance, dest, post_method, recover_method, |
| 132 | + block_migration=False, migrate_data=None): |
| 133 | + # Just call the recover_method to simulate a rollback |
| 134 | + recover_method(context, instance, dest, migrate_data) |
| 135 | + # raise test.TestingException here to imitate a virt driver |
| 136 | + raise test.TestingException() |
| 137 | + mock_lm.side_effect = _fake_live_migration_with_rollback |
| 138 | + |
| 139 | + volume_id = nova_fixtures.CinderFixture.IMAGE_BACKED_VOL |
| 140 | + server = self._build_server( |
| 141 | + name='test_bfv_live_migration_failure', image_uuid='', |
| 142 | + networks='none' |
| 143 | + ) |
| 144 | + server['block_device_mapping_v2'] = [{ |
| 145 | + 'source_type': 'volume', |
| 146 | + 'destination_type': 'volume', |
| 147 | + 'boot_index': 0, |
| 148 | + 'uuid': volume_id |
| 149 | + }] |
| 150 | + server = self.api.post_server({'server': server}) |
| 151 | + self._wait_for_state_change(server, 'ACTIVE') |
| 152 | + |
| 153 | + # Fetch the source host for use later |
| 154 | + server = self.api.get_server(server['id']) |
| 155 | + src_host = server['OS-EXT-SRV-ATTR:host'] |
| 156 | + |
| 157 | + # Assert that the volume is connected to the instance |
| 158 | + self.assertIn( |
| 159 | + volume_id, self.cinder.volume_ids_for_instance(server['id'])) |
| 160 | + |
| 161 | + # Assert that we have an active attachment in the fixture |
| 162 | + attachments = self.cinder.volume_to_attachment.get(volume_id) |
| 163 | + self.assertEqual(1, len(attachments)) |
| 164 | + |
| 165 | + # Fetch the attachment_id for use later once we have migrated |
| 166 | + src_attachment_id = list(attachments.keys())[0] |
| 167 | + |
| 168 | + # Migrate the instance and wait until the migration errors out thanks |
| 169 | + # to our mocked version of live_migration raising TestingException |
| 170 | + self.api.post_server_action( |
| 171 | + server['id'], |
| 172 | + {'os-migrateLive': {'host': None, 'block_migration': 'auto'}}) |
| 173 | + self._wait_for_migration_status(server, ['error']) |
| 174 | + |
| 175 | + # Assert that we called the fake live_migration method |
| 176 | + mock_lm.assert_called_once() |
| 177 | + |
| 178 | + # Assert that the instance is listed as ERROR on the source |
| 179 | + self._wait_for_state_change(server, 'ERROR') |
| 180 | + server = self.api.get_server(server['id']) |
| 181 | + self.assertEqual(src_host, server['OS-EXT-SRV-ATTR:host']) |
| 182 | + |
| 183 | + # Assert that the src attachment is still present |
| 184 | + attachments = self.cinder.volume_to_attachment.get(volume_id) |
| 185 | + self.assertIn(src_attachment_id, attachments.keys()) |
| 186 | + self.assertEqual(1, len(attachments)) |
0 commit comments