aboutsummaryrefslogtreecommitdiffstats
path: root/yardstick
diff options
context:
space:
mode:
authorRoss Brattain <ross.b.brattain@intel.com>2017-12-22 19:35:50 +0000
committerGerrit Code Review <gerrit@opnfv.org>2017-12-22 19:35:50 +0000
commit3984c976967e3d697473cd3bb85204577a838dcd (patch)
tree926b123337e6f7e9301ada7a799da31638576073 /yardstick
parentedb1b87fae318501b853ef02a7aa7dffddfe1e48 (diff)
parent090b1a166bd19bdb98b0311d58b85582bd1676ed (diff)
Merge "Replace subprocess "check_output" with "Popen""
Diffstat (limited to 'yardstick')
-rw-r--r--yardstick/common/exceptions.py19
-rw-r--r--yardstick/common/process.py91
-rw-r--r--yardstick/common/utils.py20
3 files changed, 125 insertions, 5 deletions
diff --git a/yardstick/common/exceptions.py b/yardstick/common/exceptions.py
new file mode 100644
index 000000000..9c0ec2c19
--- /dev/null
+++ b/yardstick/common/exceptions.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2017 Intel Corporation
+#
+# Licensed 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.
+
+
+class ProcessExecutionError(RuntimeError):
+ def __init__(self, message, returncode):
+ super(ProcessExecutionError, self).__init__(message)
+ self.returncode = returncode
diff --git a/yardstick/common/process.py b/yardstick/common/process.py
index 812ddea94..ede6cddac 100644
--- a/yardstick/common/process.py
+++ b/yardstick/common/process.py
@@ -11,10 +11,19 @@
# 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 logging
import multiprocessing
+import signal
+import subprocess
+import time
import os
+from oslo_utils import encodeutils
+
+from yardstick.common import exceptions
+from yardstick.common import utils
+
LOG = logging.getLogger(__name__)
@@ -45,3 +54,85 @@ def terminate_children(timeout=3):
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
diff --git a/yardstick/common/utils.py b/yardstick/common/utils.py
index 51f6e1360..82e20bec7 100644
--- a/yardstick/common/utils.py
+++ b/yardstick/common/utils.py
@@ -76,7 +76,7 @@ def import_modules_from_package(package):
"""
yardstick_root = os.path.dirname(os.path.dirname(yardstick.__file__))
path = os.path.join(yardstick_root, *package.split("."))
- for root, dirs, files in os.walk(path):
+ for root, _, files in os.walk(path):
matches = (filename for filename in files if filename.endswith(".py") and
not filename.startswith("__"))
new_package = os.path.relpath(root, yardstick_root).replace(os.sep, ".")
@@ -251,10 +251,10 @@ def set_dict_value(dic, keys, value):
def get_free_port(ip):
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
- while True:
+ port = random.randint(5000, 10000)
+ while s.connect_ex((ip, port)) == 0:
port = random.randint(5000, 10000)
- if s.connect_ex((ip, port)) != 0:
- return port
+ return port
def mac_address_to_hex_list(mac):
@@ -350,10 +350,13 @@ def config_to_dict(config):
def validate_non_string_sequence(value, default=None, raise_exc=None):
+ # NOTE(ralonsoh): refactor this function to check if raise_exc is an
+ # Exception. Remove duplicate code, this function is duplicated in this
+ # repository.
if isinstance(value, collections.Sequence) and not isinstance(value, six.string_types):
return value
if raise_exc:
- raise raise_exc
+ raise raise_exc # pylint: disable=raising-bad-type
return default
@@ -365,6 +368,13 @@ def join_non_strings(separator, *non_strings):
return str(separator).join(str(non_string) for non_string in non_strings)
+def safe_decode_utf8(s):
+ """Safe decode a str from UTF"""
+ if six.PY3 and isinstance(s, bytes):
+ return s.decode('utf-8', 'surrogateescape')
+ return s
+
+
class ErrorClass(object):
def __init__(self, *args, **kwargs):