diff --git a/scripts/caclmgrd b/scripts/caclmgrd index 19e42a8b48a8..8a9f99829636 100755 --- a/scripts/caclmgrd +++ b/scripts/caclmgrd @@ -80,6 +80,10 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): "dst_ports": ["22"], "multi_asic_ns_to_host_fwd":True }, + "EXTERNAL_CLIENT": { + "ip_protocols": ["tcp"], + "multi_asic_ns_to_host_fwd":True + }, "ANY": { "ip_protocols": ["any"], "dst_ports": ["0"], @@ -545,7 +549,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): # Obtain default IP protocol(s) and destination port(s) for this service ip_protocols = self.ACL_SERVICES[acl_service]["ip_protocols"] - dst_ports = self.ACL_SERVICES[acl_service]["dst_ports"] + if "dst_ports" in self.ACL_SERVICES[acl_service]: + dst_ports = self.ACL_SERVICES[acl_service]["dst_ports"] acl_rules = {} @@ -571,6 +576,19 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): elif self.is_rule_ipv4(rule_props): table_ip_version = 4 + # Read DST_PORT info from Config DB, insert it back to ACL_SERVICES + if acl_service == 'EXTERNAL_CLIENT' and "L4_DST_PORT" in rule_props: + dst_ports = [rule_props["L4_DST_PORT"]] + self.ACL_SERVICES[acl_service]["dst_ports"] = dst_ports + elif acl_service == 'EXTERNAL_CLIENT' and "L4_DST_PORT_RANGE" in rule_props: + dst_ports = [] + port_ranges = rule_props["L4_DST_PORT_RANGE"].split("-") + port_start = int(port_ranges[0]) + port_end = int(port_ranges[1]) + for port in range(port_start, port_end + 1): + dst_ports.append(port) + self.ACL_SERVICES[acl_service]["dst_ports"] = dst_ports + if (self.is_rule_ipv6(rule_props) and (table_ip_version == 4)): self.log_error("CtrlPlane ACL table {} is a IPv4 based table and rule {} is a IPV6 rule! Ignoring rule." .format(table_name, rule_id)) diff --git a/scripts/determine-reboot-cause b/scripts/determine-reboot-cause index 1408ad0e2952..e4d8a38c9f7a 100755 --- a/scripts/determine-reboot-cause +++ b/scripts/determine-reboot-cause @@ -122,7 +122,12 @@ def find_hardware_reboot_cause(): else: sonic_logger.log_info("No reboot cause found from platform api") - hardware_reboot_cause = "{} ({})".format(hardware_reboot_cause_major, hardware_reboot_cause_minor) + hardware_reboot_cause_minor_str = "" + if hardware_reboot_cause_minor: + hardware_reboot_cause_minor_str = " ({})".format(hardware_reboot_cause_minor) + + hardware_reboot_cause = hardware_reboot_cause_major + hardware_reboot_cause_minor_str + return hardware_reboot_cause @@ -158,6 +163,50 @@ def get_reboot_cause_dict(previous_reboot_cause, comment, gen_time): return reboot_cause_dict +def determine_reboot_cause(): + # This variable is kept for future-use purpose. When proc_cmd_line/vendor/software provides + # any additional_reboot_info it will be stored as a "comment" in REBOOT_CAUSE_HISTORY_FILE + additional_reboot_info = "N/A" + + # 1. Check if the previous reboot was warm/fast reboot by testing whether there is "fast|fastfast|warm" in /proc/cmdline + proc_cmdline_reboot_cause = find_proc_cmdline_reboot_cause() + + # 2. Check if the previous reboot was caused by hardware + # If yes, the hardware reboot cause will be treated as the reboot cause + hardware_reboot_cause = find_hardware_reboot_cause() + + # 3. If there is a REBOOT_CAUSE_FILE, it will contain any software-related + # reboot info. We will use it as the previous cause. + software_reboot_cause = find_software_reboot_cause() + + # The main decision logic of the reboot cause: + # If there is a valid hardware reboot cause indicated by platform API, + # check the software reboot cause to add additional rebot cause. + # If there is a reboot cause indicated by /proc/cmdline, and/or warmreboot/fastreboot/softreboot + # the software_reboot_cause which is the content of /hosts/reboot-cause/reboot-cause.txt + # will be treated as the additional reboot cause + # Elif there is a cmdline reboot cause, + # the software_reboot_cause will be treated as the reboot cause if it's not unknown + # otherwise, the cmdline_reboot_cause will be treated as the reboot cause if it's not none + # Else the software_reboot_cause will be treated as the reboot cause + if REBOOT_CAUSE_NON_HARDWARE not in hardware_reboot_cause: + previous_reboot_cause = hardware_reboot_cause + # Check if any software reboot was issued before this hardware reboot happened + if software_reboot_cause is not REBOOT_CAUSE_UNKNOWN: + additional_reboot_info = software_reboot_cause + elif proc_cmdline_reboot_cause is not None: + additional_reboot_info = proc_cmdline_reboot_cause + elif proc_cmdline_reboot_cause is not None: + if software_reboot_cause is not REBOOT_CAUSE_UNKNOWN: + # Get the reboot cause from REBOOT_CAUSE_FILE + previous_reboot_cause = software_reboot_cause + else: + previous_reboot_cause = proc_cmdline_reboot_cause + else: + previous_reboot_cause = software_reboot_cause + + return previous_reboot_cause, additional_reboot_info + def main(): # Configure logger to log all messages INFO level and higher @@ -177,22 +226,7 @@ def main(): if os.path.exists(PREVIOUS_REBOOT_CAUSE_FILE): os.remove(PREVIOUS_REBOOT_CAUSE_FILE) - # This variable is kept for future-use purpose. When proc_cmd_line/vendor/software provides - # any additional_reboot_info it will be stored as a "comment" in REBOOT_CAUSE_HISTORY_FILE - additional_reboot_info = "N/A" - - # Check if the previous reboot was warm/fast reboot by testing whether there is "fast|fastfast|warm" in /proc/cmdline - proc_cmdline_reboot_cause = find_proc_cmdline_reboot_cause() - - # If /proc/cmdline does not indicate reboot cause, check if the previous reboot was caused by hardware - if proc_cmdline_reboot_cause is None: - previous_reboot_cause = find_hardware_reboot_cause() - if previous_reboot_cause.startswith(REBOOT_CAUSE_NON_HARDWARE): - # If the reboot cause is non-hardware, get the reboot cause from REBOOT_CAUSE_FILE - previous_reboot_cause = find_software_reboot_cause() - else: - # Get the reboot cause from REBOOT_CAUSE_FILE - previous_reboot_cause = find_software_reboot_cause() + previous_reboot_cause, additional_reboot_info = determine_reboot_cause() # Current time reboot_cause_gen_time = str(datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S')) diff --git a/scripts/hostcfgd b/scripts/hostcfgd index f961875fa139..c761af8b1724 100755 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -12,6 +12,7 @@ import re import jinja2 from sonic_py_common import device_info from swsscommon.swsscommon import ConfigDBConnector, DBConnector, Table +from swsscommon import swsscommon # FILE PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic" @@ -1296,6 +1297,143 @@ class PamLimitsCfg(object): "modify pam_limits config file failed with exception: {}" .format(e)) +class DeviceMetaCfg(object): + """ + DeviceMetaCfg Config Daemon + Handles changes in DEVICE_METADATA table. + 1) Handle hostname change + """ + + def __init__(self): + self.hostname = '' + + def load(self, dev_meta={}): + # Get hostname initial + self.hostname = dev_meta.get('localhost', {}).get('hostname', '') + syslog.syslog(syslog.LOG_DEBUG, f'Initial hostname: {self.hostname}') + + def hostname_update(self, data): + """ + Apply hostname handler. + + Args: + data: Read table's key's data. + """ + syslog.syslog(syslog.LOG_DEBUG, 'DeviceMetaCfg: hostname update') + new_hostname = data.get('hostname') + + # Restart hostname-config service when hostname was changed. + # Empty not allowed + if new_hostname and new_hostname != self.hostname: + syslog.syslog(syslog.LOG_INFO, 'DeviceMetaCfg: Set new hostname: {}' + .format(new_hostname)) + self.hostname = new_hostname + try: + run_cmd('sudo service hostname-config restart', True, True) + except subprocess.CalledProcessError as e: + syslog.syslog(syslog.LOG_ERR, 'DeviceMetaCfg: Failed to set new' + ' hostname: {}'.format(e)) + return + + run_cmd('sudo monit reload') + else: + msg = 'Hostname was not updated: ' + msg += 'Already set up' if new_hostname else 'Empty not allowed' + syslog.syslog(syslog.LOG_ERR, msg) + + +class MgmtIfaceCfg(object): + """ + MgmtIfaceCfg Config Daemon + Handles changes in MGMT_INTERFACE, MGMT_VRF_CONFIG tables. + 1) Handle change of interface ip + 2) Handle change of management VRF state + """ + + def __init__(self): + self.iface_config_data = {} + self.mgmt_vrf_enabled = '' + + def load(self, mgmt_iface={}, mgmt_vrf={}): + # Get initial data + self.iface_config_data = mgmt_iface + self.mgmt_vrf_enabled = mgmt_vrf.get('mgmtVrfEnabled', '') + syslog.syslog(syslog.LOG_DEBUG, + f'Initial mgmt interface conf: {self.iface_config_data}') + syslog.syslog(syslog.LOG_DEBUG, + f'Initial mgmt VRF state: {self.mgmt_vrf_enabled}') + + def update_mgmt_iface(self, iface, key, data): + """Handle update management interface config + """ + syslog.syslog(syslog.LOG_DEBUG, 'MgmtIfaceCfg: mgmt iface update') + + # Restart management interface service when config was changed + if data != self.iface_config_data.get(key): + cfg = {key: data} + syslog.syslog(syslog.LOG_INFO, f'MgmtIfaceCfg: Set new interface ' + f'config {cfg} for {iface}') + try: + run_cmd('sudo systemctl restart interfaces-config', True, True) + run_cmd('sudo systemctl restart ntp-config', True, True) + except subprocess.CalledProcessError: + syslog.syslog(syslog.LOG_ERR, f'Failed to restart management ' + 'interface services') + return + + self.iface_config_data[key] = data + + def update_mgmt_vrf(self, data): + """Handle update management VRF state + """ + syslog.syslog(syslog.LOG_DEBUG, 'MgmtIfaceCfg: mgmt vrf state update') + + # Restart mgmt vrf services when mgmt vrf config was changed. + # Empty not allowed. + enabled = data.get('mgmtVrfEnabled', '') + if not enabled or enabled == self.mgmt_vrf_enabled: + return + + syslog.syslog(syslog.LOG_INFO, f'Set mgmt vrf state {enabled}') + + # Restart related vrfs services + try: + run_cmd('service ntp stop', True, True) + run_cmd('systemctl restart interfaces-config', True, True) + run_cmd('service ntp start', True, True) + except subprocess.CalledProcessError: + syslog.syslog(syslog.LOG_ERR, f'Failed to restart management vrf ' + 'services') + return + + # Remove mgmt if route + if enabled == 'true': + """ + The regular expression for grep in below cmd is to match eth0 line + in /proc/net/route, sample file: + $ cat /proc/net/route + Iface Destination Gateway Flags RefCnt Use + eth0 00000000 01803B0A 0003 0 0 + #################### Line break here #################### + Metric Mask MTU Window IRTT + 202 00000000 0 0 0 + """ + try: + run_cmd(r"""cat /proc/net/route | grep -E \"eth0\s+""" + r"""00000000\s+[0-9A-Z]+\s+[0-9]+\s+[0-9]+\s+[0-9]+""" + r"""\s+202\" | wc -l""", + True, True) + except subprocess.CalledProcessError: + syslog.syslog(syslog.LOG_ERR, 'MgmtIfaceCfg: Could not delete ' + 'eth0 route') + return + + run_cmd("ip -4 route del default dev eth0 metric 202", False) + + # Update cache + self.mgmt_vrf_enabled = enabled + + class HostConfigDaemon: def __init__(self): # Just a sanity check to verify if the CONFIG_DB has been initialized @@ -1327,7 +1465,6 @@ class HostConfigDaemon: self.is_multi_npu = device_info.is_multi_npu() # Initialize AAACfg - self.hostname_cache="" self.aaacfg = AaaCfg() # Initialize PasswHardening @@ -1337,6 +1474,12 @@ class HostConfigDaemon: self.pamLimitsCfg = PamLimitsCfg(self.config_db) self.pamLimitsCfg.update_config_file() + # Initialize DeviceMetaCfg + self.devmetacfg = DeviceMetaCfg() + + # Initialize MgmtIfaceCfg + self.mgmtifacecfg = MgmtIfaceCfg() + def load(self, init_data): features = init_data['FEATURE'] aaa = init_data['AAA'] @@ -1349,6 +1492,9 @@ class HostConfigDaemon: ntp_global = init_data['NTP'] kdump = init_data['KDUMP'] passwh = init_data['PASSW_HARDENING'] + dev_meta = init_data.get(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, {}) + mgmt_ifc = init_data.get(swsscommon.CFG_MGMT_INTERFACE_TABLE_NAME, {}) + mgmt_vrf = init_data.get(swsscommon.CFG_MGMT_VRF_CONFIG_TABLE_NAME, {}) self.feature_handler.sync_state_field(features) self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server) @@ -1356,14 +1502,11 @@ class HostConfigDaemon: self.ntpcfg.load(ntp_global, ntp_server) self.kdumpCfg.load(kdump) self.passwcfg.load(passwh) - - dev_meta = self.config_db.get_table('DEVICE_METADATA') - if 'localhost' in dev_meta: - if 'hostname' in dev_meta['localhost']: - self.hostname_cache = dev_meta['localhost']['hostname'] + self.devmetacfg.load(dev_meta) + self.mgmtifacecfg.load(mgmt_ifc, mgmt_vrf) # Update AAA with the hostname - self.aaacfg.hostname_update(self.hostname_cache) + self.aaacfg.hostname_update(self.devmetacfg.hostname) def __get_intf_name(self, key): if isinstance(key, tuple) and key: @@ -1413,6 +1556,10 @@ class HostConfigDaemon: mgmt_intf_name = self.__get_intf_name(key) self.aaacfg.handle_radius_source_intf_ip_chg(mgmt_intf_name) self.aaacfg.handle_radius_nas_ip_chg(mgmt_intf_name) + self.mgmtifacecfg.update_mgmt_iface(mgmt_intf_name, key, data) + + def mgmt_vrf_handler(self, key, op, data): + self.mgmtifacecfg.update_mgmt_vrf(data) def lpbk_handler(self, key, op, data): key = ConfigDBConnector.deserialize_key(key) @@ -1452,6 +1599,10 @@ class HostConfigDaemon: syslog.syslog(syslog.LOG_INFO, 'Kdump handler...') self.kdumpCfg.kdump_update(key, data) + def device_metadata_handler(self, key, op, data): + syslog.syslog(syslog.LOG_INFO, 'DeviceMeta handler...') + self.devmetacfg.hostname_update(data) + def wait_till_system_init_done(self): # No need to print the output in the log file so using the "--quiet" # flag @@ -1491,6 +1642,14 @@ class HostConfigDaemon: self.config_db.subscribe('PORTCHANNEL_INTERFACE', make_callback(self.portchannel_intf_handler)) self.config_db.subscribe('INTERFACE', make_callback(self.phy_intf_handler)) + # Handle DEVICE_MEATADATA changes + self.config_db.subscribe(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, + make_callback(self.device_metadata_handler)) + + # Handle MGMT_VRF_CONFIG changes + self.config_db.subscribe(swsscommon.CFG_MGMT_VRF_CONFIG_TABLE_NAME, + make_callback(self.mgmt_vrf_handler)) + syslog.syslog(syslog.LOG_INFO, "Waiting for systemctl to finish initialization") self.wait_till_system_init_done() diff --git a/tests/caclmgrd/caclmgrd_external_client_acl_test.py b/tests/caclmgrd/caclmgrd_external_client_acl_test.py new file mode 100644 index 000000000000..ef1aa62fe82d --- /dev/null +++ b/tests/caclmgrd/caclmgrd_external_client_acl_test.py @@ -0,0 +1,44 @@ +import os +import sys + +from swsscommon import swsscommon +from parameterized import parameterized +from sonic_py_common.general import load_module_from_source +from unittest import TestCase, mock +from pyfakefs.fake_filesystem_unittest import patchfs + +from .test_external_client_acl_vectors import EXTERNAL_CLIENT_ACL_TEST_VECTOR +from tests.common.mock_configdb import MockConfigDb + + +DBCONFIG_PATH = '/var/run/redis/sonic-db/database_config.json' + + +class TestCaclmgrdExternalClientAcl(TestCase): + """ + Test caclmgrd EXTERNAL_CLIENT_ACL + """ + def setUp(self): + swsscommon.ConfigDBConnector = MockConfigDb + test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + modules_path = os.path.dirname(test_path) + scripts_path = os.path.join(modules_path, "scripts") + sys.path.insert(0, modules_path) + caclmgrd_path = os.path.join(scripts_path, 'caclmgrd') + self.caclmgrd = load_module_from_source('caclmgrd', caclmgrd_path) + + @parameterized.expand(EXTERNAL_CLIENT_ACL_TEST_VECTOR) + @patchfs + def test_caclmgrd_external_client_acl(self, test_name, test_data, fs): + if not os.path.exists(DBCONFIG_PATH): + fs.create_file(DBCONFIG_PATH) # fake database_config.json + + MockConfigDb.set_config_db(test_data["config_db"]) + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock() + self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock() + self.caclmgrd.ControlPlaneAclManager.generate_block_ip2me_traffic_iptables_commands = mock.MagicMock(return_value=[]) + self.caclmgrd.ControlPlaneAclManager.get_chain_list = mock.MagicMock(return_value=["INPUT", "FORWARD", "OUTPUT"]) + caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") + + iptables_rules_ret, _ = caclmgrd_daemon.get_acl_rules_and_translate_to_iptables_commands('') + self.assertEqual(set(test_data["return"]).issubset(set(iptables_rules_ret)), True) diff --git a/tests/caclmgrd/test_external_client_acl_vectors.py b/tests/caclmgrd/test_external_client_acl_vectors.py new file mode 100644 index 000000000000..d55e6b8e6ce4 --- /dev/null +++ b/tests/caclmgrd/test_external_client_acl_vectors.py @@ -0,0 +1,167 @@ +from unittest.mock import call + +""" + caclmgrd test external_client_acl vector +""" +EXTERNAL_CLIENT_ACL_TEST_VECTOR = [ + [ + "Test single IPv4 dst port + src ip for EXTERNAL_CLIENT_ACL", + { + "config_db": { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": [ + "EXTERNAL_CLIENT" + ] + } + }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1" + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT": "8081", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "20.0.0.55/32" + }, + }, + "DEVICE_METADATA": { + "localhost": { + } + }, + "FEATURE": {}, + }, + "return": [ + "iptables -A INPUT -p tcp -s 20.0.0.55/32 --dport 8081 -j ACCEPT", + "iptables -A INPUT -p tcp --dport 8081 -j DROP" + ], + } + ], + [ + "Test IPv4 dst port range + src ip forEXTERNAL_CLIENT_ACL", + { + "config_db": { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": [ + "EXTERNAL_CLIENT" + ] + } + }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1" + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT_RANGE": "8081-8083", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "20.0.0.55/32" + }, + }, + "DEVICE_METADATA": { + "localhost": { + } + }, + "FEATURE": {}, + }, + "return": [ + "iptables -A INPUT -p tcp -s 20.0.0.55/32 --dport 8081 -j ACCEPT", + "iptables -A INPUT -p tcp -s 20.0.0.55/32 --dport 8082 -j ACCEPT", + "iptables -A INPUT -p tcp -s 20.0.0.55/32 --dport 8083 -j ACCEPT", + "iptables -A INPUT -p tcp --dport 8081 -j DROP", + "iptables -A INPUT -p tcp --dport 8082 -j DROP", + "iptables -A INPUT -p tcp --dport 8083 -j DROP", + ], + } + ], + [ + "Test IPv6 single dst port range + src ip forEXTERNAL_CLIENT_ACL", + { + "config_db": { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": [ + "EXTERNAL_CLIENT" + ] + } + }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1" + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT": "8081", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "2001::2/128" + }, + }, + "DEVICE_METADATA": { + "localhost": { + } + }, + "FEATURE": {}, + }, + "return": [ + "iptables -A INPUT -p tcp -s 2001::2/128 --dport 8081 -j ACCEPT", + "iptables -A INPUT -p tcp --dport 8081 -j DROP" + ], + } + ], + [ + "Test IPv6 dst port range + src ip forEXTERNAL_CLIENT_ACL", + { + "config_db": { + "ACL_TABLE": { + "EXTERNAL_CLIENT_ACL": { + "stage": "INGRESS", + "type": "CTRLPLANE", + "services": [ + "EXTERNAL_CLIENT" + ] + } + }, + "ACL_RULE": { + "EXTERNAL_CLIENT_ACL|DEFAULT_RULE": { + "ETHER_TYPE": "2048", + "PACKET_ACTION": "DROP", + "PRIORITY": "1" + }, + "EXTERNAL_CLIENT_ACL|RULE_1": { + "L4_DST_PORT_RANGE": "8081-8083", + "PACKET_ACTION": "ACCEPT", + "PRIORITY": "9998", + "SRC_IP": "2001::2/128" + }, + }, + "DEVICE_METADATA": { + "localhost": { + } + }, + "FEATURE": {}, + }, + "return": [ + "iptables -A INPUT -p tcp -s 2001::2/128 --dport 8081 -j ACCEPT", + "iptables -A INPUT -p tcp -s 2001::2/128 --dport 8082 -j ACCEPT", + "iptables -A INPUT -p tcp -s 2001::2/128 --dport 8083 -j ACCEPT", + "iptables -A INPUT -p tcp --dport 8081 -j DROP", + "iptables -A INPUT -p tcp --dport 8082 -j DROP", + "iptables -A INPUT -p tcp --dport 8083 -j DROP", + ], + } + ] +] diff --git a/tests/determine-reboot-cause_test.py b/tests/determine-reboot-cause_test.py index 7d22a512f8ee..b4c07af4683b 100644 --- a/tests/determine-reboot-cause_test.py +++ b/tests/determine-reboot-cause_test.py @@ -54,11 +54,16 @@ GEN_TIME_KERNEL_PANIC = "2021_3_28_13_48_49" +REBOOT_CAUSE_UNKNOWN = "Unknown" +REBOOT_CAUSE_NON_HARDWARE = "Non-Hardware" +EXPECTED_NON_HARDWARE_REBOOT_CAUSE = {REBOOT_CAUSE_NON_HARDWARE, "N/A"} +REBOOT_CAUSE_HARDWARE_OTHER = "Hardware - Other" +EXPECTED_HARDWARE_REBOOT_CAUSE = {REBOOT_CAUSE_HARDWARE_OTHER, ""} + EXPECTED_PARSE_WARMFAST_REBOOT_FROM_PROC_CMDLINE = "warm-reboot" EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_USER = "User issued 'warm-reboot' command [User: admin, Time: Mon Nov 2 22:37:45 UTC 2020]" EXPECTED_FIND_FIRSTBOOT_VERSION = " (First boot of SONiC version 20191130.52)" EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_FIRSTBOOT = "Unknown (First boot of SONiC version 20191130.52)" -EXPECTED_HARDWARE_REBOOT_CAUSE = {"warm-reboot", ""} EXPECTED_WATCHDOG_REBOOT_CAUSE_DICT = {'comment': '', 'gen_time': '2020_10_22_03_15_08', 'cause': 'Watchdog', 'user': 'N/A', 'time': 'N/A'} EXPECTED_USER_REBOOT_CAUSE_DICT = {'comment': '', 'gen_time': '2020_10_22_03_14_07', 'cause': 'reboot', 'user': 'admin', 'time': 'Thu Oct 22 03:11:08 UTC 2020'} @@ -104,7 +109,12 @@ def test_find_proc_cmdline_reboot_cause(self): def test_find_hardware_reboot_cause(self): with mock.patch("determine_reboot_cause.get_reboot_cause_from_platform", return_value=("Powerloss", None)): result = determine_reboot_cause.find_hardware_reboot_cause() - assert result == "Powerloss (None)" + assert result == "Powerloss" + + def test_find_hardware_reboot_cause_with_minor(self): + with mock.patch("determine_reboot_cause.get_reboot_cause_from_platform", return_value=("Powerloss", "under-voltage")): + result = determine_reboot_cause.find_hardware_reboot_cause() + assert result == "Powerloss (under-voltage)" def test_get_reboot_cause_dict_watchdog(self): reboot_cause_dict = determine_reboot_cause.get_reboot_cause_dict(REBOOT_CAUSE_WATCHDOG, "", GEN_TIME_WATCHDOG) @@ -117,3 +127,52 @@ def test_get_reboot_cause_dict_user(self): def test_get_reboot_cause_dict_kernel_panic(self): reboot_cause_dict = determine_reboot_cause.get_reboot_cause_dict(REBOOT_CAUSE_KERNEL_PANIC, "", GEN_TIME_KERNEL_PANIC) assert reboot_cause_dict == EXPECTED_KERNEL_PANIC_REBOOT_CAUSE_DICT + + def test_determine_reboot_cause_hardware(self): + with mock.patch("determine_reboot_cause.find_proc_cmdline_reboot_cause", return_value=None): + with mock.patch("determine_reboot_cause.find_software_reboot_cause", return_value=REBOOT_CAUSE_UNKNOWN): + with mock.patch("determine_reboot_cause.find_hardware_reboot_cause", return_value=EXPECTED_HARDWARE_REBOOT_CAUSE): + previous_reboot_cause, additional_reboot_info = determine_reboot_cause.determine_reboot_cause() + assert previous_reboot_cause == EXPECTED_HARDWARE_REBOOT_CAUSE + assert additional_reboot_info == "N/A" + + def test_determine_reboot_cause_software(self): + with mock.patch("determine_reboot_cause.find_proc_cmdline_reboot_cause", return_value=None): + with mock.patch("determine_reboot_cause.find_software_reboot_cause", return_value=EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_USER): + with mock.patch("determine_reboot_cause.find_hardware_reboot_cause", return_value=EXPECTED_NON_HARDWARE_REBOOT_CAUSE): + previous_reboot_cause, additional_info = determine_reboot_cause.determine_reboot_cause() + assert previous_reboot_cause == EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_USER + assert additional_info == "N/A" + + def test_determine_reboot_cause_cmdline_software(self): + with mock.patch("determine_reboot_cause.find_proc_cmdline_reboot_cause", return_value=EXPECTED_PARSE_WARMFAST_REBOOT_FROM_PROC_CMDLINE): + with mock.patch("determine_reboot_cause.find_software_reboot_cause", return_value=EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_USER): + with mock.patch("determine_reboot_cause.find_hardware_reboot_cause", return_value=EXPECTED_NON_HARDWARE_REBOOT_CAUSE): + previous_reboot_cause, additional_info = determine_reboot_cause.determine_reboot_cause() + assert previous_reboot_cause == EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_USER + assert additional_info == "N/A" + + def test_determine_reboot_cause_cmdline_no_software(self): + with mock.patch("determine_reboot_cause.find_proc_cmdline_reboot_cause", return_value=EXPECTED_PARSE_WARMFAST_REBOOT_FROM_PROC_CMDLINE): + with mock.patch("determine_reboot_cause.find_software_reboot_cause", return_value=REBOOT_CAUSE_UNKNOWN): + with mock.patch("determine_reboot_cause.find_hardware_reboot_cause", return_value=EXPECTED_NON_HARDWARE_REBOOT_CAUSE): + previous_reboot_cause, additional_info = determine_reboot_cause.determine_reboot_cause() + assert previous_reboot_cause == EXPECTED_PARSE_WARMFAST_REBOOT_FROM_PROC_CMDLINE + assert additional_info == "N/A" + + def test_determine_reboot_cause_cmdline_hardware(self): + with mock.patch("determine_reboot_cause.find_proc_cmdline_reboot_cause", return_value=EXPECTED_PARSE_WARMFAST_REBOOT_FROM_PROC_CMDLINE): + with mock.patch("determine_reboot_cause.find_software_reboot_cause", return_value=REBOOT_CAUSE_UNKNOWN): + with mock.patch("determine_reboot_cause.find_hardware_reboot_cause", return_value=EXPECTED_HARDWARE_REBOOT_CAUSE): + previous_reboot_cause, additional_info = determine_reboot_cause.determine_reboot_cause() + assert previous_reboot_cause == EXPECTED_HARDWARE_REBOOT_CAUSE + assert additional_info == EXPECTED_PARSE_WARMFAST_REBOOT_FROM_PROC_CMDLINE + + def test_determine_reboot_cause_software_hardware(self): + with mock.patch("determine_reboot_cause.find_proc_cmdline_reboot_cause", return_value=EXPECTED_PARSE_WARMFAST_REBOOT_FROM_PROC_CMDLINE): + with mock.patch("determine_reboot_cause.find_software_reboot_cause", return_value=EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_USER): + with mock.patch("determine_reboot_cause.find_hardware_reboot_cause", return_value=EXPECTED_HARDWARE_REBOOT_CAUSE): + previous_reboot_cause, additional_info = determine_reboot_cause.determine_reboot_cause() + assert previous_reboot_cause == EXPECTED_HARDWARE_REBOOT_CAUSE + assert additional_info == EXPECTED_FIND_SOFTWARE_REBOOT_CAUSE_USER + diff --git a/tests/hostcfgd/hostcfgd_test.py b/tests/hostcfgd/hostcfgd_test.py index e7434fd380e7..23fc4a9ddc39 100644 --- a/tests/hostcfgd/hostcfgd_test.py +++ b/tests/hostcfgd/hostcfgd_test.py @@ -8,6 +8,7 @@ from sonic_py_common.general import load_module_from_source from unittest import TestCase, mock +from .test_vectors import HOSTCFG_DAEMON_INIT_CFG_DB from .test_vectors import HOSTCFGD_TEST_VECTOR, HOSTCFG_DAEMON_CFG_DB from tests.common.mock_configdb import MockConfigDb, MockDBConnector @@ -377,3 +378,77 @@ def test_kdump_event(self): call('sonic-kdump-config --num_dumps 3', shell=True), call('sonic-kdump-config --memory 0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M', shell=True)] mocked_subprocess.check_call.assert_has_calls(expected, any_order=True) + + def test_devicemeta_event(self): + """ + Test handling DEVICE_METADATA events. + 1) Hostname reload + """ + MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) + MockConfigDb.event_queue = [(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, + 'localhost')] + daemon = hostcfgd.HostConfigDaemon() + daemon.aaacfg = mock.MagicMock() + daemon.iptables = mock.MagicMock() + daemon.passwcfg = mock.MagicMock() + daemon.load(HOSTCFG_DAEMON_INIT_CFG_DB) + daemon.register_callbacks() + with mock.patch('hostcfgd.subprocess') as mocked_subprocess: + popen_mock = mock.Mock() + attrs = {'communicate.return_value': ('output', 'error')} + popen_mock.configure_mock(**attrs) + mocked_subprocess.Popen.return_value = popen_mock + + try: + daemon.start() + except TimeoutError: + pass + + expected = [ + call('sudo service hostname-config restart', shell=True), + call('sudo monit reload', shell=True) + ] + mocked_subprocess.check_call.assert_has_calls(expected, + any_order=True) + + def test_mgmtiface_event(self): + """ + Test handling mgmt events. + 1) Management interface setup + 2) Management vrf setup + """ + MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) + MockConfigDb.event_queue = [ + (swsscommon.CFG_MGMT_INTERFACE_TABLE_NAME, 'eth0|1.2.3.4/24'), + (swsscommon.CFG_MGMT_VRF_CONFIG_TABLE_NAME, 'vrf_global') + ] + daemon = hostcfgd.HostConfigDaemon() + daemon.register_callbacks() + daemon.aaacfg = mock.MagicMock() + daemon.iptables = mock.MagicMock() + daemon.passwcfg = mock.MagicMock() + daemon.load(HOSTCFG_DAEMON_INIT_CFG_DB) + with mock.patch('hostcfgd.subprocess') as mocked_subprocess: + popen_mock = mock.Mock() + attrs = {'communicate.return_value': ('output', 'error')} + popen_mock.configure_mock(**attrs) + mocked_subprocess.Popen.return_value = popen_mock + + try: + daemon.start() + except TimeoutError: + pass + + expected = [ + call('sudo systemctl restart interfaces-config', shell=True), + call('sudo systemctl restart ntp-config', shell=True), + call('service ntp stop', shell=True), + call('systemctl restart interfaces-config', shell=True), + call('service ntp start', shell=True), + call('cat /proc/net/route | grep -E \\"eth0\\s+00000000' + '\\s+[0-9A-Z]+\\s+[0-9]+\\s+[0-9]+\\s+[0-9]+\\s+202\\" | ' + 'wc -l', shell=True), + call('ip -4 route del default dev eth0 metric 202', shell=True) + ] + mocked_subprocess.check_call.assert_has_calls(expected, + any_order=True) diff --git a/tests/hostcfgd/test_vectors.py b/tests/hostcfgd/test_vectors.py index c655bbe6f127..4f6dca3056a6 100644 --- a/tests/hostcfgd/test_vectors.py +++ b/tests/hostcfgd/test_vectors.py @@ -24,7 +24,7 @@ "enabled": "false", "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" - } + } }, "FEATURE": { "dhcp_relay": { @@ -128,7 +128,7 @@ "enabled": "false", "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" - } + } }, "FEATURE": { "dhcp_relay": { @@ -250,7 +250,7 @@ "enabled": "false", "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" - } + } }, "FEATURE": { "dhcp_relay": { @@ -351,7 +351,7 @@ "enabled": "false", "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" - } + } }, "FEATURE": { "dhcp_relay": { @@ -456,7 +456,7 @@ "enabled": "false", "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" - } + } }, "FEATURE": { "dhcp_relay": { @@ -1033,6 +1033,28 @@ ] +HOSTCFG_DAEMON_INIT_CFG_DB = { + "FEATURE": {}, + "AAA": {}, + "TACPLUS": {}, + "TACPLUS_SERVER": {}, + "RADIUS": {}, + "RADIUS_SERVER": {}, + "PASSW_HARDENING": {}, + "KDUMP": {}, + "NTP": {}, + "NTP_SERVER": {}, + "LOOPBACK_INTERFACE": {}, + "DEVICE_METADATA": { + "localhost": { + "hostname": "old-hostname" + } + }, + "MGMT_INTERFACE": {}, + "MGMT_VRF_CONFIG": {} +} + + HOSTCFG_DAEMON_CFG_DB = { "FEATURE": { "dhcp_relay": { @@ -1064,6 +1086,12 @@ "status": "enabled" }, }, + "AAA": {}, + "TACPLUS": {}, + "TACPLUS_SERVER": {}, + "RADIUS": {}, + "RADIUS_SERVER": {}, + "PASSW_HARDENING": {}, "KDUMP": { "config": { @@ -1088,6 +1116,15 @@ "localhost": { "subtype": "DualToR", "type": "ToRRouter", + "hostname": "SomeNewHostname" + } + }, + "MGMT_INTERFACE": { + "eth0|1.2.3.4/24": {} + }, + "MGMT_VRF_CONFIG": { + "vrf_global": { + 'mgmtVrfEnabled': 'true' } } }