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/nodes.py b/cfg_checker/nodes.py
index ef2219c..1dcec2a 100644
--- a/cfg_checker/nodes.py
+++ b/cfg_checker/nodes.py
@@ -509,6 +509,7 @@
         # prepare needed resources
         self._check_namespace()
         self._scripts = self._check_config_map()
+        self.prepared_daemonsets = []
 
     def _check_namespace(self):
         # ensure namespace
@@ -984,7 +985,11 @@
 
         # create daemonset
         logger_cli.debug("... preparing daemonset")
-        return self.kube.prepare_daemonset_from_yaml(self._namespace, _ds)
+        _ds = self.kube.prepare_daemonset_from_yaml(self._namespace, _ds)
+        # Save prepared daemonset
+        self.prepared_daemonsets.append(_ds)
+        # return it
+        return _ds
 
     def wait_for_daemonset(self, ds, timeout=120):
         # iteration timeout
@@ -1076,7 +1081,13 @@
         )
         return _result
 
-    def execute_script_on_daemon_set(self, ds, script_filename, args=None):
+    def execute_cmd_on_daemon_set(
+        self,
+        ds,
+        cmd,
+        args=None,
+        is_script=False
+    ):
         """
         Query daemonset for pods and execute script on all of them
         """
@@ -1090,6 +1101,7 @@
                     plist[2],  # namespace
                     strict=True,
                     _request_timeout=120,
+                    arguments=plist[5]
                 )
             ]
 
@@ -1103,16 +1115,24 @@
         )
         _plist = []
         _arguments = args if args else ""
-        _cmd = [
-            "python3",
-            os.path.join(
-                "/",
-                self.env_config.kube_scripts_folder,
-                script_filename
-            ),
-            _arguments
-        ]
-        _cmd = " ".join(_cmd)
+        if is_script:
+            _cmd = [
+                "python3",
+                os.path.join(
+                    "/",
+                    self.env_config.kube_scripts_folder,
+                    cmd
+                ),
+                _arguments
+            ]
+            _cmd = " ".join(_cmd)
+        else:
+            # decide if we are to wrap it to bash
+            if '|' in cmd:
+                _cmd = "bash -c"
+                _arguments = cmd
+            else:
+                _cmd = cmd
         for item in _pods.items:
             _plist.append(
                 [
@@ -1120,12 +1140,12 @@
                     item.spec.node_name,
                     item.metadata.namespace,
                     item.metadata.name,
-                    _cmd
+                    _cmd,
+                    _arguments
                 ]
             )
 
         # map func and cmd
-        logger_cli
         pool = Pool(self.env_config.threads)
         _results = {}
         self.not_responded = []
@@ -1202,3 +1222,48 @@
         # TODO: Exception handling
 
         return _target_path
+
+    def get_cmd_for_nodes(self, cmd, target_key, target_dict=None, nodes=None):
+        """Function runs command on daemonset and parses result into place
+        or into dict structure provided
+
+        :return: no return value, data pulished internally
+        """
+        logger_cli.debug(
+            "... collecting results for '{}'".format(cmd)
+        )
+        if target_dict:
+            _nodes = target_dict
+        else:
+            _nodes = self.nodes
+        # Dirty way to get daemonset that was used in checker and not deleted
+        _ds = self.prepared_daemonsets[0]
+        _result = self.execute_cmd_on_daemon_set(_ds, cmd)
+        for node, data in _nodes.items():
+
+            if node in self.skip_list:
+                logger_cli.debug(
+                    "... '{}' skipped while collecting '{}'".format(
+                        node,
+                        cmd
+                    )
+                )
+                continue
+            # Prepare target key
+            if target_key not in data:
+                data[target_key] = None
+            # Save data
+            if data['status'] in [NODE_DOWN, NODE_SKIP]:
+                data[target_key] = None
+            elif node not in _result:
+                continue
+            elif not _result[node]:
+                logger_cli.debug(
+                    "... '{}' not responded after '{}'".format(
+                        node,
+                        self.env_config.salt_timeout
+                    )
+                )
+                data[target_key] = None
+            else:
+                data[target_key] = _result[node]