Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into dev-banner-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
fastiuk committed Aug 11, 2024
2 parents 8c8143d + ca6b3cd commit b0f57f2
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Description=Reboot cause determination service
Requires=rc-local.service
After=rc-local.service
Wants=process-reboot-cause.service

[Service]
Type=oneshot
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
[Unit]
Description=Retrieve the reboot cause from the history files and save them to StateDB
Requires=database.service determine-reboot-cause.service
PartOf=database.service
After=database.service determine-reboot-cause.service

[Service]
Type=simple
ExecStartPre=/usr/bin/systemctl is-active database
ExecStartPre=/usr/bin/systemctl is-active determine-reboot-cause
Restart=on-failure
RestartSec=30
RemainAfterExit=yes
ExecStart=/usr/local/bin/process-reboot-cause

[Install]
WantedBy=multi-user.target

This file was deleted.

30 changes: 30 additions & 0 deletions host_modules/gcu.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,36 @@ def apply_patch_yang(self, patch_text):
break
return result.returncode, msg

@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
def replace_db(self, patch_text):
input_bytes = (patch_text + '\n').encode('utf-8')
cmd = ['/usr/local/bin/config', 'replace', '-f', 'CONFIGDB', '/dev/stdin']

result = subprocess.run(cmd, input=input_bytes, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
msg = ''
if result.returncode:
lines = result.stderr.decode().split('\n')
for line in lines:
if 'Error' in line:
msg = line
break
return result.returncode, msg

@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
def replace_yang(self, patch_text):
input_bytes = (patch_text + '\n').encode('utf-8')
cmd = ['/usr/local/bin/config', 'replace', '-f', 'SONICYANG', '/dev/stdin']

result = subprocess.run(cmd, input=input_bytes, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
msg = ''
if result.returncode:
lines = result.stderr.decode().split('\n')
for line in lines:
if 'Error' in line:
msg = line
break
return result.returncode, msg

@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
def create_checkpoint(self, checkpoint_file):

Expand Down
50 changes: 50 additions & 0 deletions host_modules/systemd_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Systemd service handler"""

from host_modules import host_service
import subprocess

MOD_NAME = 'systemd'
ALLOWED_SERVICES = ['snmp', 'swss', 'dhcp_relay', 'radv', 'restapi', 'lldp', 'sshd', 'pmon', 'rsyslog', 'telemetry']
EXIT_FAILURE = 1


class SystemdService(host_service.HostModule):
"""
DBus endpoint that executes the service command
"""
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
def restart_service(self, service):
if not service:
return EXIT_FAILURE, "Dbus restart_service called with no service specified"
if service not in ALLOWED_SERVICES:
return EXIT_FAILURE, "Dbus does not support {} service restart".format(service)

cmd = ['/usr/bin/systemctl', 'reset-failed', service]
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode:
possible_expected_error = "Failed to reset failed state"
msg = result.stderr.decode()
if possible_expected_error not in msg:
return result.returncode, msg # Throw error only if unexpected error

msg = ''
cmd = ['/usr/bin/systemctl', 'restart', service]
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode:
msg = result.stderr.decode()

return result.returncode, msg

@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
def stop_service(self, service):
if not service:
return EXIT_FAILURE, "Dbus stop_service called with no service specified"
if service not in ALLOWED_SERVICES:
return EXIT_FAILURE, "Dbus does not support {} service management".format(service)

cmd = ['/usr/bin/systemctl', 'stop', service]
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
msg = ''
if result.returncode:
msg = result.stderr.decode()
return result.returncode, msg
11 changes: 9 additions & 2 deletions scripts/caclmgrd
Original file line number Diff line number Diff line change
Expand Up @@ -783,8 +783,15 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):

# Add iptables/ip6tables commands to allow all incoming packets with TTL of 0 or 1
# This allows the device to respond to tools like tcptraceroute
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-m', 'ttl', '--ttl-lt', '2', '-j', 'ACCEPT'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-p', 'tcp', '-m', 'hl', '--hl-lt', '2', '-j', 'ACCEPT'])
# Allow ICMP with TTL < 2
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-p', 'icmp', '-m', 'ttl', '--ttl-lt', '2', '-j', 'ACCEPT'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-p', 'ipv6-icmp', '-m', 'hl', '--hl-lt', '2', '-j', 'ACCEPT'])

# Allow UDP and TCP with TTL < 2 and dst-port > 1024, in case traceroute based on UDP or TCP
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-p', 'udp', '-m', 'ttl', '--ttl-lt', '2', '--dport', '1025:65535', '-j', 'ACCEPT'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-p', 'tcp', '-m', 'ttl', '--ttl-lt', '2', '--dport', '1025:65535', '-j', 'ACCEPT'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-p', 'udp', '-m', 'hl', '--hl-lt', '2', '--dport', '1025:65535', '-j', 'ACCEPT'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-p', 'tcp', '-m', 'hl', '--hl-lt', '2', '--dport', '1025:65535', '-j', 'ACCEPT'])

# Finally, if the device has control plane ACLs configured,
# add iptables/ip6tables commands to drop all other incoming packets
Expand Down
8 changes: 4 additions & 4 deletions scripts/determine-reboot-cause
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ try:
import re
import sys

from sonic_py_common import device_info, logger
from sonic_py_common import device_info, syslogger

except ImportError as err:
raise ImportError("%s - required module not found" % str(err))
Expand Down Expand Up @@ -44,7 +44,7 @@ REBOOT_CAUSE_NON_HARDWARE = "Non-Hardware"
REBOOT_CAUSE_HARDWARE_OTHER = "Hardware - Other"

# Global logger class instance
sonic_logger = logger.Logger(SYSLOG_IDENTIFIER)
sonic_logger = syslogger.SysLogger(SYSLOG_IDENTIFIER)


# ============================= Functions =============================
Expand Down Expand Up @@ -108,7 +108,7 @@ def get_reboot_cause_from_platform():
chassis = platform.get_chassis()
hardware_reboot_cause_major, hardware_reboot_cause_minor = chassis.get_reboot_cause()
sonic_logger.log_info("Platform api returns reboot cause {}, {}".format(hardware_reboot_cause_major, hardware_reboot_cause_minor))
except ImportError:
except (ImportError, FileNotFoundError):
sonic_logger.log_warning("sonic_platform package not installed. Unable to detect hardware reboot causes.")
hardware_reboot_cause_major, hardware_reboot_cause_minor = REBOOT_CAUSE_NON_HARDWARE, "N/A"

Expand Down Expand Up @@ -210,7 +210,7 @@ def determine_reboot_cause():

def main():
# Configure logger to log all messages INFO level and higher
sonic_logger.set_min_log_priority_info()
sonic_logger.set_min_log_priority(sonic_logger.DEFAULT_LOG_LEVEL)

sonic_logger.log_info("Starting up...")

Expand Down
37 changes: 26 additions & 11 deletions scripts/featured
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ class Feature(object):
self.name = feature_name
self.state = self._get_feature_table_key_render_value(feature_cfg.get('state'), device_config or {}, ['enabled', 'disabled', 'always_enabled', 'always_disabled'])
self.auto_restart = feature_cfg.get('auto_restart', 'disabled')
self.delayed = safe_eval(feature_cfg.get('delayed', 'False'))
self.has_global_scope = safe_eval(feature_cfg.get('has_global_scope', 'True'))
self.delayed = safe_eval(self._get_feature_table_key_render_value(feature_cfg.get('delayed', 'False'), device_config or {}, ['True', 'False']))
self.has_global_scope = safe_eval(self._get_feature_table_key_render_value(feature_cfg.get('has_global_scope', 'True'), device_config or {}, ['True', 'False']))
self.has_per_asic_scope = safe_eval(self._get_feature_table_key_render_value(feature_cfg.get('has_per_asic_scope', 'False'), device_config or {}, ['True', 'False']))
self.has_per_dpu_scope = safe_eval(feature_cfg.get('has_per_dpu_scope', 'False'))

Expand Down Expand Up @@ -198,7 +198,7 @@ class FeatureHandler(object):
# Enable/disable the container service if the feature state was changed from its previous state.
if self._cached_config[feature_name].state != feature.state:
if self.update_feature_state(feature):
self.sync_feature_asic_scope(feature)
self.sync_feature_scope(feature)
self._cached_config[feature_name] = feature
else:
self.resync_feature_state(self._cached_config[feature_name])
Expand All @@ -222,8 +222,9 @@ class FeatureHandler(object):
self._cached_config.setdefault(feature_name, feature)
self.update_systemd_config(feature)
self.update_feature_state(feature)
self.sync_feature_asic_scope(feature)
self.sync_feature_scope(feature)
self.resync_feature_state(feature)
self.sync_feature_delay_state(feature)

def update_feature_state(self, feature):
cached_feature = self._cached_config[feature.name]
Expand Down Expand Up @@ -270,10 +271,10 @@ class FeatureHandler(object):

return True

def sync_feature_asic_scope(self, feature_config):
"""Updates the has_per_asic_scope field in the FEATURE|* tables as the field
def sync_feature_scope(self, feature_config):
"""Updates the has_global_scope or has_per_asic_scope field in the FEATURE|* tables as the field
might have to be rendered based on DEVICE_METADATA table or Device Running configuration.
Disable the ASIC instance service unit file it the render value is False and update config
Disable the Global/ASIC instance service unit file it the render value is False and update config
Args:
feature: An object represents a feature's configuration in `FEATURE`
Expand All @@ -284,12 +285,13 @@ class FeatureHandler(object):
"""
feature_names, feature_suffixes = self.get_multiasic_feature_instances(feature_config, True)
for feature_name in feature_names:
if '@' not in feature_name:
continue
unit_file_state = self.get_systemd_unit_state("{}.{}".format(feature_name, feature_suffixes[-1]))
if not unit_file_state:
continue
if unit_file_state != "disabled" and not feature_config.has_per_asic_scope:
if not self.is_multi_npu:
continue
if unit_file_state != "masked" and \
((not feature_config.has_per_asic_scope and '@' in feature_name) or (not feature_config.has_global_scope and '@' not in feature_name)):
cmds = []
for suffix in reversed(feature_suffixes):
cmds.append(["sudo", "systemctl", "stop", "{}.{}".format(feature_name, suffix)])
Expand All @@ -305,10 +307,12 @@ class FeatureHandler(object):
self.set_feature_state(feature_config, self.FEATURE_STATE_FAILED)
return
self._config_db.mod_entry(FEATURE_TBL, feature_config.name, {'has_per_asic_scope': str(feature_config.has_per_asic_scope)})
self._config_db.mod_entry(FEATURE_TBL, feature_config.name, {'has_global_scope': str(feature_config.has_global_scope)})

# sync has_per_asic_scope to CONFIG_DB in namespaces in multi-asic platform
for ns, db in self.ns_cfg_db.items():
db.mod_entry(FEATURE_TBL, feature_config.name, {'has_per_asic_scope': str(feature_config.has_per_asic_scope)})
db.mod_entry(FEATURE_TBL, feature_config.name, {'has_global_scope': str(feature_config.has_global_scope)})

def update_systemd_config(self, feature_config):
"""Updates `Restart=` field in feature's systemd configuration file
Expand Down Expand Up @@ -364,7 +368,7 @@ class FeatureHandler(object):
def get_multiasic_feature_instances(self, feature, all_instance=False):
# Create feature name suffix depending feature is running in host or namespace or in both
feature_names = (
([feature.name] if feature.has_global_scope or not self.is_multi_npu else []) +
([feature.name] if feature.has_global_scope or all_instance or not self.is_multi_npu else []) +
([(feature.name + '@' + str(asic_inst)) for asic_inst in range(device_info.get_num_npus())
if self.is_multi_npu and (all_instance or feature.has_per_asic_scope)]) +
([(feature.name + '@' + device_info.DPU_NAME_PREFIX + str(dpu_inst)) for dpu_inst in range(self.num_dpus)
Expand Down Expand Up @@ -469,6 +473,17 @@ class FeatureHandler(object):
for ns, db in self.ns_cfg_db.items():
db.mod_entry('FEATURE', feature.name, {'state': feature.state})

def sync_feature_delay_state(self, feature):
current_entry = self._config_db.get_entry('FEATURE', feature.name)
current_feature_delay_state = current_entry.get('delayed') if current_entry else None

if str(feature.delayed) == str(current_feature_delay_state):
return

self._config_db.mod_entry('FEATURE', feature.name, {'delayed': str(feature.delayed)})
for ns, db in self.ns_cfg_db.items():
db.mod_entry('FEATURE', feature.name, {'delayed': str(feature.delayed)})

def set_feature_state(self, feature, state):
self._feature_state_table.set(feature.name, [('state', state)])

Expand Down
22 changes: 13 additions & 9 deletions scripts/process-reboot-cause
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ try:
import sys

from swsscommon import swsscommon
from sonic_py_common import logger
from sonic_py_common import syslogger
except ImportError as err:
raise ImportError("%s - required module not found" % str(err))

Expand All @@ -33,7 +33,7 @@ REDIS_HOSTIP = "127.0.0.1"
state_db = None

# Global logger class instance
sonic_logger = logger.Logger(SYSLOG_IDENTIFIER)
sonic_logger = syslogger.SysLogger(SYSLOG_IDENTIFIER)


# ============================= Functions =============================
Expand All @@ -52,12 +52,16 @@ def read_reboot_cause_files_and_save_state_db():
x = TIME_SORTED_FULL_REBOOT_FILE_LIST[i]
if os.path.isfile(x):
with open(x, "r") as cause_file:
data = json.load(cause_file)
_hash = '{}|{}'.format(REBOOT_CAUSE_TABLE_NAME, data['gen_time'])
state_db.set(state_db.STATE_DB, _hash, 'cause', data['cause'])
state_db.set(state_db.STATE_DB, _hash, 'time', data['time'])
state_db.set(state_db.STATE_DB, _hash, 'user', data['user'])
state_db.set(state_db.STATE_DB, _hash, 'comment', data['comment'])
try:
data = json.load(cause_file)
_hash = '{}|{}'.format(REBOOT_CAUSE_TABLE_NAME, data['gen_time'])
state_db.set(state_db.STATE_DB, _hash, 'cause', data['cause'])
state_db.set(state_db.STATE_DB, _hash, 'time', data['time'])
state_db.set(state_db.STATE_DB, _hash, 'user', data['user'])
state_db.set(state_db.STATE_DB, _hash, 'comment', data['comment'])
except json.decoder.JSONDecodeError as je:
sonic_logger.log_info("Unable to process reload cause file {}: {}".format(x, je))
pass

if len(TIME_SORTED_FULL_REBOOT_FILE_LIST) > 10:
for i in range(len(TIME_SORTED_FULL_REBOOT_FILE_LIST)):
Expand All @@ -68,7 +72,7 @@ def read_reboot_cause_files_and_save_state_db():

def main():
# Configure logger to log all messages INFO level and higher
sonic_logger.set_min_log_priority_info()
sonic_logger.set_min_log_priority(sonic_logger.DEFAULT_LOG_LEVEL)

sonic_logger.log_info("Starting up...")

Expand Down
5 changes: 3 additions & 2 deletions scripts/sonic-host-server
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import dbus.service
import dbus.mainloop.glib

from gi.repository import GObject
from host_modules import config_engine, gcu, host_service, showtech
from host_modules import config_engine, gcu, host_service, showtech, systemd_service


def register_dbus():
Expand All @@ -21,7 +21,8 @@ def register_dbus():
'config': config_engine.Config('config'),
'gcu': gcu.GCU('gcu'),
'host_service': host_service.HostService('host_service'),
'showtech': showtech.Showtech('showtech')
'showtech': showtech.Showtech('showtech'),
'systemd': systemd_service.SystemdService('systemd')
}
for mod_name, handler_class in mod_dict.items():
handlers[mod_name] = handler_class
Expand Down
3 changes: 2 additions & 1 deletion tests/featured/featured_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ def test_feature_config_parsing_defaults(self):

@mock.patch('featured.FeatureHandler.update_systemd_config', mock.MagicMock())
@mock.patch('featured.FeatureHandler.update_feature_state', mock.MagicMock())
@mock.patch('featured.FeatureHandler.sync_feature_asic_scope', mock.MagicMock())
@mock.patch('featured.FeatureHandler.sync_feature_scope', mock.MagicMock())
@mock.patch('featured.FeatureHandler.sync_feature_delay_state', mock.MagicMock())
def test_feature_resync(self):
mock_db = mock.MagicMock()
mock_db.get_entry = mock.MagicMock()
Expand Down
Loading

0 comments on commit b0f57f2

Please sign in to comment.