Skip to content

Commit 38a6d04

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add functional tests to reproduce bug #1960412" into stable/wallaby
2 parents 639cdfe + 3d69804 commit 38a6d04

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)