|
12 | 12 | # License for the specific language governing permissions and limitations
|
13 | 13 | # under the License.
|
14 | 14 |
|
| 15 | +import copy |
15 | 16 | import threading
|
16 | 17 |
|
17 | 18 | from lxml import etree
|
18 | 19 | from nova.tests.functional import integrated_helpers
|
19 | 20 | from nova.tests.functional.libvirt import base as libvirt_base
|
20 | 21 |
|
21 | 22 |
|
22 |
| -class LiveMigrationQueuedAbortTest( |
| 23 | +class LiveMigrationWithLockBase( |
23 | 24 | libvirt_base.LibvirtMigrationMixin,
|
24 | 25 | libvirt_base.ServersTestBase,
|
25 | 26 | integrated_helpers.InstanceHelperMixin
|
26 | 27 | ):
|
27 |
| - """Functional test for bug 1949808. |
| 28 | + """Base for live migration tests which require live migration to be |
| 29 | + locked for certain period of time and then unlocked afterwards. |
28 | 30 |
|
29 |
| - This test is used to confirm that VM's state is reverted properly |
30 |
| - when queued Live migration is aborted. |
| 31 | + Separate base class is needed because locking mechanism could work |
| 32 | + in an unpredicted way if two tests for the same class would try to |
| 33 | + use it simultaneously. Every test using this mechanism should use |
| 34 | + separate class instance. |
31 | 35 | """
|
32 | 36 |
|
33 | 37 | api_major_version = 'v2.1'
|
@@ -69,7 +73,15 @@ def _migrate_stub(self, domain, destination, params, flags):
|
69 | 73 | dom = conn.lookupByUUIDString(server)
|
70 | 74 | dom.complete_job()
|
71 | 75 |
|
72 |
| - def test_queued_live_migration_abort(self): |
| 76 | + |
| 77 | +class LiveMigrationQueuedAbortTestVmStatus(LiveMigrationWithLockBase): |
| 78 | + """Functional test for bug #1949808. |
| 79 | +
|
| 80 | + This test is used to confirm that VM's state is reverted properly |
| 81 | + when queued Live migration is aborted. |
| 82 | + """ |
| 83 | + |
| 84 | + def test_queued_live_migration_abort_vm_status(self): |
73 | 85 | # Lock live migrations
|
74 | 86 | self.lock_live_migration.acquire()
|
75 | 87 |
|
@@ -115,3 +127,97 @@ def test_queued_live_migration_abort(self):
|
115 | 127 | AssertionError,
|
116 | 128 | self._wait_for_state_change, self.server_b, 'ACTIVE')
|
117 | 129 | self._wait_for_state_change(self.server_b, 'MIGRATING')
|
| 130 | + |
| 131 | + |
| 132 | +class LiveMigrationQueuedAbortTestLeftoversRemoved(LiveMigrationWithLockBase): |
| 133 | + """Functional test for bug #1960412. |
| 134 | +
|
| 135 | + Placement allocations for live migration and inactive Neutron port |
| 136 | + bindings on destination host created by Nova control plane when live |
| 137 | + migration is initiated should be removed when queued live migration |
| 138 | + is aborted using Nova API. |
| 139 | + """ |
| 140 | + |
| 141 | + def test_queued_live_migration_abort_leftovers_removed(self): |
| 142 | + # Lock live migrations |
| 143 | + self.lock_live_migration.acquire() |
| 144 | + |
| 145 | + # Start instances: first one would be used to occupy |
| 146 | + # executor's live migration queue, second one would be used |
| 147 | + # to actually confirm that queued live migrations are |
| 148 | + # aborted properly. |
| 149 | + # port_1 is created automatically when neutron fixture is |
| 150 | + # initialized, port_2 is created manually |
| 151 | + self.server_a = self._create_server( |
| 152 | + host=self.src_hostname, |
| 153 | + networks=[{'port': self.neutron.port_1['id']}]) |
| 154 | + self.neutron.create_port({'port': self.neutron.port_2}) |
| 155 | + self.server_b = self._create_server( |
| 156 | + host=self.src_hostname, |
| 157 | + networks=[{'port': self.neutron.port_2['id']}]) |
| 158 | + # Issue live migration requests for both servers. We expect that |
| 159 | + # server_a live migration would be running, but locked by |
| 160 | + # self.lock_live_migration and server_b live migration would be |
| 161 | + # queued. |
| 162 | + self._live_migrate( |
| 163 | + self.server_a, |
| 164 | + migration_expected_state='running', |
| 165 | + server_expected_state='MIGRATING' |
| 166 | + ) |
| 167 | + self._live_migrate( |
| 168 | + self.server_b, |
| 169 | + migration_expected_state='queued', |
| 170 | + server_expected_state='MIGRATING' |
| 171 | + ) |
| 172 | + |
| 173 | + # Abort live migration for server_b |
| 174 | + migration_server_a = self.api.api_get( |
| 175 | + '/os-migrations?instance_uuid=%s' % self.server_a['id'] |
| 176 | + ).body['migrations'].pop() |
| 177 | + migration_server_b = self.api.api_get( |
| 178 | + '/os-migrations?instance_uuid=%s' % self.server_b['id'] |
| 179 | + ).body['migrations'].pop() |
| 180 | + |
| 181 | + self.api.api_delete( |
| 182 | + '/servers/%s/migrations/%s' % (self.server_b['id'], |
| 183 | + migration_server_b['id'])) |
| 184 | + self._wait_for_migration_status(self.server_b, ['cancelled']) |
| 185 | + # Unlock live migrations and confirm that server_a becomes |
| 186 | + # active again after successful live migration |
| 187 | + self.lock_live_migration.release() |
| 188 | + self._wait_for_state_change(self.server_a, 'ACTIVE') |
| 189 | + self._wait_for_migration_status(self.server_a, ['completed']) |
| 190 | + # FIXME(astupnikov) Assert the server_b never comes out of 'MIGRATING' |
| 191 | + # This should be fixed after bug #1949808 is addressed |
| 192 | + self._wait_for_state_change(self.server_b, 'MIGRATING') |
| 193 | + |
| 194 | + # FIXME(astupnikov) Because of bug #1960412 allocations for aborted |
| 195 | + # queued live migration (server_b) would not be removed. Allocations |
| 196 | + # for completed live migration (server_a) should be empty. |
| 197 | + allocations_server_a_migration = self.placement.get( |
| 198 | + '/allocations/%s' % migration_server_a['uuid'] |
| 199 | + ).body['allocations'] |
| 200 | + self.assertEqual({}, allocations_server_a_migration) |
| 201 | + allocations_server_b_migration = self.placement.get( |
| 202 | + '/allocations/%s' % migration_server_b['uuid'] |
| 203 | + ).body['allocations'] |
| 204 | + src_uuid = self.api.api_get( |
| 205 | + 'os-hypervisors?hypervisor_hostname_pattern=%s' % |
| 206 | + self.src_hostname).body['hypervisors'][0]['id'] |
| 207 | + self.assertIn(src_uuid, allocations_server_b_migration) |
| 208 | + |
| 209 | + # FIXME(astupnikov) Because of bug #1960412 INACTIVE port binding |
| 210 | + # on destination host would not be removed when queued live migration |
| 211 | + # is aborted, so 2 port bindings would exist for server_b port from |
| 212 | + # Neutron's perspective. |
| 213 | + # server_a should be migrated to dest compute, server_b should still |
| 214 | + # be hosted by src compute. |
| 215 | + port_binding_server_a = copy.deepcopy( |
| 216 | + self.neutron._port_bindings[self.neutron.port_1['id']] |
| 217 | + ) |
| 218 | + self.assertEqual(1, len(port_binding_server_a)) |
| 219 | + self.assertNotIn('src', port_binding_server_a) |
| 220 | + port_binding_server_b = copy.deepcopy( |
| 221 | + self.neutron._port_bindings[self.neutron.port_2['id']] |
| 222 | + ) |
| 223 | + self.assertEqual(2, len(port_binding_server_b)) |
0 commit comments