Skip to content

Commit e156157

Browse files
committed
Unbind port when offloading a shelved instance
When offloading a shelved instance, the compute needs to remove the binding so the port will appear as "unbound" in neutron. Closes-Bug: 1983471 Change-Id: Ia49271b126870c7936c84527a4c39ab96b6c5ea7 Signed-off-by: Arnaud Morin <[email protected]> (cherry picked from commit 4eef0fe)
1 parent b9089ac commit e156157

File tree

6 files changed

+73
-26
lines changed

6 files changed

+73
-26
lines changed

nova/compute/manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6874,6 +6874,9 @@ def _shelve_offload_instance(self, context, instance, clean_shutdown,
68746874
current_power_state = self._get_power_state(instance)
68756875
network_info = self.network_api.get_instance_nw_info(context, instance)
68766876

6877+
ports_id = [vif['id'] for vif in network_info]
6878+
self.network_api.unbind_ports(context, ports_id, detach=False)
6879+
68776880
block_device_info = self._get_instance_block_device_info(context,
68786881
instance,
68796882
bdms=bdms)

nova/network/neutron.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -614,10 +614,22 @@ def _check_external_network_attach(self, context, nets):
614614
raise exception.ExternalNetworkAttachForbidden(
615615
network_uuid=net['id'])
616616

617+
def unbind_ports(self, context, ports, detach=True):
618+
"""Unbind and detach the given ports by clearing their
619+
device_owner and dns_name.
620+
The device_id will also be cleaned if detach=True.
621+
622+
:param context: The request context.
623+
:param ports: list of port IDs.
624+
"""
625+
neutron = get_client(context)
626+
self._unbind_ports(context, ports, neutron, detach=detach)
627+
617628
def _unbind_ports(self, context, ports,
618-
neutron, port_client=None):
619-
"""Unbind the given ports by clearing their device_id,
629+
neutron, port_client=None, detach=True):
630+
"""Unbind and detach the given ports by clearing their
620631
device_owner and dns_name.
632+
The device_id will also be cleaned if detach=True.
621633
622634
:param context: The request context.
623635
:param ports: list of port IDs.
@@ -640,11 +652,12 @@ def _unbind_ports(self, context, ports,
640652

641653
port_req_body: ty.Dict[str, ty.Any] = {
642654
'port': {
643-
'device_id': '',
644-
'device_owner': '',
645655
constants.BINDING_HOST_ID: None,
646656
}
647657
}
658+
if detach:
659+
port_req_body['port']['device_id'] = ''
660+
port_req_body['port']['device_owner'] = ''
648661
try:
649662
port = self._show_port(
650663
context, port_id, neutron_client=neutron,

nova/tests/functional/libvirt/test_pci_sriov_servers.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,10 +1612,7 @@ def test_shelve_offload(self):
16121612
# to any host but should still be owned by the vm
16131613
port = self.neutron.show_port(vdpa_port['id'])['port']
16141614
self.assertEqual(server['id'], port['device_id'])
1615-
# FIXME(sean-k-mooney): we should be unbinding the port from
1616-
# the host when we shelve offload but we don't today.
1617-
# This is unrelated to vdpa port and is a general issue.
1618-
self.assertEqual(hostname, port['binding:host_id'])
1615+
self.assertIsNone(port['binding:host_id'])
16191616
self.assertIn('binding:profile', port)
16201617
self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
16211618
self.assertIsNone(server['OS-EXT-SRV-ATTR:host'])
@@ -1637,9 +1634,7 @@ def test_unshelve_to_same_host(self):
16371634
self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci)
16381635
self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
16391636
port = self.neutron.show_port(vdpa_port['id'])['port']
1640-
# FIXME(sean-k-mooney): shelve offload should unbind the port
1641-
# self.assertEqual('', port['binding:host_id'])
1642-
self.assertEqual(hostname, port['binding:host_id'])
1637+
self.assertIsNone(port['binding:host_id'])
16431638

16441639
server = self._unshelve_server(server)
16451640
self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci - 2)
@@ -1670,9 +1665,7 @@ def test_unshelve_to_different_host(self):
16701665
self.assertPCIDeviceCounts(source, total=num_pci, free=num_pci)
16711666
self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
16721667
port = self.neutron.show_port(vdpa_port['id'])['port']
1673-
# FIXME(sean-k-mooney): shelve should unbind the port
1674-
# self.assertEqual('', port['binding:host_id'])
1675-
self.assertEqual(source, port['binding:host_id'])
1668+
self.assertIsNone(port['binding:host_id'])
16761669

16771670
# force the unshelve to the other host
16781671
self.api.put_service(
@@ -3926,17 +3919,7 @@ def test_shelve(self):
39263919

39273920
port = self.neutron.show_port(uuids.dpu_tunnel_port)['port']
39283921
self.assertIn('binding:profile', port)
3929-
self.assertEqual(
3930-
{
3931-
'pci_vendor_info': '15b3:101e',
3932-
'pci_slot': '0000:82:00.4',
3933-
'physical_network': None,
3934-
'pf_mac_address': '52:54:00:1e:59:02',
3935-
'vf_num': 3,
3936-
'card_serial_number': 'MT0000X00002',
3937-
},
3938-
port['binding:profile'],
3939-
)
3922+
self.assertEqual({}, port['binding:profile'])
39403923

39413924
def test_suspend(self):
39423925
self.start_compute()

nova/tests/functional/test_servers.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6526,3 +6526,41 @@ def test_accels_from_both_port_and_flavor(self):
65266526
binding_profile = neutronapi.get_binding_profile(updated_port)
65276527
self.assertNotIn('arq_uuid', binding_profile)
65286528
self.assertNotIn('pci_slot', binding_profile)
6529+
6530+
6531+
class PortBindingShelvedServerTest(integrated_helpers._IntegratedTestBase):
6532+
"""Tests for servers with ports."""
6533+
6534+
compute_driver = 'fake.SmallFakeDriver'
6535+
6536+
def setUp(self):
6537+
super(PortBindingShelvedServerTest, self).setUp()
6538+
self.flavor_id = self._create_flavor(
6539+
disk=10, ephemeral=20, swap=5 * 1024)
6540+
6541+
def test_shelve_offload_with_port(self):
6542+
# Do not wait before offloading
6543+
self.flags(shelved_offload_time=0)
6544+
6545+
server = self._create_server(
6546+
flavor_id=self.flavor_id,
6547+
networks=[{'port': self.neutron.port_1['id']}])
6548+
6549+
port = self.neutron.show_port(self.neutron.port_1['id'])['port']
6550+
6551+
# Assert that the port is actually associated to the instance
6552+
self.assertEqual(port['device_id'], server['id'])
6553+
self.assertEqual(port['binding:host_id'], 'compute')
6554+
self.assertEqual(port['binding:status'], 'ACTIVE')
6555+
6556+
# Do shelve
6557+
server = self._shelve_server(server, 'SHELVED_OFFLOADED')
6558+
6559+
# Retrieve the updated port
6560+
port = self.neutron.show_port(self.neutron.port_1['id'])['port']
6561+
6562+
# Assert that the port is still associated to the instance
6563+
# but the binding is not on the compute anymore
6564+
self.assertEqual(port['device_id'], server['id'])
6565+
self.assertIsNone(port['binding:host_id'])
6566+
self.assertNotIn('binding:status', port)

nova/tests/unit/compute/test_shelve.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ def test_shelve_offload_forced_shutdown(self, mock_power_off):
209209
instance = self._shelve_offload(clean_shutdown=False)
210210
mock_power_off.assert_called_once_with(instance, 0, 0)
211211

212+
@mock.patch.object(neutron_api.API, 'unbind_ports')
212213
@mock.patch.object(compute_utils, 'EventReporter')
213214
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
214215
@mock.patch.object(nova.compute.manager.ComputeManager,
@@ -225,7 +226,7 @@ def test_shelve_offload_forced_shutdown(self, mock_power_off):
225226
def _shelve_offload(self, mock_notify, mock_notify_instance_usage,
226227
mock_get_power_state, mock_update_resource_tracker,
227228
mock_delete_alloc, mock_terminate, mock_get_bdms,
228-
mock_event, clean_shutdown=True):
229+
mock_event, mock_unbind_ports, clean_shutdown=True):
229230
host = 'fake-mini'
230231
instance = self._create_fake_instance_obj(params={'host': host})
231232
instance.task_state = task_states.SHELVING
@@ -278,6 +279,9 @@ def stub_instance_save(inst, *args, **kwargs):
278279
instance.uuid,
279280
graceful_exit=False)
280281

282+
mock_unbind_ports.assert_called_once_with(
283+
self.context, mock.ANY, detach=False)
284+
281285
return instance
282286

283287
@mock.patch('nova.compute.utils.'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
[`bug 1983471 <https://bugs.launchpad.net/nova/+bug/1983471>`_]
5+
When offloading a shelved instance, the compute will now remove the
6+
binding so instance ports will appear as "unbound" in neutron.

0 commit comments

Comments
 (0)