summaryrefslogtreecommitdiffstats
path: root/vnftest/common/process.py
diff options
context:
space:
mode:
Diffstat (limited to 'vnftest/common/process.py')
-rw-r--r--vnftest/common/process.py140
1 files changed, 140 insertions, 0 deletions
diff --git a/vnftest/common/process.py b/vnftest/common/process.py
new file mode 100644
index 0000000..21a21ac
--- /dev/null
+++ b/vnftest/common/process.py
@@ -0,0 +1,140 @@
+##############################################################################
+# Copyright 2018 EuropeanSoftwareMarketingLtd.
+# ===================================================================
+# Licensed under the ApacheLicense, Version2.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
+#
+# 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
+##############################################################################
+# vnftest comment: this is a modified copy of
+# yardstick/common/process.py
+
+import logging
+import multiprocessing
+import signal
+import subprocess
+import time
+
+import os
+from oslo_utils import encodeutils
+
+from vnftest.common import exceptions
+from vnftest.common import utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+def check_if_process_failed(proc, timeout=1):
+ if proc is not None:
+ proc.join(timeout)
+ # Only abort if the process aborted
+ if proc.exitcode is not None and proc.exitcode > 0:
+ raise RuntimeError("{} exited with status {}".format(proc.name, proc.exitcode))
+
+
+def terminate_children(timeout=3):
+ current_proccess = multiprocessing.current_process()
+ active_children = multiprocessing.active_children()
+ if not active_children:
+ LOG.debug("no children to terminate")
+ return
+ for child in active_children:
+ LOG.debug("%s %s %s, child: %s %s", current_proccess.name, current_proccess.pid,
+ os.getpid(), child, child.pid)
+ LOG.debug("joining %s", child)
+ child.join(timeout)
+ child.terminate()
+ active_children = multiprocessing.active_children()
+ if not active_children:
+ LOG.debug("no children to terminate")
+ for child in active_children:
+ LOG.debug("%s %s %s, after terminate child: %s %s", current_proccess.name,
+ current_proccess.pid, os.getpid(), child, child.pid)
+
+
+def _additional_env_args(additional_env):
+ """Build arguments for adding additional environment vars with env"""
+ if additional_env is None:
+ return []
+ return ['env'] + ['%s=%s' % pair for pair in additional_env.items()]
+
+
+def _subprocess_setup():
+ # Python installs a SIGPIPE handler by default. This is usually not what
+ # non-Python subprocesses expect.
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+
+def subprocess_popen(args, stdin=None, stdout=None, stderr=None, shell=False,
+ env=None, preexec_fn=_subprocess_setup, close_fds=True):
+ return subprocess.Popen(args, shell=shell, stdin=stdin, stdout=stdout,
+ stderr=stderr, preexec_fn=preexec_fn,
+ close_fds=close_fds, env=env)
+
+
+def create_process(cmd, run_as_root=False, additional_env=None):
+ """Create a process object for the given command.
+
+ The return value will be a tuple of the process object and the
+ list of command arguments used to create it.
+ """
+ if not isinstance(cmd, list):
+ cmd = [cmd]
+ cmd = list(map(str, _additional_env_args(additional_env) + cmd))
+ if run_as_root:
+ # NOTE(ralonsoh): to handle a command executed as root, using
+ # a root wrapper, instead of using "sudo".
+ pass
+ LOG.debug("Running command: %s", cmd)
+ obj = subprocess_popen(cmd, shell=False, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return obj, cmd
+
+
+def execute(cmd, process_input=None, additional_env=None,
+ check_exit_code=True, return_stderr=False, log_fail_as_error=True,
+ extra_ok_codes=None, run_as_root=False):
+ try:
+ if process_input is not None:
+ _process_input = encodeutils.to_utf8(process_input)
+ else:
+ _process_input = None
+
+ # NOTE(ralonsoh): to handle the execution of a command as root,
+ # using a root wrapper, instead of using "sudo".
+ obj, cmd = create_process(cmd, run_as_root=run_as_root,
+ additional_env=additional_env)
+ _stdout, _stderr = obj.communicate(_process_input)
+ returncode = obj.returncode
+ obj.stdin.close()
+ _stdout = utils.safe_decode_utf8(_stdout)
+ _stderr = utils.safe_decode_utf8(_stderr)
+
+ extra_ok_codes = extra_ok_codes or []
+ if returncode and returncode not in extra_ok_codes:
+ msg = ("Exit code: %(returncode)d; "
+ "Stdin: %(stdin)s; "
+ "Stdout: %(stdout)s; "
+ "Stderr: %(stderr)s") % {'returncode': returncode,
+ 'stdin': process_input or '',
+ 'stdout': _stdout,
+ 'stderr': _stderr}
+ if log_fail_as_error:
+ LOG.error(msg)
+ if check_exit_code:
+ raise exceptions.ProcessExecutionError(msg,
+ returncode=returncode)
+
+ finally:
+ # This appears to be necessary in order for the subprocess to clean up
+ # something between call; without it, the second process hangs when two
+ # execute calls are made in a row.
+ time.sleep(0)
+
+ return (_stdout, _stderr) if return_stderr else _stdout