From 5ea55ec43ad56711725b276fe30cc51affc41ec2 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Wed, 24 Sep 2025 18:22:50 +0300 Subject: [PATCH 1/3] [supervisor] make supervisor a submodule Signed-off-by: Stepan Blyschak --- .gitmodules | 3 +++ dockers/docker-base-bookworm/Dockerfile.j2 | 11 ++++++++--- rules/docker-base-bookworm.mk | 2 ++ rules/supervisor.dep | 10 ++++++++++ rules/supervisor.mk | 6 ++++++ src/supervisor | 1 + 6 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 rules/supervisor.dep create mode 100644 rules/supervisor.mk create mode 160000 src/supervisor diff --git a/.gitmodules b/.gitmodules index b6eb45f33b6..b9505e3c8e8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -142,3 +142,6 @@ [submodule "platform/marvell-teralynx/mrvl-teralynx"] path = platform/marvell-teralynx/mrvl-teralynx url = https://github.com/Marvell-switching/mrvl-teralynx.git +[submodule "src/supervisor"] + path = src/supervisor + url = https://github.com/Supervisor/supervisor diff --git a/dockers/docker-base-bookworm/Dockerfile.j2 b/dockers/docker-base-bookworm/Dockerfile.j2 index 2662ac0add8..c63d3a85044 100644 --- a/dockers/docker-base-bookworm/Dockerfile.j2 +++ b/dockers/docker-base-bookworm/Dockerfile.j2 @@ -60,12 +60,17 @@ COPY ["pip.conf", "/etc/pip.conf"] RUN pip3 install --upgrade pip RUN apt purge -y python3-pip +{% if docker_base_bookworm_whls.strip() -%} +# Copy locally-built Python wheel dependencies +{{ copy_files("python-wheels/", docker_base_bookworm_whls.split(' '), "/python-wheels/") }} + +# Install locally-built Python wheel dependencies +{{ install_python_wheels(docker_base_bookworm_whls.split(' ')) }} +{% endif %} + # For templating RUN pip3 install j2cli -# Install supervisor -RUN pip3 install supervisor==4.2.5 - # Add support for supervisord to handle startup dependencies RUN pip3 install supervisord-dependent-startup==1.4.0 diff --git a/rules/docker-base-bookworm.mk b/rules/docker-base-bookworm.mk index afd74ceb56a..dbeebb4b9c8 100644 --- a/rules/docker-base-bookworm.mk +++ b/rules/docker-base-bookworm.mk @@ -16,6 +16,8 @@ ifeq ($(INCLUDE_FIPS), y) $(DOCKER_BASE_BOOKWORM)_DEPENDS += $(FIPS_OPENSSL_LIBSSL) $(FIPS_OPENSSL_LIBSSL_DEV) $(FIPS_OPENSSL) $(SYMCRYPT_OPENSSL) $(FIPS_KRB5) endif +$(DOCKER_BASE_BOOKWORM)_PYTHON_WHEELS += $(SUPERVISOR) + $(DOCKER_BASE_BOOKWORM)_DBG_IMAGE_PACKAGES += $(GDB) $(GDBSERVER) $(VIM) $(OPENSSH) $(SSHPASS) $(STRACE) SONIC_DOCKER_IMAGES += $(DOCKER_BASE_BOOKWORM) diff --git a/rules/supervisor.dep b/rules/supervisor.dep new file mode 100644 index 00000000000..71ef42c49bb --- /dev/null +++ b/rules/supervisor.dep @@ -0,0 +1,10 @@ +SPATH := $($(SUPERVISOR)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/supervisor.mk rules/supervisor.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +SMDEP_FILES := $(addprefix $(SPATH)/,$(shell cd $(SPATH) && git ls-files -- ':!:doc/*')) + +$(SUPERVISOR)_CACHE_MODE := GIT_CONTENT_SHA +$(SUPERVISOR)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(SUPERVISOR)_DEP_FILES := $(DEP_FILES) +$(SUPERVISOR)_SMDEP_FILES := $(SMDEP_FILES) +$(SUPERVISOR)_SMDEP_PATHS := $(SPATH) diff --git a/rules/supervisor.mk b/rules/supervisor.mk new file mode 100644 index 00000000000..0681a207cf3 --- /dev/null +++ b/rules/supervisor.mk @@ -0,0 +1,6 @@ +# supervisor python3 wheel + +SUPERVISOR = supervisor-4.2.5-py2.py3-none-any.whl +$(SUPERVISOR)_SRC_PATH = $(SRC_PATH)/supervisor +$(SUPERVISOR)_PYTHON_VERSION = 3 +SONIC_PYTHON_WHEELS += $(SUPERVISOR) diff --git a/src/supervisor b/src/supervisor new file mode 160000 index 00000000000..99a3e3e240d --- /dev/null +++ b/src/supervisor @@ -0,0 +1 @@ +Subproject commit 99a3e3e240d9d13e143b306818731e224e1e73d2 From f4b2b17a46b0c6307b8dd8c350befef629419f08 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Sep 2025 15:27:55 +0000 Subject: [PATCH 2/3] [supervisord] implement sd_notify Signed-off-by: Stepan Blyschak --- .../etc/supervisor/supervisord.conf | 1 + .../0002-Implement-SD_NOTIFY.patch | 207 ++++++++++++++++++ src/supervisor.patch/series | 1 + 3 files changed, 209 insertions(+) create mode 100644 src/supervisor.patch/0002-Implement-SD_NOTIFY.patch create mode 100644 src/supervisor.patch/series diff --git a/dockers/docker-base-bookworm/etc/supervisor/supervisord.conf b/dockers/docker-base-bookworm/etc/supervisor/supervisord.conf index 6d7d7390e85..9f5734c8bd4 100644 --- a/dockers/docker-base-bookworm/etc/supervisor/supervisord.conf +++ b/dockers/docker-base-bookworm/etc/supervisor/supervisord.conf @@ -7,6 +7,7 @@ chmod=0700 ; socket file mode (default 0700) [supervisord] logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log) pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) +notifysock=/var/run/supervisord_notify.sock ; (path to the notify socket file; default None) childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) user=root diff --git a/src/supervisor.patch/0002-Implement-SD_NOTIFY.patch b/src/supervisor.patch/0002-Implement-SD_NOTIFY.patch new file mode 100644 index 00000000000..9d76ba74c46 --- /dev/null +++ b/src/supervisor.patch/0002-Implement-SD_NOTIFY.patch @@ -0,0 +1,207 @@ +From 3ab7d6f2007188ddce80fa3c1d6196cf888b3051 Mon Sep 17 00:00:00 2001 +From: Stepan Blyschak +Date: Mon, 29 Sep 2025 14:09:22 +0000 +Subject: [PATCH] Implement SD_NOTIFY + +Signed-off-by: Stepan Blyschak +--- + supervisor/options.py | 20 ++++++++++++++++++++ + supervisor/process.py | 23 +++++++++++++++++++++++ + supervisor/supervisord.py | 17 +++++++++++++++++ + supervisor/tests/base.py | 5 +++++ + 4 files changed, 65 insertions(+) + +diff --git a/supervisor/options.py b/supervisor/options.py +index f5cb0c9..49415fb 100644 +--- a/supervisor/options.py ++++ b/supervisor/options.py +@@ -410,6 +410,8 @@ class ServerOptions(Options): + unlink_pidfile = False + unlink_socketfiles = False + mood = states.SupervisorStates.RUNNING ++ notify_sock_path = None ++ notify_sock = None + + def __init__(self): + Options.__init__(self) +@@ -521,6 +523,8 @@ class ServerOptions(Options): + + self.server_configs = sconfigs = section.server_configs + ++ self.notify_sock_path = section.notify_sock_path ++ + # we need to set a fallback serverurl that process.spawn can use + + # prefer a unix domain socket +@@ -645,6 +649,9 @@ class ServerOptions(Options): + section.identifier = get('identifier', 'supervisor') + section.nodaemon = boolean(get('nodaemon', 'false')) + section.silent = boolean(get('silent', 'false')) ++ section.notify_sock_path = get('notifysock', None) ++ if section.notify_sock_path is not None: ++ section.notify_sock_path = existing_dirpath(section.notify_sock_path) + + tempdir = tempfile.gettempdir() + section.childlogdir = existing_directory(get('childlogdir', tempdir)) +@@ -1246,6 +1253,11 @@ class ServerOptions(Options): + # also https://web.archive.org/web/20160729222427/http://www.plope.com/software/collector/253 + server.close() + ++ def close_notify_socket(self): ++ if self.notify_sock is None: ++ return ++ self.notify_sock.close() ++ + def close_logger(self): + self.logger.close() + +@@ -1282,6 +1294,14 @@ class ServerOptions(Options): + except ValueError as why: + self.usage(why.args[0]) + ++ def open_notify_socket(self): ++ if self.notify_sock_path is None: ++ return ++ self._try_unlink(self.notify_sock_path) ++ self.notify_sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) ++ self.notify_sock.bind(self.notify_sock_path) ++ self.notify_sock.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1) ++ + def get_autochildlog_name(self, name, identifier, channel): + prefix='%s-%s---%s-' % (name, channel, identifier) + logfile = self.mktempfile( +diff --git a/supervisor/process.py b/supervisor/process.py +index b394be8..bb6a69c 100644 +--- a/supervisor/process.py ++++ b/supervisor/process.py +@@ -310,6 +310,9 @@ class Subprocess(object): + # set environment + env = os.environ.copy() + env['SUPERVISOR_ENABLED'] = '1' ++ notify_sock_path = self.config.options.notify_sock_path ++ if notify_sock_path is not None: ++ env['NOTIFY_SOCKET'] = notify_sock_path + serverurl = self.config.serverurl + if serverurl is None: # unset + serverurl = self.config.options.serverurl # might still be None +@@ -717,6 +720,17 @@ class Subprocess(object): + self.pid)) + self.kill(signal.SIGKILL) + ++ def handle_sd_notify(self, msg): ++ for kv in msg.splitlines(): ++ if kv == "READY=1": ++ if self.state == ProcessStates.STARTING: ++ self.delay = 0 ++ self.backoff = 0 ++ self.change_state(ProcessStates.RUNNING) ++ msg = 'entered RUNNING state, process sent READY=1' ++ self.config.options.logger.info('success: %s %s' % (self.config.name, msg)) ++ ++ + class FastCGISubprocess(Subprocess): + """Extends Subprocess class to handle FastCGI subprocesses""" + +@@ -841,11 +855,20 @@ class ProcessGroupBase(object): + def before_remove(self): + pass + ++ def handle_sd_notify(self, _msg, _pid): ++ pass ++ + class ProcessGroup(ProcessGroupBase): + def transition(self): + for proc in self.processes.values(): + proc.transition() + ++ def handle_sd_notify(self, msg, pid): ++ for proc in self.processes.values(): ++ if proc.pid == pid: ++ proc.handle_sd_notify(msg) ++ break ++ + class FastCGIProcessGroup(ProcessGroup): + + def __init__(self, config, **kwargs): +diff --git a/supervisor/supervisord.py b/supervisor/supervisord.py +index 0a4f3e6..968e4d2 100755 +--- a/supervisor/supervisord.py ++++ b/supervisor/supervisord.py +@@ -85,6 +85,7 @@ class Supervisor: + for config in self.options.process_group_configs: + self.add_process_group(config) + self.options.openhttpservers(self) ++ self.options.open_notify_socket() + self.options.setsignals() + if (not self.options.nodaemon) and self.options.first: + self.options.daemonize() +@@ -176,6 +177,7 @@ class Supervisor: + timeout = 1 # this cannot be fewer than the smallest TickEvent (5) + + socket_map = self.options.get_socket_map() ++ notify_sock = self.options.notify_sock + + while 1: + combined_map = {} +@@ -200,6 +202,9 @@ class Supervisor: + # killing everything), it's OK to shutdown or reload + raise asyncore.ExitNow + ++ if self.options.notify_sock: ++ self.options.poller.register_readable(notify_sock.fileno()) ++ + for fd, dispatcher in combined_map.items(): + if dispatcher.readable(): + self.options.poller.register_readable(fd) +@@ -238,6 +243,17 @@ class Supervisor: + except: + combined_map[fd].handle_error() + ++ if notify_sock and notify_sock.fileno() in r: ++ import socket ++ import struct ++ data, ancdata, _, _ = notify_sock.recvmsg(4096, socket.CMSG_SPACE(struct.calcsize("3i"))) ++ msg = data.decode("utf-8") ++ for cmsg_level, cmsg_type, cmsg_data in ancdata: ++ if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_CREDENTIALS: ++ pid, _, _ = struct.unpack("3i", cmsg_data) ++ for group in pgroups: ++ group.handle_sd_notify(msg, pid) ++ + for group in pgroups: + group.transition() + +@@ -358,6 +374,7 @@ def main(args=None, test=False): + else: + go(options) + options.close_httpservers() ++ options.close_notify_socket() + options.close_logger() + first = False + if test or (options.mood < SupervisorStates.RESTARTING): +diff --git a/supervisor/tests/base.py b/supervisor/tests/base.py +index f608b2b..ed795db 100644 +--- a/supervisor/tests/base.py ++++ b/supervisor/tests/base.py +@@ -31,6 +31,8 @@ class DummyOptions: + make_pipes_exception = None + remove_exception = None + write_exception = None ++ notify_sock_path = None ++ notify_sock = None + + def __init__(self): + self.identifier = 'supervisor' +@@ -117,6 +119,9 @@ class DummyOptions: + def openhttpservers(self, supervisord): + self.httpservers_opened = True + ++ def open_notify_socket(self): ++ pass ++ + def daemonize(self): + self.daemonized = True + +-- +2.39.5 + diff --git a/src/supervisor.patch/series b/src/supervisor.patch/series new file mode 100644 index 00000000000..0e51b9f4ec3 --- /dev/null +++ b/src/supervisor.patch/series @@ -0,0 +1 @@ +0002-Implement-SD_NOTIFY.patch From 661db8e6cbe53eb9229fc679a80034e5e3f9aa15 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Mon, 6 Oct 2025 11:59:32 +0300 Subject: [PATCH 3/3] Add linux check for notify_sock and set notify_sock to None Signed-off-by: Stepan Blyschak --- .../0002-Implement-SD_NOTIFY.patch | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/supervisor.patch/0002-Implement-SD_NOTIFY.patch b/src/supervisor.patch/0002-Implement-SD_NOTIFY.patch index 9d76ba74c46..e44b011bb0e 100644 --- a/src/supervisor.patch/0002-Implement-SD_NOTIFY.patch +++ b/src/supervisor.patch/0002-Implement-SD_NOTIFY.patch @@ -1,18 +1,18 @@ -From 3ab7d6f2007188ddce80fa3c1d6196cf888b3051 Mon Sep 17 00:00:00 2001 +From f8f4ff9a2fe6e5f2bf30919d08d7e0a5d7511269 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak -Date: Mon, 29 Sep 2025 14:09:22 +0000 +Date: Mon, 6 Oct 2025 11:58:54 +0300 Subject: [PATCH] Implement SD_NOTIFY Signed-off-by: Stepan Blyschak --- - supervisor/options.py | 20 ++++++++++++++++++++ + supervisor/options.py | 23 +++++++++++++++++++++++ supervisor/process.py | 23 +++++++++++++++++++++++ supervisor/supervisord.py | 17 +++++++++++++++++ supervisor/tests/base.py | 5 +++++ - 4 files changed, 65 insertions(+) + 4 files changed, 68 insertions(+) diff --git a/supervisor/options.py b/supervisor/options.py -index f5cb0c9..49415fb 100644 +index f5cb0c9..e537c97 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -410,6 +410,8 @@ class ServerOptions(Options): @@ -33,17 +33,19 @@ index f5cb0c9..49415fb 100644 # we need to set a fallback serverurl that process.spawn can use # prefer a unix domain socket -@@ -645,6 +649,9 @@ class ServerOptions(Options): +@@ -645,6 +649,11 @@ class ServerOptions(Options): section.identifier = get('identifier', 'supervisor') section.nodaemon = boolean(get('nodaemon', 'false')) section.silent = boolean(get('silent', 'false')) + section.notify_sock_path = get('notifysock', None) + if section.notify_sock_path is not None: ++ if sys.platform != 'linux': ++ raise ValueError("notifysock is only supported on Linux platform") + section.notify_sock_path = existing_dirpath(section.notify_sock_path) tempdir = tempfile.gettempdir() section.childlogdir = existing_directory(get('childlogdir', tempdir)) -@@ -1246,6 +1253,11 @@ class ServerOptions(Options): +@@ -1246,6 +1255,12 @@ class ServerOptions(Options): # also https://web.archive.org/web/20160729222427/http://www.plope.com/software/collector/253 server.close() @@ -51,11 +53,12 @@ index f5cb0c9..49415fb 100644 + if self.notify_sock is None: + return + self.notify_sock.close() ++ self.notify_sock = None + def close_logger(self): self.logger.close() -@@ -1282,6 +1294,14 @@ class ServerOptions(Options): +@@ -1282,6 +1297,14 @@ class ServerOptions(Options): except ValueError as why: self.usage(why.args[0]) @@ -203,5 +206,5 @@ index f608b2b..ed795db 100644 self.daemonized = True -- -2.39.5 +2.48.1