Check the device ID and host ID during virtual port binding

If a port receives a device ID and a binding profile host ID
fields update, at the same time, this is because Nova is trying
to bind the port to a VM (device ID) in a host (host ID). In
ML2/OVN, a virtual port cannot be bound to a VM.

NOTE:
* A virtual port can receive a host ID update. That happens when
  the fixed IP port that has the virtual port IP address as
  allowed address pair is bound.
* A virtual port can receive a devide ID update. Octavia uses
  the devide ID to identify to what load balancer the virtual
  port belongs.

This check was introduced in [1].

[1]https://review.opendev.org/c/openstack/neutron/+/882588

Closes-Bug: #2028651
Related-Bug: #2018529
Change-Id: I8784c6716f5a53b91d43323771e6f30fa8e8e506
This commit is contained in:
Rodolfo Alonso Hernandez 2023-08-23 00:19:24 +00:00
parent bffb88f5f8
commit a3b00768d6
4 changed files with 14 additions and 9 deletions

View File

@ -1059,10 +1059,13 @@ def determine_bind_host(sb_idl, port, port_context=None):
def validate_port_binding_and_virtual_port(
port_context, nb_idl, sb_idl, ml2_plugin, port):
port_context, nb_idl, ml2_plugin, port, original_port):
"""If the port is type=virtual and it is bound, raise BadRequest"""
if not determine_bind_host(sb_idl, port, port_context=port_context):
# The port is not bound, exit.
# If the port receives an update of the device ID and the binding profile
# host ID fields, at the same time, this is because Nova is trying to bind
# the port to a VM (device ID) in a host (host ID).
if not (port['device_id'] != original_port['device_id'] and
port[portbindings.HOST_ID] != original_port[portbindings.HOST_ID]):
return
fixed_ips = port.get('fixed_ips', [])

View File

@ -823,7 +823,7 @@ class OVNMechanismDriver(api.MechanismDriver):
ovn_utils.validate_and_get_data_from_binding_profile(port)
self._validate_port_extra_dhcp_opts(port)
ovn_utils.validate_port_binding_and_virtual_port(
context, self.nb_ovn, self.sb_ovn, self._plugin, port)
context, self.nb_ovn, self._plugin, port, original_port)
if self._is_port_provisioning_required(port, context.host,
context.original_host):
self._insert_port_provisioning_block(context.plugin_context,

View File

@ -4434,20 +4434,21 @@ class TestOVNVVirtualPort(OVNMechanismDriverTestCase):
with self.port(subnet=self.subnet, is_admin=True) as port:
port = port['port']
updated_port = copy.deepcopy(port)
updated_port[portbindings.HOST_ID] = 'host1'
context = mock.Mock(current=updated_port, original=port)
updated_port['device_id'] = 'device_id_new'
updated_port[portbindings.HOST_ID] = 'host_id_new'
_context = mock.Mock(current=updated_port, original=port)
with mock.patch.object(self.mech_driver._plugin, 'get_subnets') \
as mock_get_subnets:
mock_get_subnets.return_value = [self.subnet['subnet']]
# 1) The port is not virtual, it has no parents.
self.mock_vp_parents.return_value = ''
self.mech_driver.update_port_precommit(context)
self.mech_driver.update_port_precommit(_context)
# 2) The port (LSP) has parents, that means it is a virtual
# port.
self.mock_vp_parents.return_value = ['parent-0', 'parent-1']
self.assertRaises(n_exc.BadRequest,
self.mech_driver.update_port_precommit,
context)
_context)
class TestOVNAvailabilityZone(OVNMechanismDriverTestCase):

View File

@ -6,4 +6,5 @@ other:
first one is considered a virtual port. If the second port (non-virtual)
is bound to ML2/OVN, the virtual port cannot be bound to a virtual
machine; a virtual port is created only to reserve a set of IP addresses
to be used by other ports.
to be used by other ports. The OVN mechanism driver prevents that a virtual
port has a device ID; a device ID is provided when the port is being bound.