mcp-agent mode for mcp-checker with web-info and REST API

New:
 - agent index page serving on 0.0.0.0:8765
 - REST API with modular approach to modules
 - 'fio' module working via thread-safe Thread able to return
   real-time info on its status
 - 'fio' module scheduled run option
 - ability to preserve multiple testrun results while active
 - dockerfile for agent image

Fixed:
 - Network report fixes to work on Kube envs
 - Fixed function for running commands inside daemonset pods

 Related-PROD: PROD-36669

Change-Id: I57e73001247af9187680bfc5744590eef219d93c
diff --git a/cfg_checker/common/kube_utils.py b/cfg_checker/common/kube_utils.py
index e1aafbb..95bb19c 100644
--- a/cfg_checker/common/kube_utils.py
+++ b/cfg_checker/common/kube_utils.py
@@ -351,6 +351,7 @@
         namespace,
         strict=False,
         _request_timeout=120,
+        arguments=None,
         **kwargs
     ):
         _pname = ""
@@ -370,16 +371,18 @@
                         ", ".join(_pnames)
                     )
                 )
-                _pname = _pnames[0]
             elif len(_pnames) < 1:
                 raise KubeException("No pods found for '{}'".format(pod_name))
+            # in case of >1 and =1 we are taking 1st anyway
+            _pname = _pnames[0]
         else:
             _pname = pod_name
         logger_cli.debug(
-            "... cmd: [CoreV1] exec {} -n {} -- {}".format(
+            "... cmd: [CoreV1] exec {} -n {} -- {} '{}'".format(
                 _pname,
                 namespace,
-                cmd
+                cmd,
+                arguments
             )
         )
         # Set preload_content to False to preserve JSON
@@ -387,6 +390,8 @@
         # Which causes to change " to '
         # After that json.loads(...) fail
         cmd = cmd if isinstance(cmd, list) else cmd.split()
+        if arguments:
+            cmd += [arguments]
         _pod_stream = stream(
             self.CoreV1.connect_get_namespaced_pod_exec,
             _pname,
@@ -404,6 +409,17 @@
         _pod_stream.run_forever(timeout=_request_timeout)
         # read the output
         _output = _pod_stream.read_stdout()
+        _error = _pod_stream.read_stderr()
+        if _error:
+            # copy error to output
+            logger_cli.warning(
+                "WARNING: cmd of '{}' returned error:\n{}\n".format(
+                    " ".join(cmd),
+                    _error
+                )
+            )
+            if not _output:
+                _output = _error
         # Force recreate of api objects
         self._coreV1 = None
         # Send output
diff --git a/cfg_checker/common/other.py b/cfg_checker/common/other.py
index 3d0cc13..b5a0406 100644
--- a/cfg_checker/common/other.py
+++ b/cfg_checker/common/other.py
@@ -15,12 +15,15 @@
 
 
 # 'Dirty' and simple way to execute piped cmds
-def piped_shell(command):
+def piped_shell(command, code=False):
     logger_cli.debug("... cmd:'{}'".format(command))
     _code, _out = subprocess.getstatusoutput(command)
     if _code:
         logger_cli.error("Non-zero return code: {}, '{}'".format(_code, _out))
-    return _out
+    if code:
+        return _code, _out
+    else:
+        return _out
 
 
 # 'Proper way to execute shell
diff --git a/cfg_checker/common/ssh_utils.py b/cfg_checker/common/ssh_utils.py
index bdfe6b5..d500e36 100644
--- a/cfg_checker/common/ssh_utils.py
+++ b/cfg_checker/common/ssh_utils.py
@@ -240,6 +240,7 @@
             return _out
 
     def wait_ready(self, cmd, timeout=60):
+        # Wait for command to finish inside SSH
         def _strip_cmd_carrets(_str, carret='\r', skip_chars=1):
             _cnt = _str.count(carret)
             while _cnt > 0: