1 # Copyright (c) 2017 Intel Corporation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
16 import multiprocessing
22 from oslo_utils import encodeutils
24 from yardstick.common import exceptions
25 from yardstick.common import utils
28 LOG = logging.getLogger(__name__)
31 def check_if_process_failed(proc, timeout=1):
34 # Only abort if the process aborted
35 if proc.exitcode is not None and proc.exitcode > 0:
36 raise RuntimeError("{} exited with status {}".format(proc.name, proc.exitcode))
39 def terminate_children(timeout=3):
40 current_proccess = multiprocessing.current_process()
41 active_children = multiprocessing.active_children()
42 if not active_children:
43 LOG.debug("no children to terminate")
45 for child in active_children:
46 LOG.debug("%s %s %s, child: %s %s", current_proccess.name, current_proccess.pid,
47 os.getpid(), child, child.pid)
48 LOG.debug("joining %s", child)
51 active_children = multiprocessing.active_children()
52 if not active_children:
53 LOG.debug("no children to terminate")
54 for child in active_children:
55 LOG.debug("%s %s %s, after terminate child: %s %s", current_proccess.name,
56 current_proccess.pid, os.getpid(), child, child.pid)
59 def _additional_env_args(additional_env):
60 """Build arguments for adding additional environment vars with env"""
61 if additional_env is None:
63 return ['env'] + ['%s=%s' % pair for pair in additional_env.items()]
66 def _subprocess_setup():
67 # Python installs a SIGPIPE handler by default. This is usually not what
68 # non-Python subprocesses expect.
69 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
72 def subprocess_popen(args, stdin=None, stdout=None, stderr=None, shell=False,
73 env=None, preexec_fn=_subprocess_setup, close_fds=True):
74 return subprocess.Popen(args, shell=shell, stdin=stdin, stdout=stdout,
75 stderr=stderr, preexec_fn=preexec_fn,
76 close_fds=close_fds, env=env)
79 def create_process(cmd, run_as_root=False, additional_env=None):
80 """Create a process object for the given command.
82 The return value will be a tuple of the process object and the
83 list of command arguments used to create it.
85 if not isinstance(cmd, list):
87 cmd = list(map(str, _additional_env_args(additional_env) + cmd))
89 # NOTE(ralonsoh): to handle a command executed as root, using
90 # a root wrapper, instead of using "sudo".
92 LOG.debug("Running command: %s", cmd)
93 obj = subprocess_popen(cmd, shell=False, stdin=subprocess.PIPE,
94 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
98 def execute(cmd, process_input=None, additional_env=None,
99 check_exit_code=True, return_stderr=False, log_fail_as_error=True,
100 extra_ok_codes=None, run_as_root=False):
102 if process_input is not None:
103 _process_input = encodeutils.to_utf8(process_input)
105 _process_input = None
107 # NOTE(ralonsoh): to handle the execution of a command as root,
108 # using a root wrapper, instead of using "sudo".
109 obj, cmd = create_process(cmd, run_as_root=run_as_root,
110 additional_env=additional_env)
111 _stdout, _stderr = obj.communicate(_process_input)
112 returncode = obj.returncode
114 _stdout = utils.safe_decode_utf8(_stdout)
115 _stderr = utils.safe_decode_utf8(_stderr)
117 extra_ok_codes = extra_ok_codes or []
118 if returncode and returncode not in extra_ok_codes:
119 msg = ("Exit code: %(returncode)d; "
121 "Stdout: %(stdout)s; "
122 "Stderr: %(stderr)s") % {'returncode': returncode,
123 'stdin': process_input or '',
126 if log_fail_as_error:
129 raise exceptions.ProcessExecutionError(msg,
130 returncode=returncode)
133 # This appears to be necessary in order for the subprocess to clean up
134 # something between call; without it, the second process hangs when two
135 # execute calls are made in a row.
138 return (_stdout, _stderr) if return_stderr else _stdout