From 35fade3b5fbe898885338cbe74843e903d4e8a87 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Fri, 28 Jan 2022 19:48:28 +0000 Subject: [PATCH] ovn: use requested-chassis list format for live migration This makes OVN asynchronously set up destination port while VMs are being migrated, reducing network downtime. Also configure 'rarp' activation strategy to wait until libvirt notifies the network about new VM location with a RARP issued from the vif. Change-Id: I55811ea72b2bf0d0b244f422861dca4a7bf8f257 --- .../ovn/mech_driver/ovsdb/ovn_client.py | 22 ++++++-- .../ovn/mech_driver/test_mech_driver.py | 53 +++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index 470079b4701..22efc190252 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -372,9 +372,25 @@ class OVNClient(object): ovn_const.VIF_DETAILS_PF_MAC_ADDRESS)), ovn_const.LSP_OPTIONS_VIF_PLUG_REPRESENTOR_VF_NUM_KEY: str( binding_prof.get(ovn_const.VIF_DETAILS_VF_NUM))}) - options.update({ - ovn_const.LSP_OPTIONS_REQUESTED_CHASSIS_KEY: ( - self.determine_bind_host(port))}) + chassis = self.determine_bind_host(port) + if chassis: + # If OVN supports multi-chassis port bindings, use it for live + # migration to asynchronously configure destination port while + # VM is migrating + if self._sb_idl.is_col_present('Port_Binding', + 'additional_chassis'): + mdst = port.get( + portbindings.PROFILE, {}).get( + ovn_const.MIGRATING_ATTR) + if mdst: + # Let OVN know that the port should be configured on + # destination too + chassis += ',%s' % mdst + # Block traffic on destination host until libvirt sends + # a RARP packet from it to inform network about the new + # location of the port + options['activation-strategy'] = 'rarp' + options[ovn_const.LSP_OPTIONS_REQUESTED_CHASSIS_KEY] = chassis # TODO(lucasagomes): Enable the mcast_flood_reports by default, # according to core OVN developers it shouldn't cause any harm diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index 16665cf96ea..ea3ce139d13 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -1843,6 +1843,59 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): mock.ANY, filters={'id': subnet_ids}) + def test__get_port_options_migrating_additional_chassis_missing(self): + port = { + 'id': 'virt-port', + 'mac_address': '00:00:00:00:00:00', + 'device_owner': 'device_owner', + 'network_id': 'foo', + 'fixed_ips': [], + portbindings.HOST_ID: 'fake-src', + portbindings.PROFILE: { + ovn_const.MIGRATING_ATTR: 'fake-dest', + } + } + options = self.mech_driver._ovn_client._get_port_options(port) + self.assertNotIn('activation-strategy', options.options) + self.assertEqual('fake-src', options.options['requested-chassis']) + + def test__get_port_options_migrating_additional_chassis_present(self): + port = { + 'id': 'virt-port', + 'mac_address': '00:00:00:00:00:00', + 'device_owner': 'device_owner', + 'network_id': 'foo', + 'fixed_ips': [], + portbindings.HOST_ID: 'fake-src', + portbindings.PROFILE: { + ovn_const.MIGRATING_ATTR: 'fake-dest', + } + } + with mock.patch.object( + self.mech_driver._ovn_client._sb_idl, 'is_col_present', + return_value=True): + options = self.mech_driver._ovn_client._get_port_options(port) + self.assertEqual('rarp', options.options['activation-strategy']) + self.assertEqual('fake-src,fake-dest', + options.options['requested-chassis']) + + def test__get_port_options_not_migrating_additional_chassis_present(self): + port = { + 'id': 'virt-port', + 'mac_address': '00:00:00:00:00:00', + 'device_owner': 'device_owner', + 'network_id': 'foo', + 'fixed_ips': [], + portbindings.HOST_ID: 'fake-src', + } + with mock.patch.object( + self.mech_driver._ovn_client._sb_idl, 'is_col_present', + return_value=True): + options = self.mech_driver._ovn_client._get_port_options(port) + self.assertNotIn('activation-strategy', options.options) + self.assertEqual('fake-src', + options.options['requested-chassis']) + def test_update_port(self): with mock.patch.object( self.mech_driver._ovn_client, 'is_metadata_port') as \