Add constant to identify OVN LB HM ports

OVN metadata and OVN LB HM port (used for health checks) are both
using network:distributed in the device_owner field. Some tasks
in Neutron search by that value, considering just one port will
exist per network with that value.

This patch adds a new constant to differentiate OVN metadata ports
from the OVN LB HM ones. The latter will use a device_owner
of ovn-lb-hm:distributed to be different but will still receive the
treatment of a localport in the OVN NB DB.

Related-Bug: 2038091
Change-Id: I70de163fad34371de10fec28c51959384900ebc8
This commit is contained in:
Fernando Royo 2023-10-04 17:58:43 +02:00
parent 0e17bd15eb
commit b2e14b23f3
9 changed files with 102 additions and 15 deletions

View File

@ -70,6 +70,10 @@ OVN_ROUTER_PORT_GW_MTU_OPTION = 'gateway_mtu'
OVN_PROVNET_PORT_NAME_PREFIX = 'provnet-' OVN_PROVNET_PORT_NAME_PREFIX = 'provnet-'
OVN_NAME_PREFIX = 'neutron-' OVN_NAME_PREFIX = 'neutron-'
# TODO(froyo): Move this to neutron-lib as soon as possible, and when a new
# release is created and pointed to in the requirements remove this code
OVN_LB_HM_PORT_DISTRIBUTED = 'ovn-lb-hm:distributed'
# Agent extension constants # Agent extension constants
OVN_AGENT_DESC_KEY = 'neutron:description' OVN_AGENT_DESC_KEY = 'neutron:description'
OVN_AGENT_METADATA_SB_CFG_KEY = 'neutron:ovn-metadata-sb-cfg' OVN_AGENT_METADATA_SB_CFG_KEY = 'neutron:ovn-metadata-sb-cfg'

View File

@ -608,6 +608,11 @@ def is_ovn_metadata_port(port):
port['device_id'].startswith('ovnmeta')) port['device_id'].startswith('ovnmeta'))
def is_ovn_lb_hm_port(port):
return (port['device_owner'] == constants.OVN_LB_HM_PORT_DISTRIBUTED and
port['device_id'].startswith('ovn-lb-hm'))
def is_gateway_chassis_invalid(chassis_name, gw_chassis, def is_gateway_chassis_invalid(chassis_name, gw_chassis,
physnet, chassis_physnets, physnet, chassis_physnets,
az_hints, chassis_with_azs): az_hints, chassis_with_azs):

View File

@ -15,6 +15,7 @@ import functools
import socket import socket
import uuid import uuid
from neutron_lib import constants
from neutron_lib import exceptions as n_exc from neutron_lib import exceptions as n_exc
from neutron_lib.utils import helpers from neutron_lib.utils import helpers
from oslo_log import log from oslo_log import log
@ -929,7 +930,10 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
except idlutils.RowNotFound: except idlutils.RowNotFound:
return None return None
cmd = self.db_find_rows('Port_Binding', ('datapath', '=', dp), cmd = self.db_find_rows('Port_Binding', ('datapath', '=', dp),
('type', '=', ovn_const.LSP_TYPE_LOCALPORT)) ('type', '=', ovn_const.LSP_TYPE_LOCALPORT),
('external_ids', '=', {
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
constants.DEVICE_OWNER_DISTRIBUTED}))
return next(iter(cmd.execute(check_error=True)), None) return next(iter(cmd.execute(check_error=True)), None)
def set_chassis_neutron_description(self, chassis, description, def set_chassis_neutron_description(self, chassis, description,

View File

@ -379,8 +379,9 @@ class OVNClient(object):
cidrs += ' {}/{}'.format(ip['ip_address'], cidrs += ' {}/{}'.format(ip['ip_address'],
subnet['cidr'].split('/')[1]) subnet['cidr'].split('/')[1])
# Metadata port. # Metadata or OVN LB HM port.
if utils.is_ovn_metadata_port(port): if (utils.is_ovn_metadata_port(port) or
utils.is_ovn_lb_hm_port(port)):
port_type = ovn_const.LSP_TYPE_LOCALPORT port_type = ovn_const.LSP_TYPE_LOCALPORT
if utils.is_port_external(port): if utils.is_port_external(port):

View File

@ -16,6 +16,7 @@
import re import re
from unittest import mock from unittest import mock
from neutron_lib import constants
from oslo_config import fixture as fixture_config from oslo_config import fixture as fixture_config
from oslo_utils import uuidutils from oslo_utils import uuidutils
from ovsdbapp.backend.ovs_idl import event from ovsdbapp.backend.ovs_idl import event
@ -154,7 +155,9 @@ class TestMetadataAgent(base.TestOVNFunctionalBase):
addresses='AA:AA:AA:AA:AA:AA 192.168.122.123', addresses='AA:AA:AA:AA:AA:AA 192.168.122.123',
external_ids={ external_ids={
ovn_const.OVN_CIDRS_EXT_ID_KEY: '192.168.122.123/24', ovn_const.OVN_CIDRS_EXT_ID_KEY: '192.168.122.123/24',
ovn_const.OVN_DEVID_EXT_ID_KEY: 'ovnmeta-' + lswitch_name ovn_const.OVN_DEVID_EXT_ID_KEY: 'ovnmeta-' + lswitch_name,
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
constants.DEVICE_OWNER_DISTRIBUTED
})) }))
def _update_metadata_port_ip(self, metadata_port_name): def _update_metadata_port_ip(self, metadata_port_name):

View File

@ -15,6 +15,7 @@
import copy import copy
import uuid import uuid
from neutron_lib import constants
from oslo_utils import uuidutils from oslo_utils import uuidutils
from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp import constants as const from ovsdbapp import constants as const
@ -104,34 +105,62 @@ class TestSbApi(BaseOvnIdlTest):
self.load_test_data() self.load_test_data()
self.assertRaises(ValueError, self.api.get_chassis_and_physnets) self.assertRaises(ValueError, self.api.get_chassis_and_physnets)
def _add_switch_port(self, chassis_name, def _add_switch(self, chassis_name):
type=ovn_const.LSP_TYPE_LOCALPORT): sname = utils.get_rand_device_name(prefix='switch')
sname, pname = (utils.get_rand_device_name(prefix=p)
for p in ('switch', 'port'))
chassis = self.api.lookup('Chassis', chassis_name) chassis = self.api.lookup('Chassis', chassis_name)
with self.nbapi.transaction(check_error=True) as txn:
switch = txn.add(self.nbapi.ls_add(sname))
return chassis, switch.result
def _add_port_to_switch(
self, switch, type=ovn_const.LSP_TYPE_LOCALPORT,
device_owner=constants.DEVICE_OWNER_DISTRIBUTED):
pname = utils.get_rand_device_name(prefix='port')
row_event = events.WaitForCreatePortBindingEvent(pname) row_event = events.WaitForCreatePortBindingEvent(pname)
self.handler.watch_event(row_event) self.handler.watch_event(row_event)
with self.nbapi.transaction(check_error=True) as txn: with self.nbapi.transaction(check_error=True) as txn:
switch = txn.add(self.nbapi.ls_add(sname)) port = txn.add(self.nbapi.lsp_add(
port = txn.add(self.nbapi.lsp_add(sname, pname, type=type)) switch.uuid, pname, type=type,
external_ids={
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: device_owner}))
row_event.wait() row_event.wait()
return chassis, switch.result, port.result, row_event.row return port.result, row_event.row
def test_get_metadata_port_network(self): def test_get_metadata_port_network(self):
chassis, switch, port, binding = self._add_switch_port( chassis, switch = self._add_switch(self.data['chassis'][0]['name'])
self.data['chassis'][0]['name']) port, binding = self._add_port_to_switch(switch)
result = self.api.get_metadata_port_network(str(binding.datapath.uuid)) result = self.api.get_metadata_port_network(str(binding.datapath.uuid))
self.assertEqual(binding, result) self.assertEqual(binding, result)
self.assertEqual(binding.datapath.external_ids['logical-switch'], self.assertEqual(binding.datapath.external_ids['logical-switch'],
str(switch.uuid)) str(switch.uuid))
self.assertEqual(
port.external_ids[ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY],
constants.DEVICE_OWNER_DISTRIBUTED)
def test_get_metadata_port_network_other_non_metadata_port(self):
chassis, switch = self._add_switch(self.data['chassis'][0]['name'])
port, binding = self._add_port_to_switch(switch)
port_lbhm, binding_port_lbhm = self._add_port_to_switch(
switch, device_owner=ovn_const.OVN_LB_HM_PORT_DISTRIBUTED)
result = self.api.get_metadata_port_network(str(binding.datapath.uuid))
self.assertEqual(binding, result)
self.assertEqual(binding.datapath.external_ids['logical-switch'],
str(switch.uuid))
self.assertEqual(
binding_port_lbhm.datapath.external_ids['logical-switch'],
str(switch.uuid))
self.assertEqual(
port_lbhm.external_ids[ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY],
ovn_const.OVN_LB_HM_PORT_DISTRIBUTED)
def test_get_metadata_port_network_missing(self): def test_get_metadata_port_network_missing(self):
val = str(uuid.uuid4()) val = str(uuid.uuid4())
self.assertIsNone(self.api.get_metadata_port_network(val)) self.assertIsNone(self.api.get_metadata_port_network(val))
def _create_bound_port_with_ip(self): def _create_bound_port_with_ip(self):
chassis, switch, port, binding = self._add_switch_port( chassis, switch = self._add_switch(
self.data['chassis'][0]['name']) self.data['chassis'][0]['name'])
port, binding = self._add_port_to_switch(switch)
mac = 'de:ad:be:ef:4d:ad' mac = 'de:ad:be:ef:4d:ad'
ipaddr = '192.0.2.1' ipaddr = '192.0.2.1'
mac_ip = '%s %s' % (mac, ipaddr) mac_ip = '%s %s' % (mac, ipaddr)
@ -165,8 +194,9 @@ class TestSbApi(BaseOvnIdlTest):
self.assertEqual(1, len(result)) self.assertEqual(1, len(result))
def test_get_ports_on_chassis(self): def test_get_ports_on_chassis(self):
chassis, switch, port, binding = self._add_switch_port( chassis, switch = self._add_switch(
self.data['chassis'][0]['name']) self.data['chassis'][0]['name'])
port, binding = self._add_port_to_switch(switch)
self.api.lsp_bind(port.name, chassis.name).execute(check_error=True) self.api.lsp_bind(port.name, chassis.name).execute(check_error=True)
self.assertEqual([binding], self.assertEqual([binding],
self.api.get_ports_on_chassis(chassis.name)) self.api.get_ports_on_chassis(chassis.name))

View File

@ -217,6 +217,21 @@ class TestUtils(base.BaseTestCase):
self.assertFalse(utils.is_ovn_metadata_port(non_meta_port_0)) self.assertFalse(utils.is_ovn_metadata_port(non_meta_port_0))
self.assertFalse(utils.is_ovn_metadata_port(non_meta_port_1)) self.assertFalse(utils.is_ovn_metadata_port(non_meta_port_1))
def test_is_ovn_lb_hm_port(self):
ovn_lb_hm_port = {
'device_owner': constants.OVN_LB_HM_PORT_DISTRIBUTED,
'device_id': 'ovn-lb-hm-12345'}
non_ovn_lb_hm_port_0 = {
'device_owner': n_const.DEVICE_OWNER_DISTRIBUTED,
'device_id': 'ovnmeta-12345'}
non_ovn_lb_hm_port_1 = {
'device_owner': n_const.DEVICE_OWNER_DHCP,
'device_id': 'dhcp-12345'}
self.assertTrue(utils.is_ovn_lb_hm_port(ovn_lb_hm_port))
self.assertFalse(utils.is_ovn_lb_hm_port(non_ovn_lb_hm_port_0))
self.assertFalse(utils.is_ovn_lb_hm_port(non_ovn_lb_hm_port_1))
class TestGateWayChassisValidity(base.BaseTestCase): class TestGateWayChassisValidity(base.BaseTestCase):

View File

@ -1913,6 +1913,22 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.assertEqual("address_scope_v4", options.address4_scope_id) self.assertEqual("address_scope_v4", options.address4_scope_id)
self.assertEqual("address_scope_v6", options.address6_scope_id) self.assertEqual("address_scope_v6", options.address6_scope_id)
def test__get_port_options_with_ovn_lb_hm_port(self):
port = {
'id': 'ovn-lb-hm-port',
'mac_address': '00:00:00:00:00:00',
'device_owner': ovn_const.OVN_LB_HM_PORT_DISTRIBUTED,
'device_id': 'ovn-lb-hm-foo',
'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.assertEqual('fake-src', options.options['requested-chassis'])
def test__get_port_options_migrating_additional_chassis_missing(self): def test__get_port_options_migrating_additional_chassis_missing(self):
port = { port = {
'id': 'virt-port', 'id': 'virt-port',

View File

@ -0,0 +1,9 @@
---
other:
- |
The new value for 'device_owner' for OVN loadbalancer health monitor ports
(ovn-lb-hm:distributed) is now supported by Neutron, providing a LOCALPORT
behavior to these ports. The responsibility to define these ports with the
new value instead of the old one (network:distributed) is under the
OVN-Octavia Provider driver, which will take care of database conversion
for these ports.