Skip to content

Commit 3d69804

Browse files
committed
Add functional tests to reproduce bug #1960412
Instance would be affected by problems described in bug #1949808 and bug #1960412 when queued live migration is aborted. This change adds functional test to reproduce problems with placement allocations (record for aborted live migration is not removed when queued live migration is aborted) and with Neutron port bindings (INACTIVE port binding records for destination host are not removed when queued live migration is aborted). It looks like there are no other modifications introduced by Nova control plane which should be reverted when queued live migration is aborted. This patch also changes neutron fixture: - neutron fixture was changed to improve active port binding's tracking during live migration: without this change port's binding:host_id is not updated when activate_port_binding() is called. As a result, list_ports() function returns empty list when constants.BINDING_HOST_ID is used in search_opts, which is the case for setup_networks_on_host() called with teardown=True. Conflicts: - nova/tests/fixtures/libvirt.py - nova/tests/fixtures/neutron.py NOTE. There is no need to change libvirt fixture because original problem with lack of address element is no longer there (I also removed related note from commit message itself). NeutronFixture class is defined in different place instable/wallaby, but code staus the same. Related-bug: #1960412 Related-bug: #1949808 Change-Id: I152581deb6e659c551f78eed66e4b0b958b20c53 (cherry picked from commit 1ad287b) (cherry picked from commit 479b8db)
1 parent 8f00289 commit 3d69804

File tree

2 files changed

+116
-7
lines changed

2 files changed

+116
-7
lines changed

nova/tests/fixtures.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,13 +1844,16 @@ def delete_port_binding(self, context, client, port_id, host):
18441844

18451845
return fake_requests.FakeResponse(204)
18461846

1847-
def _activate_port_binding(self, port_id, host):
1847+
def _activate_port_binding(self, port_id, host, modify_port=False):
18481848
# It makes sure that only one binding is active for a port
18491849
for h, binding in self._port_bindings[port_id].items():
18501850
if h == host:
18511851
# NOTE(gibi): neutron returns 409 if this binding is already
18521852
# active but nova does not depend on this behaviour yet.
18531853
binding['status'] = 'ACTIVE'
1854+
if modify_port:
1855+
# We need to ensure that port's binding:host_id is valid
1856+
self._merge_in_active_binding(self._ports[port_id])
18541857
else:
18551858
binding['status'] = 'INACTIVE'
18561859

@@ -1860,7 +1863,7 @@ def activate_port_binding(self, context, client, port_id, host):
18601863
if failure is not None:
18611864
return failure
18621865

1863-
self._activate_port_binding(port_id, host)
1866+
self._activate_port_binding(port_id, host, modify_port=True)
18641867

18651868
return fake_requests.FakeResponse(200)
18661869

nova/tests/functional/libvirt/test_live_migration.py

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,26 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414

15+
import copy
1516
import threading
1617

1718
from lxml import etree
1819
from nova.tests.functional import integrated_helpers
1920
from nova.tests.functional.libvirt import base as libvirt_base
2021

2122

22-
class LiveMigrationQueuedAbortTest(
23+
class LiveMigrationWithLockBase(
2324
libvirt_base.LibvirtMigrationMixin,
2425
libvirt_base.ServersTestBase,
2526
integrated_helpers.InstanceHelperMixin
2627
):
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.
2830
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.
3135
"""
3236

3337
api_major_version = 'v2.1'
@@ -69,7 +73,15 @@ def _migrate_stub(self, domain, destination, params, flags):
6973
dom = conn.lookupByUUIDString(server)
7074
dom.complete_job()
7175

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):
7385
# Lock live migrations
7486
self.lock_live_migration.acquire()
7587

@@ -115,3 +127,97 @@ def test_queued_live_migration_abort(self):
115127
AssertionError,
116128
self._wait_for_state_change, self.server_b, 'ACTIVE')
117129
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

Comments
 (0)