Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions host_modules/docker_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"swss",
"syncd",
"teamd",
"telemetry",
}

# The set of allowed images that can be managed by this service.
Expand All @@ -42,7 +41,6 @@
"docker-sonic-bmp",
"docker-sonic-gnmi",
"docker-sonic-restapi",
"docker-sonic-telemetry",
"docker-syncd-brcm",
"docker-syncd-cisco",
"docker-teamd",
Expand Down
13 changes: 13 additions & 0 deletions scripts/featured
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class FeatureHandler(object):
FEATURE_STATE_ENABLED = "enabled"
FEATURE_STATE_DISABLED = "disabled"
FEATURE_STATE_FAILED = "failed"
FEATURE_EXCLUSION_LIST = {"telemetry", "frr_bmp"}

def __init__(self, config_db, feature_state_table, device_config, is_advanced_boot):
self._config_db = config_db
Expand Down Expand Up @@ -408,7 +409,15 @@ class FeatureHandler(object):
props = dict([line.split("=") for line in stdout.decode().strip().splitlines()])
return props["UnitFileState"]

def is_exclusion_listed(self, feature_name):
"""Return True if the feature is in the exclusion list."""
return str(feature_name).lower() in self.FEATURE_EXCLUSION_LIST

def enable_feature(self, feature):
if self.is_exclusion_listed(feature.name):
syslog.syslog(syslog.LOG_INFO, f"ExclusionList: skip enabling '{feature.name}'")
return

feature_names, feature_suffixes = self.get_multiasic_feature_instances(feature)
for feature_name in feature_names:
# Check if it is already enabled, if yes skip the system call
Expand Down Expand Up @@ -438,6 +447,10 @@ class FeatureHandler(object):
self.set_feature_state(feature, self.FEATURE_STATE_ENABLED)

def disable_feature(self, feature):
if self.is_exclusion_listed(feature.name):
syslog.syslog(syslog.LOG_INFO, f"ExclusionList: skip disabling '{feature.name}'")
return

feature_names, feature_suffixes = self.get_multiasic_feature_instances(feature)
for feature_name in feature_names:
# Check if it is already disabled, if yes skip the system call
Expand Down
62 changes: 42 additions & 20 deletions tests/featured/featured_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,43 @@ def test_port_init_done_twice(self):
feature_handler.port_listener(key='PortInitDone', op='SET', data=None)
feature_handler.enable_delayed_services.assert_called_once()

@mock.patch("syslog.syslog", side_effect=syslog_side_effect)
def test_enable_and_disable_feature_ExclusionList_skips_actions(self, mock_syslog):
"""Verify that ExclusionList feature 'frr_bmp' is skipped in both enable and disable."""
feature_state_table_mock = mock.Mock()
device_cfg = {"DEVICE_METADATA": {"localhost": {"type": "FixedSwitch"}}}
handler = featured.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_cfg, False)

feat_cfg = {"state": "enabled", "auto_restart": "enabled"}
feature = featured.Feature("frr_bmp", feat_cfg, device_cfg)

with mock.patch.object(handler, "get_multiasic_feature_instances",
return_value=(["frr_bmp"], ["service"])), \
mock.patch.object(handler, "get_systemd_unit_state", return_value="disabled"), \
mock.patch("featured.run_cmd") as mocked_run_cmd, \
mock.patch.object(handler, "set_feature_state") as mocked_set_state:

# --- enable_feature() ---
handler.enable_feature(feature)

mocked_run_cmd.assert_not_called()
mocked_set_state.assert_not_called()
assert any("ExclusionList: skip enabling 'frr_bmp'" in str(c.args[1]) for c in mock_syslog.call_args_list)

mock_syslog.reset_mock()
with mock.patch.object(handler, "get_multiasic_feature_instances",
return_value=(["frr_bmp"], ["service"])), \
mock.patch.object(handler, "get_systemd_unit_state", return_value="enabled"), \
mock.patch("featured.run_cmd") as mocked_run_cmd, \
mock.patch.object(handler, "set_feature_state") as mocked_set_state:

# --- disable_feature() ---
handler.disable_feature(feature)

mocked_run_cmd.assert_not_called()
mocked_set_state.assert_not_called()
assert any("ExclusionList: skip disabling 'frr_bmp'" in str(c.args[1]) for c in mock_syslog.call_args_list)


@mock.patch("syslog.syslog", side_effect=syslog_side_effect)
@mock.patch('sonic_py_common.device_info.get_device_runtime_metadata')
Expand All @@ -361,8 +398,7 @@ def tearDown(self):

def test_feature_events(self, mock_syslog, get_runtime):
MockSelect.set_event_queue([('FEATURE', 'dhcp_relay'),
('FEATURE', 'mux'),
('FEATURE', 'telemetry')])
('FEATURE', 'mux')])
with mock.patch('featured.subprocess') as mocked_subprocess:
popen_mock = mock.Mock()
attrs = {'communicate.return_value': ('output', 'error')}
Expand Down Expand Up @@ -401,7 +437,6 @@ def test_feature_events(self, mock_syslog, get_runtime):
def test_delayed_service(self, mock_syslog, get_runtime):
MockSelect.set_event_queue([('FEATURE', 'dhcp_relay'),
('FEATURE', 'mux'),
('FEATURE', 'telemetry'),
('PORT_TABLE', 'PortInitDone')])
# Note: To simplify testing, subscriberstatetable only read from CONFIG_DB
MockConfigDb.CONFIG_DB['PORT_TABLE'] = {'PortInitDone': {'lanes': '0'}, 'PortConfigDone': {'val': 'true'}}
Expand All @@ -424,11 +459,7 @@ def test_delayed_service(self, mock_syslog, get_runtime):
call(['sudo', 'systemctl', 'daemon-reload'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'unmask', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'enable', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'start', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'daemon-reload'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'unmask', 'telemetry.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'enable', 'telemetry.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'start', 'telemetry.service'], capture_output=True, check=True, text=True)]
call(['sudo', 'systemctl', 'start', 'mux.service'], capture_output=True, check=True, text=True)]

mocked_subprocess.run.assert_has_calls(expected, any_order=True)

Expand All @@ -454,20 +485,15 @@ def test_advanced_reboot(self, mock_syslog, get_runtime):
call(['sudo', 'systemctl', 'daemon-reload'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'unmask', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'enable', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'start', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'daemon-reload'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'unmask', 'telemetry.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'enable', 'telemetry.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'start', 'telemetry.service'], capture_output=True, check=True, text=True)]
call(['sudo', 'systemctl', 'start', 'mux.service'], capture_output=True, check=True, text=True)]

mocked_subprocess.run.assert_has_calls(expected, any_order=True)

def test_portinit_timeout(self, mock_syslog, get_runtime):
print(MockConfigDb.CONFIG_DB)
MockSelect.NUM_TIMEOUT_TRIES = 1
MockSelect.set_event_queue([('FEATURE', 'dhcp_relay'),
('FEATURE', 'mux'),
('FEATURE', 'telemetry')])
('FEATURE', 'mux')])
with mock.patch('featured.subprocess') as mocked_subprocess:
popen_mock = mock.Mock()
attrs = {'communicate.return_value': ('output', 'error')}
Expand All @@ -487,9 +513,5 @@ def test_portinit_timeout(self, mock_syslog, get_runtime):
call(['sudo', 'systemctl', 'daemon-reload'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'unmask', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'enable', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'start', 'mux.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'daemon-reload'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'unmask', 'telemetry.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'enable', 'telemetry.service'], capture_output=True, check=True, text=True),
call(['sudo', 'systemctl', 'start', 'telemetry.service'], capture_output=True, check=True, text=True)]
call(['sudo', 'systemctl', 'start', 'mux.service'], capture_output=True, check=True, text=True)]
mocked_subprocess.run.assert_has_calls(expected, any_order=True)
Loading
Loading