This is an automated email from the ASF dual-hosted git repository.

hapylestat pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new d9e90e9  [AMBARI-22888] Cancel operation during package deployment 
causing repository manager to be broken (dgrinenko)
d9e90e9 is described below

commit d9e90e907d5cd6c970940e3a5c7ea9e1c9d966a8
Author: Reishin <[email protected]>
AuthorDate: Thu Feb 1 15:17:39 2018 +0200

    [AMBARI-22888] Cancel operation during package deployment causing 
repository manager to be broken (dgrinenko)
---
 .../test/python/ambari_agent/TestProcessUtils.py   | 224 ---------------------
 .../src/test/python/ambari_agent/TestShell.py      | 177 +++++++++++-----
 .../main/python/ambari_commons/process_utils.py    | 100 ---------
 .../src/main/python/ambari_commons/shell.py        | 182 ++++++++++++++---
 4 files changed, 280 insertions(+), 403 deletions(-)

diff --git a/ambari-agent/src/test/python/ambari_agent/TestProcessUtils.py 
b/ambari-agent/src/test/python/ambari_agent/TestProcessUtils.py
deleted file mode 100644
index 8331910..0000000
--- a/ambari-agent/src/test/python/ambari_agent/TestProcessUtils.py
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-'''
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-'''
-
-from ambari_agent import main
-
-main.MEMORY_LEAK_DEBUG_FILEPATH = "/tmp/memory_leak_debug.out"
-import unittest
-import signal
-import subprocess, time
-from mock.mock import patch, MagicMock, PropertyMock, call
-from ambari_commons import process_utils
-
-process_tree = {"111": "222\n 22",
-                "222": "333\n 33",
-                "22": "44\n 444",}
-
-
-class TestProcessUtils(unittest.TestCase):
-  @patch("subprocess.Popen")
-  def test_kill(self, popen_mock):
-    process_mock = MagicMock()
-    process_mock.communicate.return_value = (None, None)
-    returncode_mock = PropertyMock()
-    returncode_mock.return_value = 0
-    type(process_mock).returncode = returncode_mock
-    popen_mock.return_value = process_mock
-    process_utils.kill_pids(["12321113230", "2312415453"], signal.SIGTERM)
-    expected = [call(['kill', '-15', '12321113230', '2312415453'], stderr=-1, 
stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("subprocess.Popen")
-  def test_get_children(self, popen_mock):
-
-    process_mock = MagicMock()
-    process_mock.communicate.return_value = ("123 \n \n 321\n", None)
-    popen_mock.return_value = process_mock
-    returncode_mock = PropertyMock()
-    returncode_mock.return_value = 0
-    type(process_mock).returncode = returncode_mock
-    result = process_utils.get_children("2312415453")
-
-    self.assertEquals(result, ["123", "321"])
-
-    expected = [
-      call(['ps', '-o', 'pid', '--no-headers', '--ppid', '2312415453'], 
stderr=subprocess.PIPE, stdout=subprocess.PIPE)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("subprocess.Popen")
-  def test_get_flat_process_tree(self, popen_mock):
-    def side_effect(*args, **kwargs):
-      process_mock = MagicMock()
-      returncode_mock = PropertyMock()
-      returncode_mock.return_value = 0
-      type(process_mock).returncode = returncode_mock
-      if args[0][5] in process_tree.keys():
-        process_mock.communicate.return_value = (process_tree[args[0][5]], 
None)
-      else:
-        process_mock.communicate.return_value = ("", None)
-      return process_mock
-
-    popen_mock.side_effect = side_effect
-    result = process_utils.get_flat_process_tree("111")
-    self.assertEquals(result, ['111', '222', '333', '33', '22', '44', '444'])
-
-    expected = [call(['ps', '-o', 'pid', '--no-headers', '--ppid', '111'], 
stderr=-1, stdout=-1),
-                call(['ps', '-o', 'pid', '--no-headers', '--ppid', '222'], 
stderr=-1, stdout=-1),
-                call(['ps', '-o', 'pid', '--no-headers', '--ppid', '333'], 
stderr=-1, stdout=-1),
-                call(['ps', '-o', 'pid', '--no-headers', '--ppid', '33'], 
stderr=-1, stdout=-1),
-                call(['ps', '-o', 'pid', '--no-headers', '--ppid', '22'], 
stderr=-1, stdout=-1),
-                call(['ps', '-o', 'pid', '--no-headers', '--ppid', '44'], 
stderr=-1, stdout=-1),
-                call(['ps', '-o', 'pid', '--no-headers', '--ppid', '444'], 
stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("subprocess.Popen")
-  def test_get_command_by_pid(self, popen_mock):
-
-    process_mock = MagicMock()
-    process_mock.communicate.return_value = ("yum something", None)
-    returncode_mock = PropertyMock()
-    returncode_mock.return_value = 0
-    type(process_mock).returncode = returncode_mock
-    popen_mock.return_value = process_mock
-
-    result = process_utils.get_command_by_pid("2312415453")
-
-    self.assertEquals(result, "yum something")
-
-    expected = [call(['ps', '-p', '2312415453', '-o', 'command', 
'--no-headers'], stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("subprocess.Popen")
-  def test_get_command_by_pid_not_exist(self, popen_mock):
-
-    process_mock = MagicMock()
-    process_mock.communicate.return_value = ("", None)
-    returncode_mock = PropertyMock()
-    returncode_mock.return_value = 1
-    type(process_mock).returncode = returncode_mock
-    popen_mock.return_value = process_mock
-
-    result = process_utils.get_command_by_pid("2312415453")
-
-    self.assertEquals(result, "NOT_FOUND[2312415453]")
-
-    expected = [call(['ps', '-p', '2312415453', '-o', 'command', 
'--no-headers'], stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("subprocess.Popen")
-  def test_is_process_running(self, popen_mock):
-
-    process_mock = MagicMock()
-    process_mock.communicate.return_value = ("2312415453", None)
-    returncode_mock = PropertyMock()
-    returncode_mock.return_value = 0
-    type(process_mock).returncode = returncode_mock
-    popen_mock.return_value = process_mock
-
-    result = process_utils.is_process_running("2312415453")
-
-    self.assertEquals(result, True)
-
-    expected = [call(['ps', '-p', '2312415453', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("subprocess.Popen")
-  def test_is_process_not_running(self, popen_mock):
-
-    process_mock = MagicMock()
-    process_mock.communicate.return_value = ("", None)
-    returncode_mock = PropertyMock()
-    returncode_mock.return_value = 1
-    type(process_mock).returncode = returncode_mock
-    popen_mock.return_value = process_mock
-
-    result = process_utils.is_process_running("2312415453")
-
-    self.assertEquals(result, False)
-
-    expected = [call(['ps', '-p', '2312415453', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("subprocess.Popen")
-  def test_get_processes_running(self, popen_mock):
-    def side_effect(*args, **kwargs):
-      process_mock = MagicMock()
-      returncode_mock = PropertyMock()
-      if args[0][2] == "4321":
-        returncode_mock.return_value = 0
-        process_mock.communicate.return_value = ("4321", None)
-      else:
-        returncode_mock.return_value = 1
-        process_mock.communicate.return_value = (None, None)
-      type(process_mock).returncode = returncode_mock
-      return process_mock
-
-    popen_mock.side_effect = side_effect
-
-    result = process_utils.get_processes_running(["1234", "4321"])
-
-    self.assertEquals(result, ["4321"])
-
-    expected = [call(['ps', '-p', '1234', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1),
-                call(['ps', '-p', '4321', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-
-  @patch("time.sleep")
-  @patch("subprocess.Popen")
-  def test_wait_for_process_death(self, popen_mock, sleep_mock):
-
-    process_mock = MagicMock()
-    process_mock.communicate.side_effect = [("4321", None),("4321", 
None),(None, None)]
-    returncode_mock = PropertyMock()
-    returncode_mock.side_effect = [0, 0, 1]
-    type(process_mock).returncode = returncode_mock
-    popen_mock.return_value = process_mock
-
-    process_utils.wait_for_process_death("4321")
-
-    expected = [call(['ps', '-p', '4321', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1),
-                call(['ps', '-p', '4321', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1),
-                call(['ps', '-p', '4321', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-    expected = [call(0.1), call(0.1)]
-    self.assertEquals(sleep_mock.call_args_list, expected)
-
-  @patch("time.sleep")
-  @patch("subprocess.Popen")
-  def test_wait_for_entire_process_tree_death(self, popen_mock, sleep_mock):
-
-    process_mock = MagicMock()
-    process_mock.communicate.side_effect = [("1234", None), (None, None), 
("4321", None), ("4321", None), (None, None)]
-    returncode_mock = PropertyMock()
-    returncode_mock.side_effect = [0, 1, 0, 0, 1]
-    type(process_mock).returncode = returncode_mock
-    popen_mock.return_value = process_mock
-
-    process_utils.wait_for_entire_process_tree_death(["1234", "4321"])
-
-    expected = [call(['ps', '-p', '1234', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1),
-                call(['ps', '-p', '1234', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1),
-                call(['ps', '-p', '4321', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1),
-                call(['ps', '-p', '4321', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1),
-                call(['ps', '-p', '4321', '-o', 'pid', '--no-headers'], 
stderr=-1, stdout=-1)]
-    self.assertEquals(popen_mock.call_args_list, expected)
-    expected = [call(0.1), call(0.1), call(0.1)]
-    self.assertEquals(sleep_mock.call_args_list, expected)
diff --git a/ambari-agent/src/test/python/ambari_agent/TestShell.py 
b/ambari-agent/src/test/python/ambari_agent/TestShell.py
index 47923bd..0f72020 100644
--- a/ambari-agent/src/test/python/ambari_agent/TestShell.py
+++ b/ambari-agent/src/test/python/ambari_agent/TestShell.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-'''
+"""
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
@@ -9,71 +9,138 @@ regarding copyright ownership.  The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at
-
     http://www.apache.org/licenses/LICENSE-2.0
-
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-'''
+"""
+from contextlib import contextmanager
 
-from ambari_agent import main
-main.MEMORY_LEAK_DEBUG_FILEPATH = "/tmp/memory_leak_debug.out"
-import os
 import unittest
-import tempfile
+import signal
 from mock.mock import patch, MagicMock, call
-from ambari_agent.AmbariConfig import AmbariConfig
 from ambari_commons import shell
-from ambari_commons.shell import shellRunner
-from sys import platform as _platform
-from only_for_platform import not_for_platform, PLATFORM_WINDOWS
-import subprocess, time
+from ambari_commons import OSCheck
+from StringIO import StringIO
+
+ROOT_PID = 10
+ROOT_PID_CHILDREN = [10, 11, 12, 13]
+shell.logger = MagicMock()  # suppress any log output
+
+__proc_fs = {
+  "/proc/10/task/10/children": "11 12",
+  "/proc/10/comm": "a",
+  "/proc/10/cmdline": "",
+
+  "/proc/11/task/11/children": "13",
+  "/proc/11/comm": "b",
+  "/proc/11/cmdline": "",
+
+  "/proc/12/task/12/children": "",
+  "/proc/12/comm": "c",
+  "/proc/12/cmdline": "",
+
+  "/proc/13/task/13/children": "",
+  "/proc/13/comm": "d",
+  "/proc/13/cmdline": ""
+}
+
+__proc_fs_yum = {
+  "/proc/10/task/10/children": "11",
+  "/proc/10/comm": "a",
+  "/proc/10/cmdline": "",
+
+  "/proc/11/task/11/children": "",
+  "/proc/11/comm": "yum",
+  "/proc/11/cmdline": "yum install something"
+}
+
+# Remove any wait delay, no need for tests
+__old_waiter = shell.wait_for_process_list_kill
+
+
+def __wait_for_process_list_kill(pids, timeout=5, check_step_time=0.1):
+  return __old_waiter(pids, 0, check_step_time)
+
+
+shell.wait_for_process_list_kill = __wait_for_process_list_kill
+
+
+class FakeSignals(object):
+  SIGTERM = signal.SIG_IGN
+  SIGKILL = signal.SIG_IGN
+
+
+@contextmanager
+def _open_mock(path, open_mode):
+  if path in __proc_fs:
+    yield StringIO(__proc_fs[path])
+  else:
+    yield StringIO("")
+
+
+@contextmanager
+def _open_mock_yum(path, open_mode):
+  if path in __proc_fs:
+    yield StringIO(__proc_fs_yum[path])
+  else:
+    yield StringIO("")
+
 
-@not_for_platform(PLATFORM_WINDOWS)
 class TestShell(unittest.TestCase):
 
+  @patch("__builtin__.open", new=MagicMock(side_effect=_open_mock))
+  def test_get_all_children(self):
+
+    pid_list = [item[0] for item in shell.get_all_children(ROOT_PID)]
+
+    self.assertEquals(len(ROOT_PID_CHILDREN), len(pid_list))
+    self.assertEquals(ROOT_PID, pid_list[0])
+
+    for i in ROOT_PID_CHILDREN:
+      self.assertEquals(True, i in pid_list)
+
+  @patch("__builtin__.open", new=MagicMock(side_effect=_open_mock))
+  @patch.object(OSCheck, "get_os_family", new=MagicMock(return_value="redhat"))
+  @patch.object(shell, "signal", new_callable=FakeSignals)
+  @patch("os.listdir")
+  @patch("os.kill")
+  def test_kill_process_with_children(self, os_kill_mock, os_list_dir_mock, 
fake_signals):
+    pid_list = [item[0] for item in shell.get_all_children(ROOT_PID)]
+    pid_list_str = [str(i) for i in ROOT_PID_CHILDREN]
+    reverse_pid_list = sorted(pid_list, reverse=True)
+    os_list_dir_mock.side_effect = [pid_list_str, [], [], []]
+
+    shell.kill_process_with_children(ROOT_PID)
+
+    # test pid kill by SIGTERM
+    os_kill_pids = [item[0][0] for item in os_kill_mock.call_args_list]
+    self.assertEquals(len(os_kill_pids), len(pid_list))
+    self.assertEquals(reverse_pid_list, os_kill_pids)
+
+    os_kill_mock.reset_mock()
+    os_list_dir_mock.reset_mock()
+
+    os_list_dir_mock.side_effect = [pid_list_str, pid_list_str, pid_list_str, 
pid_list_str, [], []]
+    shell.kill_process_with_children(ROOT_PID)
+
+    # test pid kill by SIGKILL
+    os_kill_pids = [item[0][0] for item in os_kill_mock.call_args_list]
+    self.assertEquals(len(os_kill_pids), len(pid_list)*2)
+    self.assertEquals(reverse_pid_list + reverse_pid_list, os_kill_pids)
+
+  @patch("__builtin__.open", new=MagicMock(side_effect=_open_mock_yum))
+  @patch.object(OSCheck, "get_os_family", new=MagicMock(return_value="redhat"))
+  @patch.object(shell, "signal", new_callable=FakeSignals)
+  @patch("os.listdir")
+  @patch("os.kill")
+  def test_kill_process_with_children_except_yum(self, os_kill_mock, 
os_list_dir_mock, fake_signals):
+    os_list_dir_mock.side_effect = [["10", "12", "20"], [], [], []]
+    shell.kill_process_with_children(ROOT_PID)
 
-  @patch("os.setuid")
-  def test_changeUid(self, os_setUIDMock):
-    shell.threadLocal.uid = 9999
-    shell.changeUid()
-    self.assertTrue(os_setUIDMock.called)
-
-
-  @patch("pwd.getpwnam")
-  def test_shellRunner_run(self, getpwnamMock):
-    sh = shellRunner()
-    result = sh.run(['echo'])
-    self.assertEquals(result['exitCode'], 0)
-    self.assertEquals(result['error'], '')
-
-    getpwnamMock.return_value = [os.getuid(), os.getuid(), os.getuid()]
-    result = sh.run(['echo'], 'non_exist_user_name')
-    self.assertEquals(result['exitCode'], 0)
-    self.assertEquals(result['error'], '')
-
-  def test_kill_process_with_children(self):
-    if _platform == "linux" or _platform == "linux2": # Test is Linux-specific
-      sleep_cmd = "sleep 314"
-      test_cmd = """ (({0}) & ({0} & {0})) """.format(sleep_cmd)
-      # Starting process tree (multiple process groups)
-      test_process = subprocess.Popen(test_cmd, stderr=subprocess.PIPE, 
stdout=subprocess.PIPE, shell=True)
-      time.sleep(0.3) # Delay to allow subprocess to start
-      # Check if processes are running
-      ps_cmd = """ps auxww """
-      ps_process = subprocess.Popen(ps_cmd, stderr=subprocess.PIPE, 
stdout=subprocess.PIPE, shell=True)
-      (out, err) = ps_process.communicate()
-      self.assertTrue(sleep_cmd in out)
-      # Kill test process
-      shell.kill_process_with_children(test_process.pid)
-      test_process.communicate()
-      # Now test process should not be running
-      ps_process = subprocess.Popen(ps_cmd, stderr=subprocess.PIPE, 
stdout=subprocess.PIPE, shell=True)
-      (out, err) = ps_process.communicate()
-      self.assertFalse(sleep_cmd in out)
-    else:
-      # Do not run under other systems
-      pass
+    # test clean pid by SIGTERM
+    os_kill_pids = [item[0][0] for item in os_kill_mock.call_args_list]
+    self.assertEquals(len(os_kill_pids), 1)
+    self.assertEquals([10], os_kill_pids)
diff --git a/ambari-common/src/main/python/ambari_commons/process_utils.py 
b/ambari-common/src/main/python/ambari_commons/process_utils.py
deleted file mode 100644
index 50ffd02..0000000
--- a/ambari-common/src/main/python/ambari_commons/process_utils.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# !/usr/bin/env python
-
-'''
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-'''
-
-import subprocess
-import time
-
-check_time_delay = 0.1  # seconds between checks of process killed
-
-
-def get_children(pid):
-  PSCMD = ["ps", "-o", "pid", "--no-headers", "--ppid", str(pid)]
-  ps_process = subprocess.Popen(PSCMD, stdout=subprocess.PIPE, 
stderr=subprocess.PIPE)
-  stdout, stderr = ps_process.communicate()
-  if ps_process.returncode != 0:
-    return []
-  return stdout.split()
-
-
-def get_flat_process_tree(pid):
-  """
-  :param pid: process id of parent process
-  :return: list of child process pids. Resulting list also includes parent pid
-  """
-  res = [str(pid)]
-  children = get_children(pid)
-  for child in children:
-    res += get_flat_process_tree(child)
-  return res
-
-
-def kill_pids(pids, signal):
-  from resource_management.core.exceptions import Fail
-  CMD = ["kill", "-" + str(signal)]
-  CMD.extend(pids)
-  process = subprocess.Popen(CMD, stdout=subprocess.PIPE, 
stderr=subprocess.PIPE)
-  stdout, stderr = process.communicate()
-  if process.returncode != 0:
-    raise Fail("Unable to kill PIDs {0} : {1}".format(str(pids),stderr))
-
-
-def get_command_by_pid(pid):
-  CMD = ["ps", "-p", str(pid), "-o", "command", "--no-headers"]
-  process = subprocess.Popen(CMD, stdout=subprocess.PIPE, 
stderr=subprocess.PIPE)
-  stdout, stderr = process.communicate()
-  if process.returncode != 0:
-    return "NOT_FOUND[%s]" % pid
-  return stdout
-
-
-def wait_for_entire_process_tree_death(pids):
-  for child in pids:
-    wait_for_process_death(child)
-
-
-def wait_for_process_death(pid, timeout=5):
-  start = time.time()
-  current_time = start
-  while is_process_running(pid) and current_time < start + timeout:
-    time.sleep(check_time_delay)
-    current_time = time.time()
-
-
-def is_process_running(pid):
-  CMD = ["ps", "-p", str(pid), "-o", "pid", "--no-headers"]
-  process = subprocess.Popen(CMD, stdout=subprocess.PIPE, 
stderr=subprocess.PIPE)
-  stdout, stderr = process.communicate()
-  if process.returncode != 0:
-    return False
-  return pid in stdout
-
-
-
-def get_processes_running(process_pids):
-  """
-  Checks what processes are still running
-  :param process_pids: list of process pids
-  :return: list of pids for processes that are still running
-  """
-  result = []
-  for pid in process_pids:
-    if is_process_running(pid):
-      result.append(pid)
-  return result
diff --git a/ambari-common/src/main/python/ambari_commons/shell.py 
b/ambari-common/src/main/python/ambari_commons/shell.py
index 5b29de0..33756da 100644
--- a/ambari-common/src/main/python/ambari_commons/shell.py
+++ b/ambari-common/src/main/python/ambari_commons/shell.py
@@ -27,8 +27,7 @@ from contextlib import contextmanager
 
 from ambari_commons import OSConst
 from ambari_commons.os_family_impl import OsFamilyImpl, OsFamilyFuncImpl
-from ambari_commons.process_utils import get_flat_process_tree, kill_pids, 
wait_for_entire_process_tree_death, \
-  get_processes_running, get_command_by_pid
+from resource_management.core import sudo
 
 logger = logging.getLogger()
 
@@ -231,35 +230,170 @@ class shellRunnerWindows(shellRunner):
     return _dict_to_object({'exitCode': code, 'output': out, 'error': err})
 
 
-#linux specific code
-@OsFamilyFuncImpl(os_family=OsFamilyImpl.DEFAULT)
-def kill_process_with_children(parent_pid):
+def get_all_children(base_pid):
   """
-  Kills process tree starting from a given pid.
-  :param parent_pid: head of tree
-  :param graceful_kill_delays: map <command name, custom delay between SIGTERM 
and SIGKILL>
-  :return:
+  Return all child PIDs of base_pid process
+
+  :param base_pid starting PID to scan for children
+  :return tuple of the following: pid, binary name, command line incl. binary
+
+  :type base_pid int
+  :rtype list[(int, str, str)]
   """
+  parent_pid_path_pattern = "/proc/{0}/task/{0}/children"
+  comm_path_pattern = "/proc/{0}/comm"
+  cmdline_path_pattern = "/proc/{0}/cmdline"
+
+  def read_children(pid):
+    try:
+      with open(parent_pid_path_pattern.format(pid), "r") as f:
+        return [int(item) for item in f.readline().strip().split(" ")]
+    except (IOError, ValueError):
+      return []
+
+  def read_command(pid):
+    try:
+      with open(comm_path_pattern.format(pid), "r") as f:
+        return f.readline().strip()
+    except IOError:
+      return ""
+
+  def read_cmdline(pid):
+    try:
+      with open(cmdline_path_pattern.format(pid), "r") as f:
+        return f.readline().strip()
+    except IOError:
+      return ""
+
+  pids = []
+  scan_pending = [int(base_pid)]
+
+  while scan_pending:
+    curr_pid = scan_pending.pop(0)
+    children = read_children(curr_pid)
 
-  pids = get_flat_process_tree(parent_pid)
+    pids.append((curr_pid, read_command(curr_pid), read_cmdline(curr_pid)))
+    scan_pending.extend(children)
+
+  return pids
+
+
+def is_pid_exists(pid):
+  """
+  Check if process with PID still exist (not counting it real state)
+
+  :type pid int
+  :rtype bool
+  """
+  pid_path = "/proc/{0}"
   try:
-    kill_pids(pids, signal.SIGTERM)
-  except Exception, e:
-    logger.warn("Failed to kill PID %d" % parent_pid)
-    logger.warn("Reported error: " + repr(e))
+    return os.path.exists(pid_path.format(pid))
+  except (OSError, IOError):
+    logger.debug("Failed to check PID existence")
+    return False
+
+
+def get_existing_pids(pids):
+  """
+  Check if process with pid still exists (not counting it real state).
+
+  Optimized to check PID list at once.
+
+  :param pids list of PIDs to filter
+  :return list of still existing PID
 
-  wait_for_entire_process_tree_death(pids)
+  :type pids list[int]
+  :rtype list[int]
+  """
+
+  existing_pid_list = []
 
   try:
-    running_processes = get_processes_running(pids)
-    if running_processes:
-      process_names = map(lambda x: get_command_by_pid(x),  running_processes)
-      logger.warn("These PIDs %s did not die after SIGTERM, sending SIGKILL. 
Exact commands to be killed:\n %s" %
-                  (", ".join(running_processes), "\n".join(process_names)))
-      kill_pids(running_processes, signal.SIGKILL)
-  except Exception, e:
-    logger.error("Failed to send SIGKILL to PID %d. Process exited?" % 
parent_pid)
-    logger.error("Reported error: " + repr(e))
+    all_existing_pid_list = [int(item) for item in os.listdir("/proc") if 
item.isdigit()]
+  except (OSError, IOError):
+    logger.debug("Failed to check PIDs existence")
+    return existing_pid_list
+
+  for pid_item in pids:
+    if pid_item in all_existing_pid_list:
+      existing_pid_list.append(pid_item)
+
+  return existing_pid_list
+
+
+def wait_for_process_list_kill(pids, timeout=5, check_step_time=0.1):
+  """
+  Process tree waiter
+
+  :type pids list[int]
+  :type timeout int|float
+  :type check_step_time int|float
+
+  :param pids list of PIDs to watch
+  :param timeout how long wait till giving up, seconds. Set 0 for nowait or 
None for infinite time
+  :param check_step_time how often scan for existing PIDs, seconds
+  """
+  from threading import Thread, Event
+  import time
+
+  stop_waiting = Event()
+
+  def _wait_loop():
+    while not stop_waiting.is_set() and get_existing_pids(pids):
+      time.sleep(check_step_time)
+
+  if timeout == 0:  # no need for loop if no timeout is set
+    return
+
+  th = Thread(target=_wait_loop)
+  stop_waiting.clear()
+
+  th.start()
+  th.join(timeout=timeout)
+  stop_waiting.set()
+
+  th.join()
+
+
+@OsFamilyFuncImpl(os_family=OsFamilyImpl.DEFAULT)
+def kill_process_with_children(base_pid):
+  """
+  Process tree killer
+
+  :type base_pid int
+  """
+  exception_list = ["apt-get", "apt", "yum", "zypper", "zypp"]
+  signals_to_post = {
+    "SIGTERM": signal.SIGTERM,
+    "SIGKILL": signal.SIGKILL
+  }
+  full_child_pids = get_all_children(base_pid)
+  all_child_pids = [item[0] for item in full_child_pids if item[1].lower() not 
in exception_list and item[0] != os.getpid()]
+  error_log = []
+
+  for sig_name, sig in signals_to_post.items():
+    # we need to kill processes from the bottom of the tree
+    pids_to_kill = sorted(get_existing_pids(all_child_pids), reverse=True)
+    for pid in pids_to_kill:
+      try:
+        sudo.kill(pid, sig)
+      except OSError as e:
+        error_log.append((sig_name, pid, repr(e)))
+
+    if pids_to_kill:
+      wait_for_process_list_kill(pids_to_kill)
+      still_existing_pids = get_existing_pids(pids_to_kill)
+      if still_existing_pids:
+        logger.warn("These PIDs {0} did not respond to {1} signal. Detailed 
commands list:\n {2}".format(
+          ", ".join([str(i) for i in still_existing_pids]),
+          sig_name,
+          "\n".join([i[2] for i in full_child_pids if i[0] in 
still_existing_pids])
+        ))
+
+  if get_existing_pids(all_child_pids) and error_log:  # we're unable to kill 
all requested PIDs
+    logger.warn("Process termination error log:\n")
+    for error_item in error_log:
+      logger.warn("PID: {0}, Process: {1}, Exception message: 
{2}".format(*error_item))
 
 
 def _changeUid():

-- 
To stop receiving notification emails like this one, please contact
[email protected].

Reply via email to