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 0cc085a1feb..d745e0f43a2 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 \