Commit 21147419 authored by Jérome Perrin's avatar Jérome Perrin

testnode: make killall support processes with changed title

testnode uses ProcessManager.killall to terminate all processes from a
path. To determine if a process is from a path, it looks at the command
line. This does not work for processes using setproctitle to change
their command line.

We can see in ps:

    $ ps -edf | grep nginx
    slapuse+ 115059  45574  0 16:14 ?        00:00:00 nginx: master process /srv/slapgrid/slappart46/t/cvt/i/0/tmp/shared/nginx/6d79cb0e7d81dce1be97eec8a5712f08/sbin/nginx -c /srv/slapgrid/slappart46/t/cvt/i/0/tmp/inst/T-0/etc/nginx-master-introspection.conf
    slapuse+ 115090 115059  0 16:14 ?        00:00:00 nginx: worker process

or by looking at cmdline, which is what psutil.Process.cmdline is using:

    $ cat /proc/115090/cmdline
    nginx: worker process

and that's why sometimes when cancelling a software release test while
it is running tests from a software using nginx, some processes are
leaked, they keep using the port and next test running on this testnode
fail.

In that case, killall is called with /srv/slapgrid/slappart46/t/cvt , we
can not find such process with cmdline, but we can extend this heuristic
to use the current working directory:

    $ ls -al /proc/115090/cwd
    lrwxrwxrwx 1 slapuser46 slapuser46 0 Oct 19 16:16 /proc/115090/cwd -> /srv/slapgrid/slappart46/t/cvt/i/0/tmp/inst/T-0

This also applies an optimization of only considering processes of the
current unix user.
parent 4463bfb4
Pipeline #30798 running with stage
##############################################################################
#
# Copyright (c) 2023 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import os
import shutil
import subprocess
import tempfile
import unittest
import sys
import time
from erp5.util.testnode.ProcessManager import ProcessManager
class TestProcessManagerKillAll(unittest.TestCase):
def setUp(self):
self.path = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.path)
self.script = os.path.join(self.path, 'script')
with open(self.script, 'w') as f:
f.write('#!/bin/sh\nsleep 30')
os.chmod(self.script, 0o700)
self.pm = ProcessManager()
self.start_time = time.time()
def tearDown(self):
self.assertLess(
time.time() - self.start_time,
29,
'Process did not stop in time',
)
def test_killall_script_in_path(self):
process = subprocess.Popen([self.script])
self.pm.killall(self.path)
process.communicate()
self.assertTrue(process.poll())
def test_killall_another_path(self):
process = subprocess.Popen([self.script])
self.pm.killall("another path")
self.assertIsNone(process.poll())
process.kill()
process.communicate()
process.wait()
def test_killall_proctitle_cwd(self):
with open(self.script, 'w') as f:
f.write('''if 1:
import setproctitle
setproctitle.setproctitle('hidden')
import time
time.sleep(30)
''')
process = subprocess.Popen([sys.executable, self.script], cwd=self.path)
self.pm.killall(self.path)
process.communicate()
self.assertTrue(process.poll())
...@@ -24,13 +24,14 @@ ...@@ -24,13 +24,14 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# #
############################################################################## ##############################################################################
import getpass
import os import os
import psutil import psutil
import re import re
import subprocess
import threading
import signal import signal
import subprocess
import sys import sys
import threading
import time import time
from . import logger from . import logger
...@@ -204,15 +205,18 @@ class ProcessManager(object): ...@@ -204,15 +205,18 @@ class ProcessManager(object):
re.findall(br'^ (--\w+)', self.spawn(program_path, '--help')['stdout'], re.M)) re.findall(br'^ (--\w+)', self.spawn(program_path, '--help')['stdout'], re.M))
def killall(self, path): def killall(self, path):
""" """Kill all process from the user running in a given path
Kill processes of given name, only if they're orphan or subprocesses of
the testnode. Processes from a given path are either executables contained in path or
executables running with current directory contained in path.
""" """
to_kill_list = [] to_kill_list = []
pid = os.getpid() current_username = getpass.getuser()
for process in psutil.process_iter(): for process in psutil.process_iter(['username']):
if process.info['username'] != current_username:
continue
try: try:
if not(path in str(process.cmdline())): if path not in str(process.cmdline()) and path not in process.cwd():
continue continue
except (psutil.AccessDenied, psutil.NoSuchProcess): except (psutil.AccessDenied, psutil.NoSuchProcess):
continue continue
......
...@@ -60,6 +60,7 @@ setup(name=name, ...@@ -60,6 +60,7 @@ setup(name=name,
'test': [ 'test': [
'mock; python_version < "3"', 'mock; python_version < "3"',
'psutil >= 0.5.0', 'psutil >= 0.5.0',
'setproctitle',
'slapos.core', 'slapos.core',
'xml_marshaller', 'xml_marshaller',
] ]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment