2021-01-07 21:52:22 -06:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
"""
|
|
|
|
container_checker
|
|
|
|
|
|
|
|
This script is intended to be run by Monit. It will write an alerting message into
|
|
|
|
syslog if it found containers which were expected to run but were not running. At
|
|
|
|
the same time, if some containers were unexpected to run, it also writes an alerting
|
|
|
|
syslog message. Note that if print(...) statement in this script was executed, the
|
|
|
|
string in it will be appended to Monit syslog messages.
|
|
|
|
|
|
|
|
The following is an example in Monit configuration file to show how Monit will run
|
|
|
|
this script:
|
|
|
|
|
|
|
|
check program container_checker with path "/usr/bin/container_checker"
|
|
|
|
if status != 0 for 5 times within 5 cycles then alert repeat every 1 cycles
|
|
|
|
"""
|
|
|
|
|
2021-05-07 10:42:15 -05:00
|
|
|
import docker
|
2021-01-07 21:52:22 -06:00
|
|
|
import sys
|
|
|
|
|
2022-03-03 19:56:08 -06:00
|
|
|
from sonic_py_common import multi_asic, device_info
|
2021-05-07 10:42:15 -05:00
|
|
|
from swsscommon import swsscommon
|
2021-01-07 21:52:22 -06:00
|
|
|
|
2022-09-21 11:20:53 -05:00
|
|
|
EVENTS_PUBLISHER_SOURCE = "sonic-events-host"
|
|
|
|
EVENTS_PUBLISHER_TAG = "event-down-ctr"
|
|
|
|
|
2021-01-07 21:52:22 -06:00
|
|
|
def get_expected_running_containers():
|
|
|
|
"""
|
2021-05-07 10:42:15 -05:00
|
|
|
@summary: This function will get the expected running & always-enabled containers by following the rule:
|
2021-04-02 10:05:46 -05:00
|
|
|
The 'state' field of container in 'FEATURE' table should not be 'disabled' or 'always_disabled'.
|
|
|
|
If the device has Multi-ASIC, this function will get container list by determining the
|
2021-01-07 21:52:22 -06:00
|
|
|
value of field 'has_global_scope', the number of ASICs and the value of field
|
2021-04-02 10:05:46 -05:00
|
|
|
'has_per_asic_scope'.
|
|
|
|
If the device has single ASIC, the container name was put into the list.
|
2022-03-14 06:45:27 -05:00
|
|
|
@return: A set which contains the expected running containers and a set that has
|
2021-05-07 10:42:15 -05:00
|
|
|
containers marked as "always_enabled".
|
2021-01-07 21:52:22 -06:00
|
|
|
"""
|
2022-08-24 19:35:51 -05:00
|
|
|
config_db = swsscommon.ConfigDBConnector()
|
2021-01-07 21:52:22 -06:00
|
|
|
config_db.connect()
|
|
|
|
feature_table = config_db.get_table("FEATURE")
|
|
|
|
|
|
|
|
expected_running_containers = set()
|
2021-05-07 10:42:15 -05:00
|
|
|
always_running_containers = set()
|
2022-08-22 12:08:29 -05:00
|
|
|
|
|
|
|
# Get current asic presence list. For multi_asic system, multi instance containers
|
|
|
|
# should be checked only for asics present.
|
|
|
|
asics_id_presence = multi_asic.get_asic_presence_list()
|
|
|
|
|
|
|
|
# Some services may run all the instances irrespective of asic presence.
|
|
|
|
# Add those to exception list.
|
|
|
|
# database service: Currently services have dependency on all database services to
|
|
|
|
# be up irrespective of asic presence.
|
|
|
|
# bgp service: Currently bgp runs all instances. Once this is fixed to be config driven,
|
|
|
|
# it will be removed from exception list.
|
|
|
|
run_all_instance_list = ['database', 'bgp']
|
|
|
|
|
2021-01-07 21:52:22 -06:00
|
|
|
for container_name in feature_table.keys():
|
2021-04-02 10:05:46 -05:00
|
|
|
if feature_table[container_name]["state"] not in ["disabled", "always_disabled"]:
|
2021-01-07 21:52:22 -06:00
|
|
|
if multi_asic.is_multi_asic():
|
|
|
|
if feature_table[container_name]["has_global_scope"] == "True":
|
|
|
|
expected_running_containers.add(container_name)
|
|
|
|
if feature_table[container_name]["has_per_asic_scope"] == "True":
|
|
|
|
num_asics = multi_asic.get_num_asics()
|
|
|
|
for asic_id in range(num_asics):
|
2022-08-22 12:08:29 -05:00
|
|
|
if asic_id in asics_id_presence or container_name in run_all_instance_list:
|
|
|
|
expected_running_containers.add(container_name + str(asic_id))
|
2021-01-07 21:52:22 -06:00
|
|
|
else:
|
|
|
|
expected_running_containers.add(container_name)
|
2022-02-23 20:06:30 -06:00
|
|
|
if feature_table[container_name]["state"] == 'always_enabled':
|
|
|
|
if multi_asic.is_multi_asic():
|
|
|
|
if feature_table[container_name]["has_global_scope"] == "True":
|
|
|
|
always_running_containers.add(container_name)
|
|
|
|
if feature_table[container_name]["has_per_asic_scope"] == "True":
|
|
|
|
num_asics = multi_asic.get_num_asics()
|
|
|
|
for asic_id in range(num_asics):
|
2022-08-22 12:08:29 -05:00
|
|
|
if asic_id in asics_id_presence or container_name in run_all_instance_list:
|
|
|
|
always_running_containers.add(container_name + str(asic_id))
|
2022-02-23 20:06:30 -06:00
|
|
|
else:
|
|
|
|
always_running_containers.add(container_name)
|
2022-08-22 12:08:29 -05:00
|
|
|
|
2022-03-03 19:56:08 -06:00
|
|
|
if device_info.is_supervisor():
|
|
|
|
always_running_containers.add("database-chassis")
|
2021-05-07 10:42:15 -05:00
|
|
|
return expected_running_containers, always_running_containers
|
2021-01-07 21:52:22 -06:00
|
|
|
|
2021-05-07 10:42:15 -05:00
|
|
|
def get_current_running_from_DB(always_running_containers):
|
2021-01-07 21:52:22 -06:00
|
|
|
"""
|
2021-05-07 10:42:15 -05:00
|
|
|
@summary: This function will get the current running container list
|
|
|
|
from FEATURE table @ STATE_DB, if this table is available.
|
|
|
|
@return: a tuple
|
|
|
|
First: Return value indicating if info can be obtained from
|
|
|
|
DB or not.
|
|
|
|
Second: A set which contains the current running containers,
|
|
|
|
if this info is available in DB.
|
2021-01-07 21:52:22 -06:00
|
|
|
"""
|
|
|
|
running_containers = set()
|
|
|
|
|
2021-05-07 10:42:15 -05:00
|
|
|
state_db = swsscommon.DBConnector("STATE_DB", 0)
|
|
|
|
tbl = swsscommon.Table(state_db, "FEATURE")
|
|
|
|
if not tbl.getKeys():
|
2022-03-14 06:45:27 -05:00
|
|
|
return running_containers
|
2021-05-07 10:42:15 -05:00
|
|
|
|
|
|
|
for name in tbl.getKeys():
|
|
|
|
data = dict(tbl.get(name)[1])
|
|
|
|
if data.get('container_id'):
|
|
|
|
running_containers.add(name)
|
|
|
|
|
|
|
|
DOCKER_CLIENT = docker.DockerClient(base_url='unix://var/run/docker.sock')
|
|
|
|
RUNNING = 'running'
|
|
|
|
for name in always_running_containers:
|
|
|
|
try:
|
|
|
|
container = DOCKER_CLIENT.containers.get(name)
|
|
|
|
container_state = container.attrs.get('State', {})
|
|
|
|
if container_state.get('Status', "") == RUNNING:
|
|
|
|
running_containers.add(name)
|
|
|
|
except (docker.errors.NotFound, docker.errors.APIError) as err:
|
|
|
|
print("Failed to get container '{}'. Error: '{}'".format(name, err))
|
|
|
|
pass
|
|
|
|
|
2022-03-14 06:45:27 -05:00
|
|
|
return running_containers
|
2021-01-07 21:52:22 -06:00
|
|
|
|
2021-05-07 10:42:15 -05:00
|
|
|
|
|
|
|
def get_current_running_from_dockers():
|
|
|
|
"""
|
|
|
|
@summary: This function will get all running containers from
|
|
|
|
the list of docker containers in running state.
|
|
|
|
@return: A set which contains containers that are
|
|
|
|
in running state.
|
|
|
|
"""
|
|
|
|
DOCKER_CLIENT = docker.DockerClient(base_url='unix://var/run/docker.sock')
|
|
|
|
running_containers = set()
|
|
|
|
ctrs = DOCKER_CLIENT.containers
|
|
|
|
try:
|
|
|
|
lst = ctrs.list(filters={"status": "running"})
|
|
|
|
for ctr in lst:
|
|
|
|
running_containers.add(ctr.name)
|
|
|
|
except docker.errors.APIError as err:
|
|
|
|
print("Failed to retrieve the running container list. Error: '{}'".format(err))
|
|
|
|
pass
|
2021-01-07 21:52:22 -06:00
|
|
|
return running_containers
|
|
|
|
|
|
|
|
|
2021-05-07 10:42:15 -05:00
|
|
|
def get_current_running_containers(always_running_containers):
|
|
|
|
"""
|
|
|
|
@summary: This function will get the list of currently running containers.
|
|
|
|
If available in STATE-DB, get from DB else from list of dockers.
|
2022-03-14 06:45:27 -05:00
|
|
|
|
2021-05-07 10:42:15 -05:00
|
|
|
@return: A set of currently running containers.
|
|
|
|
"""
|
|
|
|
|
2022-03-14 06:45:27 -05:00
|
|
|
current_running_containers = get_current_running_from_DB(always_running_containers)
|
|
|
|
current_running_containers.update(get_current_running_from_dockers())
|
2021-05-07 10:42:15 -05:00
|
|
|
return current_running_containers
|
|
|
|
|
|
|
|
|
2022-09-21 11:20:53 -05:00
|
|
|
def publish_events(lst):
|
|
|
|
events_handle = swsscommon.events_init_publisher(EVENTS_PUBLISHER_SOURCE)
|
|
|
|
params = swsscommon.FieldValueMap()
|
|
|
|
|
|
|
|
for ctr in lst:
|
|
|
|
params["name"] = ctr;
|
|
|
|
swsscommon.event_publish(events_handle, EVENTS_PUBLISHER_TAG, params)
|
|
|
|
|
|
|
|
swsscommon.events_deinit_publisher(events_handle)
|
|
|
|
|
|
|
|
|
2021-01-07 21:52:22 -06:00
|
|
|
def main():
|
|
|
|
"""
|
|
|
|
@summary: This function will compare the difference between the current running containers
|
|
|
|
and the containers which were expected to run. If containers which were exepcted
|
|
|
|
to run were not running, then an alerting message will be written into syslog.
|
|
|
|
"""
|
2021-05-07 10:42:15 -05:00
|
|
|
expected_running_containers, always_running_containers = get_expected_running_containers()
|
|
|
|
current_running_containers = get_current_running_containers(always_running_containers)
|
2021-01-07 21:52:22 -06:00
|
|
|
|
2021-05-07 10:42:15 -05:00
|
|
|
expected_running_containers |= always_running_containers
|
2021-01-07 21:52:22 -06:00
|
|
|
not_running_containers = expected_running_containers.difference(current_running_containers)
|
|
|
|
if not_running_containers:
|
2022-09-21 11:20:53 -05:00
|
|
|
publish_events(not_running_containers)
|
2021-01-07 21:52:22 -06:00
|
|
|
print("Expected containers not running: " + ", ".join(not_running_containers))
|
|
|
|
sys.exit(3)
|
|
|
|
|
|
|
|
unexpected_running_containers = current_running_containers.difference(expected_running_containers)
|
|
|
|
if unexpected_running_containers:
|
|
|
|
print("Unexpected running containers: " + ", ".join(unexpected_running_containers))
|
|
|
|
sys.exit(4)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|
2021-05-07 10:42:15 -05:00
|
|
|
sys.exit(0)
|