Skip to content

Commit 479b8db

Browse files
MrStupnikovkajinamit
authored andcommitted
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 libvirt and neutron fixtures: - libvirt fixture was changed to support live migrations of instances with regular ports: without this change _update_vif_xml() complains about lack of address element in VIF's XML. - 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. Related-bug: #1960412 Related-bug: #1949808 Change-Id: I152581deb6e659c551f78eed66e4b0b958b20c53 (cherry picked from commit 1ad287b)
1 parent 19c4f8e commit 479b8db

File tree

3 files changed

+118
-7
lines changed

3 files changed

+118
-7
lines changed

nova/tests/fixtures/libvirt.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,8 @@ def XMLDesc(self, flags):
13911391
nics += '''<interface type='%(type)s'>
13921392
<mac address='%(mac)s'/>
13931393
<target dev='tap274487d1-6%(func)s'/>
1394+
<address type='pci' domain='0x0000' bus='0x00' slot='0x03'
1395+
function='0x%(func)s'/>
13941396
</interface>''' % nic
13951397
else:
13961398
# This branch covers the macvtap vnic-type.

nova/tests/fixtures/neutron.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -729,19 +729,22 @@ def delete_port_binding(self, port_id, host_id):
729729
self._validate_port_binding(port_id, host_id)
730730
del self._port_bindings[port_id][host_id]
731731

732-
def _activate_port_binding(self, port_id, host_id):
732+
def _activate_port_binding(self, port_id, host_id, modify_port=False):
733733
# It makes sure that only one binding is active for a port
734734
for host, binding in self._port_bindings[port_id].items():
735735
if host == host_id:
736736
# NOTE(gibi): neutron returns 409 if this binding is already
737737
# active but nova does not depend on this behaviour yet.
738738
binding['status'] = 'ACTIVE'
739+
if modify_port:
740+
# We need to ensure that port's binding:host_id is valid
741+
self._merge_in_active_binding(self._ports[port_id])
739742
else:
740743
binding['status'] = 'INACTIVE'
741744

742745
def activate_port_binding(self, port_id, host_id):
743746
self._validate_port_binding(port_id, host_id)
744-
self._activate_port_binding(port_id, host_id)
747+
self._activate_port_binding(port_id, host_id, modify_port=True)
745748

746749
def show_port_binding(self, port_id, host_id):
747750
self._validate_port_binding(port_id, host_id)

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)