[smart_switch][dhcp_server] Add smart_switch support in dhcpservd (#17576)
* [smart_switch][dhcp_server] Add related checker for smart_switch in dhcp_db_monitor * [smart_switch][dhcp_server] Add smart_switch support in dhcpservd
This commit is contained in:
parent
e30782b0fe
commit
b2ca36aa1c
@ -163,3 +163,14 @@ def get_target_process_cmds(process_name):
|
||||
if proc.name() == process_name:
|
||||
res.append(proc.cmdline())
|
||||
return res
|
||||
|
||||
|
||||
def is_smart_switch(device_metadata):
|
||||
"""
|
||||
Check in device metadata whether subtype is smartswitch
|
||||
Args:
|
||||
device_metadata: DEVICE_METADATA table
|
||||
Returns:
|
||||
If subtype is "SmartSwitch", return True. Else, return False
|
||||
"""
|
||||
return device_metadata.get("localhost", {}).get("subtype", "") == "SmartSwitch"
|
||||
|
@ -5,7 +5,7 @@ import os
|
||||
import syslog
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from dhcp_utilities.common.utils import merge_intervals, validate_str_type
|
||||
from dhcp_utilities.common.utils import merge_intervals, validate_str_type, is_smart_switch
|
||||
|
||||
PORT_MAP_PATH = "/tmp/port-name-alias-map.txt"
|
||||
UNICODE_TYPE = str
|
||||
@ -15,9 +15,12 @@ DHCP_SERVER_IPV4_RANGE = "DHCP_SERVER_IPV4_RANGE"
|
||||
DHCP_SERVER_IPV4_PORT = "DHCP_SERVER_IPV4_PORT"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
VLAN_MEMBER = "VLAN_MEMBER"
|
||||
DPUS = "DPUS"
|
||||
MID_PLANE_BRIDGE = "MID_PLANE_BRIDGE"
|
||||
PORT_MODE_CHECKER = ["DhcpServerTableCfgChangeEventChecker", "DhcpPortTableEventChecker", "DhcpRangeTableEventChecker",
|
||||
"DhcpOptionTableEventChecker", "VlanTableEventChecker", "VlanIntfTableEventChecker",
|
||||
"VlanMemberTableEventChecker"]
|
||||
SMART_SWITCH_CHECKER = ["DpusTableEventChecker", "MidPlaneTableEventChecker"]
|
||||
LEASE_UPDATE_SCRIPT_PATH = "/etc/kea/lease_update.sh"
|
||||
DEFAULT_LEASE_TIME = 900
|
||||
DEFAULT_LEASE_PATH = "/tmp/kea-lease.csv"
|
||||
@ -58,21 +61,62 @@ class DhcpServCfgGenerator(object):
|
||||
# Get host name
|
||||
device_metadata = self.db_connector.get_config_db_table("DEVICE_METADATA")
|
||||
hostname = self._parse_hostname(device_metadata)
|
||||
smart_switch = is_smart_switch(device_metadata)
|
||||
# Get ip information of vlan
|
||||
vlan_interface = self.db_connector.get_config_db_table(VLAN_INTERFACE)
|
||||
vlan_member_table = self.db_connector.get_config_db_table(VLAN_MEMBER)
|
||||
vlan_interfaces, vlan_members = self._parse_vlan(vlan_interface, vlan_member_table)
|
||||
|
||||
# Parse dpu
|
||||
dpus_table = self.db_connector.get_config_db_table(DPUS)
|
||||
mid_plane_table = self.db_connector.get_config_db_table(MID_PLANE_BRIDGE)
|
||||
mid_plane, dpus = self._parse_dpu(dpus_table, mid_plane_table) if smart_switch else {}, {}
|
||||
|
||||
dhcp_server_ipv4, customized_options_ipv4, range_ipv4, port_ipv4 = self._get_dhcp_ipv4_tables_from_db()
|
||||
# Parse range table
|
||||
ranges = self._parse_range(range_ipv4)
|
||||
|
||||
# Parse port table
|
||||
port_ips, used_ranges = self._parse_port(port_ipv4, vlan_interfaces, vlan_members, ranges)
|
||||
dhcp_interfaces = vlan_interfaces
|
||||
if smart_switch and "bridge" in mid_plane and "ip_prefix" in mid_plane:
|
||||
mid_plane_name = mid_plane["bridge"]
|
||||
dhcp_interfaces[mid_plane_name] = [{
|
||||
"network": ipaddress.ip_network(mid_plane["ip_prefix"], strict=False),
|
||||
"ip": mid_plane["ip_prefix"]
|
||||
}]
|
||||
dpus = ["{}|{}".format(mid_plane_name, dpu) for dpu in dpus]
|
||||
dhcp_members = vlan_members | set(dpus)
|
||||
port_ips, used_ranges = self._parse_port(port_ipv4, dhcp_interfaces, dhcp_members, ranges)
|
||||
customized_options = self._parse_customized_options(customized_options_ipv4)
|
||||
render_obj, enabled_dhcp_interfaces, used_options, subscribe_table = \
|
||||
self._construct_obj_for_template(dhcp_server_ipv4, port_ips, hostname, customized_options)
|
||||
|
||||
if smart_switch:
|
||||
subscribe_table |= set(SMART_SWITCH_CHECKER)
|
||||
|
||||
return self._render_config(render_obj), used_ranges, enabled_dhcp_interfaces, used_options, subscribe_table
|
||||
|
||||
def _parse_dpu(self, dpus_table, mid_plane_table):
|
||||
"""
|
||||
Parse dpu related tables
|
||||
Args:
|
||||
dpus_table: DPU table dict
|
||||
mid_plane_table: mid_plane table dict
|
||||
Returns:
|
||||
Parsed obj, sample:
|
||||
mid_plane = {
|
||||
"bridge": "bridge_midplane",
|
||||
"address": "169.254.200.254/24"
|
||||
}
|
||||
dpus = {
|
||||
"dpu0"
|
||||
}
|
||||
"""
|
||||
mid_plane = mid_plane_table.get("GLOBAL", {})
|
||||
dpus = set([dpu_value["midplane_interface"] for dpu_value in dpus_table.values()
|
||||
if "midplane_interface" in dpu_value])
|
||||
return mid_plane, dpus
|
||||
|
||||
def _parse_customized_options(self, customized_options_ipv4):
|
||||
customized_options = {}
|
||||
for option_name, config in customized_options_ipv4.items():
|
||||
@ -107,7 +151,7 @@ class DhcpServCfgGenerator(object):
|
||||
|
||||
def _parse_vlan(self, vlan_interface, vlan_member):
|
||||
vlan_interfaces = self._get_vlan_ipv4_interface(vlan_interface.keys())
|
||||
vlan_members = vlan_member.keys()
|
||||
vlan_members = set(vlan_member.keys())
|
||||
return vlan_interfaces, vlan_members
|
||||
|
||||
def _parse_hostname(self, device_metadata):
|
||||
@ -306,19 +350,19 @@ class DhcpServCfgGenerator(object):
|
||||
port_ips[dhcp_interface_name][dhcp_interface_ip_str][port].append([range[0], range[1]])
|
||||
break
|
||||
|
||||
def _parse_port(self, port_ipv4, vlan_interfaces, vlan_members, ranges):
|
||||
def _parse_port(self, port_ipv4, dhcp_interfaces, dhcp_members, ranges):
|
||||
"""
|
||||
Parse content in DHCP_SERVER_IPV4_PORT table to below format, which indicate ip ranges assign to interface.
|
||||
Args:
|
||||
port_ipv4: Table object.
|
||||
vlan_interfaces: Vlan information, sample:
|
||||
dhcp_interfaces: DHCP interfaces information, sample:
|
||||
{
|
||||
'Vlan1000': [{
|
||||
'network': IPv4Network('192.168.0.0/24'),
|
||||
'ip': '192.168.0.1/24'
|
||||
}]
|
||||
}
|
||||
vlan_members: List of vlan members
|
||||
dhcp_members: List of DHCP members
|
||||
ranges: Dict of ranges
|
||||
Returns:
|
||||
Dict of dhcp conf, sample:
|
||||
@ -349,23 +393,21 @@ class DhcpServCfgGenerator(object):
|
||||
continue
|
||||
splits = port_key.split("|")
|
||||
# Skip port not in correct vlan
|
||||
if port_key not in vlan_members:
|
||||
if port_key not in dhcp_members:
|
||||
syslog.syslog(syslog.LOG_WARNING, f"Port {splits[1]} is not in {splits[0]}")
|
||||
continue
|
||||
# Get dhcp interface name like Vlan1000
|
||||
dhcp_interface_name = splits[0]
|
||||
# Get dhcp member interface name like etp1
|
||||
if splits[1] not in self.port_alias_map:
|
||||
syslog.syslog(syslog.LOG_WARNING, f"Cannot find {splits[1]} in port_alias_map")
|
||||
continue
|
||||
port = self.port_alias_map[splits[1]]
|
||||
if dhcp_interface_name not in vlan_interfaces:
|
||||
# Get dhcp member interface name like etp1, be consistent with dhcp_relay, if alias doesn't exist,
|
||||
# use port name directly
|
||||
port = self.port_alias_map[splits[1]] if splits[1] in self.port_alias_map else splits[1]
|
||||
if dhcp_interface_name not in dhcp_interfaces:
|
||||
syslog.syslog(syslog.LOG_WARNING, f"Interface {dhcp_interface_name} doesn't have IPv4 address")
|
||||
continue
|
||||
if dhcp_interface_name not in port_ips:
|
||||
port_ips[dhcp_interface_name] = {}
|
||||
# Get ip information of Vlan
|
||||
dhcp_interface = vlan_interfaces[dhcp_interface_name]
|
||||
dhcp_interface = dhcp_interfaces[dhcp_interface_name]
|
||||
|
||||
for dhcp_interface_ip in dhcp_interface:
|
||||
ip_ports[str(dhcp_interface_ip["network"])] = dhcp_interface_name
|
||||
|
@ -9,7 +9,7 @@ from .dhcp_lease import LeaseManager
|
||||
from dhcp_utilities.common.utils import DhcpDbConnector
|
||||
from dhcp_utilities.common.dhcp_db_monitor import DhcpServdDbMonitor, DhcpServerTableCfgChangeEventChecker, \
|
||||
DhcpOptionTableEventChecker, DhcpRangeTableEventChecker, DhcpPortTableEventChecker, VlanIntfTableEventChecker, \
|
||||
VlanMemberTableEventChecker, VlanTableEventChecker
|
||||
VlanMemberTableEventChecker, VlanTableEventChecker, MidPlaneTableEventChecker, DpusTableEventChecker
|
||||
from swsscommon import swsscommon
|
||||
|
||||
KEA_DHCP4_CONFIG = "/etc/kea/kea-dhcp4.conf"
|
||||
@ -110,6 +110,8 @@ def main():
|
||||
checkers.append(VlanTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(VlanIntfTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(VlanMemberTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(DpusTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(MidPlaneTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
dhcp_servd_monitor = DhcpServdDbMonitor(dhcp_db_connector, sel, checkers, DEFAULT_SELECT_TIMEOUT)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector, dhcp_servd_monitor)
|
||||
dhcpservd.start()
|
||||
|
@ -14,6 +14,7 @@ VLAN_MEMBER = "VLAN_MEMBER"
|
||||
PORT_MODE_CHECKER = ["DhcpServerTableCfgChangeEventChecker", "DhcpPortTableEventChecker", "DhcpRangeTableEventChecker",
|
||||
"DhcpOptionTableEventChecker", "VlanTableEventChecker", "VlanIntfTableEventChecker",
|
||||
"VlanMemberTableEventChecker"]
|
||||
SMART_SWITCH_CHECKER = ["DpusTableEventChecker", "MidPlaneTableEventChecker"]
|
||||
|
||||
|
||||
class MockConfigDb(object):
|
||||
|
@ -2,10 +2,10 @@ import copy
|
||||
import ipaddress
|
||||
import json
|
||||
import pytest
|
||||
from common_utils import MockConfigDb, mock_get_config_db_table, PORT_MODE_CHECKER
|
||||
from common_utils import MockConfigDb, mock_get_config_db_table, PORT_MODE_CHECKER, SMART_SWITCH_CHECKER
|
||||
from dhcp_utilities.common.utils import DhcpDbConnector
|
||||
from dhcp_utilities.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
expected_dhcp_config = {
|
||||
"Dhcp4": {
|
||||
@ -165,7 +165,8 @@ expected_parsed_port = {
|
||||
"Vlan1000": {
|
||||
"192.168.0.1/21": {
|
||||
"etp8": [["192.168.0.2", "192.168.0.5"], ["192.168.0.10", "192.168.0.10"]],
|
||||
"etp7": [["192.168.0.7", "192.168.0.7"]]
|
||||
"etp7": [["192.168.0.7", "192.168.0.7"]],
|
||||
"Ethernet40": [["192.168.0.10", "192.168.0.10"]]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -302,8 +303,8 @@ def test_parse_vlan(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias,
|
||||
vlan_interfaces, vlan_members = dhcp_cfg_generator._parse_vlan(mock_config_db.config_db.get("VLAN_INTERFACE"),
|
||||
mock_config_db.config_db.get("VLAN_MEMBER"))
|
||||
assert vlan_interfaces == expected_vlan_ipv4_interface
|
||||
expeceted_members = ["Vlan1000|Ethernet24", "Vlan1000|Ethernet28", "Vlan1000|Ethernet40", "Vlan3000|Ethernet44"]
|
||||
assert list(vlan_members) == expeceted_members
|
||||
assert vlan_members == set(["Vlan1000|Ethernet24", "Vlan1000|Ethernet28", "Vlan1000|Ethernet40",
|
||||
"Vlan3000|Ethernet44"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_config_db", ["mock_config_db.json", "mock_config_db_without_port_config.json"])
|
||||
@ -323,17 +324,22 @@ def test_parse_port(test_config_db, mock_swsscommon_dbconnector_init, mock_get_r
|
||||
if test_config_db == "mock_config_db.json" else set())
|
||||
|
||||
|
||||
def test_generate(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, mock_get_render_template):
|
||||
@pytest.mark.parametrize("mid_plane", [{}, {"bridge": "mid_plane", "ip_prefix": "192.168.0.1/24"}])
|
||||
@pytest.mark.parametrize("is_smart_switch", [True, False])
|
||||
def test_generate(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, mock_get_render_template, mid_plane,
|
||||
is_smart_switch):
|
||||
with patch.object(DhcpServCfgGenerator, "_parse_hostname"), \
|
||||
patch.object(DhcpServCfgGenerator, "_parse_vlan", return_value=(None, None)), \
|
||||
patch.object(DhcpServCfgGenerator, "_parse_vlan", return_value=({}, set(["Ethernet0"]))), \
|
||||
patch.object(DhcpServCfgGenerator, "_get_dhcp_ipv4_tables_from_db", return_value=(None, None, None, None)), \
|
||||
patch.object(DhcpServCfgGenerator, "_parse_range"), \
|
||||
patch.object(DhcpServCfgGenerator, "_parse_port", return_value=(None, set(["range1"]))), \
|
||||
patch.object(DhcpServCfgGenerator, "_parse_customized_options"), \
|
||||
patch.object(DhcpServCfgGenerator, "_parse_dpu", side_effect=[mid_plane, set()]), \
|
||||
patch.object(DhcpServCfgGenerator, "_construct_obj_for_template",
|
||||
return_value=(None, set(["Vlan1000"]), set(["option1"]), set(["dummy"]))), \
|
||||
patch.object(DhcpServCfgGenerator, "_render_config", return_value="dummy_config"), \
|
||||
patch.object(DhcpDbConnector, "get_config_db_table", side_effect=mock_get_config_db_table):
|
||||
patch.object(DhcpDbConnector, "get_config_db_table", side_effect=mock_get_config_db_table), \
|
||||
patch("dhcp_utilities.dhcpservd.dhcp_cfggen.is_smart_switch", return_value=is_smart_switch):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
kea_dhcp4_config, used_ranges, enabled_dhcp_interfaces, used_options, subscribe_table = \
|
||||
@ -342,7 +348,11 @@ def test_generate(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, m
|
||||
assert used_ranges == set(["range1"])
|
||||
assert enabled_dhcp_interfaces == set(["Vlan1000"])
|
||||
assert used_options == set(["option1"])
|
||||
assert subscribe_table == set(["dummy"])
|
||||
expected_tables = set(["dummy"])
|
||||
if is_smart_switch:
|
||||
expected_tables |= set(["DpusTableEventChecker", "MidPlaneTableEventChecker"])
|
||||
|
||||
assert subscribe_table == expected_tables
|
||||
|
||||
|
||||
def test_construct_obj_for_template(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias,
|
||||
@ -414,3 +424,13 @@ def test_parse_customized_options(mock_swsscommon_dbconnector_init, mock_get_ren
|
||||
}
|
||||
else:
|
||||
assert customized_options == {}
|
||||
|
||||
|
||||
def test_parse_dpus(mock_swsscommon_dbconnector_init, mock_get_render_template, mock_parse_port_map_alias):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
dpus_table = {"dpu0": {"midplane_interface": "dpu0"}}
|
||||
mid_plane_table = {"GLOBAL": {"bridge": "bridge_midplane", "ip_prefix": "169.254.200.254/24"}}
|
||||
mid_plane, dpus = dhcp_cfg_generator._parse_dpu(dpus_table, mid_plane_table)
|
||||
assert mid_plane == {"bridge": "bridge_midplane", "ip_prefix": "169.254.200.254/24"}
|
||||
assert dpus == set(["dpu0"])
|
||||
|
@ -369,7 +369,7 @@ def test_feature_table_checker(mock_swsscommon_dbconnector_init, tested_data, te
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": {"bridge_midplane": ["dpu0"]}}, {}])
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": {"bridge_midplane"}}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_mid_plane_update"))
|
||||
def test_mid_plane_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
@ -386,9 +386,8 @@ def test_mid_plane_table_checker(mock_swsscommon_dbconnector_init, tested_data,
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": {"bridge_midplane": ["dpu0"]}}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_dpus_update"))
|
||||
def test_dpus_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot):
|
||||
def test_dpus_table_checker(mock_swsscommon_dbconnector_init, tested_data):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
@ -396,8 +395,5 @@ def test_dpus_table_checker(mock_swsscommon_dbconnector_init, tested_data, teste
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = DpusTableEventChecker(sel, MagicMock())
|
||||
expected_res = tested_data["exp_res"]
|
||||
check_res = db_event_checker.check_update_event(tested_db_snapshot)
|
||||
if "enabled_dhcp_interfaces" not in tested_db_snapshot:
|
||||
assert check_res
|
||||
else:
|
||||
check_res = db_event_checker.check_update_event({})
|
||||
assert expected_res == check_res
|
||||
|
@ -152,3 +152,9 @@ def test_get_target_process_cmds():
|
||||
]
|
||||
]
|
||||
assert res == expected_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_smart_switch", [True, False])
|
||||
def test_is_smart_switch(is_smart_switch):
|
||||
device_metadata = {"localhost": {"subtype": "SmartSwitch"}} if is_smart_switch else {"localhost": {}}
|
||||
assert utils.is_smart_switch(device_metadata) == is_smart_switch
|
||||
|
Reference in New Issue
Block a user