Add template and css for nsb report
[yardstick.git] / yardstick / common / process.py
1 # Copyright (c) 2017 Intel Corporation
2 #
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
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 import logging
16 import multiprocessing
17 import signal
18 import subprocess
19 import time
20
21 import os
22 from oslo_utils import encodeutils
23
24 from yardstick.common import exceptions
25 from yardstick.common import utils
26
27
28 LOG = logging.getLogger(__name__)
29
30
31 def check_if_process_failed(proc, timeout=1):
32     if proc is not None:
33         proc.join(timeout)
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))
37
38
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")
44         return
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)
49         child.join(timeout)
50         child.terminate()
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)
57
58
59 def _additional_env_args(additional_env):
60     """Build arguments for adding additional environment vars with env"""
61     if additional_env is None:
62         return []
63     return ['env'] + ['%s=%s' % pair for pair in additional_env.items()]
64
65
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)
70
71
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)
77
78
79 def create_process(cmd, run_as_root=False, additional_env=None):
80     """Create a process object for the given command.
81
82     The return value will be a tuple of the process object and the
83     list of command arguments used to create it.
84     """
85     if not isinstance(cmd, list):
86         cmd = [cmd]
87     cmd = list(map(str, _additional_env_args(additional_env) + cmd))
88     if run_as_root:
89         # NOTE(ralonsoh): to handle a command executed as root, using
90         # a root wrapper, instead of using "sudo".
91         pass
92     LOG.debug("Running command: %s", cmd)
93     obj = subprocess_popen(cmd, shell=False, stdin=subprocess.PIPE,
94                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
95     return obj, cmd
96
97
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):
101     try:
102         if process_input is not None:
103             _process_input = encodeutils.to_utf8(process_input)
104         else:
105             _process_input = None
106
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
113         obj.stdin.close()
114         _stdout = utils.safe_decode_utf8(_stdout)
115         _stderr = utils.safe_decode_utf8(_stderr)
116
117         extra_ok_codes = extra_ok_codes or []
118         if returncode and returncode not in extra_ok_codes:
119             msg = ("Exit code: %(returncode)d; "
120                    "Stdin: %(stdin)s; "
121                    "Stdout: %(stdout)s; "
122                    "Stderr: %(stderr)s") % {'returncode': returncode,
123                                             'stdin': process_input or '',
124                                             'stdout': _stdout,
125                                             'stderr': _stderr}
126             if log_fail_as_error:
127                 LOG.error(msg)
128             if check_exit_code:
129                 raise exceptions.ProcessExecutionError(msg,
130                                                        returncode=returncode)
131
132     finally:
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.
136         time.sleep(0)
137
138     return (_stdout, _stderr) if return_stderr else _stdout