Skip to content

Commit c6d4a3e

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "[OVN] Sanitize the classless-static-route DHCP option" into stable/2024.1
2 parents 7e36795 + 3d97c4f commit c6d4a3e

File tree

5 files changed

+81
-0
lines changed

5 files changed

+81
-0
lines changed

neutron/common/ovn/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@
217217
'wpad',
218218
'tftp_server']
219219

220+
OVN_MAP_TYPE_DHCP_OPTS = [
221+
'classless_static_route',
222+
]
223+
220224
# Special option for disabling DHCP via extra DHCP options
221225
DHCP_DISABLED_OPT = 'dhcp_disabled'
222226

neutron/common/ovn/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ def is_dhcp_option_quoted(opt_value):
250250
return opt_value.startswith('"') and opt_value.endswith('"')
251251

252252

253+
def is_dhcp_option_a_map(opt_value):
254+
return opt_value.startswith('{') and opt_value.endswith('}')
255+
256+
253257
def get_lsp_dhcp_opts(port, ip_version):
254258
# Get dhcp options from Neutron port, for setting DHCP_Options row
255259
# in OVN.
@@ -288,6 +292,9 @@ def get_lsp_dhcp_opts(port, ip_version):
288292
if (opt in constants.OVN_STR_TYPE_DHCP_OPTS and
289293
not is_dhcp_option_quoted(edo['opt_value'])):
290294
edo['opt_value'] = '"%s"' % edo['opt_value']
295+
elif (opt in constants.OVN_MAP_TYPE_DHCP_OPTS and
296+
not is_dhcp_option_a_map(edo['opt_value'])):
297+
edo['opt_value'] = '{%s}' % edo['opt_value']
291298
lsp_dhcp_opts[opt] = edo['opt_value']
292299

293300
return (lsp_dhcp_disabled, lsp_dhcp_opts)

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,18 @@ def _get_subnet_dhcp_options_for_port(self, port, ip_version):
194194
return opts
195195
return get_opts[0]
196196

197+
def _merge_map_dhcp_option(self, opt, port_opts, subnet_opts):
198+
"""Merge a port and subnet map DHCP option.
199+
200+
If a DHCP option exists in both port and subnet, the port
201+
should inherit the values from the subnet.
202+
"""
203+
port_opt = port_opts[opt]
204+
subnet_opt = subnet_opts.get(opt)
205+
if not subnet_opt:
206+
return port_opt
207+
return '{%s, %s}' % (subnet_opt[1:-1], port_opt[1:-1])
208+
197209
def _get_port_dhcp_options(self, port, ip_version):
198210
"""Return dhcp options for port.
199211
@@ -226,6 +238,12 @@ def _get_port_dhcp_options(self, port, ip_version):
226238
if not lsp_dhcp_opts:
227239
return subnet_dhcp_options
228240

241+
# Check for map DHCP options
242+
for opt in ovn_const.OVN_MAP_TYPE_DHCP_OPTS:
243+
if opt in lsp_dhcp_opts:
244+
lsp_dhcp_opts[opt] = self._merge_map_dhcp_option(
245+
opt, lsp_dhcp_opts, subnet_dhcp_options['options'])
246+
229247
# This port has extra DHCP options defined, so we will create a new
230248
# row in DHCP_Options table for it.
231249
subnet_dhcp_options['options'].update(lsp_dhcp_opts)

neutron/tests/unit/common/ovn/test_utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,20 @@ def test_get_lsp_dhcp_opts_for_domain_search(self):
521521
expected_options = {'domain_search_list': '"openstack.org,ovn.org"'}
522522
self.assertEqual(expected_options, options)
523523

524+
def test_get_lsp_dhcp_opts_sanitize_map(self):
525+
opt = {'opt_name': 'classless-static-route',
526+
'opt_value': '128.128.128.128/32,22.2.0.2',
527+
'ip_version': 4}
528+
port = {portbindings.VNIC_TYPE: portbindings.VNIC_NORMAL,
529+
edo_ext.EXTRADHCPOPTS: [opt]}
530+
dhcp_disabled, options = utils.get_lsp_dhcp_opts(port, 4)
531+
self.assertFalse(dhcp_disabled)
532+
# Assert option got translated to "classless_static_route" and
533+
# the value is a map (wrapped with {})
534+
expected_options = {
535+
'classless_static_route': '{128.128.128.128/32,22.2.0.2}'}
536+
self.assertEqual(expected_options, options)
537+
524538

525539
class TestGetDhcpDnsServers(base.BaseTestCase):
526540

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4009,6 +4009,44 @@ def test__get_subnet_dhcp_options_for_port_v6_dhcp_disabled(self):
40094009
self._test__get_subnet_dhcp_options_for_port(ip_version=6,
40104010
enable_dhcp=False)
40114011

4012+
def test_get_port_dhcp_options_classless_static_route(self):
4013+
port = {
4014+
'id': 'foo-port',
4015+
'device_owner': 'compute:None',
4016+
'fixed_ips': [{'subnet_id': 'foo-subnet',
4017+
'ip_address': '10.0.0.11'}],
4018+
'extra_dhcp_opts': [
4019+
{'ip_version': 4, 'opt_name': 'classless-static-route',
4020+
'opt_value': '128.128.128.128/32,22.2.0.2'}]}
4021+
4022+
self.mech_driver._ovn_client._get_subnet_dhcp_options_for_port = (
4023+
mock.Mock(
4024+
return_value=({
4025+
'cidr': '10.0.0.0/24',
4026+
'external_ids': {'subnet_id': 'foo-subnet'},
4027+
'options': {
4028+
'classless_static_route':
4029+
'{169.254.169.254/32,10.0.0.2}',},
4030+
'uuid': 'foo-uuid'})))
4031+
4032+
# Expect both the subnet and port classless_static_route
4033+
# to be merged
4034+
expected_routes = ('{169.254.169.254/32,10.0.0.2, '
4035+
'128.128.128.128/32,22.2.0.2}')
4036+
expected_dhcp_options = {
4037+
'cidr': '10.0.0.0/24',
4038+
'external_ids': {'subnet_id': 'foo-subnet',
4039+
'port_id': 'foo-port'},
4040+
'options': {'classless_static_route': expected_routes}
4041+
}
4042+
4043+
self.mech_driver.nb_ovn.add_dhcp_options.return_value = 'foo-val'
4044+
dhcp_options = self.mech_driver._ovn_client._get_port_dhcp_options(
4045+
port, 4)
4046+
self.assertEqual({'cmd': 'foo-val'}, dhcp_options)
4047+
self.mech_driver.nb_ovn.add_dhcp_options.assert_called_once_with(
4048+
'foo-subnet', port_id='foo-port', **expected_dhcp_options)
4049+
40124050

40134051
class TestOVNMechanismDriverSecurityGroup(MechDriverSetupBase,
40144052
test_security_group.Ml2SecurityGroupsTestCase):

0 commit comments

Comments
 (0)