Skip to content

Commit 025c6d1

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add functional tests to reproduce bug #1960412" into stable/xena
2 parents b41c0fd + 479b8db commit 025c6d1

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)