[CVP] Refactor salt client class

Change-Id: I91cfffe1c8d5df0224657ce9e36be9063b56f0b3
Related-PROD: PROD-28981
Related-PROD: PROD-28729
Related-PROD: PROD-28624
Related-PROD: PROD-29286
diff --git a/test_set/cvp-sanity/utils/__init__.py b/test_set/cvp-sanity/utils/__init__.py
index aeb4cd8..62ccae7 100644
--- a/test_set/cvp-sanity/utils/__init__.py
+++ b/test_set/cvp-sanity/utils/__init__.py
@@ -3,6 +3,7 @@
 import requests
 import re
 import sys, traceback
+import time
 
 
 class AuthenticationError(Exception):
@@ -10,46 +11,100 @@
 
 
 class salt_remote:
-    def cmd(self, tgt, fun, param=None, expr_form=None, tgt_type=None):
-        config = get_configuration()
-        url = config['SALT_URL'].strip()
-        if not re.match("^(http|https)://", url):
+    def __init__(self):
+        self.config = get_configuration()
+        self.skipped_nodes = self.config.get('skipped_nodes') or []
+        self.url = self.config['SALT_URL'].strip()
+        if not re.match("^(http|https)://", self.url):
             raise AuthenticationError("Salt URL should start \
             with http or https, given - {}".format(url))
-        proxies = {"http": None, "https": None}
-        headers = {'Accept': 'application/json'}
-        login_payload = {'username': config['SALT_USERNAME'],
-                         'password': config['SALT_PASSWORD'], 'eauth': 'pam'}
-        accept_key_payload = {'fun': fun, 'tgt': tgt, 'client': 'local',
-                              'expr_form': expr_form, 'tgt_type': tgt_type,
-                              'timeout': config['salt_timeout']}
-        if param:
-            accept_key_payload['arg'] = param
+        self.login_payload = {'username': self.config['SALT_USERNAME'],
+                              'password': self.config['SALT_PASSWORD'], 'eauth': 'pam'}
+        # TODO: proxies
+        self.proxies = {"http": None, "https": None}
+        self.expires = ''
+        self.cookies = []
+        self.headers = {'Accept': 'application/json'}
+        self._login()
 
+    def _login (self):
         try:
-            login_request = requests.post(os.path.join(url, 'login'),
-                                          headers=headers, data=login_payload,
-                                          proxies=proxies)
+            login_request = requests.post(os.path.join(self.url, 'login'),
+                                          headers={'Accept': 'application/json'},
+                                          data=self.login_payload,
+                                          proxies=self.proxies)
             if not login_request.ok:
                 raise AuthenticationError("Authentication to SaltMaster failed")
-
-            request = requests.post(url, headers=headers,
-                                    data=accept_key_payload,
-                                    cookies=login_request.cookies,
-                                    proxies=proxies)
-
-            response = request.json()['return'][0]
-            return response
-
         except Exception as e:
             print ("\033[91m\nConnection to SaltMaster "
                   "was not established.\n"
                   "Please make sure that you "
                   "provided correct credentials.\n"
-                  "Error message: {}\033[0m\n".format(e.message or e)
-            )
+                  "Error message: {}\033[0m\n".format(e.message or e))
             traceback.print_exc(file=sys.stdout)
             sys.exit()
+        self.expire = login_request.json()['return'][0]['expire']
+        self.cookies = login_request.cookies
+        self.headers['X-Auth-Token'] = login_request.json()['return'][0]['token']
+
+    def cmd(self, tgt, fun='cmd.run', param=None, expr_form=None, tgt_type=None, check_status=False, retries=3):
+        if self.expire < time.time() + 300:
+            self.headers['X-Auth-Token'] = self._login()
+        accept_key_payload = {'fun': fun, 'tgt': tgt, 'client': 'local',
+                              'expr_form': expr_form, 'tgt_type': tgt_type,
+                              'timeout': self.config['salt_timeout']}
+        if param:
+            accept_key_payload['arg'] = param
+
+        for i in range(retries):
+            request = requests.post(self.url, headers=self.headers,
+                                    data=accept_key_payload,
+                                    cookies=self.cookies,
+                                    proxies=self.proxies)
+            if not request.ok or not isinstance(request.json()['return'][0], dict):
+                print("Salt master is not responding or response is incorrect. Output: {}".format(request))
+                continue
+            response = request.json()['return'][0]
+            result = {key: response[key] for key in response if key not in self.skipped_nodes}
+            if check_status:
+                if False in result.values():
+                    print(
+                         "One or several nodes are not responding. Output {}".format(json.dumps(result, indent=4)))
+                    continue
+            break
+        else:
+            raise Exception("Error with Salt Master response")
+        return result
+
+    def test_ping(self, tgt, expr_form='pillar'):
+        return self.cmd(tgt=tgt, fun='test.ping', param=None, expr_form=expr_form)
+
+    def cmd_any(self, tgt, param=None, expr_form='pillar'):
+        """
+        This method returns first non-empty result on node or nodes.
+        If all nodes returns nothing, then exception is thrown.
+        """
+        response = self.cmd(tgt=tgt, param=param, expr_form=expr_form)
+        for node in response.keys():
+            if response[node] or response[node] == '':
+                return response[node]
+        else:
+            raise Exception("All minions are down")
+
+    def pillar_get(self, tgt='salt:master', param=None, expr_form='pillar', fail_if_empty=False):
+        """
+        This method is for fetching pillars only.
+        Returns value for pillar, False (if no such pillar) or if fail_if_empty=True - exception
+        """
+        response = self.cmd(tgt=tgt, fun='pillar.get', param=param, expr_form=expr_form)
+        for node in response.keys():
+            if response[node] or response[node] != '':
+                return response[node]
+        else:
+            if fail_if_empty:
+                raise Exception("No pillar found or it is empty.")
+            else:
+                return False
 
 
 def init_salt_client():
@@ -63,43 +118,14 @@
     return separator.join(node_list)
 
 
-def get_monitoring_ip(param_name):
-    local_salt_client = init_salt_client()
-    salt_output = local_salt_client.cmd(
-        'salt:master',
-        'pillar.get',
-        ['_param:{}'.format(param_name)],
-        expr_form='pillar')
-    return salt_output[salt_output.keys()[0]]
-
-
-def get_active_nodes(test=None):
-    config = get_configuration()
-    local_salt_client = init_salt_client()
-
-    skipped_nodes = config.get('skipped_nodes') or []
-    if test:
-        testname = test.split('.')[0]
-        if 'skipped_nodes' in config.get(testname).keys():
-            skipped_nodes += config.get(testname)['skipped_nodes'] or []
-    if skipped_nodes != ['']:
-        print "\nNotice: {0} nodes will be skipped".format(skipped_nodes)
-        nodes = local_salt_client.cmd(
-            '* and not ' + list_to_target_string(skipped_nodes, 'and not'),
-            'test.ping',
-            expr_form='compound')
-    else:
-        nodes = local_salt_client.cmd('*', 'test.ping')
-    return nodes
-
-
 def calculate_groups():
     config = get_configuration()
     local_salt_client = init_salt_client()
     node_groups = {}
     nodes_names = set ()
     expr_form = ''
-    all_nodes = set(local_salt_client.cmd('*', 'test.ping'))
+    all_nodes = set(local_salt_client.test_ping(tgt='*',expr_form=None))
+    print all_nodes
     if 'groups' in config.keys() and 'PB_GROUPS' in os.environ.keys() and \
        os.environ['PB_GROUPS'].lower() != 'false':
         nodes_names.update(config['groups'].keys())
@@ -113,25 +139,23 @@
                 nodes_names.add(node)
         expr_form = 'pcre'
 
-    gluster_nodes = local_salt_client.cmd('I@salt:control and '
+    gluster_nodes = local_salt_client.test_ping(tgt='I@salt:control and '
                                           'I@glusterfs:server',
-                                          'test.ping', expr_form='compound')
-    kvm_nodes = local_salt_client.cmd('I@salt:control and not '
+                                           expr_form='compound')
+    kvm_nodes = local_salt_client.test_ping(tgt='I@salt:control and not '
                                       'I@glusterfs:server',
-                                      'test.ping', expr_form='compound')
+                                       expr_form='compound')
 
     for node_name in nodes_names:
         skipped_groups = config.get('skipped_groups') or []
         if node_name in skipped_groups:
             continue
         if expr_form == 'pcre':
-            nodes = local_salt_client.cmd('{}[0-9]{{1,3}}'.format(node_name),
-                                          'test.ping',
-                                          expr_form=expr_form)
+            nodes = local_salt_client.test_ping(tgt='{}[0-9]{{1,3}}'.format(node_name),
+                                                 expr_form=expr_form)
         else:
-            nodes = local_salt_client.cmd(config['groups'][node_name],
-                                          'test.ping',
-                                          expr_form=expr_form)
+            nodes = local_salt_client.test_ping(tgt=config['groups'][node_name],
+                                                 expr_form=expr_form)
             if nodes == {}:
                 continue