[dhcp_server] Add config_db monitor and customize options for dhcpservd (#17051)
Why I did it Add config_db monitor and customize options for dhcpservd. HLD: sonic-net/SONiC#1282 Work item tracking Microsoft ADO (number only): 25600859 How I did it Add support to customize unassigned DHCP options. Current support type: binary, boolean, ipv4-address, string, uint8, uint16, uint32 Add db config change monitor for dhcpservd How to verify it Unit tests in sonic-dhcp-server all passed
This commit is contained in:
parent
682057945f
commit
3223ca0156
@ -1,6 +1,17 @@
|
||||
{%- set default_lease_time = 900 -%}
|
||||
{
|
||||
"Dhcp4": {
|
||||
{%- if customized_options %}
|
||||
"option-def": [
|
||||
{%- for option_name, config in customized_options.items() %}
|
||||
{
|
||||
"name": "{{ option_name }}",
|
||||
"code": {{ config["id"] }},
|
||||
"type": "{{ config["type"] }}"
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{%- endfor %}
|
||||
],
|
||||
{%- endif %}
|
||||
"hooks-libraries": [
|
||||
{
|
||||
"library": "/usr/local/lib/kea/hooks/libdhcp_run_script.so",
|
||||
@ -44,6 +55,13 @@
|
||||
{%- endfor%}
|
||||
],
|
||||
"option-data": [
|
||||
{%- for option_name, config in subnet_info["customized_options"].items() %}
|
||||
{
|
||||
"name": "{{ option_name }}",
|
||||
"data": "{{ config["value"] }}",
|
||||
"always-send": {{ config["always_send"] }}
|
||||
},
|
||||
{%- endfor %}
|
||||
{
|
||||
"name": "routers",
|
||||
"data": "{{ subnet_info["gateway"] if "gateway" in subnet_info else subnet_info["server_id"] }}"
|
||||
|
@ -1,131 +1,439 @@
|
||||
import ipaddress
|
||||
import sys
|
||||
import syslog
|
||||
from abc import abstractmethod
|
||||
from swsscommon import swsscommon
|
||||
|
||||
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
|
||||
DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
|
||||
DHCP_SERVER_IPV4_PORT = "DHCP_SERVER_IPV4_PORT"
|
||||
DHCP_SERVER_IPV4_RANGE = "DHCP_SERVER_IPV4_RANGE"
|
||||
DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS"
|
||||
VLAN = "VLAN"
|
||||
VLAN_MEMBER = "VLAN_MEMBER"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
|
||||
|
||||
|
||||
class DhcpDbMonitor(object):
|
||||
def __init__(self, db_connector, select_timeout=DEFAULT_SELECT_TIMEOUT):
|
||||
class ConfigDbEventChecker(object):
|
||||
table_name = ""
|
||||
subscriber_state_table = None
|
||||
enabled = False
|
||||
|
||||
def __init__(self, sel, db):
|
||||
"""
|
||||
Init function
|
||||
Args:
|
||||
sel: select object to manage subscribe table
|
||||
db_connector: db connector
|
||||
"""
|
||||
self.sel = sel
|
||||
self.db = db
|
||||
self.subscriber_state_table = None
|
||||
self.enabled = False
|
||||
|
||||
@classmethod
|
||||
def get_parameter_by_name(cls, db_snapshot, param_name):
|
||||
"""
|
||||
Check whether db_snapshot valid
|
||||
Args:
|
||||
db_snapshot: dict contains db_snapshot param
|
||||
param_name: parameter name need to check
|
||||
Returns:
|
||||
If param_name in db_snapshot return tuple of (True, parameter), else return tuple of (False, None)
|
||||
"""
|
||||
if param_name not in db_snapshot:
|
||||
return False, None
|
||||
return True, db_snapshot[param_name]
|
||||
|
||||
def is_enabled(self):
|
||||
"""
|
||||
Check whether checker is enabled
|
||||
Returns:
|
||||
If enabled, return True. Else return False
|
||||
"""
|
||||
return self.enabled
|
||||
|
||||
def enable(self):
|
||||
"""
|
||||
Enable checker by subscribe table
|
||||
Args:
|
||||
db: db object
|
||||
"""
|
||||
if self.enabled:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot enable {} checker due to it is enabled"
|
||||
.format(self.table_name))
|
||||
sys.exit(1)
|
||||
self.subscriber_state_table = swsscommon.SubscriberStateTable(self.db, self.table_name)
|
||||
self.sel.addSelectable(self.subscriber_state_table)
|
||||
self.enabled = True
|
||||
|
||||
def disable(self):
|
||||
"""
|
||||
Disable checker
|
||||
"""
|
||||
if not self.enabled:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot disable {} checker due to it is disabled"
|
||||
.format(self.table_name))
|
||||
sys.exit(1)
|
||||
self.sel.removeSelectable(self.subscriber_state_table)
|
||||
self.enabled = False
|
||||
|
||||
def clear_event(self):
|
||||
"""
|
||||
Clear update event of subscirbe table
|
||||
"""
|
||||
if not self.enabled:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot clear event for table {} due to it is disabled"
|
||||
.format(self.table_name))
|
||||
sys.exit(1)
|
||||
while self.subscriber_state_table.hasData():
|
||||
_, _, _ = self.subscriber_state_table.pop()
|
||||
|
||||
@abstractmethod
|
||||
def _get_parameter(self, db_snapshot):
|
||||
"""
|
||||
Get paramter depends on subclass
|
||||
Args:
|
||||
db_snapshot: dict of db snapshot
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def _process_check(self, key, op, entry, parameter):
|
||||
"""
|
||||
Check whether this event contains value we interested
|
||||
Args:
|
||||
key: key of event
|
||||
op: operation of event
|
||||
entry: operation entry of event
|
||||
paramter: parameter used in check
|
||||
Returns:
|
||||
If contains, return True, else return False
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def check_update_event(self, db_snapshot):
|
||||
"""
|
||||
Function to check whether interested field changed in subscribe table
|
||||
Args:
|
||||
db_snapshot: dict contains db_snapshot param
|
||||
Returns:
|
||||
If changed, return True, else return False
|
||||
"""
|
||||
res, parameter = self._get_parameter(db_snapshot)
|
||||
if not res:
|
||||
return True
|
||||
need_refresh = False
|
||||
while self.subscriber_state_table.hasData():
|
||||
key, op, entry = self.subscriber_state_table.pop()
|
||||
need_refresh |= self._process_check(key, op, entry, parameter)
|
||||
if need_refresh:
|
||||
self.clear_event()
|
||||
return True
|
||||
return False
|
||||
|
||||
def _check_db_snapshot(self, db_snapshot, param_name):
|
||||
"""
|
||||
Check whether db_snapshot valid
|
||||
Args:
|
||||
db_snapshot: dict contains db_snapshot param
|
||||
param_name: parameter name need to check
|
||||
Returns:
|
||||
If param_name in db_snapshot return True, else return False
|
||||
"""
|
||||
if param_name not in db_snapshot:
|
||||
syslog.syslog(syslog.LOG_ERR, "Expected param: {} is not in db_snapshot".format(param_name))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_class_name(self):
|
||||
"""
|
||||
Get class name of this object
|
||||
"""
|
||||
return type(self).__name__
|
||||
|
||||
|
||||
class DhcpServerTableCfgChangeEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in all DHCP server related config change event in DHCP_SERVER_IPV4 table
|
||||
"""
|
||||
table_name = DHCP_SERVER_IPV4
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = DHCP_SERVER_IPV4
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "enabled_dhcp_interfaces")
|
||||
|
||||
def _process_check(self, key, op, entry, enabled_dhcp_interfaces):
|
||||
# If old state is enabled, need refresh
|
||||
if key in enabled_dhcp_interfaces:
|
||||
return True
|
||||
elif op == "SET":
|
||||
for field, value in entry:
|
||||
if field != "state":
|
||||
continue
|
||||
# If old state is not consistent with new state, need refresh
|
||||
if value == "enabled":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DhcpServerTableIntfEnablementEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker only interested in DHCP interface enabled/disabled in DHCP_SERVER_IPV4 table
|
||||
"""
|
||||
table_name = DHCP_SERVER_IPV4
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = DHCP_SERVER_IPV4
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "enabled_dhcp_interfaces")
|
||||
|
||||
def _process_check(self, key, op, entry, enabled_dhcp_interfaces):
|
||||
if op == "SET":
|
||||
for field, value in entry:
|
||||
if field != "state":
|
||||
continue
|
||||
# Only if new state is not consistent with old state, we need to refresh
|
||||
if key in enabled_dhcp_interfaces and value == "disabled":
|
||||
return True
|
||||
elif key not in enabled_dhcp_interfaces and value == "enabled":
|
||||
return True
|
||||
# For del operation, we can skip disabled change
|
||||
if op == "DEL":
|
||||
if key in enabled_dhcp_interfaces:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DhcpPortTableEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in changes in DHCP_SERVER_IPV4_PORT table
|
||||
"""
|
||||
table_name = DHCP_SERVER_IPV4_PORT
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = DHCP_SERVER_IPV4_PORT
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "enabled_dhcp_interfaces")
|
||||
|
||||
def _process_check(self, key, op, entry, enabled_dhcp_interfaces):
|
||||
dhcp_interface = key.split("|")[0]
|
||||
# If dhcp interface is enabled, need to generate new configuration
|
||||
if dhcp_interface in enabled_dhcp_interfaces:
|
||||
self.clear_event()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DhcpRangeTableEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in changes in DHCP_SERVER_IPV4_RANGE table
|
||||
"""
|
||||
table_name = DHCP_SERVER_IPV4_RANGE
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = DHCP_SERVER_IPV4_RANGE
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "used_range")
|
||||
|
||||
def _process_check(self, key, op, entry, used_range):
|
||||
# If range is used, need to generate new configuration
|
||||
if key in used_range:
|
||||
self.clear_event()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DhcpOptionTableEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in changes in DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS table
|
||||
"""
|
||||
table_name = DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "used_options")
|
||||
|
||||
def _process_check(self, key, op, entry, used_options):
|
||||
# If option is used, need to generate new configuration
|
||||
if key in used_options:
|
||||
self.clear_event()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class VlanTableEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in changes in VLAN table
|
||||
"""
|
||||
table_name = VLAN
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = VLAN
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "enabled_dhcp_interfaces")
|
||||
|
||||
def _process_check(self, key, op, entry, enabled_dhcp_interfaces):
|
||||
# For vlan doesn't have related dhcp entry, not need to refresh dhcrelay process
|
||||
if key in enabled_dhcp_interfaces:
|
||||
self.clear_event()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class VlanIntfTableEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in changes in VLAN_INTERFACE table
|
||||
"""
|
||||
table_name = VLAN_INTERFACE
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = VLAN_INTERFACE
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "enabled_dhcp_interfaces")
|
||||
|
||||
def _process_check(self, key, op, entry, enabled_dhcp_interfaces):
|
||||
splits = key.split("|")
|
||||
vlan_name = splits[0]
|
||||
ip_address = splits[1].split("/")[0] if len(splits) > 1 else None
|
||||
# For vlan doesn't have related dhcp entry, not need to refresh dhcrelay process
|
||||
if vlan_name in enabled_dhcp_interfaces and ip_address is not None and \
|
||||
ipaddress.ip_address(ip_address).version == 4:
|
||||
self.clear_event()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class VlanMemberTableEventChecker(ConfigDbEventChecker):
|
||||
"""
|
||||
This event checker interested in changes in VLAN_MEMBER table
|
||||
"""
|
||||
table_name = VLAN_MEMBER
|
||||
|
||||
def __init__(self, sel, db):
|
||||
self.table_name = VLAN_MEMBER
|
||||
ConfigDbEventChecker.__init__(self, sel, db)
|
||||
|
||||
def _get_parameter(self, db_snapshot):
|
||||
return ConfigDbEventChecker.get_parameter_by_name(db_snapshot, "enabled_dhcp_interfaces")
|
||||
|
||||
def _process_check(self, key, op, entry, enabled_dhcp_interfaces):
|
||||
dhcp_interface = key.split("|")[0]
|
||||
# If dhcp interface is enabled, need to generate new configuration
|
||||
if dhcp_interface in enabled_dhcp_interfaces:
|
||||
self.clear_event()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DhcpRelaydDbMonitor(object):
|
||||
checker_dict = {}
|
||||
|
||||
def __init__(self, db_connector, sel, checkers, select_timeout=DEFAULT_SELECT_TIMEOUT):
|
||||
self.db_connector = db_connector
|
||||
self.sel = swsscommon.Select()
|
||||
self.sel = sel
|
||||
self.select_timeout = select_timeout
|
||||
self.checker_dict = {}
|
||||
for checker in checkers:
|
||||
self.checker_dict[checker.get_class_name()] = checker
|
||||
|
||||
@abstractmethod
|
||||
def subscribe_table(self):
|
||||
def enable_checker(self, checker_names):
|
||||
"""
|
||||
Subcribe db table to monitor
|
||||
Enable checkers
|
||||
Args:
|
||||
checker_names: set of tables checker to be enable
|
||||
"""
|
||||
raise NotImplementedError
|
||||
for table in checker_names:
|
||||
if table not in self.checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(table))
|
||||
continue
|
||||
self.checker_dict[table].enable()
|
||||
|
||||
@abstractmethod
|
||||
def _do_check(self):
|
||||
"""
|
||||
Check whether interested table content changed
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def check_db_update(self, check_param):
|
||||
def check_db_update(self, db_snapshot):
|
||||
"""
|
||||
Fetch db and check update
|
||||
Args:
|
||||
db_snapshot: dict contains db snapshot parameter
|
||||
Returns:
|
||||
Tuple of dhcp_server table result, vlan table result, vlan_intf table result
|
||||
"""
|
||||
state, _ = self.sel.select(self.select_timeout)
|
||||
if state == swsscommon.Select.TIMEOUT or state != swsscommon.Select.OBJECT:
|
||||
return (False, False, False)
|
||||
return (self.checker_dict["DhcpServerTableIntfEnablementEventChecker"].check_update_event(db_snapshot),
|
||||
self.checker_dict["VlanTableEventChecker"].check_update_event(db_snapshot),
|
||||
self.checker_dict["VlanIntfTableEventChecker"].check_update_event(db_snapshot))
|
||||
|
||||
|
||||
class DhcpServdDbMonitor(object):
|
||||
checker_dict = {}
|
||||
|
||||
def __init__(self, db_connector, sel, checkers, select_timeout=DEFAULT_SELECT_TIMEOUT):
|
||||
self.db_connector = db_connector
|
||||
self.sel = sel
|
||||
self.select_timeout = select_timeout
|
||||
self.checker_dict = {}
|
||||
for checker in checkers:
|
||||
self.checker_dict[checker.get_class_name()] = checker
|
||||
|
||||
def disable_checkers(self, checker_names):
|
||||
"""
|
||||
Disable checkers
|
||||
Args:
|
||||
checker_names: set contains name of tables need to be disable
|
||||
"""
|
||||
for table in checker_names:
|
||||
if table not in self.checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(table))
|
||||
continue
|
||||
self.checker_dict[table].disable()
|
||||
|
||||
def enable_checkers(self, checker_names):
|
||||
"""
|
||||
Enable checkers
|
||||
Args:
|
||||
checker_names: set contains name of tables need to be enable
|
||||
"""
|
||||
for table in checker_names:
|
||||
if table not in self.checker_dict:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot find checker for {} in checker_dict".format(table))
|
||||
continue
|
||||
self.checker_dict[table].enable()
|
||||
|
||||
def check_db_update(self, db_snapshot):
|
||||
"""
|
||||
Fetch db and check update
|
||||
Args:
|
||||
db_snapshot: dict contains db snapshot parameter
|
||||
Returns:
|
||||
Whether need to refresh config file for kea-dhcp-server
|
||||
"""
|
||||
state, _ = self.sel.select(self.select_timeout)
|
||||
if state == swsscommon.Select.TIMEOUT or state != swsscommon.Select.OBJECT:
|
||||
return False
|
||||
return self._do_check(check_param)
|
||||
|
||||
|
||||
class DhcpRelaydDbMonitor(DhcpDbMonitor):
|
||||
subscribe_dhcp_server_table = None
|
||||
subscribe_vlan_table = None
|
||||
subscribe_vlan_intf_table = None
|
||||
|
||||
def subscribe_table(self):
|
||||
self.subscribe_dhcp_server_table = swsscommon.SubscriberStateTable(self.db_connector.config_db,
|
||||
DHCP_SERVER_IPV4)
|
||||
self.subscribe_vlan_table = swsscommon.SubscriberStateTable(self.db_connector.config_db, VLAN)
|
||||
self.subscribe_vlan_intf_table = swsscommon.SubscriberStateTable(self.db_connector.config_db, VLAN_INTERFACE)
|
||||
# Subscribe dhcp_server_ipv4 and vlan/vlan_interface table. No need to subscribe vlan_member table
|
||||
self.sel.addSelectable(self.subscribe_dhcp_server_table)
|
||||
self.sel.addSelectable(self.subscribe_vlan_table)
|
||||
self.sel.addSelectable(self.subscribe_vlan_intf_table)
|
||||
|
||||
def _do_check(self, check_param):
|
||||
if "enabled_dhcp_interfaces" not in check_param:
|
||||
syslog.syslog(syslog.LOG_ERR, "Cannot get enabled_dhcp_interfaces")
|
||||
return (True, True, True)
|
||||
enabled_dhcp_interfaces = check_param["enabled_dhcp_interfaces"]
|
||||
return (self._check_dhcp_server_update(enabled_dhcp_interfaces),
|
||||
self._check_vlan_update(enabled_dhcp_interfaces),
|
||||
self._check_vlan_intf_update(enabled_dhcp_interfaces))
|
||||
|
||||
def _check_dhcp_server_update(self, enabled_dhcp_interfaces):
|
||||
"""
|
||||
Check dhcp_server_ipv4 table
|
||||
Args:
|
||||
enabled_dhcp_interfaces: DHCP interface that enabled dhcp_server
|
||||
Returns:
|
||||
Whether need to refresh
|
||||
"""
|
||||
need_refresh = False
|
||||
while self.subscribe_dhcp_server_table.hasData():
|
||||
key, op, entry = self.subscribe_dhcp_server_table.pop()
|
||||
if op == "SET":
|
||||
for field, value in entry:
|
||||
if field != "state":
|
||||
continue
|
||||
# Only if new state is not consistent with old state, we need to refresh
|
||||
if key in enabled_dhcp_interfaces and value == "disabled":
|
||||
need_refresh = True
|
||||
elif key not in enabled_dhcp_interfaces and value == "enabled":
|
||||
need_refresh = True
|
||||
# For del operation, we can skip disabled change
|
||||
if op == "DEL":
|
||||
if key in enabled_dhcp_interfaces:
|
||||
need_refresh = True
|
||||
return need_refresh
|
||||
|
||||
def _check_vlan_update(self, enabled_dhcp_interfaces):
|
||||
"""
|
||||
Check vlan table
|
||||
Args:
|
||||
enabled_dhcp_interfaces: DHCP interface that enabled dhcp_server
|
||||
Returns:
|
||||
Whether need to refresh
|
||||
"""
|
||||
need_refresh = False
|
||||
while self.subscribe_vlan_table.hasData():
|
||||
key, op, _ = self.subscribe_vlan_table.pop()
|
||||
# For vlan doesn't have related dhcp entry, not need to refresh dhcrelay process
|
||||
if key not in enabled_dhcp_interfaces:
|
||||
for checker in self.checker_dict.values():
|
||||
if not checker.is_enabled():
|
||||
continue
|
||||
need_refresh = True
|
||||
return need_refresh
|
||||
|
||||
def _check_vlan_intf_update(self, enabled_dhcp_interfaces):
|
||||
"""
|
||||
Check vlan_interface table
|
||||
Args:
|
||||
enabled_dhcp_interfaces: DHCP interface that enabled dhcp_server
|
||||
Returns:
|
||||
Whether need to refresh
|
||||
"""
|
||||
need_refresh = False
|
||||
while self.subscribe_vlan_intf_table.hasData():
|
||||
key, _, _ = self.subscribe_vlan_intf_table.pop()
|
||||
splits = key.split("|")
|
||||
vlan_name = splits[0]
|
||||
ip_address = splits[1].split("/")[0] if len(splits) > 1 else None
|
||||
if vlan_name not in enabled_dhcp_interfaces:
|
||||
continue
|
||||
if ip_address is None or ipaddress.ip_address(ip_address).version != 4:
|
||||
continue
|
||||
need_refresh = True
|
||||
if need_refresh:
|
||||
checker.clear_event()
|
||||
else:
|
||||
need_refresh |= checker.check_update_event(db_snapshot)
|
||||
return need_refresh
|
||||
|
@ -1,7 +1,10 @@
|
||||
import ipaddress
|
||||
import string
|
||||
from swsscommon import swsscommon
|
||||
|
||||
DEFAULT_REDIS_HOST = "127.0.0.1"
|
||||
DEFAULT_REDIS_PORT = 6379
|
||||
SUPPORT_TYPE = ["binary", "boolean", "ipv4-address", "string", "uint8", "uint16", "uint32"]
|
||||
|
||||
|
||||
class DhcpDbConnector(object):
|
||||
@ -93,6 +96,43 @@ def merge_intervals(intervals):
|
||||
return ret
|
||||
|
||||
|
||||
def validate_str_type(type, value):
|
||||
"""
|
||||
To validate whether type is consistent with string value
|
||||
Args:
|
||||
type: string, value type
|
||||
value: checked value
|
||||
Returns:
|
||||
True, type consistent with value
|
||||
False, type not consistent with value
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
if type not in SUPPORT_TYPE:
|
||||
return False
|
||||
if type == "string":
|
||||
return True
|
||||
if type == "binary":
|
||||
if len(value) == 0 or len(value) % 2 != 0:
|
||||
return False
|
||||
return all(c in set(string.hexdigits) for c in value)
|
||||
if type == "boolean":
|
||||
return value in ["true", "false"]
|
||||
if type == "ipv4-address":
|
||||
try:
|
||||
if len(value.split(".")) != 4:
|
||||
return False
|
||||
return ipaddress.ip_address(value).version == 4
|
||||
except ValueError:
|
||||
return False
|
||||
if type.startswith("uint"):
|
||||
if not value.isdigit():
|
||||
return False
|
||||
length = int("".join([c for c in type if c.isdigit()]))
|
||||
return 0 <= int(value) <= int(pow(2, length)) - 1
|
||||
return False
|
||||
|
||||
|
||||
def _parse_table_to_dict(table):
|
||||
ret = {}
|
||||
for key in table.getKeys():
|
||||
|
@ -8,27 +8,25 @@ import syslog
|
||||
import time
|
||||
from swsscommon import swsscommon
|
||||
from dhcp_server.common.utils import DhcpDbConnector, terminate_proc
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor, DhcpServerTableIntfEnablementEventChecker, \
|
||||
VlanTableEventChecker, VlanIntfTableEventChecker
|
||||
|
||||
REDIS_SOCK_PATH = "/var/run/redis/redis.sock"
|
||||
DHCP_SERVER_IPV4_SERVER_IP = "DHCP_SERVER_IPV4_SERVER_IP"
|
||||
DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
|
||||
VLAN = "VLAN"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
|
||||
DHCP_SERVER_INTERFACE = "eth0"
|
||||
DEFAULT_REFRESH_INTERVAL = 2
|
||||
|
||||
DEFAULT_CHECKER = ["DhcpServerTableIntfEnablementEventChecker", "VlanTableEventChecker", "VlanIntfTableEventChecker"]
|
||||
KILLED_OLD = 1
|
||||
NOT_KILLED = 2
|
||||
NOT_FOUND_PROC = 3
|
||||
|
||||
|
||||
class DhcpRelayd(object):
|
||||
sel = None
|
||||
enabled_dhcp_interfaces = set()
|
||||
|
||||
def __init__(self, db_connector, select_timeout=DEFAULT_SELECT_TIMEOUT):
|
||||
def __init__(self, db_connector, db_monitor):
|
||||
"""
|
||||
Args:
|
||||
db_connector: db connector obj
|
||||
@ -36,18 +34,20 @@ class DhcpRelayd(object):
|
||||
"""
|
||||
self.db_connector = db_connector
|
||||
self.last_refresh_time = None
|
||||
self.dhcp_relayd_monitor = DhcpRelaydDbMonitor(db_connector, select_timeout)
|
||||
self.dhcp_relayd_monitor = db_monitor
|
||||
self.enabled_dhcp_interfaces = set()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start function
|
||||
"""
|
||||
self.refresh_dhcrelay()
|
||||
self.dhcp_relayd_monitor.subscribe_table()
|
||||
|
||||
def refresh_dhcrelay(self, force_kill=False):
|
||||
"""
|
||||
To refresh dhcrelay/dhcpmon process (start or restart)
|
||||
Args:
|
||||
force_kill: if True, force kill old processes
|
||||
"""
|
||||
syslog.syslog(syslog.LOG_INFO, "Start to refresh dhcrelay related processes")
|
||||
dhcp_server_ip = self._get_dhcp_server_ip()
|
||||
@ -75,9 +75,6 @@ class DhcpRelayd(object):
|
||||
"""
|
||||
while True:
|
||||
res = (self.dhcp_relayd_monitor.check_db_update({"enabled_dhcp_interfaces": self.enabled_dhcp_interfaces}))
|
||||
# Select timeout or no successful
|
||||
if isinstance(res, bool):
|
||||
continue
|
||||
(dhcp_server_res, vlan_res, vlan_intf_res) = res
|
||||
# vlan ip change require kill old dhcp_relay related processes
|
||||
if vlan_intf_res:
|
||||
@ -182,7 +179,14 @@ class DhcpRelayd(object):
|
||||
|
||||
def main():
|
||||
dhcp_db_connector = DhcpDbConnector(redis_sock=REDIS_SOCK_PATH)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
sel = swsscommon.Select()
|
||||
checkers = []
|
||||
checkers.append(DhcpServerTableIntfEnablementEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(VlanIntfTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(VlanTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
db_monitor = DhcpRelaydDbMonitor(dhcp_db_connector, sel, checkers, DEFAULT_SELECT_TIMEOUT)
|
||||
db_monitor.enable_checker(DEFAULT_CHECKER)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, db_monitor)
|
||||
dhcprelayd.start()
|
||||
dhcprelayd.wait()
|
||||
|
||||
|
@ -5,7 +5,7 @@ import os
|
||||
import syslog
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from dhcp_server.common.utils import merge_intervals
|
||||
from dhcp_server.common.utils import merge_intervals, validate_str_type
|
||||
|
||||
PORT_MAP_PATH = "/tmp/port-name-alias-map.txt"
|
||||
UNICODE_TYPE = str
|
||||
@ -13,12 +13,18 @@ DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
|
||||
DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS"
|
||||
DHCP_SERVER_IPV4_RANGE = "DHCP_SERVER_IPV4_RANGE"
|
||||
DHCP_SERVER_IPV4_PORT = "DHCP_SERVER_IPV4_PORT"
|
||||
DHCP_SERVER_IPV4_LEASE = "DHCP_SERVER_IPV4_LEASE"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
VLAN_MEMBER = "VLAN_MEMBER"
|
||||
PORT_MODE_CHECKER = ["DhcpServerTableCfgChangeEventChecker", "DhcpPortTableEventChecker", "DhcpRangeTableEventChecker",
|
||||
"DhcpOptionTableEventChecker", "VlanTableEventChecker", "VlanIntfTableEventChecker",
|
||||
"VlanMemberTableEventChecker"]
|
||||
LEASE_UPDATE_SCRIPT_PATH = "/etc/kea/lease_update.sh"
|
||||
DEFAULT_LEASE_TIME = 900
|
||||
DEFAULT_LEASE_PATH = "/tmp/kea-lease.csv"
|
||||
KEA_DHCP4_CONF_TEMPLATE_PATH = "/usr/share/sonic/templates/kea-dhcp4.conf.j2"
|
||||
# Default lease time of DHCP
|
||||
DEFAULT_LEASE_TIME = 900
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
DHCP_OPTION_FILE = f"{SCRIPT_DIR}/dhcp_option.csv"
|
||||
SUPPORT_DHCP_OPTION_TYPE = ["binary", "boolean", "ipv4-address", "string", "uint8", "uint16", "uint32"]
|
||||
|
||||
|
||||
class DhcpServCfgGenerator(object):
|
||||
@ -27,7 +33,7 @@ class DhcpServCfgGenerator(object):
|
||||
lease_path = ""
|
||||
|
||||
def __init__(self, dhcp_db_connector, lease_path=DEFAULT_LEASE_PATH, port_map_path=PORT_MAP_PATH,
|
||||
lease_update_script_path=LEASE_UPDATE_SCRIPT_PATH,
|
||||
lease_update_script_path=LEASE_UPDATE_SCRIPT_PATH, dhcp_option_path=DHCP_OPTION_FILE,
|
||||
kea_conf_template_path=KEA_DHCP4_CONF_TEMPLATE_PATH):
|
||||
self.db_connector = dhcp_db_connector
|
||||
self.lease_path = lease_path
|
||||
@ -36,33 +42,64 @@ class DhcpServCfgGenerator(object):
|
||||
self._parse_port_map_alias(port_map_path)
|
||||
# Get kea config template
|
||||
self._get_render_template(kea_conf_template_path)
|
||||
self._read_dhcp_option(dhcp_option_path)
|
||||
|
||||
def generate(self):
|
||||
"""
|
||||
Generate dhcp server config
|
||||
Returns:
|
||||
config dict
|
||||
config string
|
||||
set of ranges used
|
||||
set of enabled dhcp interface
|
||||
set of used options
|
||||
set of db table need to be monitored
|
||||
"""
|
||||
# Generate from running config_db
|
||||
# Get host name
|
||||
device_metadata = self.db_connector.get_config_db_table("DEVICE_METADATA")
|
||||
hostname = self._parse_hostname(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_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)
|
||||
|
||||
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)
|
||||
|
||||
# TODO Add support for customizing options
|
||||
|
||||
# Parse port table
|
||||
port_ips = self._parse_port(port_ipv4, vlan_interfaces, vlan_members, ranges)
|
||||
render_obj = self._construct_obj_for_template(dhcp_server_ipv4, port_ips, hostname)
|
||||
port_ips, used_ranges = self._parse_port(port_ipv4, vlan_interfaces, vlan_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)
|
||||
return self._render_config(render_obj), used_ranges, enabled_dhcp_interfaces, used_options, subscribe_table
|
||||
|
||||
return self._render_config(render_obj)
|
||||
def _parse_customized_options(self, customized_options_ipv4):
|
||||
customized_options = {}
|
||||
for option_name, config in customized_options_ipv4.items():
|
||||
if config["id"] not in self.dhcp_option.keys():
|
||||
syslog.syslog(syslog.LOG_ERR, "Unsupported option: {}, currently only support unassigned options"
|
||||
.format(config["id"]))
|
||||
continue
|
||||
option_type = config["type"] if "type" in config else "string"
|
||||
if option_type not in SUPPORT_DHCP_OPTION_TYPE:
|
||||
syslog.syslog(syslog.LOG_ERR, "Unsupported type: {}, currently only support {}"
|
||||
.format(option_type, SUPPORT_DHCP_OPTION_TYPE))
|
||||
continue
|
||||
if not validate_str_type(option_type, config["value"]):
|
||||
syslog.syslog(syslog.LOG_ERR, "Option type [{}] and value [{}] are not consistent"
|
||||
.format(option_type, config["value"]))
|
||||
continue
|
||||
if option_type == "string" and len(config["value"]) > 253:
|
||||
syslog.syslog(syslog.LOG_ERR, "String option value too long: {}".format(option_name))
|
||||
continue
|
||||
always_send = config["always_send"] if "always_send" in config else "true"
|
||||
customized_options[option_name] = {
|
||||
"id": config["id"],
|
||||
"value": config["value"],
|
||||
"type": option_type,
|
||||
"always_send": always_send
|
||||
}
|
||||
return customized_options
|
||||
|
||||
def _render_config(self, render_obj):
|
||||
output = self.kea_template.render(render_obj)
|
||||
@ -94,17 +131,30 @@ class DhcpServCfgGenerator(object):
|
||||
continue
|
||||
self.port_alias_map[splits[0]] = splits[1]
|
||||
|
||||
def _construct_obj_for_template(self, dhcp_server_ipv4, port_ips, hostname):
|
||||
def _construct_obj_for_template(self, dhcp_server_ipv4, port_ips, hostname, customized_options):
|
||||
subnets = []
|
||||
client_classes = []
|
||||
enabled_dhcp_interfaces = set()
|
||||
used_options = set()
|
||||
# Different mode would subscribe different table, always subscribe DHCP_SERVER_IPV4
|
||||
subscribe_table = set(["DhcpServerTableCfgChangeEventChecker"])
|
||||
for dhcp_interface_name, dhcp_config in dhcp_server_ipv4.items():
|
||||
if "state" not in dhcp_config or dhcp_config["state"] != "enabled":
|
||||
continue
|
||||
enabled_dhcp_interfaces.add(dhcp_interface_name)
|
||||
if dhcp_config["mode"] == "PORT":
|
||||
subscribe_table |= set(PORT_MODE_CHECKER)
|
||||
if dhcp_interface_name not in port_ips:
|
||||
syslog.syslog(syslog.LOG_WARNING, "Cannot get DHCP port config for {}"
|
||||
.format(dhcp_interface_name))
|
||||
continue
|
||||
curr_options = {}
|
||||
for option in dhcp_config["customized_options"]:
|
||||
if option in customized_options.keys():
|
||||
curr_options[option] = {
|
||||
"always_send": customized_options[option]["always_send"],
|
||||
"value": customized_options[option]["value"]
|
||||
}
|
||||
for dhcp_interface_ip, port_config in port_ips[dhcp_interface_name].items():
|
||||
pools = []
|
||||
for port_name, ip_ranges in port_config.items():
|
||||
@ -128,16 +178,19 @@ class DhcpServCfgGenerator(object):
|
||||
"pools": pools,
|
||||
"gateway": dhcp_config["gateway"],
|
||||
"server_id": dhcp_interface_ip.split("/")[0],
|
||||
"lease_time": dhcp_config["lease_time"]
|
||||
"lease_time": dhcp_config["lease_time"] if "lease_time" in dhcp_config else DEFAULT_LEASE_TIME,
|
||||
"customized_options": curr_options
|
||||
}
|
||||
used_options = used_options | set(subnet_obj["customized_options"])
|
||||
subnets.append(subnet_obj)
|
||||
render_obj = {
|
||||
"subnets": subnets,
|
||||
"client_classes": client_classes,
|
||||
"lease_update_script_path": self.lease_update_script_path,
|
||||
"lease_path": self.lease_path
|
||||
"lease_path": self.lease_path,
|
||||
"customized_options": customized_options
|
||||
}
|
||||
return render_obj
|
||||
return render_obj, enabled_dhcp_interfaces, used_options, subscribe_table
|
||||
|
||||
def _get_dhcp_ipv4_tables_from_db(self):
|
||||
"""
|
||||
@ -262,22 +315,24 @@ class DhcpServCfgGenerator(object):
|
||||
ranges: Dict of ranges
|
||||
Returns:
|
||||
Dict of dhcp conf, sample:
|
||||
{
|
||||
'Vlan1000': {
|
||||
'192.168.0.1/24': {
|
||||
'etp2': [
|
||||
['192.168.0.7', '192.168.0.7']
|
||||
],
|
||||
'etp3': [
|
||||
['192.168.0.2', '192.168.0.6'],
|
||||
['192.168.0.10', '192.168.0.10']
|
||||
]
|
||||
{
|
||||
'Vlan1000': {
|
||||
'192.168.0.1/24': {
|
||||
'etp2': [
|
||||
['192.168.0.7', '192.168.0.7']
|
||||
],
|
||||
'etp3': [
|
||||
['192.168.0.2', '192.168.0.6'],
|
||||
['192.168.0.10', '192.168.0.10']
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Set of used ranges.
|
||||
"""
|
||||
port_ips = {}
|
||||
ip_ports = {}
|
||||
used_ranges = set()
|
||||
for port_key in list(port_ipv4.keys()):
|
||||
port_config = port_ipv4.get(port_key, {})
|
||||
# Cannot specify both 'ips' and 'ranges'
|
||||
@ -316,6 +371,7 @@ class DhcpServCfgGenerator(object):
|
||||
if range_name not in ranges:
|
||||
syslog.syslog(syslog.LOG_WARNING, f"Range {range_name} is not in range table, skip")
|
||||
continue
|
||||
used_ranges.add(range_name)
|
||||
range = ranges[range_name]
|
||||
# Loop the IP of the dhcp interface and find the network that target range is in this network.
|
||||
self._match_range_network(dhcp_interface, dhcp_interface_name, port, range, port_ips)
|
||||
@ -326,4 +382,17 @@ class DhcpServCfgGenerator(object):
|
||||
ranges = merge_intervals(ip_range)
|
||||
ranges = [[str(range[0]), str(range[1])] for range in ranges]
|
||||
port_ips[dhcp_interface_name][dhcp_interface_ip][port_name] = ranges
|
||||
return port_ips
|
||||
return port_ips, used_ranges
|
||||
|
||||
def _read_dhcp_option(self, file_path):
|
||||
# TODO current only support unassigned options, use dict in case support more options in the future
|
||||
# key: option cod, value: option type list
|
||||
self.dhcp_option = {}
|
||||
with open(file_path, "r") as file:
|
||||
lines = file.readlines()
|
||||
for line in lines:
|
||||
if "Code,Type,Customized Type" in line:
|
||||
continue
|
||||
splits = line.strip().split(",")
|
||||
if splits[-1] == "unassigned":
|
||||
self.dhcp_option[splits[0]] = []
|
||||
|
255
src/sonic-dhcp-server/dhcp_server/dhcpservd/dhcp_option.csv
Normal file
255
src/sonic-dhcp-server/dhcp_server/dhcpservd/dhcp_option.csv
Normal file
@ -0,0 +1,255 @@
|
||||
Code,Type,Customized Type
|
||||
1,ipv4-address,defined_unsupported
|
||||
2,int32,defined_supported
|
||||
3,ipv4-address,defined_supported
|
||||
4,ipv4-address,defined_supported
|
||||
5,ipv4-address,defined_supported
|
||||
6,ipv4-address,defined_supported
|
||||
7,ipv4-address,defined_supported
|
||||
8,ipv4-address,defined_supported
|
||||
9,ipv4-address,defined_supported
|
||||
10,ipv4-address,defined_supported
|
||||
11,ipv4-address,defined_supported
|
||||
12,string,defined_unsupported
|
||||
13,uint16,defined_supported
|
||||
14,string,defined_supported
|
||||
15,fqdn,defined_supported
|
||||
16,ipv4-address,defined_supported
|
||||
17,string,defined_supported
|
||||
18,string,defined_supported
|
||||
19,boolean,defined_supported
|
||||
20,boolean,defined_supported
|
||||
21,ipv4-address,defined_supported
|
||||
22,uint16,defined_supported
|
||||
23,uint8,defined_supported
|
||||
24,uint32,defined_supported
|
||||
25,uint16,defined_supported
|
||||
26,uint16,defined_supported
|
||||
27,boolean,defined_supported
|
||||
28,ipv4-address,defined_supported
|
||||
29,boolean,defined_supported
|
||||
30,boolean,defined_supported
|
||||
31,boolean,defined_supported
|
||||
32,ipv4-address,defined_supported
|
||||
33,ipv4-address,defined_supported
|
||||
34,boolean,defined_supported
|
||||
35,uint32,defined_supported
|
||||
36,boolean,defined_supported
|
||||
37,uint8,defined_supported
|
||||
38,uint32,defined_supported
|
||||
39,boolean,defined_supported
|
||||
40,string,defined_supported
|
||||
41,ipv4-address,defined_supported
|
||||
42,ipv4-address,defined_supported
|
||||
43,empty,defined_supported
|
||||
44,ipv4-address,defined_supported
|
||||
45,ipv4-address,defined_supported
|
||||
46,uint8,defined_supported
|
||||
47,string,defined_supported
|
||||
48,ipv4-address,defined_supported
|
||||
49,ipv4-address,defined_supported
|
||||
50,ipv4-address,defined_unsupported
|
||||
51,uint32,defined_unsupported
|
||||
52,uint8,defined_supported
|
||||
53,string,defined_unsupported
|
||||
54,ipv4-address,defined_supported
|
||||
55,uint8 array,defined_unsupported
|
||||
56,string,defined_supported
|
||||
57,uint16,defined_supported
|
||||
58,uint32,defined_unsupported
|
||||
59,uint32,defined_unsupported
|
||||
60,string,defined_supported
|
||||
61,binary,defined_unsupported
|
||||
62,string,defined_supported
|
||||
63,binary,defined_supported
|
||||
64,string,defined_supported
|
||||
65,ipv4-address,defined_supported
|
||||
66,string,defined_supported
|
||||
67,string,defined_supported
|
||||
68,ipv4-address,defined_supported
|
||||
69,ipv4-address,defined_supported
|
||||
70,ipv4-address,defined_supported
|
||||
71,ipv4-address,defined_supported
|
||||
72,ipv4-address,defined_supported
|
||||
73,ipv4-address,defined_supported
|
||||
74,ipv4-address,defined_supported
|
||||
75,ipv4-address,defined_supported
|
||||
76,ipv4-address,defined_supported
|
||||
77,binary,defined_supported
|
||||
78,"record (boolean, ipv4-address)",defined_supported
|
||||
79,"record (boolean, string)",defined_supported
|
||||
80,,assigned
|
||||
81,"record (uint8, uint8, uint8, fqdn)",defined_unsupported
|
||||
82,empty,defined_unsupported
|
||||
83,,assigned
|
||||
84,,assigned_unused
|
||||
85,ipv4-address,defined_supported
|
||||
86,string,defined_supported
|
||||
87,string,defined_supported
|
||||
88,fqdn,defined_supported
|
||||
89,ipv4-address,defined_supported
|
||||
90,binary,defined_unsupported
|
||||
91,uint32,defined_unsupported
|
||||
92,ipv4-address array,defined_unsupported
|
||||
93,uint16,defined_supported
|
||||
94,"record (uint8, uint8, uint8)",defined_supported
|
||||
95,,assigned_unused
|
||||
96,,assigned_unused
|
||||
97,"record (uint8, binary)",defined_supported
|
||||
98,string,defined_supported
|
||||
99,binary,defined_supported
|
||||
100,string,defined_supported
|
||||
101,string,defined_supported
|
||||
102,,assigned_unused
|
||||
103,,assigned_unused
|
||||
104,,assigned_unused
|
||||
105,,assigned_unused
|
||||
106,,assigned_unused
|
||||
107,,assigned_unused
|
||||
108,uint32,defined_supported
|
||||
109,,assigned_unused
|
||||
110,,assigned_unused
|
||||
111,,assigned_unused
|
||||
112,ipv4-address,defined_supported
|
||||
113,string,defined_supported
|
||||
114,string,defined_supported
|
||||
115,,assigned_unused
|
||||
116,uint8,defined_supported
|
||||
117,uint16,defined_supported
|
||||
118,ipv4-address,defined_unsupported
|
||||
118,,assigned
|
||||
119,fqdn,defined_supported
|
||||
120,,assigned
|
||||
122,,assigned
|
||||
123,,assigned
|
||||
124,"record (uint32, binary)",defined_supported
|
||||
125,uint32,defined_supported
|
||||
126,,assigned_unused
|
||||
127,,assigned_unused
|
||||
128,,assigned
|
||||
129,,assigned
|
||||
130,,assigned
|
||||
131,,assigned
|
||||
132,,assigned
|
||||
133,,assigned
|
||||
134,,assigned
|
||||
135,,assigned
|
||||
136,ipv4-address,defined_supported
|
||||
137,fqdn,defined_supported
|
||||
138,ipv4-address,defined_supported
|
||||
139,,assigned
|
||||
140,,assigned
|
||||
141,fqdn,defined_supported
|
||||
142,,assigned
|
||||
143,tuple,defined_supported
|
||||
144,,assigned
|
||||
145,,assigned
|
||||
146,"record (uint8, ipv4-address, ipv4-address, fqdn)",defined_supported
|
||||
147,,unassigned
|
||||
148,,unassigned
|
||||
149,,unassigned
|
||||
150,,assigned
|
||||
151,,assigned
|
||||
152,,assigned
|
||||
153,,assigned
|
||||
154,,assigned
|
||||
155,,assigned
|
||||
156,,assigned
|
||||
157,,assigned
|
||||
158,,assigned
|
||||
159,"record (uint8, psid)",defined_supported
|
||||
160,,assigned
|
||||
161,,assigned
|
||||
162,"record (uint16, uint16, uint8, fqdn, binary)",defined_supported
|
||||
163,,unassigned
|
||||
164,,unassigned
|
||||
165,,unassigned
|
||||
166,,unassigned
|
||||
167,,unassigned
|
||||
168,,unassigned
|
||||
169,,unassigned
|
||||
170,,unassigned
|
||||
171,,unassigned
|
||||
172,,unassigned
|
||||
173,,unassigned
|
||||
174,,unassigned
|
||||
175,,assigned
|
||||
176,,assigned
|
||||
177,,assigned
|
||||
178,,unassigned
|
||||
179,,unassigned
|
||||
180,,unassigned
|
||||
181,,unassigned
|
||||
182,,unassigned
|
||||
183,,unassigned
|
||||
184,,unassigned
|
||||
185,,unassigned
|
||||
186,,unassigned
|
||||
187,,unassigned
|
||||
188,,unassigned
|
||||
189,,unassigned
|
||||
190,,unassigned
|
||||
191,,unassigned
|
||||
192,,unassigned
|
||||
193,,unassigned
|
||||
194,,unassigned
|
||||
195,,unassigned
|
||||
196,,unassigned
|
||||
197,,unassigned
|
||||
198,,unassigned
|
||||
199,,unassigned
|
||||
200,,unassigned
|
||||
201,,unassigned
|
||||
202,,unassigned
|
||||
203,,unassigned
|
||||
204,,unassigned
|
||||
205,,unassigned
|
||||
206,,unassigned
|
||||
207,,unassigned
|
||||
208,,assigned
|
||||
209,,assigned
|
||||
210,,assigned
|
||||
211,,assigned
|
||||
212,"record (uint8, uint8, ipv6-address, ipv4-address)",defined_supported
|
||||
213,fqdn,defined_supported
|
||||
214,,unassigned
|
||||
215,,unassigned
|
||||
216,,unassigned
|
||||
217,,unassigned
|
||||
218,,unassigned
|
||||
219,,unassigned
|
||||
220,,assigned
|
||||
221,,assigned
|
||||
222,,unassigned
|
||||
223,,unassigned
|
||||
224,,reserved
|
||||
225,,reserved
|
||||
226,,reserved
|
||||
227,,reserved
|
||||
228,,reserved
|
||||
229,,reserved
|
||||
230,,reserved
|
||||
231,,reserved
|
||||
232,,reserved
|
||||
233,,reserved
|
||||
234,,reserved
|
||||
235,,reserved
|
||||
236,,reserved
|
||||
237,,reserved
|
||||
238,,reserved
|
||||
239,,reserved
|
||||
240,,reserved
|
||||
241,,reserved
|
||||
242,,reserved
|
||||
243,,reserved
|
||||
244,,reserved
|
||||
245,,reserved
|
||||
246,,reserved
|
||||
247,,reserved
|
||||
248,,reserved
|
||||
249,,reserved
|
||||
250,,reserved
|
||||
251,,reserved
|
||||
252,,reserved
|
||||
253,,reserved
|
||||
254,,reserved
|
|
@ -7,6 +7,10 @@ import syslog
|
||||
from .dhcp_cfggen import DhcpServCfgGenerator
|
||||
from .dhcp_lease import LeaseManager
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpServdDbMonitor, DhcpServerTableCfgChangeEventChecker, \
|
||||
DhcpOptionTableEventChecker, DhcpRangeTableEventChecker, DhcpPortTableEventChecker, VlanIntfTableEventChecker, \
|
||||
VlanMemberTableEventChecker, VlanTableEventChecker
|
||||
from swsscommon import swsscommon
|
||||
|
||||
KEA_DHCP4_CONFIG = "/etc/kea/kea-dhcp4.conf"
|
||||
KEA_DHCP4_PROC_NAME = "kea-dhcp4"
|
||||
@ -15,13 +19,19 @@ REDIS_SOCK_PATH = "/var/run/redis/redis.sock"
|
||||
DHCP_SERVER_IPV4_SERVER_IP = "DHCP_SERVER_IPV4_SERVER_IP"
|
||||
DHCP_SERVER_INTERFACE = "eth0"
|
||||
AF_INET = 2
|
||||
DEFAULT_SELECT_TIMEOUT = 5000 # millisecond
|
||||
|
||||
|
||||
class DhcpServd(object):
|
||||
def __init__(self, dhcp_cfg_generator, db_connector, kea_dhcp4_config_path=KEA_DHCP4_CONFIG):
|
||||
enabled_checker = None
|
||||
dhcp_servd_monitor = None
|
||||
|
||||
def __init__(self, dhcp_cfg_generator, db_connector, monitor, kea_dhcp4_config_path=KEA_DHCP4_CONFIG):
|
||||
self.dhcp_cfg_generator = dhcp_cfg_generator
|
||||
self.db_connector = db_connector
|
||||
self.kea_dhcp4_config_path = kea_dhcp4_config_path
|
||||
self.dhcp_servd_monitor = monitor
|
||||
self.enabled_checker = None
|
||||
|
||||
def _notify_kea_dhcp4_proc(self):
|
||||
"""
|
||||
@ -36,7 +46,16 @@ class DhcpServd(object):
|
||||
"""
|
||||
Generate kea-dhcp4 config file and dump it to config folder
|
||||
"""
|
||||
kea_dhcp4_config = self.dhcp_cfg_generator.generate()
|
||||
kea_dhcp4_config, used_ranges, enabled_dhcp_interfaces, used_options, enable_checker = \
|
||||
self.dhcp_cfg_generator.generate()
|
||||
if self.enabled_checker is not None and self.enabled_checker != enable_checker:
|
||||
# Has subcribe table and no equal, need to resubscribe
|
||||
self.dhcp_servd_monitor.disable_checkers(self.enabled_checker - enable_checker)
|
||||
self.dhcp_servd_monitor.enable_checkers(enable_checker - self.enabled_checker)
|
||||
self.enabled_checker = enable_checker
|
||||
self.used_range = used_ranges
|
||||
self.enabled_dhcp_interfaces = enabled_dhcp_interfaces
|
||||
self.used_options = used_options
|
||||
with open(self.kea_dhcp4_config_path, "w") as write_file:
|
||||
write_file.write(kea_dhcp4_config)
|
||||
# After refresh kea-config, we need to SIGHUP kea-dhcp4 process to read new config
|
||||
@ -63,20 +82,36 @@ class DhcpServd(object):
|
||||
def start(self):
|
||||
self.dump_dhcp4_config()
|
||||
self._update_dhcp_server_ip()
|
||||
self.dhcp_servd_monitor.enable_checkers(self.enabled_checker)
|
||||
lease_manager = LeaseManager(self.db_connector, KEA_LEASE_FILE_PATH)
|
||||
lease_manager.start()
|
||||
|
||||
# TODO Add config db subcribe to re-generate kea-dhcp4 config after config_db change.
|
||||
|
||||
def wait(self):
|
||||
while True:
|
||||
time.sleep(5)
|
||||
db_snapshot = {
|
||||
"enabled_dhcp_interfaces": self.enabled_dhcp_interfaces,
|
||||
"used_range": self.used_range,
|
||||
"used_options": self.used_options
|
||||
}
|
||||
res = self.dhcp_servd_monitor.check_db_update(db_snapshot)
|
||||
if res:
|
||||
self.dump_dhcp4_config()
|
||||
|
||||
|
||||
def main():
|
||||
dhcp_db_connector = DhcpDbConnector(redis_sock=REDIS_SOCK_PATH)
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
||||
sel = swsscommon.Select()
|
||||
checkers = []
|
||||
checkers.append(DhcpServerTableCfgChangeEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(DhcpPortTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(DhcpOptionTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
checkers.append(DhcpRangeTableEventChecker(sel, dhcp_db_connector.config_db))
|
||||
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))
|
||||
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()
|
||||
dhcpservd.wait()
|
||||
|
||||
|
@ -41,5 +41,8 @@ setup(
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8"
|
||||
]
|
||||
],
|
||||
package_data={
|
||||
"dhcp_server.dhcpservd": ["dhcp_option.csv"]
|
||||
}
|
||||
)
|
||||
|
@ -3,7 +3,17 @@ import json
|
||||
import psutil
|
||||
|
||||
MOCK_CONFIG_DB_PATH = "tests/test_data/mock_config_db.json"
|
||||
MOCK_STATE_DB_PATH = "tests/test_data/mock_state_db.json"
|
||||
TEST_DATA_PATH = "tests/test_data/dhcp_db_monitor_test_data.json"
|
||||
DHCP_SERVER_IPV4 = "DHCP_SERVER_IPV4"
|
||||
DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS"
|
||||
DHCP_SERVER_IPV4_RANGE = "DHCP_SERVER_IPV4_RANGE"
|
||||
DHCP_SERVER_IPV4_PORT = "DHCP_SERVER_IPV4_PORT"
|
||||
VLAN = "VLAN"
|
||||
VLAN_INTERFACE = "VLAN_INTERFACE"
|
||||
VLAN_MEMBER = "VLAN_MEMBER"
|
||||
PORT_MODE_CHECKER = ["DhcpServerTableCfgChangeEventChecker", "DhcpPortTableEventChecker", "DhcpRangeTableEventChecker",
|
||||
"DhcpOptionTableEventChecker", "VlanTableEventChecker", "VlanIntfTableEventChecker",
|
||||
"VlanMemberTableEventChecker"]
|
||||
|
||||
|
||||
class MockConfigDb(object):
|
||||
@ -25,21 +35,16 @@ class MockSelect(object):
|
||||
|
||||
class MockSubscribeTable(object):
|
||||
def __init__(self, tables):
|
||||
"""
|
||||
Args:
|
||||
tables: table update event, sample: [
|
||||
("Vlan1000", "SET", (("state", "enabled"),)),
|
||||
("Vlan1000", "SET", (("customized_options", "option1"), ("state", "enabled"),))
|
||||
]
|
||||
"""
|
||||
self.stack = []
|
||||
for item in tables:
|
||||
heapq.heappush(self.stack, item)
|
||||
# if table_name == "DHCP_SERVER_IPV4":
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "SET", (("state", "enabled"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "SET", (("customized_options", "option1"), ("state", "enabled"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan2000", "SET", (("state", "enabled"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "DEL", ()))
|
||||
# heapq.heappush(self.stack, ("Vlan2000", "DEL", ()))
|
||||
# if table_name == "VLAN":
|
||||
# heapq.heappush(self.stack, ("Vlan1000", "SET", (("vlanid", "1000"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1001", "SET", (("vlanid", "1001"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1001", "DEL", (("vlanid", "1001"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan1002", "SET", (("vlanid", "1002"),)))
|
||||
# heapq.heappush(self.stack, ("Vlan2000", "SET", (("vlanid", "2000"),)))
|
||||
|
||||
def pop(self):
|
||||
res = heapq.heappop(self.stack)
|
||||
@ -85,3 +90,21 @@ class MockProc(object):
|
||||
class MockPopen(object):
|
||||
def __init__(self, pid):
|
||||
self.pid = pid
|
||||
|
||||
|
||||
def mock_exit_func(status):
|
||||
raise SystemExit(status)
|
||||
|
||||
|
||||
def get_subscribe_table_tested_data(test_name):
|
||||
test_obj = {}
|
||||
with open(TEST_DATA_PATH, "r") as file:
|
||||
test_obj = json.loads(file.read())
|
||||
tested_data = test_obj[test_name]
|
||||
for data in tested_data:
|
||||
for i in range(len(data["table"])):
|
||||
for j in range(len(data["table"][i][2])):
|
||||
data["table"][i][2][j] = tuple(data["table"][i][2][j])
|
||||
data["table"][i][2] = tuple(data["table"][i][2])
|
||||
data["table"][i] = tuple(data["table"][i])
|
||||
return tested_data
|
||||
|
@ -5,11 +5,13 @@ import sys
|
||||
from unittest.mock import patch, PropertyMock
|
||||
from dhcp_server.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||
|
||||
|
||||
test_path = os.path.dirname(os.path.abspath(__file__))
|
||||
modules_path = os.path.dirname(test_path)
|
||||
sys.path.insert(0, test_path)
|
||||
sys.path.insert(0, modules_path)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_swsscommon_dbconnector_init():
|
||||
with patch.object(utils.swsscommon.DBConnector, "__init__", return_value=None) as mock_dbconnector_init:
|
||||
@ -39,4 +41,3 @@ def mock_parse_port_map_alias(scope="function"):
|
||||
new_callable=PropertyMock), \
|
||||
patch.object(DhcpServCfgGenerator, "lease_path", return_value="/tmp/kea-lease.csv", new_callable=PropertyMock):
|
||||
yield mock_map
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
{%- set default_lease_time = 900 -%}
|
||||
{
|
||||
"Dhcp4": {
|
||||
"hooks-libraries": [
|
||||
{
|
||||
"library": "/usr/local/lib/kea/hooks/libdhcp_run_script.so",
|
||||
"parameters": {
|
||||
"name": "{{ lease_update_script_path }}",
|
||||
"sync": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"interfaces-config": {
|
||||
"interfaces": [
|
||||
"eth0"
|
||||
]
|
||||
},
|
||||
"control-socket": {
|
||||
"socket-type": "unix",
|
||||
"socket-name": "/run/kea/kea4-ctrl-socket"
|
||||
},
|
||||
"lease-database": {
|
||||
"type": "memfile",
|
||||
"persist": true,
|
||||
"name": "{{ lease_path }}",
|
||||
"lfc-interval": 3600
|
||||
},
|
||||
"subnet4": [
|
||||
{%- set add_subnet_preceding_comma = { 'flag': False } %}
|
||||
{%- for subnet_info in subnets %}
|
||||
{%- if add_subnet_preceding_comma.flag -%},{%- endif -%}
|
||||
{%- set _dummy = add_subnet_preceding_comma.update({'flag': True}) %}
|
||||
{
|
||||
"subnet": "{{ subnet_info["subnet"] }}",
|
||||
"pools": [
|
||||
{%- set add_pool_preceding_comma = { 'flag': False } %}
|
||||
{%- for pool in subnet_info["pools"] %}
|
||||
{%- if add_pool_preceding_comma.flag -%},{%- endif -%}
|
||||
{%- set _dummy = add_pool_preceding_comma.update({'flag': True}) %}
|
||||
{
|
||||
"pool": "{{ pool["range"] }}",
|
||||
"client-class": "{{ pool["client_class"] }}"
|
||||
}
|
||||
{%- endfor%}
|
||||
],
|
||||
"option-data": [
|
||||
{
|
||||
"name": "routers",
|
||||
"data": "{{ subnet_info["gateway"] if "gateway" in subnet_info else subnet_info["server_id"] }}"
|
||||
},
|
||||
{
|
||||
"name": "dhcp-server-identifier",
|
||||
"data": "{{ subnet_info["server_id"] }}"
|
||||
}
|
||||
],
|
||||
"valid-lifetime": {{ subnet_info["lease_time"] if "lease_time" in subnet_info else default_lease_time }},
|
||||
"reservations": []
|
||||
}
|
||||
{%- endfor %}
|
||||
],
|
||||
"loggers": [
|
||||
{
|
||||
"name": "kea-dhcp4",
|
||||
"output_options": [
|
||||
{
|
||||
"output": "/var/log/kea-dhcp.log",
|
||||
"pattern": "%-5p %m\n"
|
||||
}
|
||||
],
|
||||
"severity": "INFO",
|
||||
"debuglevel": 0
|
||||
}
|
||||
]{%- if client_classes -%},
|
||||
"client-classes": [
|
||||
{%- set add_preceding_comma = { 'flag': False } %}
|
||||
{%- for class in client_classes %}
|
||||
{%- if add_preceding_comma.flag -%},{%- endif -%}
|
||||
{%- set _dummy = add_preceding_comma.update({'flag': True}) %}
|
||||
{
|
||||
"name": "{{ class["name"] }}",
|
||||
"test": "{{ class["condition"] }}"
|
||||
}
|
||||
{%- endfor %}
|
||||
]
|
||||
{%- endif %}
|
||||
}
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
{
|
||||
"test_table_clear": [
|
||||
{
|
||||
"table": [
|
||||
["range1", "SET", [["ranges", "192.168.0.1,192.168.0.5"]]]
|
||||
]
|
||||
}
|
||||
],
|
||||
"test_range_update": [
|
||||
{
|
||||
"table": [
|
||||
["range1", "SET", [["ranges", "192.168.0.1,192.168.0.5"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["range2", "SET", [["ranges", "192.168.0.1,192.168.0.3"]]]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["range1", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["range2", "DEL", []]
|
||||
],
|
||||
"exp_res": false
|
||||
}
|
||||
],
|
||||
"test_port_update": [
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000|Ethernet15", "SET", [["ips", "192.168.0.1,192.168.0.2"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000|Ethernet15", "SET", [["ips", "192.168.0.1,192.168.0.2"]]]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000|Ethernet15", "DEL", []]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000|Ethernet15", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
}
|
||||
],
|
||||
"test_option_update": [
|
||||
{
|
||||
"table": [
|
||||
["option223", "SET", [["type", "string"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["option222", "SET", [["type", "string"]]]
|
||||
],
|
||||
"exp_res": false
|
||||
}
|
||||
],
|
||||
"test_vlan_intf_update": [
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "SET", []]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000|192.168.0.1/24", "SET", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000|fc02::8/64", "SET", []]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000|192.168.0.1/24", "SET", []]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000|192.168.0.1/24", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "DEL", []]
|
||||
],
|
||||
"exp_res": false
|
||||
}
|
||||
],
|
||||
"test_vlan_update": [
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "SET", [["vlanid", "1000"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1001", "SET", [["vlanid", "1001"]]]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "SET", [["vlanid", "1000"]]],
|
||||
["Vlan1002", "SET", [["vlanid", "1002"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1001", "DEL", []]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "SET", [["vlanid", "1000"]]],
|
||||
["Vlan1001", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1003", "SET", [["vlanid", "1003"]]]
|
||||
],
|
||||
"exp_res": false
|
||||
}
|
||||
],
|
||||
"test_dhcp_server_update": [
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "SET", [["customized_options", "option1"], ["state", "enabled"]]]
|
||||
],
|
||||
"exp_res": {"enablement": false, "cfg_change": true}
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000", "SET", [["customized_options", "option1"], ["state", "enabled"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000", "SET", [["state", "enabled"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "SET", [["state", "disabled"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan3000", "SET", [["state", "disabled"]]]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000", "DEL", []]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000", "DEL", []],
|
||||
["Vlan1000", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan3000", "SET", [["state", "enabled"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
}
|
||||
],
|
||||
"test_vlan_member_update": [
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000|Ethernet0", "SET", [["tagging_mode", "untagged"]]]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan1000|Ethernet0", "DEL", []]
|
||||
],
|
||||
"exp_res": true
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000|Ethernet0", "SET", [["tagging_mode", "untagged"]]]
|
||||
],
|
||||
"exp_res": false
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
["Vlan2000|Ethernet0", "DEL", []]
|
||||
],
|
||||
"exp_res": false
|
||||
}
|
||||
]
|
||||
}
|
@ -1,6 +1,17 @@
|
||||
{%- set default_lease_time = 900 -%}
|
||||
{
|
||||
"Dhcp4": {
|
||||
{%- if customized_options %}
|
||||
"option-def": [
|
||||
{%- for option_name, config in customized_options.items() %}
|
||||
{
|
||||
"name": "{{ option_name }}",
|
||||
"code": {{ config["id"] }},
|
||||
"type": "{{ config["type"] }}"
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{%- endfor %}
|
||||
],
|
||||
{%- endif %}
|
||||
"hooks-libraries": [
|
||||
{
|
||||
"library": "/usr/local/lib/kea/hooks/libdhcp_run_script.so",
|
||||
@ -44,6 +55,13 @@
|
||||
{%- endfor%}
|
||||
],
|
||||
"option-data": [
|
||||
{%- for option_name, config in subnet_info["customized_options"].items() %}
|
||||
{
|
||||
"name": "{{ option_name }}",
|
||||
"data": "{{ config["value"] }}",
|
||||
"always-send": {{ config["always_send"] }}
|
||||
},
|
||||
{%- endfor %}
|
||||
{
|
||||
"name": "routers",
|
||||
"data": "{{ subnet_info["gateway"] if "gateway" in subnet_info else subnet_info["server_id"] }}"
|
||||
|
@ -94,18 +94,6 @@
|
||||
"state": "disabled"
|
||||
}
|
||||
},
|
||||
"DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS": {
|
||||
"option223": {
|
||||
"id": "223",
|
||||
"type": "text",
|
||||
"value": "dummy_value"
|
||||
},
|
||||
"option60": {
|
||||
"id": "60",
|
||||
"type": "text",
|
||||
"value": "dummy_value"
|
||||
}
|
||||
},
|
||||
"DHCP_SERVER_IPV4_RANGE": {
|
||||
"range3": {
|
||||
"range": [
|
||||
|
@ -2,12 +2,20 @@ import copy
|
||||
import ipaddress
|
||||
import json
|
||||
import pytest
|
||||
from common_utils import MockConfigDb
|
||||
from common_utils import MockConfigDb, mock_get_config_db_table, PORT_MODE_CHECKER
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||
from unittest.mock import patch
|
||||
|
||||
expected_dhcp_config = {
|
||||
"Dhcp4": {
|
||||
"option-def": [
|
||||
{
|
||||
"name": "option223",
|
||||
"code": 223,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"hooks-libraries": [
|
||||
{
|
||||
"library": "/usr/local/lib/kea/hooks/libdhcp_run_script.so",
|
||||
@ -171,27 +179,98 @@ tested_parsed_port = {
|
||||
}
|
||||
}
|
||||
expected_render_obj = {
|
||||
"subnets": [{
|
||||
"subnets": [
|
||||
{
|
||||
"subnet": "192.168.0.0/21",
|
||||
"pools": [{"range": "192.168.0.2 - 192.168.0.6", "client_class": "sonic-host:etp8"},
|
||||
{"range": "192.168.0.10 - 192.168.0.10", "client_class": "sonic-host:etp8"},
|
||||
{"range": "192.168.0.7 - 192.168.0.7", "client_class": "sonic-host:etp7"}],
|
||||
"gateway": "192.168.0.1", "server_id": "192.168.0.1", "lease_time": "900"
|
||||
}],
|
||||
"gateway": "192.168.0.1", "server_id": "192.168.0.1", "lease_time": "900",
|
||||
"customized_options": {
|
||||
"option223": {
|
||||
"always_send": "true",
|
||||
"value": "dummy_value"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"client_classes": [
|
||||
{"name": "sonic-host:etp8", "condition": "substring(relay4[1].hex, -15, 15) == 'sonic-host:etp8'"},
|
||||
{"name": "sonic-host:etp7", "condition": "substring(relay4[1].hex, -15, 15) == 'sonic-host:etp7'"}
|
||||
],
|
||||
"lease_update_script_path": "/etc/kea/lease_update.sh",
|
||||
"lease_path": "/tmp/kea-lease.csv"
|
||||
"lease_path": "/tmp/kea-lease.csv",
|
||||
"customized_options": {
|
||||
"option223": {
|
||||
"id": "223",
|
||||
"value": "dummy_value",
|
||||
"type": "string",
|
||||
"always_send": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
tested_options_data = [
|
||||
{
|
||||
"data": {
|
||||
"option223": {
|
||||
"id": "223",
|
||||
"type": "string",
|
||||
"value": "dummy_value"
|
||||
}
|
||||
},
|
||||
"res": True
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"option60": {
|
||||
"id": "60",
|
||||
"type": "string",
|
||||
"value": "dummy_value"
|
||||
}
|
||||
},
|
||||
"res": False
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"option222": {
|
||||
"id": "222",
|
||||
"type": "text",
|
||||
"value": "dummy_value"
|
||||
}
|
||||
},
|
||||
"res": False
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"option219": {
|
||||
"id": "219",
|
||||
"type": "uint8",
|
||||
"value": "259"
|
||||
}
|
||||
},
|
||||
"res": False
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"option223": {
|
||||
"id": "223",
|
||||
"type": "string",
|
||||
"value": "long_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_value" +
|
||||
"long_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_value" +
|
||||
"long_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_valuelong_value" +
|
||||
"long_valuelong_valuelong_valuelong_valuelong_value"
|
||||
}
|
||||
},
|
||||
"res": False
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_parse_port_alias(mock_swsscommon_dbconnector_init, mock_get_render_template):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector,
|
||||
port_map_path="tests/test_data/port-name-alias-map.txt")
|
||||
assert dhcp_cfg_generator.port_alias_map == {'Ethernet24': 'etp7', 'Ethernet28': 'etp8'}
|
||||
assert dhcp_cfg_generator.port_alias_map == {"Ethernet24": "etp7", "Ethernet28": "etp8"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_success", [True, False])
|
||||
@ -236,29 +315,90 @@ def test_parse_port(test_config_db, mock_swsscommon_dbconnector_init, mock_get_r
|
||||
tested_ranges = expected_parsed_range
|
||||
ipv4_port = mock_config_db.config_db.get("DHCP_SERVER_IPV4_PORT")
|
||||
vlan_members = mock_config_db.config_db.get("VLAN_MEMBER").keys()
|
||||
parse_result = dhcp_cfg_generator._parse_port(ipv4_port, tested_vlan_interfaces, vlan_members, tested_ranges)
|
||||
assert parse_result == (expected_parsed_port if test_config_db == "mock_config_db.json" else {})
|
||||
parsed_port, used_ranges = dhcp_cfg_generator._parse_port(ipv4_port, tested_vlan_interfaces, vlan_members,
|
||||
tested_ranges)
|
||||
assert parsed_port == (expected_parsed_port if test_config_db == "mock_config_db.json" else {})
|
||||
assert used_ranges == ({"range2", "range1", "range0", "range3"}
|
||||
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):
|
||||
with patch.object(DhcpServCfgGenerator, "_parse_hostname"), \
|
||||
patch.object(DhcpServCfgGenerator, "_parse_vlan", return_value=(None, None)), \
|
||||
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, "_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):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
kea_dhcp4_config, used_ranges, enabled_dhcp_interfaces, used_options, subscribe_table = \
|
||||
dhcp_cfg_generator.generate()
|
||||
assert kea_dhcp4_config == "dummy_config"
|
||||
assert used_ranges == set(["range1"])
|
||||
assert enabled_dhcp_interfaces == set(["Vlan1000"])
|
||||
assert used_options == set(["option1"])
|
||||
assert subscribe_table == set(["dummy"])
|
||||
|
||||
|
||||
def test_construct_obj_for_template(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias,
|
||||
mock_get_render_template):
|
||||
mock_config_db = MockConfigDb(config_db_path="tests/test_data/mock_config_db.json")
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
customized_options = {"option223": {"id": "223", "value": "dummy_value", "type": "string", "always_send": "true"}}
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
tested_hostname = "sonic-host"
|
||||
render_obj = dhcp_cfg_generator._construct_obj_for_template(mock_config_db.config_db.get("DHCP_SERVER_IPV4"),
|
||||
tested_parsed_port, tested_hostname)
|
||||
render_obj, enabled_dhcp_interfaces, used_options, subscribe_table = \
|
||||
dhcp_cfg_generator._construct_obj_for_template(mock_config_db.config_db.get("DHCP_SERVER_IPV4"),
|
||||
tested_parsed_port, tested_hostname, customized_options)
|
||||
assert render_obj == expected_render_obj
|
||||
assert enabled_dhcp_interfaces == {"Vlan1000", "Vlan4000", "Vlan3000"}
|
||||
assert used_options == set(["option223"])
|
||||
assert subscribe_table == set(PORT_MODE_CHECKER)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("with_port_config", [True, False])
|
||||
def test_render_config(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, with_port_config):
|
||||
@pytest.mark.parametrize("with_option_config", [True, False])
|
||||
def test_render_config(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, with_port_config,
|
||||
with_option_config):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector,
|
||||
kea_conf_template_path="tests/test_data/kea-dhcp4.conf.j2")
|
||||
render_obj = copy.deepcopy(expected_render_obj)
|
||||
expected_config = copy.deepcopy(expected_dhcp_config)
|
||||
if not with_port_config:
|
||||
render_obj["client_classes"] = []
|
||||
render_obj["subnets"] = []
|
||||
elif not with_option_config:
|
||||
render_obj["subnets"][0]["customized_options"] = {}
|
||||
config = dhcp_cfg_generator._render_config(render_obj)
|
||||
assert json.loads(config) == expected_dhcp_config if with_port_config else expected_dhcp_config_without_port_config
|
||||
if with_option_config:
|
||||
expected_config["Dhcp4"]["subnet4"][0]["option-data"].insert(0, {
|
||||
"name": "option223",
|
||||
"data": "dummy_value",
|
||||
"always-send": True
|
||||
})
|
||||
assert json.loads(config) == expected_config if with_port_config else expected_config
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_options_data", tested_options_data)
|
||||
def test_parse_customized_options(mock_swsscommon_dbconnector_init, mock_get_render_template,
|
||||
mock_parse_port_map_alias, tested_options_data):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
customized_options_ipv4 = tested_options_data["data"]
|
||||
customized_options = dhcp_cfg_generator._parse_customized_options(customized_options_ipv4)
|
||||
if tested_options_data["res"]:
|
||||
assert customized_options == {
|
||||
"option223": {
|
||||
"id": "223",
|
||||
"value": "dummy_value",
|
||||
"type": "string",
|
||||
"always_send": "true"
|
||||
}
|
||||
}
|
||||
else:
|
||||
assert customized_options == {}
|
||||
|
@ -1,219 +1,351 @@
|
||||
import pytest
|
||||
from common_utils import MockSubscribeTable
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpDbMonitor, DhcpRelaydDbMonitor
|
||||
import sys
|
||||
from common_utils import MockSubscribeTable, get_subscribe_table_tested_data, \
|
||||
PORT_MODE_CHECKER, mock_exit_func
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor, DhcpServdDbMonitor, ConfigDbEventChecker, \
|
||||
DhcpServerTableIntfEnablementEventChecker, DhcpServerTableCfgChangeEventChecker, \
|
||||
DhcpPortTableEventChecker, DhcpRangeTableEventChecker, DhcpOptionTableEventChecker, \
|
||||
VlanTableEventChecker, VlanMemberTableEventChecker, VlanIntfTableEventChecker
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call, ANY, PropertyMock
|
||||
|
||||
tested_subscribe_dhcp_server_table = [
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("customized_options", "option1"), ("state", "enabled"),))
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000", "SET", (("state", "enabled"),))
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000", "DEL", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000", "DEL", ()),
|
||||
("Vlan1000", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan3000", "SET", (("state", "enabled"),))
|
||||
],
|
||||
"exp_res": True
|
||||
}
|
||||
]
|
||||
tested_subscribe_vlan_table = [
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("vlanid", "1000"),))
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1001", "SET", (("vlanid", "1001"),))
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("vlanid", "1000"),)),
|
||||
("Vlan1002", "SET", (("vlanid", "1002"),))
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1001", "DEL", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", (("vlanid", "1000"),)),
|
||||
("Vlan1001", "DEL", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1003", "SET", (("vlanid", "1003"),))
|
||||
],
|
||||
"exp_res": False
|
||||
}
|
||||
]
|
||||
tested_subscribe_vlan_intf_table = [
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000|192.168.0.1/24", "SET", ())
|
||||
],
|
||||
"exp_res": True
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1000|fc02::8/64", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan2000|192.168.0.1/24", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
},
|
||||
{
|
||||
"table": [
|
||||
("Vlan1001|192.168.0.1/24", "SET", ())
|
||||
],
|
||||
"exp_res": False
|
||||
}
|
||||
]
|
||||
from unittest.mock import patch, ANY, PropertyMock, MagicMock
|
||||
|
||||
|
||||
@pytest.mark.parametrize("select_result", [swsscommon.Select.TIMEOUT, swsscommon.Select.OBJECT])
|
||||
def test_dhcp_db_monitor(mock_swsscommon_dbconnector_init, select_result):
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_db_monitor = DhcpDbMonitor(db_connector)
|
||||
try:
|
||||
dhcp_db_monitor.subscribe_table()
|
||||
except NotImplementedError:
|
||||
pass
|
||||
try:
|
||||
dhcp_db_monitor._do_check()
|
||||
except NotImplementedError:
|
||||
pass
|
||||
with patch.object(DhcpDbMonitor, "_do_check", return_value=None) as mock_do_check, \
|
||||
patch.object(swsscommon.Select, "select", return_value=(select_result, None)):
|
||||
dhcp_db_monitor.check_db_update("mock_param")
|
||||
if select_result == swsscommon.Select.TIMEOUT:
|
||||
mock_do_check.assert_not_called()
|
||||
elif select_result == swsscommon.Select.OBJECT:
|
||||
mock_do_check.assert_called_once_with("mock_param")
|
||||
|
||||
|
||||
def test_dhcp_relayd_monitor_subscribe_table(mock_swsscommon_dbconnector_init):
|
||||
with patch.object(swsscommon, "SubscriberStateTable", side_effect=mock_subscriber_state_table) as mock_subscribe, \
|
||||
patch.object(swsscommon.Select, "addSelectable", return_value=None) as mock_add_select:
|
||||
def test_dhcp_relayd_monitor_check_db_update(mock_swsscommon_dbconnector_init, select_result):
|
||||
with patch.object(DhcpServerTableIntfEnablementEventChecker, "check_update_event") \
|
||||
as mock_check_update_event, \
|
||||
patch.object(VlanTableEventChecker, "check_update_event") as mock_check_vlan_update, \
|
||||
patch.object(VlanIntfTableEventChecker, "check_update_event") as mock_check_vlan_intf_update, \
|
||||
patch.object(swsscommon.Select, "select", return_value=(select_result, None)), \
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
dhcp_relayd_db_monitor.subscribe_table()
|
||||
mock_subscribe.assert_has_calls([
|
||||
call(ANY, "DHCP_SERVER_IPV4"),
|
||||
call(ANY, "VLAN"),
|
||||
call(ANY, "VLAN_INTERFACE")
|
||||
])
|
||||
mock_add_select.assert_has_calls([
|
||||
call("DHCP_SERVER_IPV4"),
|
||||
call("VLAN"),
|
||||
call("VLAN_INTERFACE")
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("check_param", [{}, {"enabled_dhcp_interfaces": "dummy"}])
|
||||
def test_dhcp_relayd_monitor_do_check(mock_swsscommon_dbconnector_init, check_param):
|
||||
with patch.object(DhcpRelaydDbMonitor, "_check_dhcp_server_update") as mock_check_dhcp_server_update, \
|
||||
patch.object(DhcpRelaydDbMonitor, "_check_vlan_update") as mock_check_vlan_update, \
|
||||
patch.object(DhcpRelaydDbMonitor, "_check_vlan_intf_update") as mock_check_vlan_intf_update:
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
dhcp_relayd_db_monitor._do_check(check_param)
|
||||
if "enabled_dhcp_interfaces" in check_param:
|
||||
mock_check_dhcp_server_update.assert_called_once_with("dummy")
|
||||
mock_check_vlan_update.assert_called_once_with("dummy")
|
||||
mock_check_vlan_intf_update.assert_called_once_with("dummy")
|
||||
checkers = [VlanTableEventChecker(None, None), VlanIntfTableEventChecker(None, None),
|
||||
DhcpServerTableIntfEnablementEventChecker(None, None)]
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector, swsscommon.Select(), checkers)
|
||||
tested_db_snapshot = {"enabled_dhcp_interfaces": "dummy"}
|
||||
dhcp_relayd_db_monitor.check_db_update(tested_db_snapshot)
|
||||
if select_result == swsscommon.Select.OBJECT:
|
||||
mock_check_update_event.assert_called_once_with(tested_db_snapshot)
|
||||
mock_check_vlan_update.assert_called_once_with(tested_db_snapshot)
|
||||
mock_check_vlan_intf_update.assert_called_once_with(tested_db_snapshot)
|
||||
else:
|
||||
mock_check_dhcp_server_update.assert_not_called()
|
||||
mock_check_update_event.assert_not_called()
|
||||
mock_check_vlan_update.assert_not_called()
|
||||
mock_check_vlan_intf_update.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dhcp_server_table_update", tested_subscribe_dhcp_server_table)
|
||||
def test_dhcp_relayd_monitor_check_dhcp_server_update(mock_swsscommon_dbconnector_init, dhcp_server_table_update):
|
||||
tested_table = dhcp_server_table_update["table"]
|
||||
with patch.object(DhcpRelaydDbMonitor, "subscribe_dhcp_server_table",
|
||||
return_value=MockSubscribeTable(tested_table),
|
||||
new_callable=PropertyMock):
|
||||
@pytest.mark.parametrize("tables", [["VlanTableEventChecker"], ["dummy"]])
|
||||
def test_dhcp_relayd_enable_checker(tables, mock_swsscommon_dbconnector_init):
|
||||
with patch.object(ConfigDbEventChecker, "enable") as mock_enable:
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
check_res = dhcp_relayd_db_monitor._check_dhcp_server_update(set(["Vlan1000"]))
|
||||
assert check_res == dhcp_server_table_update["exp_res"]
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector, None, [VlanTableEventChecker(None, None)])
|
||||
dhcp_relayd_db_monitor.enable_checker(set(tables))
|
||||
if "VlanTableEventChecker" in tables:
|
||||
mock_enable.assert_called_once()
|
||||
else:
|
||||
mock_enable.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("vlan_table_update", tested_subscribe_vlan_table)
|
||||
def test_dhcp_relayd_monitor_check_vlan_update(mock_swsscommon_dbconnector_init, vlan_table_update):
|
||||
tested_table = vlan_table_update["table"]
|
||||
with patch.object(DhcpRelaydDbMonitor, "subscribe_vlan_table", return_value=MockSubscribeTable(tested_table),
|
||||
new_callable=PropertyMock):
|
||||
@pytest.mark.parametrize("select_result", [swsscommon.Select.TIMEOUT, swsscommon.Select.OBJECT])
|
||||
@pytest.mark.parametrize("is_checker_enabled", [True, False])
|
||||
def test_dhcp_servd_monitor_check_db_update(mock_swsscommon_dbconnector_init, select_result,
|
||||
is_checker_enabled):
|
||||
with patch.object(DhcpServerTableCfgChangeEventChecker, "check_update_event") \
|
||||
as mock_check_dhcp_server_update_event, \
|
||||
patch.object(DhcpPortTableEventChecker, "check_update_event") as mock_check_dhcp_server_port_update, \
|
||||
patch.object(DhcpRangeTableEventChecker, "check_update_event") as mock_check_dhcp_server_range_update, \
|
||||
patch.object(DhcpOptionTableEventChecker, "check_update_event") as mock_check_dhcp_server_option_update, \
|
||||
patch.object(VlanMemberTableEventChecker, "check_update_event") as mock_check_vlan_member_update, \
|
||||
patch.object(VlanTableEventChecker, "check_update_event") as mock_check_vlan_update, \
|
||||
patch.object(VlanIntfTableEventChecker, "check_update_event") as mock_check_vlan_intf_update, \
|
||||
patch.object(ConfigDbEventChecker, "is_enabled", return_value=is_checker_enabled), \
|
||||
patch.object(swsscommon.Select, "select", return_value=(select_result, None)), \
|
||||
patch.object(ConfigDbEventChecker, "clear_event") as mock_clear:
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
check_res = dhcp_relayd_db_monitor._check_vlan_update(set(["Vlan1000"]))
|
||||
assert check_res == vlan_table_update["exp_res"]
|
||||
dhcp_checker = DhcpServerTableCfgChangeEventChecker(None, None)
|
||||
dhcp_checker.enabled = True
|
||||
vlan_checker = VlanIntfTableEventChecker(None, None)
|
||||
db_monitor = DhcpServdDbMonitor(db_connector, swsscommon.Select(), [dhcp_checker, vlan_checker])
|
||||
tested_db_snapshot = {"enabled_dhcp_interfaces": "dummy1", "used_range": "dummy2",
|
||||
"used_options": "dummy3"}
|
||||
db_monitor.check_db_update(tested_db_snapshot)
|
||||
if select_result == swsscommon.Select.OBJECT and is_checker_enabled:
|
||||
mock_check_dhcp_server_update_event.assert_called_once_with(tested_db_snapshot)
|
||||
mock_check_vlan_update.assert_not_called()
|
||||
mock_clear.assert_called_once_with()
|
||||
else:
|
||||
mock_check_dhcp_server_update_event.assert_not_called()
|
||||
mock_check_dhcp_server_port_update.assert_not_called()
|
||||
mock_check_dhcp_server_range_update.assert_not_called()
|
||||
mock_check_vlan_member_update.assert_not_called()
|
||||
mock_check_dhcp_server_option_update.assert_not_called()
|
||||
mock_check_vlan_update.assert_not_called()
|
||||
mock_check_vlan_intf_update.assert_not_called()
|
||||
mock_clear.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("vlan_intf_table_update", tested_subscribe_vlan_intf_table)
|
||||
def test_dhcp_relayd_monitor_check_vlan_intf_update(mock_swsscommon_dbconnector_init, vlan_intf_table_update):
|
||||
tested_table = vlan_intf_table_update["table"]
|
||||
with patch.object(DhcpRelaydDbMonitor, "subscribe_vlan_intf_table", return_value=MockSubscribeTable(tested_table),
|
||||
new_callable=PropertyMock):
|
||||
@pytest.mark.parametrize("tables", [set(["VlanIntfTableEventChecker"]), set(["dummy1"])])
|
||||
def test_dhcp_servd_monitor_enable_checkers(mock_swsscommon_dbconnector_init, tables):
|
||||
with patch.object(ConfigDbEventChecker, "enable") as mock_enable:
|
||||
db_connector = DhcpDbConnector()
|
||||
dhcp_relayd_db_monitor = DhcpRelaydDbMonitor(db_connector)
|
||||
check_res = dhcp_relayd_db_monitor._check_vlan_intf_update(set(["Vlan1000"]))
|
||||
assert check_res == vlan_intf_table_update["exp_res"]
|
||||
checker = VlanIntfTableEventChecker(None, None)
|
||||
db_monitor = DhcpServdDbMonitor(db_connector, None, [checker])
|
||||
db_monitor.enable_checkers(tables)
|
||||
if tables == set(["VlanIntfTableEventChecker"]):
|
||||
mock_enable.assert_called_once()
|
||||
else:
|
||||
mock_enable.assert_not_called()
|
||||
|
||||
|
||||
def mock_subscriber_state_table(db, table_name):
|
||||
return table_name
|
||||
@pytest.mark.parametrize("tables", [set(PORT_MODE_CHECKER), set(["dummy"])])
|
||||
def test_dhcp_servd_monitor_disble_checkers(mock_swsscommon_dbconnector_init, tables):
|
||||
with patch.object(ConfigDbEventChecker, "disable") as mock_disable:
|
||||
db_connector = DhcpDbConnector()
|
||||
checker = DhcpPortTableEventChecker(None, None)
|
||||
db_monitor = DhcpServdDbMonitor(db_connector, None, [checker])
|
||||
db_monitor.disable_checkers(tables)
|
||||
if tables == set(PORT_MODE_CHECKER):
|
||||
mock_disable.assert_called_once_with()
|
||||
else:
|
||||
mock_disable.assert_not_called()
|
||||
|
||||
|
||||
def test_db_event_checker_init(mock_swsscommon_dbconnector_init):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = ConfigDbEventChecker(sel, MagicMock())
|
||||
try:
|
||||
db_event_checker._get_parameter(None)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
else:
|
||||
pytest.fail("Run _get_parameter didn't get error")
|
||||
try:
|
||||
db_event_checker._process_check(None, None, None, None)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
else:
|
||||
pytest.fail("Run _process_check didn't get error")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_table_clear"))
|
||||
def test_db_event_checker_clear_event(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):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = ConfigDbEventChecker(sel, MagicMock())
|
||||
db_event_checker.enabled = True
|
||||
assert db_event_checker.subscriber_state_table.hasData()
|
||||
db_event_checker.clear_event()
|
||||
assert not db_event_checker.subscriber_state_table.hasData()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_enabled", [True, False])
|
||||
def test_db_event_checker_is_enabled(is_enabled):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = ConfigDbEventChecker(sel, MagicMock())
|
||||
db_event_checker.enabled = is_enabled
|
||||
assert db_event_checker.is_enabled() == is_enabled
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param_name", ["param1", "param2"])
|
||||
def test_db_event_checker_check_db_snapshot(mock_swsscommon_dbconnector_init, param_name):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = ConfigDbEventChecker(sel, MagicMock())
|
||||
tested_db_snapshot = {"param1": "value1"}
|
||||
check_res = db_event_checker._check_db_snapshot(tested_db_snapshot, param_name)
|
||||
assert check_res == (param_name in tested_db_snapshot)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_db_event_checker_disable(mock_swsscommon_dbconnector_init, enabled):
|
||||
with patch.object(swsscommon.Select, "removeSelectable") as mock_remove, \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit", side_effect=mock_exit_func) as mock_exit:
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = ConfigDbEventChecker(sel, MagicMock())
|
||||
try:
|
||||
db_event_checker.disable()
|
||||
except SystemExit:
|
||||
mock_remove.assert_not_called()
|
||||
mock_exit.assert_called_once_with(1)
|
||||
else:
|
||||
mock_remove.assert_called_once_with(None)
|
||||
mock_exit.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_db_event_checker_subscribe_table(mock_swsscommon_dbconnector_init, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit", side_effect=mock_exit_func) as mock_exit, \
|
||||
patch.object(swsscommon, "SubscriberStateTable") as mock_sub, \
|
||||
patch.object(swsscommon.Select, "addSelectable") as mock_add_sel_tbl:
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = ConfigDbEventChecker(sel, MagicMock())
|
||||
try:
|
||||
db_event_checker.enable()
|
||||
except SystemExit:
|
||||
mock_exit.assert_called_once_with(1)
|
||||
mock_add_sel_tbl.assert_not_called()
|
||||
mock_sub.assert_not_called()
|
||||
else:
|
||||
mock_exit.assert_not_called()
|
||||
mock_add_sel_tbl.assert_called_once_with(ANY)
|
||||
mock_sub.assert_called_once_with(ANY, "")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": "Vlan1000"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_dhcp_server_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_dhcp_server_table_cfg_change_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot,
|
||||
enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = DhcpServerTableCfgChangeEventChecker(sel, MagicMock())
|
||||
expected_res = tested_data["exp_res"] if isinstance(tested_data["exp_res"], bool) else \
|
||||
tested_data["exp_res"]["cfg_change"]
|
||||
check_res = db_event_checker.check_update_event(tested_db_snapshot)
|
||||
if "enabled_dhcp_interfaces" not in tested_db_snapshot:
|
||||
assert check_res
|
||||
else:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": "Vlan1000"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_dhcp_server_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_dhcp_server_table_enablement_change_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot,
|
||||
enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = DhcpServerTableIntfEnablementEventChecker(sel, MagicMock())
|
||||
expected_res = tested_data["exp_res"] if isinstance(tested_data["exp_res"], bool) else \
|
||||
tested_data["exp_res"]["enablement"]
|
||||
check_res = db_event_checker.check_update_event(tested_db_snapshot)
|
||||
if "enabled_dhcp_interfaces" not in tested_db_snapshot:
|
||||
assert check_res
|
||||
else:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": "Vlan1000"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_port_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_dhcp_port_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = DhcpPortTableEventChecker(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:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"used_range": "range1"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_range_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_dhcp_range_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = DhcpRangeTableEventChecker(sel, MagicMock())
|
||||
expected_res = tested_data["exp_res"]
|
||||
check_res = db_event_checker.check_update_event(tested_db_snapshot)
|
||||
if "used_range" not in tested_db_snapshot:
|
||||
assert check_res
|
||||
else:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"used_options": "option223"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_option_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_dhcp_option_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = DhcpOptionTableEventChecker(sel, MagicMock())
|
||||
expected_res = tested_data["exp_res"]
|
||||
check_res = db_event_checker.check_update_event(tested_db_snapshot)
|
||||
if "used_options" not in tested_db_snapshot:
|
||||
assert check_res
|
||||
else:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": "Vlan1000"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_vlan_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_vlan_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = VlanTableEventChecker(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:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": "Vlan1000"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_vlan_intf_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_vlan_intf_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = VlanIntfTableEventChecker(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:
|
||||
assert expected_res == check_res
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tested_db_snapshot", [{"enabled_dhcp_interfaces": "Vlan1000"}, {}])
|
||||
@pytest.mark.parametrize("tested_data", get_subscribe_table_tested_data("test_vlan_member_update"))
|
||||
@pytest.mark.parametrize("enabled", [True, False])
|
||||
def test_vlan_member_table_checker(mock_swsscommon_dbconnector_init, tested_data, tested_db_snapshot, enabled):
|
||||
with patch.object(ConfigDbEventChecker, "enable"), \
|
||||
patch.object(ConfigDbEventChecker, "subscriber_state_table",
|
||||
return_value=MockSubscribeTable(tested_data["table"]), new_callable=PropertyMock), \
|
||||
patch.object(ConfigDbEventChecker, "enabled", return_value=enabled, new_callable=PropertyMock), \
|
||||
patch.object(sys, "exit"):
|
||||
sel = swsscommon.Select()
|
||||
db_event_checker = VlanMemberTableEventChecker(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:
|
||||
assert expected_res == check_res
|
||||
|
@ -5,45 +5,46 @@ import sys
|
||||
import time
|
||||
from common_utils import mock_get_config_db_table, MockProc, MockPopen
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.common.dhcp_db_monitor import ConfigDbEventChecker
|
||||
from dhcp_server.dhcprelayd.dhcprelayd import DhcpRelayd, KILLED_OLD, NOT_KILLED, NOT_FOUND_PROC
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpRelaydDbMonitor
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call
|
||||
|
||||
|
||||
def test_start(mock_swsscommon_dbconnector_init):
|
||||
with patch.object(DhcpRelayd, "refresh_dhcrelay", return_value=None) as mock_refresh, \
|
||||
patch.object(DhcpRelaydDbMonitor, "subscribe_table", return_value=None) as mock_subscribe:
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
dhcprelayd.start()
|
||||
mock_refresh.assert_called_once_with()
|
||||
mock_subscribe.assert_called_once_with()
|
||||
|
||||
|
||||
def test_refresh_dhcrelay(mock_swsscommon_dbconnector_init):
|
||||
with patch.object(DhcpRelayd, "_get_dhcp_server_ip", return_value="240.127.1.2"), \
|
||||
patch.object(DhcpDbConnector, "get_config_db_table", side_effect=mock_get_config_db_table), \
|
||||
patch.object(DhcpRelayd, "_start_dhcrelay_process", return_value=None), \
|
||||
patch.object(DhcpRelayd, "_start_dhcpmon_process", return_value=None):
|
||||
patch.object(DhcpRelayd, "_start_dhcpmon_process", return_value=None), \
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
dhcprelayd.refresh_dhcrelay()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_dhcp_interfaces", [[], ["Vlan1000"], ["Vlan1000", "Vlan2000"]])
|
||||
@pytest.mark.parametrize("kill_res", [KILLED_OLD, NOT_KILLED, NOT_FOUND_PROC])
|
||||
@pytest.mark.parametrize("proc_status", [psutil.STATUS_ZOMBIE, psutil.STATUS_RUNNING])
|
||||
def test_start_dhcrelay_process(mock_swsscommon_dbconnector_init, new_dhcp_interfaces, kill_res, proc_status):
|
||||
def test_start_dhcrelay_process(mock_swsscommon_dbconnector_init, new_dhcp_interfaces, kill_res, proc_status,):
|
||||
with patch.object(DhcpRelayd, "_kill_exist_relay_releated_process", return_value=kill_res), \
|
||||
patch.object(subprocess, "Popen", return_value=MockPopen(999)) as mock_popen, \
|
||||
patch.object(time, "sleep"), \
|
||||
patch("dhcp_server.dhcprelayd.dhcprelayd.terminate_proc", return_value=None) as mock_terminate, \
|
||||
patch.object(psutil.Process, "__init__", return_value=None), \
|
||||
patch.object(psutil.Process, "status", return_value=proc_status), \
|
||||
patch.object(sys, "exit") as mock_exit:
|
||||
patch.object(sys, "exit") as mock_exit, \
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
dhcprelayd._start_dhcrelay_process(new_dhcp_interfaces, "240.127.1.2", False)
|
||||
if len(new_dhcp_interfaces) == 0 or kill_res == NOT_KILLED:
|
||||
mock_popen.assert_not_called()
|
||||
@ -72,9 +73,10 @@ def test_start_dhcpmon_process(mock_swsscommon_dbconnector_init, new_dhcp_interf
|
||||
patch.object(time, "sleep"), \
|
||||
patch("dhcp_server.dhcprelayd.dhcprelayd.terminate_proc", return_value=None) as mock_terminate, \
|
||||
patch.object(psutil.Process, "__init__", return_value=None), \
|
||||
patch.object(psutil.Process, "status", return_value=proc_status):
|
||||
patch.object(psutil.Process, "status", return_value=proc_status), \
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
dhcprelayd._start_dhcpmon_process(new_dhcp_interfaces, False)
|
||||
if len(new_dhcp_interfaces) == 0 or kill_res == NOT_KILLED:
|
||||
mock_popen.assert_not_called()
|
||||
@ -100,9 +102,10 @@ def test_kill_exist_relay_releated_process(mock_swsscommon_dbconnector_init, new
|
||||
process_iter_ret = []
|
||||
for running_proc in running_procs:
|
||||
process_iter_ret.append(MockProc(running_proc))
|
||||
with patch.object(psutil, "process_iter", return_value=process_iter_ret):
|
||||
with patch.object(psutil, "process_iter", return_value=process_iter_ret), \
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
res = dhcprelayd._kill_exist_relay_releated_process(new_dhcp_interfaces, process_name, force_kill)
|
||||
if force_kill and process_name in running_procs:
|
||||
assert res == KILLED_OLD
|
||||
@ -118,9 +121,10 @@ def test_kill_exist_relay_releated_process(mock_swsscommon_dbconnector_init, new
|
||||
def test_get_dhcp_server_ip(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init, get_res):
|
||||
with patch.object(swsscommon.Table, "hget", return_value=get_res), \
|
||||
patch.object(time, "sleep") as mock_sleep, \
|
||||
patch.object(sys, "exit") as mock_exit:
|
||||
patch.object(sys, "exit") as mock_exit, \
|
||||
patch.object(ConfigDbEventChecker, "enable"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector)
|
||||
dhcprelayd = DhcpRelayd(dhcp_db_connector, None)
|
||||
ret = dhcprelayd._get_dhcp_server_ip()
|
||||
if get_res[0] == 1:
|
||||
assert ret == get_res[1]
|
||||
|
@ -5,28 +5,48 @@ import sys
|
||||
import time
|
||||
from common_utils import MockProc
|
||||
from dhcp_server.common.utils import DhcpDbConnector
|
||||
from dhcp_server.common.dhcp_db_monitor import DhcpServdDbMonitor
|
||||
from dhcp_server.dhcpservd.dhcp_cfggen import DhcpServCfgGenerator
|
||||
from dhcp_server.dhcpservd.dhcpservd import DhcpServd
|
||||
from swsscommon import swsscommon
|
||||
from unittest.mock import patch, call, MagicMock
|
||||
from unittest.mock import patch, call, MagicMock, PropertyMock
|
||||
|
||||
AF_INET = 2
|
||||
AF_INET6 = 10
|
||||
PORT_MODE_CHECKER = ["DhcpServerTableCfgChangeEventChecker", "DhcpPortTableEventChecker", "DhcpRangeTableEventChecker",
|
||||
"DhcpOptionTableEventChecker", "VlanTableEventChecker", "VlanIntfTableEventChecker",
|
||||
"VlanMemberTableEventChecker"]
|
||||
|
||||
|
||||
def test_dump_dhcp4_config(mock_swsscommon_dbconnector_init):
|
||||
with patch("dhcp_server.dhcpservd.dhcp_cfggen.DhcpServCfgGenerator.generate", return_value="dummy_config") as mock_generate, \
|
||||
patch("dhcp_server.dhcpservd.dhcpservd.DhcpServd._notify_kea_dhcp4_proc", MagicMock()) as mock_notify_kea_dhcp4_proc:
|
||||
@pytest.mark.parametrize("enabled_checker", [None, set(PORT_MODE_CHECKER)])
|
||||
def test_dump_dhcp4_config(mock_swsscommon_dbconnector_init, enabled_checker):
|
||||
new_enabled_checker = set(["VlanTableEventChecker"])
|
||||
with patch("dhcp_server.dhcpservd.dhcp_cfggen.DhcpServCfgGenerator.generate",
|
||||
return_value=("dummy_config", set(), set(), set(), new_enabled_checker)) as mock_generate, \
|
||||
patch("dhcp_server.dhcpservd.dhcpservd.DhcpServd._notify_kea_dhcp4_proc",
|
||||
MagicMock()) as mock_notify_kea_dhcp4_proc, \
|
||||
patch.object(DhcpServd, "dhcp_servd_monitor", return_value=DhcpServdDbMonitor,
|
||||
new_callable=PropertyMock), \
|
||||
patch.object(DhcpServdDbMonitor, "disable_checkers") as mock_unsubscribe, \
|
||||
patch.object(DhcpServdDbMonitor, "enable_checkers") as mock_subscribe, \
|
||||
patch.object(DhcpServd, "enabled_checker", return_value=enabled_checker, new_callable=PropertyMock):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector,
|
||||
port_map_path="tests/test_data/port-name-alias-map.txt",
|
||||
kea_conf_template_path="tests/test_data/kea-dhcp4.conf.j2")
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector, kea_dhcp4_config_path="/tmp/kea-dhcp4.conf")
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector, None,
|
||||
kea_dhcp4_config_path="/tmp/kea-dhcp4.conf")
|
||||
dhcpservd.dump_dhcp4_config()
|
||||
# Verfiy whether generate() func of dhcp_cfggen is called
|
||||
mock_generate.assert_called_once_with()
|
||||
# Verify whether notify func of dhcpservd is called, which is expected to call after new config generated
|
||||
mock_notify_kea_dhcp4_proc.assert_called_once_with()
|
||||
if enabled_checker is None:
|
||||
mock_subscribe.assert_not_called()
|
||||
mock_unsubscribe.assert_not_called()
|
||||
else:
|
||||
mock_unsubscribe.assert_called_once_with(enabled_checker - new_enabled_checker)
|
||||
mock_subscribe.assert_called_once_with(new_enabled_checker - enabled_checker)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("process_list", [["proc1", "proc2", "kea-dhcp4"], ["proc1", "proc2"]])
|
||||
@ -37,7 +57,7 @@ def test_notify_kea_dhcp4_proc(process_list, mock_swsscommon_dbconnector_init, m
|
||||
patch.object(MockProc, "send_signal", MagicMock()) as mock_send_signal:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector, None)
|
||||
dhcpservd._notify_kea_dhcp4_proc()
|
||||
if "kea-dhcp4" in process_list:
|
||||
mock_send_signal.assert_has_calls([
|
||||
@ -62,7 +82,7 @@ def test_update_dhcp_server_ip(mock_swsscommon_dbconnector_init, mock_parse_port
|
||||
patch.object(sys, "exit") as mock_exit:
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector, None)
|
||||
dhcpservd._update_dhcp_server_ip()
|
||||
if mock_intf:
|
||||
mock_hset.assert_has_calls([
|
||||
@ -76,10 +96,11 @@ def test_update_dhcp_server_ip(mock_swsscommon_dbconnector_init, mock_parse_port
|
||||
|
||||
def test_start(mock_swsscommon_dbconnector_init, mock_parse_port_map_alias, mock_get_render_template):
|
||||
with patch.object(DhcpServd, "dump_dhcp4_config") as mock_dump, \
|
||||
patch.object(DhcpServd, "_update_dhcp_server_ip") as mock_update_dhcp_server_ip:
|
||||
patch.object(DhcpServd, "_update_dhcp_server_ip") as mock_update_dhcp_server_ip, \
|
||||
patch.object(DhcpServdDbMonitor, "enable_checkers"):
|
||||
dhcp_db_connector = DhcpDbConnector()
|
||||
dhcp_cfg_generator = DhcpServCfgGenerator(dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector)
|
||||
dhcpservd = DhcpServd(dhcp_cfg_generator, dhcp_db_connector, MagicMock())
|
||||
dhcpservd.start()
|
||||
mock_dump.assert_called_once_with()
|
||||
mock_update_dhcp_server_ip.assert_called_once_with()
|
||||
|
@ -26,6 +26,37 @@ interval_test_data = {
|
||||
"expected_res": [["192.168.0.10", "192.168.0.10"]]
|
||||
}
|
||||
}
|
||||
validate_str_type_data = [
|
||||
# type, value, expected_res
|
||||
["string", 123, False],
|
||||
["string", "123", True],
|
||||
["binary", "01020304ef", True],
|
||||
# False because we only support octet-based binary
|
||||
["binary", "01020304e", False],
|
||||
["binary", "0102ab0304ef", True],
|
||||
["binary", "we", False],
|
||||
["boolean", "true", True],
|
||||
["boolean", "false", True],
|
||||
["boolean", "0", False],
|
||||
["boolean", "1", False],
|
||||
["boolean", True, False],
|
||||
["boolean", "213", False],
|
||||
["ipv4-address", "192.168.0.1", True],
|
||||
["ipv4-address", "300.168.0.1", False],
|
||||
["ipv4-address", 123, False],
|
||||
["ipv4-address", "123", False],
|
||||
["ipv4-address", "192.168.0.1/24", False],
|
||||
["uint8", "e123", False],
|
||||
["uint8", 123, False],
|
||||
["uint8", "300", False],
|
||||
["uint8", "128", True],
|
||||
["uint16", "1000", True],
|
||||
["uint16", "65536", False],
|
||||
["uint32", "4294967296", False],
|
||||
["uint32", "65536", True],
|
||||
# False because we don't support uint64
|
||||
["uint64", "65536", False]
|
||||
]
|
||||
|
||||
|
||||
def test_construct_without_sock(mock_swsscommon_dbconnector_init):
|
||||
@ -47,18 +78,38 @@ def test_construct_sock(mock_swsscommon_dbconnector_init):
|
||||
])
|
||||
|
||||
|
||||
def test_get_config_db_table(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init):
|
||||
def test_get_db_table(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init):
|
||||
dhcp_db_connector = utils.DhcpDbConnector()
|
||||
with patch.object(swsscommon.Table, "getKeys", return_value=["key1", "key2"]) as mock_get_keys, \
|
||||
patch.object(utils, "get_entry", return_value={"list": "1,2", "value": "3,4"}), \
|
||||
patch.object(swsscommon.Table, "hget", side_effect=mock_hget):
|
||||
ret = dhcp_db_connector.get_config_db_table("VLAN")
|
||||
mock_swsscommon_table_init.assert_called_once_with(dhcp_db_connector.config_db, "VLAN")
|
||||
mock_get_keys.assert_called_once_with()
|
||||
assert ret == {
|
||||
"key1": {"list": ["1", "2"], "value": "3,4"},
|
||||
"key2": {"list": ["1", "2"], "value": "3,4"}
|
||||
}
|
||||
ret = dhcp_db_connector.get_state_db_table("VLAN")
|
||||
mock_swsscommon_table_init.assert_has_calls([
|
||||
call(dhcp_db_connector.config_db, "VLAN"),
|
||||
call(dhcp_db_connector.state_db, "VLAN")
|
||||
])
|
||||
mock_get_keys.assert_has_calls([
|
||||
call(),
|
||||
call()
|
||||
])
|
||||
assert ret == {
|
||||
"key1": {"list": ["1", "2"], "value": "3,4"},
|
||||
"key2": {"list": ["1", "2"], "value": "3,4"}
|
||||
}
|
||||
|
||||
|
||||
def test_get_entry(mock_swsscommon_dbconnector_init, mock_swsscommon_table_init):
|
||||
tested_entry = {"key": "value"}
|
||||
dhcp_db_connector = utils.DhcpDbConnector()
|
||||
with patch.object(swsscommon.Table, "get", return_value=(None, tested_entry)) as mock_get:
|
||||
res = utils.get_entry(swsscommon.Table(dhcp_db_connector.config_db, "VLAN"), "dummy_entry")
|
||||
assert res == tested_entry
|
||||
mock_get.assert_called_once_with("dummy_entry")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_type", interval_test_data.keys())
|
||||
@ -80,3 +131,9 @@ def convert_ip_address_intervals(intervals):
|
||||
for interval in intervals:
|
||||
ret.append([ipaddress.ip_address(interval[0]), ipaddress.ip_address(interval[1])])
|
||||
return ret
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_data", validate_str_type_data)
|
||||
def test_validate_ttr_type(test_data):
|
||||
res = utils.validate_str_type(test_data[0], test_data[1])
|
||||
assert res == test_data[2]
|
||||
|
Reference in New Issue
Block a user