Merge "Remove tempest_version hardcode, add description"
diff --git a/.gitreview b/.gitreview
index 3a1eac3..b0ed746 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
-host=gerrit.mcp.mirantis.net
+host=gerrit.mcp.mirantis.com
 port=29418
 project=mcp-ci/pipeline-library.git
diff --git a/src/com/mirantis/mcp/Validate.groovy b/src/com/mirantis/mcp/Validate.groovy
index 57615d0..4671c3c 100644
--- a/src/com/mirantis/mcp/Validate.groovy
+++ b/src/com/mirantis/mcp/Validate.groovy
@@ -522,19 +522,17 @@
     def salt = new com.mirantis.mk.Salt()
     def xml_file = "${output_filename}.xml"
     def html_file = "${output_filename}.html"
-    def log_file = "${output_filename}.log"
     skip_list_cmd = ''
     if (skip_list != '') {
         skip_list_cmd = "--skip-list ${skip_list}"
     }
-    salt.cmdRun(master, target, "docker exec cvp rally verify start --pattern ${test_pattern} ${skip_list_cmd} " +
-                                "--detailed > ${log_file}", false)
-    salt.cmdRun(master, target, "cat ${log_file}")
+    salt.cmdRun(master, target, "docker exec cvp rally verify start --pattern ${test_pattern} ${skip_list_cmd} --detailed")
     salt.cmdRun(master, target, "docker exec cvp rally verify report --type junit-xml --to /home/rally/${xml_file}")
     salt.cmdRun(master, target, "docker exec cvp rally verify report --type html --to /home/rally/${html_file}")
     salt.cmdRun(master, target, "docker cp cvp:/home/rally/${xml_file} ${output_dir}")
     salt.cmdRun(master, target, "docker cp cvp:/home/rally/${html_file} ${output_dir}")
-    return salt.cmdRun(master, target, "docker exec cvp rally verify show | head -5 | tail -1 | awk '{print \$4}'")['return'][0].values()[0].split()[0]
+    return salt.cmdRun(master, target, "docker exec cvp rally verify show | head -5 | tail -1 | " +
+                                       "awk '{print \$4}'")['return'][0].values()[0].split()[0]
 }
 
 /**
@@ -548,10 +546,8 @@
 def runCVPrally(master, target, scenarios_path, output_dir, output_filename="docker-rally") {
     def salt = new com.mirantis.mk.Salt()
     def xml_file = "${output_filename}.xml"
-    def log_file = "${output_filename}.log"
     def html_file = "${output_filename}.html"
-    salt.cmdRun(master, target, "docker exec cvp rally task start ${scenarios_path} > ${log_file}", false)
-    salt.cmdRun(master, target, "cat ${log_file}")
+    salt.cmdRun(master, target, "docker exec cvp rally task start ${scenarios_path}")
     salt.cmdRun(master, target, "docker exec cvp rally task report --out ${html_file}")
     salt.cmdRun(master, target, "docker exec cvp rally task report --junit --out ${xml_file}")
     salt.cmdRun(master, target, "docker cp cvp:/home/rally/${xml_file} ${output_dir}")
diff --git a/src/com/mirantis/mk/Common.groovy b/src/com/mirantis/mk/Common.groovy
index 6e90c7a..87b1696 100644
--- a/src/com/mirantis/mk/Common.groovy
+++ b/src/com/mirantis/mk/Common.groovy
@@ -445,9 +445,12 @@
 /**
  * Test pipeline input parameter existence and validity (not null and not empty string)
  * @param paramName input parameter name (usually uppercase)
- */
+  */
 def validInputParam(paramName) {
-    return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
+    if (paramName instanceof java.lang.String) {
+        return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
+    }
+    return false
 }
 
 /**
@@ -614,8 +617,9 @@
  * return - html-based string
  * TODO: allow to specify subdir for results?
  **/
+
 def comparePillars(compRoot, b_url, grepOpts) {
-    common = new com.mirantis.mk.Common()
+
     // Some global constants. Don't change\move them!
     keyNew = 'new'
     keyRemoved = 'removed'
@@ -639,40 +643,47 @@
                 returnStatus: true
             )
             if (grep_status == 1) {
-                common.warningMsg("Grep regexp ${grepOpts} removed all diff!")
+                warningMsg("Grep regexp ${grepOpts} removed all diff!")
                 diff_status = 0
             }
         }
     }
     // Set job description
-    String description = ''
+    description = ''
     if (diff_status == 1) {
         // Analyse output file and prepare array with results
         String data_ = readFile file: "${compRoot}/pillar.diff"
         def diff_list = diffCheckMultidir(data_.split("\\r?\\n"))
-        common.infoMsg(diff_list)
+        infoMsg(diff_list)
         dir(compRoot) {
             if (diff_list[keyDiff].size() > 0) {
                 if (!fileExists('diff')) {
                     sh('mkdir -p diff')
                 }
                 description += '<b>CHANGED</b><ul>'
-                common.infoMsg('Changed items:')
-                for (item in diff_list[keyDiff]) {
-                    // We don't want to handle sub-dirs structure. So, simply make diff 'flat'
-                    item_f = item.toString().replace('/', '_')
-                    description += "<li><a href=\"${httpWS}/diff/${item_f}/*view*/\">${item}</a></li>"
-                    // Generate diff file
-                    def diff_exit_code = sh([
-                        script      : "diff -U 50 old/${item} new/${item} > diff/${item_f}",
-                        returnStdout: false,
-                        returnStatus: true,
-                    ])
-                    // catch normal errors, diff should always return 1
-                    if (diff_exit_code != 1) {
-                        error 'Error with diff file generation'
-                    }
+                infoMsg('Changed items:')
+                def stepsForParallel = [:]
+                stepsForParallel.failFast = true
+                diff_list[keyDiff].each {
+                    stepsForParallel.put("Differ for:${it}",
+                        {
+                            // We don't want to handle sub-dirs structure. So, simply make diff 'flat'
+                            def item_f = it.toString().replace('/', '_')
+                            description += "<li><a href=\"${httpWS}/diff/${item_f}/*view*/\">${it}</a></li>"
+                            // Generate diff file
+                            def diff_exit_code = sh([
+                                script      : "diff -U 50 old/${it} new/${it} > diff/${item_f}",
+                                returnStdout: false,
+                                returnStatus: true,
+                            ])
+                            // catch normal errors, diff should always return 1
+                            if (diff_exit_code != 1) {
+                                error 'Error with diff file generation'
+                            }
+                        })
                 }
+
+                parallel stepsForParallel
             }
             if (diff_list[keyNew].size() > 0) {
                 description += '<b>ADDED</b><ul>'
@@ -699,7 +710,7 @@
         }
         return description.toString()
     } else {
-        return 'No job changes'
+        return '<b>No job changes</b>'
     }
 }
 
@@ -782,22 +793,24 @@
     printCurrentStage(stageMap, currentStage)
 
     stage(currentStage) {
+      if (interactive){
         input message: getColorizedString("We are going to execute stage \'${currentStage}\' on the following target ${target}.\nPlease review stage information above.", "yellow")
-        try {
-            return body.call()
-            stageMap[currentStage]['Status'] = "SUCCESS"
-        } catch (Exception err) {
-            def msg = "Stage ${currentStage} failed with the following exception:\n${err}"
-            print getColorizedString(msg, "yellow")
-            common.errorMsg(err)
-            if (interactive) {
-                input message: getColorizedString("Please make sure problem is fixed to proceed with retry. Ready to proceed?", "yellow")
-                stageMap[currentStage]['Status'] = "RETRYING"
-                stageWrapper(stageMap, currentStage, target, interactive, body)
-            } else {
-                error(msg)
-            }
+      }
+      try {
+        return body.call()
+        stageMap[currentStage]['Status'] = "SUCCESS"
+      } catch (Exception err) {
+        def msg = "Stage ${currentStage} failed with the following exception:\n${err}"
+        print getColorizedString(msg, "yellow")
+        common.errorMsg(err)
+        if (interactive) {
+          input message: getColorizedString("Please make sure problem is fixed to proceed with retry. Ready to proceed?", "yellow")
+          stageMap[currentStage]['Status'] = "RETRYING"
+          stageWrapper(stageMap, currentStage, target, interactive, body)
+        } else {
+          error(msg)
         }
+      }
     }
 }
 
@@ -817,10 +830,98 @@
 
     if (config.get('verify', true)) {
         MirrorRootStatus = sh(script: "wget  --auth-no-challenge --spider ${res['linux_system_repo_url']} 2>/dev/null", returnStatus: true)
-        if (MirrorRootStatus != '0') {
+        if (MirrorRootStatus != 0) {
             common.warningMsg("Resource: ${res['linux_system_repo_url']} not exist")
             res['linux_system_repo_url'] = false
         }
     }
     return res
 }
+
+/**
+ *  Workaround to update env properties, like GERRIT_* vars,
+ *  which should be passed from upstream job to downstream.
+ *  Will not fail entire job in case any issues.
+ *  @param envVar - EnvActionImpl env job
+ *  @param extraVars - Multiline YAML text with extra vars
+ */
+def mergeEnv(envVar, extraVars) {
+    def common = new com.mirantis.mk.Common()
+    try {
+        def extraParams = readYaml text: extraVars
+        for(String key in extraParams.keySet()) {
+            envVar[key] = extraParams[key]
+            common.warningMsg("Parameter ${key} is updated from EXTRA vars.")
+        }
+    } catch (Exception e) {
+        common.errorMsg("Can't update env parameteres, because: ${e.toString()}")
+    }
+}
+
+/**
+ * Wrapper around parallel pipeline function
+ * with ability to restrict number of parallel threads
+ * running simultaneously
+ *
+ * @param branches - Map with Clousers to be executed
+ * @param maxParallelJob - Integer number of parallel threads allowed
+ *                         to run simultaneously
+ */
+def runParallel(branches, maxParallelJob = 10) {
+    def runningSteps = 0
+    branches.each { branchName, branchBody ->
+        if (branchBody instanceof Closure) {
+            branches[branchName] = {
+                while (!(runningSteps < maxParallelJob)) {
+                    continue
+                }
+                runningSteps += 1
+                branchBody.call()
+                runningSteps -= 1
+            }
+        }
+    }
+    if (branches) {
+        parallel branches
+    }
+}
+
+/**
+ * Ugly processing basic funcs with /etc/apt
+ * @param configYaml
+ * Example :
+ configYaml = '''
+ ---
+ distrib_revision: 'nightly'
+ aprConfD: |-
+    APT::Get::AllowUnauthenticated 'true';
+ repo:
+    mcp_saltstack:
+        source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/saltstack-2017.7/xenial xenial main"
+        pinning: |-
+            Package: libsodium18
+            Pin: release o=SaltStack
+            Pin-Priority: 50
+ '''
+ *
+ */
+
+def debianExtraRepos(configYaml) {
+    def config = readYaml text: configYaml
+    def distribRevision = config.get('distrib_revision', 'nightly')
+    if (config.get('repo', false)) {
+        for (String repo in config['repo'].keySet()) {
+            source = config['repo'][repo]['source'].replace('SUB_DISTRIB_REVISION', distribRevision)
+            warningMsg("Write ${source} >  /etc/apt/sources.list.d/${repo}.list")
+            sh("echo '${source}' > /etc/apt/sources.list.d/${repo}.list")
+            // TODO implement pining
+        }
+    }
+    if (config.get('aprConfD', false)) {
+        for (String pref in config['aprConfD'].tokenize('\n')) {
+            warningMsg("Adding ${pref} => /etc/apt/apt.conf.d/99setupAndTestNode")
+            sh("echo '${pref}' >> /etc/apt/apt.conf.d/99setupAndTestNode")
+        }
+        sh('cat /etc/apt/apt.conf.d/99setupAndTestNode')
+    }
+}
diff --git a/src/com/mirantis/mk/Gerrit.groovy b/src/com/mirantis/mk/Gerrit.groovy
index d1c0a1c..3761789 100644
--- a/src/com/mirantis/mk/Gerrit.groovy
+++ b/src/com/mirantis/mk/Gerrit.groovy
@@ -16,6 +16,8 @@
  *          - withMerge, merge master before build
  *          - withLocalBranch, prevent detached mode in repo
  *          - withWipeOut, wipe repository and force clone
+ *          - GerritTriggerBuildChooser - use magic GerritTriggerBuildChooser class from gerrit-trigger-plugin.
+ *            By default,enabled.
  *        Gerrit properties like GERRIT_SCHEMA can be passed in config as gerritSchema or will be obtained from env
  * @param extraScmExtensions list of extra scm extensions which will be used for checkout (optional)
  * @return boolean result
@@ -48,13 +50,13 @@
     def path = config.get('path', "")
     def depth = config.get('depth', 0)
     def timeout = config.get('timeout', 20)
+    def GerritTriggerBuildChooser = config.get('useGerritTriggerBuildChooser', true)
 
     def invalidParams = _getInvalidGerritParams(config)
     if (invalidParams.isEmpty()) {
         // default parameters
         def scmExtensions = [
             [$class: 'CleanCheckout'],
-            [$class: 'BuildChooserSetting', buildChooser: [$class: 'GerritTriggerBuildChooser']],
             [$class: 'CheckoutOption', timeout: timeout],
             [$class: 'CloneOption', depth: depth, noTags: false, reference: '', shallow: depth > 0, timeout: timeout]
         ]
@@ -74,6 +76,11 @@
             scmUserRemoteConfigs.put('credentialsId',credentials)
         }
 
+        // Usefull, if we only need to clone branch. W\o any refspec magic
+        if (GerritTriggerBuildChooser) {
+            scmExtensions.add([$class: 'BuildChooserSetting', buildChooser: [$class: 'GerritTriggerBuildChooser']],)
+        }
+
         // if we need to "merge" code from patchset to GERRIT_BRANCH branch
         if (merge) {
             scmExtensions.add([$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'gerrit', mergeStrategy: 'DEFAULT', mergeTarget: gerritBranch]])
diff --git a/src/com/mirantis/mk/Http.groovy b/src/com/mirantis/mk/Http.groovy
index 987a998..b752b42 100644
--- a/src/com/mirantis/mk/Http.groovy
+++ b/src/com/mirantis/mk/Http.groovy
@@ -112,25 +112,25 @@
 }
 
 /**
- * Make generic call using Salt REST API and return parsed JSON
+ * Make generic call using REST API and return parsed JSON
  *
- * @param master   Salt connection object
- * @param uri   URI which will be appended to Salt server base URL
- * @param method    HTTP method to use (default GET)
- * @param data      JSON data to POST or PUT
- * @param headers   Map of additional request headers
+ * @param base    connection object, map with 'url' and optional 'authToken' keys
+ * @param uri     URI which will be appended to connection base URL
+ * @param method  HTTP method to use (default GET)
+ * @param data    JSON data to POST, PUT or PATCH
+ * @param headers Map of additional request headers
  */
-def restCall(master, uri, method = 'GET', data = null, headers = [:]) {
-    def connection = new URL("${master.url}${uri}").openConnection()
+def restCall(base, uri, method = 'GET', data = null, headers = [:]) {
+    def connection = new URL("${base.url}${uri}").openConnection()
     if (method != 'GET') {
         connection.setRequestMethod(method)
     }
 
     connection.setRequestProperty('User-Agent', 'jenkins-groovy')
     connection.setRequestProperty('Accept', 'application/json')
-    if (master.authToken) {
-        // XXX: removeme
-        connection.setRequestProperty('X-Auth-Token', master.authToken)
+    if (base.authToken) {
+        // XXX: removeme, explicitly use headers instead
+        connection.setRequestProperty('X-Auth-Token', base.authToken)
     }
 
     for (header in headers) {
@@ -163,34 +163,56 @@
 }
 
 /**
- * Make GET request using Salt REST API and return parsed JSON
+ * Make GET request using REST API and return parsed JSON
  *
- * @param master   Salt connection object
- * @param uri   URI which will be appended to Salt server base URL
+ * @param base  connection object, map with 'url' and optional 'authToken' keys
+ * @param uri   URI which will be appended to server base URL
  */
-def restGet(master, uri, data = null) {
-    return restCall(master, uri, 'GET', data)
+def restGet(base, uri, data = null, headers = [:]) {
+    return restCall(base, uri, 'GET', data, headers)
 }
 
 /**
- * Make POST request using Salt REST API and return parsed JSON
+ * Make POST request using REST API and return parsed JSON
  *
- * @param master   Salt connection object
- * @param uri   URI which will be appended to Docker server base URL
+ * @param base  connection object, map with 'url' and optional 'authToken' keys
+ * @param uri   URI which will be appended to server base URL
+ * @param data  JSON Data to POST
+ */
+def restPost(base, uri, data = null, headers = ['Accept': '*/*']) {
+    return restCall(base, uri, 'POST', data, headers)
+}
+
+/**
+ * Make PUT request using REST API and return parsed JSON
+ *
+ * @param base  connection object, map with 'url' and optional 'authToken' keys
+ * @param uri   URI which will be appended to server base URL
  * @param data  JSON Data to PUT
  */
-def restPost(master, uri, data = null) {
-    return restCall(master, uri, 'POST', data, ['Accept': '*/*'])
+def restPut(base, uri, data = null, headers = ['Accept': '*/*']) {
+    return restCall(base, uri, 'PUT', data, headers)
 }
 
 /**
- * Make DELETE request using Salt REST API and return parsed JSON
+ * Make PATCH request using REST API and return parsed JSON
  *
- * @param master   Salt connection object
- * @param uri   URI which will be appended to Salt server base URL
+ * @param base  connection object, map with 'url' and optional 'authToken' keys
+ * @param uri   URI which will be appended to server base URL
+ * @param data  JSON Data to PUT
  */
-def restDelete(master, uri, data = null) {
-    return restCall(master, uri, 'DELETE', data)
+def restPatch(base, uri, data = null, headers = ['Accept': '*/*']) {
+    return restCall(base, uri, 'PATCH', data, headers)
+}
+
+/**
+ * Make DELETE request using REST API and return parsed JSON
+ *
+ * @param base  connection object, map with 'url' and optional 'authToken' keys
+ * @param uri   URI which will be appended to server base URL
+ */
+def restDelete(base, uri, data = null, headers = [:]) {
+    return restCall(base, uri, 'DELETE', data, headers)
 }
 
 /**
diff --git a/src/com/mirantis/mk/Openscap.groovy b/src/com/mirantis/mk/Openscap.groovy
new file mode 100644
index 0000000..1841f16
--- /dev/null
+++ b/src/com/mirantis/mk/Openscap.groovy
@@ -0,0 +1,52 @@
+package com.mirantis.mk
+
+/**
+ * Run salt oscap.eval xccdf
+ *
+ * @param target            the target where the benchmark will be evaluated
+ * @param evaltype          what to evaluate (xccdf or oval)
+ * @param benchmark         the benchmark which will be evaluated by openscap
+ * @param resultsDir        the directory where artifacts will be moved
+ * @param profile           the XCCDF profile name
+ * @param xccdfVersion      XCCDF benchmark version (default 1.2)
+ * @param tailoringId       The id of your tailoring data (from the corresponding pillar)
+ */
+def openscapEval(master, target, evaltype, benchmark, resultsDir, profile = 'default', xccdfVersion = '1.2', tailoringId = 'None') {
+    def salt = new com.mirantis.mk.Salt()
+    def common = new com.mirantis.mk.Common()
+    salt.runSaltProcessStep(master, target, 'oscap.eval', [evaltype, benchmark, results_dir = resultsDir, profile = profile, xccdf_version = xccdfVersion, tailoring_id= tailoringId])
+}
+
+/**
+ * Upload results to the security dashboard
+ *
+ * @param apiUrl        the security dashboard url
+ * @param file          the file to upload
+ * @param cloud_name    the cloud_name
+ * @param nodename      the scanned node name
+ */
+def uploadScanResultsToDashboard(apiUrl, results, cloud_name, nodename) {
+    def common = new com.mirantis.mk.Common()
+    def http = new com.mirantis.mk.Http()
+    def data = [:]
+
+    // Skip authorization until there is no authorization in the worp
+
+    // Get cloud_id
+    data['name'] = cloud_name
+    def cloudId = common.parseJSON(http.sendHttpPostRequest(apiUrl+'/environment', data))['id']
+    // Get report_id
+    data['env_uuid'] = cloudId
+    def reportId = common.parseJSON(http.sendHttpPostRequest(apiUrl+'/reports/openscap/', data))['id']
+
+    // Create node
+    def nodes = []
+    nodes.add[nodename]
+    data['nodes'] = nodes
+    http.sendHttpPostRequest(apiUrl+'/environment/'+cloudId+'/nodes', data)
+
+    // Upload results
+    data['results'] = results
+    data['node'] = nodename
+    http.sendHttpPostRequest(apiUrl+'/reports/openscap/'+reportId, data)
+}
diff --git a/src/com/mirantis/mk/Orchestrate.groovy b/src/com/mirantis/mk/Orchestrate.groovy
index ba9f627..265cfa0 100644
--- a/src/com/mirantis/mk/Orchestrate.groovy
+++ b/src/com/mirantis/mk/Orchestrate.groovy
@@ -61,24 +61,16 @@
     salt.enforceState(master, "* ${extra_tgt}", ['linux.network.host'])
 
     // Install and configure iptables
-    if (salt.testTarget(master, "I@iptables:service ${extra_tgt}")) {
-        salt.enforceState(master, "I@iptables:service ${extra_tgt}", 'iptables')
-    }
+    salt.enforceStateWithTest(master, "I@iptables:service ${extra_tgt}", 'iptables')
 
     // Install and configure logrotate
-    if (salt.testTarget(master, "I@logrotate:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@logrotate:server ${extra_tgt}", 'logrotate')
-    }
+    salt.enforceStateWithTest(master, "I@logrotate:server ${extra_tgt}", 'logrotate')
 
     // Install and configure auditd
-    if (salt.testTarget(master, "I@auditd:service ${extra_tgt}")) {
-        salt.enforceState(master, "I@auditd:service ${extra_tgt}", 'auditd')
-    }
+    salt.enforceStateWithTest(master, "I@auditd:service ${extra_tgt}", 'auditd')
 
     // Install and configure openscap
-    if (salt.testTarget(master, "I@openscap:service ${extra_tgt}")) {
-        salt.enforceState(master, "I@openscap:service ${extra_tgt}", 'openscap')
-    }
+    salt.enforceStateWithTest(master, "I@openscap:service ${extra_tgt}", 'openscap')
 }
 
 def installFoundationInfraOnTarget(master, target, staticMgmtNet=false, extra_tgt = '') {
@@ -174,20 +166,19 @@
 def installInfra(master, extra_tgt = '') {
     def common = new com.mirantis.mk.Common()
     def salt = new com.mirantis.mk.Salt()
+    def first_target
 
     // Install glusterfs
     if (salt.testTarget(master, "I@glusterfs:server ${extra_tgt}")) {
         salt.enforceState(master, "I@glusterfs:server ${extra_tgt}", 'glusterfs.server.service')
 
-        salt.enforceState(master, "I@glusterfs:server and *01* ${extra_tgt}", 'glusterfs.server.setup', true, true, null, false, -1, 5)
+        salt.enforceState(master, "I@glusterfs:server:role:primary ${extra_tgt}", 'glusterfs.server.setup', true, true, null, false, -1, 5)
         sleep(10)
         salt.cmdRun(master, "I@glusterfs:server ${extra_tgt}", "gluster peer status; gluster volume status")
     }
 
     // Ensure glusterfs clusters is ready
-    if (salt.testTarget(master, "I@glusterfs:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', true, true, null, false, -1, 2)
-    }
+    salt.enforceStateWithTest(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', "", true, true, null, false, -1, 2)
 
     // Install galera
     if (salt.testTarget(master, "I@galera:master ${extra_tgt}") || salt.testTarget(master, "I@galera:slave ${extra_tgt}")) {
@@ -198,11 +189,9 @@
         salt.runSaltProcessStep(master, "I@galera:master ${extra_tgt}", 'mysql.status')
         salt.runSaltProcessStep(master, "I@galera:slave ${extra_tgt}", 'mysql.status')
     // If galera is not enabled check if we need to install mysql:server
-    } else if (salt.testTarget(master, "I@mysql:server ${extra_tgt}")){
-        salt.enforceState(master, "I@mysql:server ${extra_tgt}", 'mysql.server')
-        if (salt.testTarget(master, "I@mysql:client ${extra_tgt}")){
-            salt.enforceState(master, "I@mysql:client ${extra_tgt}", 'mysql.client')
-        }
+    } else {
+    salt.enforceStateWithTest(master, "I@mysql:server ${extra_tgt}", 'mysql.server')
+    salt.enforceStateWithTest(master, "I@mysql:client ${extra_tgt}", 'mysql.client')
     }
     installBackup(master, 'mysql', extra_tgt)
 
@@ -214,7 +203,8 @@
 
     // Install keepalived
     if (salt.testTarget(master, "I@keepalived:cluster ${extra_tgt}")) {
-        salt.enforceState(master, "I@keepalived:cluster and *01* ${extra_tgt}", 'keepalived')
+        first_target = salt.getFirstMinion(master, "I@keepalived:cluster ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'keepalived')
         salt.enforceState(master, "I@keepalived:cluster ${extra_tgt}", 'keepalived')
     }
 
@@ -236,9 +226,7 @@
     }
 
     // Install memcached
-    if (salt.testTarget(master, "I@memcached:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@memcached:server ${extra_tgt}", 'memcached')
-    }
+    salt.enforceStateWithTest(master, "I@memcached:server ${extra_tgt}", 'memcached')
 
     // Install etcd
     if (salt.testTarget(master, "I@etcd:server ${extra_tgt}")) {
@@ -250,9 +238,7 @@
 
     // Install redis
     if (salt.testTarget(master, "I@redis:server ${extra_tgt}")) {
-        if (salt.testTarget(master, "I@redis:cluster:role:master ${extra_tgt}")) {
-            salt.enforceState(master, "I@redis:cluster:role:master ${extra_tgt}", 'redis')
-        }
+        salt.enforceStateWithTest(master, "I@redis:cluster:role:master ${extra_tgt}", 'redis')
         salt.enforceState(master, "I@redis:server ${extra_tgt}", 'redis')
     }
 
@@ -277,23 +263,18 @@
 def installOpenstackControl(master, extra_tgt = '') {
     def salt = new com.mirantis.mk.Salt()
     def common = new com.mirantis.mk.Common()
+    def first_target
 
     // Install horizon dashboard
-    if (salt.testTarget(master, "I@horizon:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@horizon:server ${extra_tgt}", 'horizon')
-    }
+    salt.enforceStateWithTest(master, "I@horizon:server ${extra_tgt}", 'horizon')
     // Install sphinx server
-    if (salt.testTarget(master, "I@sphinx:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
-    }
-    if (salt.testTarget(master, "I@nginx:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@nginx:server ${extra_tgt}", 'salt.minion')
-        salt.enforceState(master, "I@nginx:server ${extra_tgt}", 'nginx')
-    }
+    salt.enforceStateWithTest(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
+    salt.enforceStateWithTest(master, "I@nginx:server ${extra_tgt}", 'salt.minion')
+    salt.enforceStateWithTest(master, "I@nginx:server ${extra_tgt}", 'nginx')
 
     // setup keystone service
     if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@keystone:server and *01* ${extra_tgt}", 'keystone.server')
+        salt.enforceState(master, "I@keystone:server:role:primary ${extra_tgt}", 'keystone.server')
         salt.enforceState(master, "I@keystone:server ${extra_tgt}", 'keystone.server')
         // populate keystone services/tenants/roles/users
 
@@ -303,7 +284,8 @@
         sleep(30)
     }
     if (salt.testTarget(master, "I@keystone:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@keystone:client and *01* ${extra_tgt}", 'keystone.client')
+        first_target = salt.getFirstMinion(master, "I@keystone:client ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'keystone.client')
         salt.enforceState(master, "I@keystone:client ${extra_tgt}", 'keystone.client')
     }
     if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
@@ -313,11 +295,8 @@
     }
 
     // Install glance
-    if (salt.testTarget(master, "I@glance:server ${extra_tgt}")) {
-        //runSaltProcessStep(master, 'I@glance:server', 'state.sls', ['glance.server'], 1)
-        salt.enforceState(master, "I@glance:server and *01* ${extra_tgt}", 'glance.server')
-       salt.enforceState(master, "I@glance:server ${extra_tgt}", 'glance.server')
-    }
+    salt.enforceStateWithTest(master, "I@glance:server:role:primary ${extra_tgt}", 'glance.server', "I@glance:server ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@glance:server ${extra_tgt}", 'glance.server')
 
     // Check glance service
     if (salt.testTarget(master, "I@glance:server ${extra_tgt}")) {
@@ -327,60 +306,48 @@
     }
 
     // Create glance resources
-    if (salt.testTarget(master, "I@glance:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@glance:client ${extra_tgt}", 'glance.client')
-    }
+    salt.enforceStateWithTest(master, "I@glance:client ${extra_tgt}", 'glance.client')
 
     // Install and check nova service
-    if (salt.testTarget(master, "I@nova:controller ${extra_tgt}")) {
-        // run on first node first
-        salt.enforceState(master, "I@nova:controller and *01* ${extra_tgt}", 'nova.controller')
-        salt.enforceState(master, "I@nova:controller ${extra_tgt}", 'nova.controller')
-        if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
-           common.retry(3,5){
-               salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; nova service-list')
-           }
+    // run on first node first
+    salt.enforceStateWithTest(master, "I@nova:controller:role:primary ${extra_tgt}", 'nova.controller', "I@nova:controller ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@nova:controller ${extra_tgt}", 'nova.controller')
+    if (salt.testTarget(master, "I@keystone:server and I@nova:controller ${extra_tgt}")) {
+        common.retry(3,5){
+            salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; nova service-list')
         }
     }
 
+
     // Create nova resources
-    if (salt.testTarget(master, "I@nova:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@nova:client ${extra_tgt}", 'nova.client')
-    }
+    salt.enforceStateWithTest(master, "I@nova:client ${extra_tgt}", 'nova.client')
 
     // Install and check cinder service
-    if (salt.testTarget(master, "I@cinder:controller ${extra_tgt}")) {
-        // run on first node first
-        salt.enforceState(master, "I@cinder:controller and *01* ${extra_tgt}", 'cinder')
-        salt.enforceState(master, "I@cinder:controller ${extra_tgt}", 'cinder')
-        if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
-            common.retry(3,5){
-                salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; cinder list')
-            }
+    // run on first node first
+    salt.enforceStateWithTest(master, "I@cinder:controller:role:primary ${extra_tgt}", 'cinder', "I@cinder:controller ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@cinder:controller ${extra_tgt}", 'cinder')
+    if (salt.testTarget(master, "I@keystone:server and I@cinder:controller ${extra_tgt}")) {
+        common.retry(3,5){
+            salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; cinder list')
         }
     }
 
     // Install neutron service
-    if (salt.testTarget(master, "I@neutron:server ${extra_tgt}")) {
-        // run on first node first
-        salt.enforceState(master, "I@neutron:server and *01* ${extra_tgt}", 'neutron.server')
-        salt.enforceState(master, "I@neutron:server ${extra_tgt}", 'neutron.server')
-        if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
-            common.retry(3,5){
-                salt.cmdRun(master, "I@keystone:server ${extra_tgt}",'. /root/keystonercv3; neutron agent-list')
-            }
+    // run on first node first
+    salt.enforceStateWithTest(master, "I@neutron:server:role:primary ${extra_tgt}", 'neutron.server', "I@neutron:server ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@neutron:server ${extra_tgt}", 'neutron.server')
+    if (salt.testTarget(master, "I@keystone:server and I@neutron:server ${extra_tgt}")) {
+        common.retry(3,5){
+            salt.cmdRun(master, "I@keystone:server ${extra_tgt}",'. /root/keystonercv3; neutron agent-list')
         }
     }
 
     // Install heat service
-    if (salt.testTarget(master, "I@heat:server ${extra_tgt}")) {
-        // run on first node first
-        salt.enforceState(master, "I@heat:server and *01* ${extra_tgt}", 'heat')
-        salt.enforceState(master, "I@heat:server ${extra_tgt}", 'heat')
-        if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
-            common.retry(3,5){
-                salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; openstack orchestration resource type list')
-            }
+    salt.enforceStateWithTest(master, "I@heat:server:role:primary ${extra_tgt}", 'heat', "I@heat:server ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@heat:server ${extra_tgt}", 'heat')
+    if (salt.testTarget(master, "I@keystone:server and I@heat:server ${extra_tgt}")) {
+        common.retry(3,5){
+            salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; openstack orchestration resource type list')
         }
     }
 
@@ -390,82 +357,69 @@
     }
 
     // Install ironic service
-    if (salt.testTarget(master, "I@ironic:api ${extra_tgt}")) {
-        salt.enforceState(master, "I@ironic:api and *01* ${extra_tgt}", 'ironic.api')
-        salt.enforceState(master, "I@ironic:api ${extra_tgt}", 'ironic.api')
-    }
+    salt.enforceStateWithTest(master, "I@ironic:api:role:primary ${extra_tgt}", 'ironic.api', "I@ironic:api ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@ironic:api ${extra_tgt}", 'ironic.api')
 
     // Install manila service
-    if (salt.testTarget(master, "I@manila:api ${extra_tgt}")) {
-        salt.enforceState(master, "I@manila:api and *01* ${extra_tgt}", 'manila.api')
-        salt.enforceState(master, "I@manila:api ${extra_tgt}", 'manila.api')
-    }
-    if (salt.testTarget(master, "I@manila:scheduler ${extra_tgt}")) {
-        salt.enforceState(master, "I@manila:scheduler ${extra_tgt}", 'manila.scheduler')
-    }
+    salt.enforceStateWithTest(master, "I@manila:api:role:primary ${extra_tgt}", 'manila.api', "I@manila:api ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@manila:api ${extra_tgt}", 'manila.api')
+    salt.enforceStateWithTest(master, "I@manila:scheduler ${extra_tgt}", 'manila.scheduler')
 
     // Install designate services
     if (salt.testTarget(master, "I@designate:server:enabled ${extra_tgt}")) {
-        salt.enforceState(master, "I@designate:server and *01* ${extra_tgt}", 'designate.server')
+        salt.enforceState(master, "I@designate:server:role:primary ${extra_tgt}", 'designate.server')
         salt.enforceState(master, "I@designate:server ${extra_tgt}", 'designate')
     }
 
     // Install octavia api service
-    if (salt.testTarget(master, "I@octavia:api ${extra_tgt}")) {
-        salt.enforceState(master, "I@octavia:api and *01* ${extra_tgt}", 'octavia')
-        salt.enforceState(master, "I@octavia:api ${extra_tgt}", 'octavia')
-    }
+    salt.enforceStateWithTest(master, "I@octavia:api:role:primary ${extra_tgt}", 'octavia.api', "I@octavia:api ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@octavia:api ${extra_tgt}", 'octavia.api')
 
     // Install DogTag server service
-    if (salt.testTarget(master, "I@dogtag:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@dogtag:server and *01* ${extra_tgt}", 'dogtag.server')
-        salt.enforceState(master, "I@dogtag:server ${extra_tgt}", 'dogtag.server')
-    }
+    salt.enforceStateWithTest(master, "I@dogtag:server:role:master ${extra_tgt}", 'dogtag.server', "I@dogtag:server ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@dogtag:server ${extra_tgt}", 'dogtag.server')
 
     // Install barbican server service
-    if (salt.testTarget(master, "I@barbican:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@barbican:server and *01* ${extra_tgt}", 'barbican.server')
-        salt.enforceState(master, "I@barbican:server ${extra_tgt}", 'barbican.server')
-    }
+    salt.enforceStateWithTest(master, "I@barbican:server:role:primary ${extra_tgt}", 'barbican.server', "I@barbican:server ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@barbican:server ${extra_tgt}", 'barbican.server')
+
     // Install barbican client
-    if (salt.testTarget(master, "I@barbican:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@barbican:client ${extra_tgt}", 'barbican.client')
-    }
+    salt.enforceStateWithTest(master, "I@barbican:client ${extra_tgt}", 'barbican.client')
 
     // Install gnocchi server
-    if (salt.testTarget(master, "I@gnocchi:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@gnocchi:server and *01* ${extra_tgt}", 'gnocchi.server')
-        salt.enforceState(master, "I@gnocchi:server ${extra_tgt}", 'gnocchi.server')
-    }
+    salt.enforceStateWithTest(master, "I@gnocchi:server:role:primary ${extra_tgt}", 'gnocchi.server', "I@gnocchi:server ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@gnocchi:server ${extra_tgt}", 'gnocchi.server')
 
     // Apply gnocchi client state to create gnocchi archive policies, due to possible
     // races, apply on the first node initially
     if (salt.testTarget(master, "I@gnocchi:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@gnocchi:client and *01* ${extra_tgt}", 'gnocchi.client')
+        first_target = salt.getFirstMinion(master, "I@gnocchi:client ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'gnocchi.client')
         salt.enforceState(master, "I@gnocchi:client ${extra_tgt}", 'gnocchi.client')
     }
 
     // Install gnocchi statsd
     if (salt.testTarget(master, "I@gnocchi:statsd ${extra_tgt}")) {
-        salt.enforceState(master, "I@gnocchi:statsd and *01* ${extra_tgt}", 'gnocchi.statsd')
+        first_target = salt.getFirstMinion(master, "I@gnocchi:statsd ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'gnocchi.statsd')
         salt.enforceState(master, "I@gnocchi:statsd ${extra_tgt}", 'gnocchi.statsd')
     }
 
     // Install panko server
     if (salt.testTarget(master, "I@panko:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@panko:server and *01* ${extra_tgt}", 'panko')
+        first_target = salt.getFirstMinion(master, "I@panko:server ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'panko')
         salt.enforceState(master, "I@panko:server ${extra_tgt}", 'panko')
     }
 
     // Install ceilometer server
-    if (salt.testTarget(master, "I@ceilometer:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@ceilometer:server and *01* ${extra_tgt}", 'ceilometer')
-        salt.enforceState(master, "I@ceilometer:server ${extra_tgt}", 'ceilometer')
-    }
+    salt.enforceStateWithTest(master, "I@ceilometer:server:role:primary ${extra_tgt}", 'ceilometer', "I@ceilometer:server ${extra_tgt}")
+    salt.enforceStateWithTest(master, "I@ceilometer:server ${extra_tgt}", 'ceilometer')
 
     // Install aodh server
     if (salt.testTarget(master, "I@aodh:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@aodh:server and *01* ${extra_tgt}", 'aodh')
+        first_target = salt.getFirstMinion(master, "I@aodh:server ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'aodh')
         salt.enforceState(master, "I@aodh:server ${extra_tgt}", 'aodh')
     }
 }
@@ -474,39 +428,24 @@
 def installIronicConductor(master, extra_tgt = ''){
     def salt = new com.mirantis.mk.Salt()
 
-    if (salt.testTarget(master, "I@ironic:conductor ${extra_tgt}")) {
-        salt.enforceState(master, "I@ironic:conductor ${extra_tgt}", 'ironic.conductor')
-        salt.enforceState(master, "I@ironic:conductor ${extra_tgt}", 'apache')
-    }
-    if (salt.testTarget(master, "I@tftpd_hpa:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@tftpd_hpa:server ${extra_tgt}", 'tftpd_hpa')
-    }
+    salt.enforceStateWithTest(master, "I@ironic:conductor ${extra_tgt}", 'ironic.conductor')
+    salt.enforceStateWithTest(master, "I@ironic:conductor ${extra_tgt}", 'apache')
+    salt.enforceStateWithTest(master, "I@tftpd_hpa:server ${extra_tgt}", 'tftpd_hpa')
 
     if (salt.testTarget(master, "I@nova:compute ${extra_tgt}")) {
         salt.runSaltProcessStep(master, "I@nova:compute ${extra_tgt}", 'service.restart', ['nova-compute'])
     }
 
-    if (salt.testTarget(master, "I@baremetal_simulator:enabled ${extra_tgt}")) {
-        salt.enforceState(master, "I@baremetal_simulator:enabled ${extra_tgt}", 'baremetal_simulator')
-    }
-    if (salt.testTarget(master, "I@ironic:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@ironic:client ${extra_tgt}", 'ironic.client')
-    }
+    salt.enforceStateWithTest(master, "I@baremetal_simulator:enabled ${extra_tgt}", 'baremetal_simulator')
+    salt.enforceStateWithTest(master, "I@ironic:client ${extra_tgt}", 'ironic.client')
 }
 
 def installManilaShare(master, extra_tgt = ''){
     def salt = new com.mirantis.mk.Salt()
 
-    if (salt.testTarget(master, "I@manila:share ${extra_tgt}")) {
-        salt.enforceState(master, "I@manila:share ${extra_tgt}", 'manila.share')
-    }
-    if (salt.testTarget(master, "I@manila:data ${extra_tgt}")) {
-        salt.enforceState(master, "I@manila:data ${extra_tgt}", 'manila.data')
-    }
-
-    if (salt.testTarget(master, "I@manila:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@manila:client ${extra_tgt}", 'manila.client')
-    }
+    salt.enforceStateWithTest(master, "I@manila:share ${extra_tgt}", 'manila.share')
+    salt.enforceStateWithTest(master, "I@manila:data ${extra_tgt}", 'manila.data')
+    salt.enforceStateWithTest(master, "I@manila:client ${extra_tgt}", 'manila.client')
 }
 
 
@@ -516,23 +455,19 @@
     //neutron agents in addition to neutron server. Once neutron agents
     //are up neutron resources can be created without hitting the situation when neutron resources are created
     //prior to neutron agents which results in creating ports in non-usable state
-    if (salt.testTarget(master, "I@neutron:gateway ${extra_tgt}")) {
-            salt.enforceState(master, "I@neutron:gateway ${extra_tgt}", 'neutron')
-    }
+    salt.enforceStateWithTest(master, "I@neutron:gateway ${extra_tgt}", 'neutron')
 
     // Create neutron resources - this step was moved here to ensure that
     //neutron resources are created after neutron agens are up. In this case neutron ports will be in
     //usable state. More information: https://bugs.launchpad.net/neutron/+bug/1399249
-    if (salt.testTarget(master, "I@neutron:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@neutron:client ${extra_tgt}", 'neutron.client')
-    }
+    salt.enforceStateWithTest(master, "I@neutron:client ${extra_tgt}", 'neutron.client')
 
     salt.enforceHighstate(master, "I@neutron:gateway ${extra_tgt}")
 
     // install octavia manager services
     if (salt.testTarget(master, "I@octavia:manager ${extra_tgt}")) {
         salt.runSaltProcessStep(master, "I@salt:master ${extra_tgt}", 'mine.update', ['*'])
-        salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'octavia')
+        salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'octavia.manager')
         salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'salt.minion.ca')
         salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'salt.minion.cert')
     }
@@ -552,7 +487,7 @@
         def hightstateTarget = "${compute_compound} and not ${gluster_compound} and not ${salt_ca_compound}"
         if (salt.testTarget(master, hightstateTarget)) {
             retry(2) {
-                salt.enforceHighstateWithExclude(master, hightstateTarget, 'opencontrail.client')
+                salt.enforceHighstate(master, hightstateTarget)
             }
         } else {
             common.infoMsg("No minions matching highstate target found for target ${hightstateTarget}")
@@ -565,7 +500,7 @@
                 if ( target == cmp_target ) {
                     // Enforce highstate one by one on salt ca servers which are compute nodes
                     retry(2) {
-                        salt.enforceHighstateWithExclude(master, target, 'opencontrail.client')
+                        salt.enforceHighstate(master, target)
                     }
                 }
             }
@@ -578,7 +513,7 @@
                 if ( target == cmp_target ) {
                     // Enforce highstate one by one on glusterfs servers which are compute nodes
                     retry(2) {
-                        salt.enforceHighstateWithExclude(master, target, 'opencontrail.client')
+                        salt.enforceHighstate(master, target)
                     }
                 }
             }
@@ -586,30 +521,29 @@
     }
 
     // Run nova:controller to map cmp with cells
-    if (salt.testTarget(master, "I@nova:controller ${extra_tgt}")) {
-      salt.enforceState(master, "I@nova:controller and *01* ${extra_tgt}", 'nova.controller')
-    }
+    salt.enforceState(master, "I@nova:controller:role:primary ${extra_tgt}", 'nova.controller', "I@nova:controller ${extra_tgt}")
 }
 
 
 def installContrailNetwork(master, extra_tgt = '') {
     def common = new com.mirantis.mk.Common()
     def salt = new com.mirantis.mk.Salt()
-
+    def first_target
 
     // Install opencontrail database services
-    salt.enforceState(master, "I@opencontrail:database and *01* ${extra_tgt}", 'opencontrail.database')
+    first_target = salt.getFirstMinion(master, "I@opencontrail:database ${extra_tgt}")
+    salt.enforceState(master, "${first_target} ${extra_tgt}", 'opencontrail.database')
     salt.enforceState(master, "I@opencontrail:database ${extra_tgt}", 'opencontrail.database')
 
     // Install opencontrail control services
-    salt.enforceStateWithExclude(master, "I@opencontrail:control and *01* ${extra_tgt}", "opencontrail", "opencontrail.client")
+    first_target = salt.getFirstMinion(master, "I@opencontrail:control ${extra_tgt}")
+    salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "opencontrail", "opencontrail.client")
     salt.enforceStateWithExclude(master, "I@opencontrail:control ${extra_tgt}", "opencontrail", "opencontrail.client")
-    salt.enforceStateWithExclude(master, "I@opencontrail:collector and *01* ${extra_tgt}", "opencontrail", "opencontrail.client")
+    first_target = salt.getFirstMinion(master, "I@opencontrail:collector ${extra_tgt}")
+    salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "opencontrail", "opencontrail.client")
     salt.enforceStateWithExclude(master, "I@opencontrail:collector ${extra_tgt}", "opencontrail", "opencontrail.client")
 
-    if (salt.testTarget(master, "I@docker:client and I@opencontrail:control ${extra_tgt}")) {
-        salt.enforceState(master, "( I@opencontrail:control or I@opencontrail:collector ) ${extra_tgt}", 'docker.client')
-    }
+    salt.enforceStateWithTest(master, "( I@opencontrail:control or I@opencontrail:collector ) ${extra_tgt}", 'docker.client', "I@docker:client and I@opencontrail:control ${extra_tgt}")
     installBackup(master, 'contrail', extra_tgt)
 }
 
@@ -632,10 +566,8 @@
     }
 
     sleep(300)
-    if (salt.testTarget(master, "I@opencontrail:compute ${extra_tgt}")) {
-        salt.enforceState(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail.client')
-        salt.enforceState(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail')
-    }
+    salt.enforceStateWithTest(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail.client')
+    salt.enforceStateWithTest(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail')
 }
 
 
@@ -648,39 +580,56 @@
 
 def installKubernetesControl(master, extra_tgt = '') {
     def salt = new com.mirantis.mk.Salt()
+    def first_target
     salt.fullRefresh(master, "* ${extra_tgt}")
 
     // Bootstrap all nodes
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'linux')
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'salt.minion')
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", ['openssh', 'ntp'])
+    salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'linux')
+    salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'salt.minion')
+    salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", ['openssh', 'ntp'])
 
     // Create and distribute SSL certificates for services using salt state
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'salt.minion.cert')
+    salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'salt.minion.cert')
 
     // Install docker
     salt.enforceState(master, "I@docker:host ${extra_tgt}", 'docker.host')
 
+     // If network engine is not opencontrail, run addons state for kubernetes
+    if (!salt.getPillar(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes:master:network:opencontrail:enabled')) {
+        salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.kube-addons')
+    }
+
     // Install Kubernetes pool and Calico
-    salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.kube-addons')
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'kubernetes.pool')
+    salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.pool')
 
     if (salt.testTarget(master, "I@etcd:server:setup ${extra_tgt}")) {
         // Setup etcd server
-        salt.enforceState(master, "I@kubernetes:master and *01* ${extra_tgt}", 'etcd.server.setup')
+        first_target = salt.getFirstMinion(master, "I@kubernetes:master ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'etcd.server.setup')
     }
 
     // Run k8s master at *01* to simplify namespaces creation
-    salt.enforceStateWithExclude(master, "I@kubernetes:master and *01* ${extra_tgt}", "kubernetes.master", "kubernetes.master.setup")
+    first_target = salt.getFirstMinion(master, "I@kubernetes:master ${extra_tgt}")
 
-    // Run k8s without master.setup
-    salt.enforceStateWithExclude(master, "I@kubernetes:master ${extra_tgt}", "kubernetes", "kubernetes.master.setup")
+    // If network engine is opencontrail, run master state for kubernetes without kube-addons
+    // The kube-addons state will be called later only in case of opencontrail
+    if (salt.getPillar(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes:master:network:opencontrail:enabled')) {
+        // Run k8s on first node without master.setup and master.kube-addons
+        salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "kubernetes.master", "kubernetes.master.setup,kubernetes.master.kube-addons")
+        // Run k8s without master.setup and master.kube-addons
+        salt.enforceStateWithExclude(master, "I@kubernetes:master ${extra_tgt}", "kubernetes", "kubernetes.master.setup,kubernetes.master.kube-addons")
+    } else {
+        // Run k8s on first node without master.setup and master.kube-addons
+        salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "kubernetes.master", "kubernetes.master.setup")
+        // Run k8s without master.setup
+        salt.enforceStateWithExclude(master, "I@kubernetes:master ${extra_tgt}", "kubernetes", "kubernetes.master.setup")
+    }
 
     // Run k8s master setup
-    salt.enforceState(master, "I@kubernetes:master and *01* ${extra_tgt}", 'kubernetes.master.setup')
+    salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.setup')
 
     // Restart kubelet
-    salt.runSaltProcessStep(master, "I@kubernetes:pool ${extra_tgt}", 'service.restart', ['kubelet'])
+    salt.runSaltProcessStep(master, "I@kubernetes:master ${extra_tgt}", 'service.restart', ['kubelet'])
 }
 
 
@@ -689,23 +638,22 @@
     salt.fullRefresh(master, "*")
 
     // Bootstrap all nodes
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'linux')
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'salt.minion')
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", ['openssh', 'ntp'])
+    salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'linux')
+    salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'salt.minion')
+    salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", ['openssh', 'ntp'])
 
     // Create and distribute SSL certificates for services using salt state
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'salt.minion.cert')
+    salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'salt.minion.cert')
 
     // Install docker
     salt.enforceState(master, "I@docker:host ${extra_tgt}", 'docker.host')
 
     // Install Kubernetes and Calico
-    salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'kubernetes.pool')
+    salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'kubernetes.pool')
 
     // Install Tiller and all configured releases
-    if (salt.testTarget(master, "I@helm:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@helm:client ${extra_tgt}", 'helm')
-    }
+    salt.enforceStateWithTest(master, "I@helm:client ${extra_tgt}", 'helm')
+    salt.runSaltProcessStep(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'service.restart', ['kubelet'])
 }
 
 
@@ -721,14 +669,22 @@
         salt.runSaltProcessStep(master, "I@docker:swarm ${extra_tgt}", 'saltutil.refresh_modules')
         sleep(5)
         salt.enforceState(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker.swarm')
-        if (salt.testTarget(master, "I@docker:swarm:role:manager ${extra_tgt}")){
-            salt.enforceState(master, "I@docker:swarm:role:manager ${extra_tgt}", 'docker.swarm')
-        }
+        salt.enforceStateWithTest(master, "I@docker:swarm:role:manager ${extra_tgt}", 'docker.swarm')
         sleep(10)
         salt.cmdRun(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker node ls')
     }
 }
 
+// Setup addons for kubernetes - For OpenContrail network engine
+// Use after compute nodes are ready, because K8s addons like DNS should be placed on cmp nodes
+def setupKubeAddonForContrail(master, extra_tgt = '') {
+    def salt = new com.mirantis.mk.Salt()
+
+    if (salt.getPillar(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes:master:network:opencontrail:enabled')){
+        // Setup  Addons for Kubernetes only in case of OpenContrail is used as neteork engine
+        salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.kube-addons')
+    }
+}
 
 def installCicd(master, extra_tgt = '') {
     def salt = new com.mirantis.mk.Salt()
@@ -792,13 +748,9 @@
       salt.cmdRun(master, jenkins_compound, 'timeout ' + (wait_timeout*60+3) + ' /bin/sh -c -- ' + '"' + check_jenkins_cmd + '"')
     }
 
-    if (salt.testTarget(master, "I@openldap:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@openldap:client ${extra_tgt}", 'openldap', true, true, null, false, -1, 2)
-    }
+    salt.enforceStateWithTest(master, "I@openldap:client ${extra_tgt}", 'openldap', "", true, true, null, false, -1, 2)
 
-    if (salt.testTarget(master, "I@python:environment ${extra_tgt}")) {
-        salt.enforceState(master, "I@python:environment ${extra_tgt}", 'python')
-    }
+    salt.enforceStateWithTest(master, "I@python:environment ${extra_tgt}", 'python')
 
     withEnv(['ASK_ON_ERROR=false']){
         retry(2){
@@ -826,6 +778,7 @@
     def salt = new com.mirantis.mk.Salt()
     def retries_wait = 20
     def retries = 15
+    def first_target
 
     // Install core services for K8S environments:
     // HAProxy, Nginx and lusterFS clients
@@ -834,13 +787,9 @@
         salt.enforceState(master, "I@haproxy:proxy ${extra_tgt}", 'haproxy')
         salt.runSaltProcessStep(master, "I@haproxy:proxy ${extra_tgt}", 'service.status', ['haproxy'])
 
-        if (salt.testTarget(master, "I@nginx:server ${extra_tgt}")) {
-            salt.enforceState(master, "I@nginx:server ${extra_tgt}", 'nginx')
-        }
+        salt.enforceStateWithTest(master, "I@nginx:server ${extra_tgt}", 'nginx')
 
-        if (salt.testTarget(master, "I@glusterfs:client ${extra_tgt}")) {
-            salt.enforceState(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', true, true, null, false, -1, 2)
-        }
+        salt.enforceStateWithTest(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', "", true, true, null, false, -1, 2)
     }
 
     // Install MongoDB for Alerta
@@ -857,23 +806,20 @@
     salt.enforceState(master, "( I@telegraf:agent or I@telegraf:remote_agent ) ${extra_tgt}", 'telegraf')
 
     // Install Prometheus exporters
-    if (salt.testTarget(master, "I@prometheus:exporters ${extra_tgt}")) {
-        salt.enforceState(master, "I@prometheus:exporters ${extra_tgt}", 'prometheus')
-    }
+    salt.enforceStateWithTest(master, "I@prometheus:exporters ${extra_tgt}", 'prometheus')
 
     //Install Elasticsearch and Kibana
-    if (salt.testTarget(master, "*01* and I@elasticsearch:server:enabled:true ${extra_tgt}")) {
-        salt.enforceState(master, "*01* and I@elasticsearch:server:enabled:true ${extra_tgt}", 'elasticsearch.server')
-    }
     if (salt.testTarget(master, "I@elasticsearch:server:enabled:true ${extra_tgt}")) {
-        salt.enforceState(master, "I@elasticsearch:server:enabled:true ${extra_tgt}", 'elasticsearch.server')
+        first_target = salt.getFirstMinion(master, "I@elasticsearch:server:enabled:true ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'elasticsearch.server')
     }
-    if (salt.testTarget(master, "*01* and I@kibana:server:enabled:true ${extra_tgt}")) {
-        salt.enforceState(master, "*01* and I@kibana:server:enabled:true ${extra_tgt}", 'kibana.server')
-    }
+    salt.enforceStateWithTest(master, "I@elasticsearch:server:enabled:true ${extra_tgt}", 'elasticsearch.server')
     if (salt.testTarget(master, "I@kibana:server:enabled:true ${extra_tgt}")) {
-        salt.enforceState(master, "I@kibana:server:enabled:true ${extra_tgt}", 'kibana.server')
+        first_target = salt.getFirstMinion(master, "I@kibana:server:enabled:true ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'kibana.server')
     }
+    salt.enforceStateWithTest(master, "I@kibana:server:enabled:true ${extra_tgt}", 'kibana.server')
+
     // Check ES health cluster status
     def pillar = salt.getPillar(master, "I@elasticsearch:client ${extra_tgt}", 'elasticsearch:client:server:host')
     def elasticsearch_vip
@@ -904,7 +850,8 @@
 
     //Install InfluxDB
     if (salt.testTarget(master, "I@influxdb:server ${extra_tgt}")) {
-        salt.enforceState(master, "*01* and I@influxdb:server ${extra_tgt}", 'influxdb')
+        first_target = salt.getFirstMinion(master, "I@influxdb:server ${extra_tgt}")
+        salt.enforceState(master, "${first_target} ${extra_tgt}", 'influxdb')
         salt.enforceState(master, "I@influxdb:server ${extra_tgt}", 'influxdb')
     }
 
@@ -950,14 +897,10 @@
     salt.runSaltProcessStep(master, "I@docker:swarm and I@prometheus:server ${extra_tgt}", 'dockerng.ps')
 
     //Install Prometheus LTS
-    if (salt.testTarget(master, "I@prometheus:relay ${extra_tgt}")) {
-        salt.enforceState(master, "I@prometheus:relay ${extra_tgt}", 'prometheus')
-    }
+    salt.enforceStateWithTest(master, "I@prometheus:relay ${extra_tgt}", 'prometheus')
 
     // Install sphinx server
-    if (salt.testTarget(master, "I@sphinx:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
-    }
+    salt.enforceStateWithTest(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
 
     //Configure Grafana
     pillar = salt.getPillar(master, "ctl01* ${extra_tgt}", '_param:stacklight_monitor_address')
@@ -1103,10 +1046,8 @@
             salt.runSaltProcessStep(master, "I@backupninja:client ${extra_tgt}", 'mine.update')
             salt.enforceState(master, "I@backupninja:client ${extra_tgt}", 'backupninja')
         }
-        if (salt.testTarget(master, "I@backupninja:server ${extra_tgt}")) {
-            salt.enforceState(master, "I@backupninja:server ${extra_tgt}", 'salt.minion.grains')
-            salt.enforceState(master, "I@backupninja:server ${extra_tgt}", 'backupninja')
-        }
+        salt.enforceStateWithTest(master, "I@backupninja:server ${extra_tgt}", 'salt.minion.grains')
+        salt.enforceStateWithTest(master, "I@backupninja:server ${extra_tgt}", 'backupninja')
     } else if (component == 'mysql') {
         // Install Xtrabackup
         if (salt.testTarget(master, "I@xtrabackup:client ${extra_tgt}")) {
@@ -1116,9 +1057,7 @@
             salt.runSaltProcessStep(master, "I@xtrabackup:client ${extra_tgt}", 'mine.update')
             salt.enforceState(master, "I@xtrabackup:client ${extra_tgt}", 'xtrabackup')
         }
-        if (salt.testTarget(master, "I@xtrabackup:server ${extra_tgt}")) {
-            salt.enforceState(master, "I@xtrabackup:server ${extra_tgt}", 'xtrabackup')
-        }
+        salt.enforceStateWithTest(master, "I@xtrabackup:server ${extra_tgt}", 'xtrabackup')
     } else if (component == 'contrail') {
 
         // Install Cassandra backup
@@ -1129,9 +1068,7 @@
             salt.runSaltProcessStep(master, "I@cassandra:backup:client ${extra_tgt}", 'mine.update')
             salt.enforceState(master, "I@cassandra:backup:client ${extra_tgt}", 'cassandra.backup')
         }
-        if (salt.testTarget(master, "I@cassandra:backup:server ${extra_tgt}")) {
-            salt.enforceState(master, "I@cassandra:backup:server ${extra_tgt}", 'cassandra.backup')
-        }
+        salt.enforceStateWithTest(master, "I@cassandra:backup:server ${extra_tgt}", 'cassandra.backup')
         // Install Zookeeper backup
         if (salt.testTarget(master, "I@zookeeper:backup:client ${extra_tgt}")) {
             salt.enforceState(master, "I@zookeeper:backup:client ${extra_tgt}", 'salt.minion.grains')
@@ -1140,9 +1077,7 @@
             salt.runSaltProcessStep(master, "I@zookeeper:backup:client ${extra_tgt}", 'mine.update')
             salt.enforceState(master, "I@zookeeper:backup:client ${extra_tgt}", 'zookeeper.backup')
         }
-        if (salt.testTarget(master, "I@zookeeper:backup:server ${extra_tgt}")) {
-            salt.enforceState(master, "I@zookeeper:backup:server ${extra_tgt}", 'zookeeper.backup')
-        }
+        salt.enforceStateWithTest(master, "I@zookeeper:backup:server ${extra_tgt}", 'zookeeper.backup')
     } else if (component == 'ceph') {
         // Install Ceph backup
         if (salt.testTarget(master, "I@ceph:backup:client ${extra_tgt}")) {
@@ -1152,9 +1087,7 @@
             salt.runSaltProcessStep(master, "I@ceph:backup:client ${extra_tgt}", 'mine.update')
             salt.enforceState(master, "I@ceph:backup:client ${extra_tgt}", 'ceph.backup')
         }
-        if (salt.testTarget(master, "I@ceph:backup:server ${extra_tgt}")) {
-            salt.enforceState(master, "I@ceph:backup:server ${extra_tgt}", 'ceph.backup')
-        }
+        salt.enforceStateWithTest(master, "I@ceph:backup:server ${extra_tgt}", 'ceph.backup')
     }
 
 }
@@ -1177,9 +1110,7 @@
     }
     // install Ceph Mons
     salt.enforceState(master, target, 'ceph.mon')
-    if (salt.testTarget(master, "I@ceph:mgr ${extra_tgt}")) {
-        salt.enforceState(master, "I@ceph:mgr ${extra_tgt}", 'ceph.mgr')
-    }
+    salt.enforceStateWithTest(master, "I@ceph:mgr ${extra_tgt}", 'ceph.mgr')
 }
 
 def installCephOsd(master, target="I@ceph:osd", setup=true, extra_tgt = '') {
@@ -1204,45 +1135,56 @@
     def salt = new com.mirantis.mk.Salt()
 
     // install Ceph Radosgw
-    if (salt.testTarget(master, "I@ceph:radosgw ${extra_tgt}")) {
+    if (salt.testTarget(master, "I@ceph:radosgw ${extra_tgt} and I@node_role.openstack-control")) {
         salt.runSaltProcessStep(master, "I@ceph:radosgw ${extra_tgt}", 'saltutil.sync_grains')
         salt.enforceState(master, "I@ceph:radosgw ${extra_tgt}", 'ceph.radosgw')
     }
-    // setup Keystone service and endpoints for swift or / and S3
-    if (salt.testTarget(master, "I@keystone:client ${extra_tgt}")) {
-        salt.enforceState(master, "I@keystone:client ${extra_tgt}", 'keystone.client')
+
+    // setup keyring for Openstack services
+    salt.enforceStateWithTest(master, "I@ceph:common and I@glance:server ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
+
+    salt.enforceStateWithTest(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
+
+    if (salt.testTarget(master, "I@ceph:common and I@nova:compute ${extra_tgt}")) {
+        salt.enforceState(master, "I@ceph:common and I@nova:compute ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
+        salt.runSaltProcessStep(master, "I@ceph:common and I@nova:compute ${extra_tgt}", 'saltutil.sync_grains')
     }
+
+    salt.enforceStateWithTest(master, "I@ceph:common and I@gnocchi:server ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
 }
 
 def connectCeph(master, extra_tgt = '') {
     def salt = new com.mirantis.mk.Salt()
 
+    // setup Keystone service and endpoints for swift or / and S3
+    salt.enforceStateWithTest(master, "I@keystone:client ${extra_tgt}", 'keystone.client')
+
     // connect Ceph to the env
     if (salt.testTarget(master, "I@ceph:common and I@glance:server ${extra_tgt}")) {
-        salt.enforceState(master, "I@ceph:common and I@glance:server ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring', 'glance'])
+        salt.enforceState(master, "I@ceph:common and I@glance:server ${extra_tgt}", ['glance'])
         salt.runSaltProcessStep(master, "I@ceph:common and I@glance:server ${extra_tgt}", 'service.restart', ['glance-api'])
     }
     if (salt.testTarget(master, "I@ceph:common and I@cinder:controller ${extra_tgt}")) {
-        salt.enforceState(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring', 'cinder'])
+        salt.enforceState(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", ['cinder'])
         salt.runSaltProcessStep(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", 'service.restart', ['cinder-volume'])
     }
     if (salt.testTarget(master, "I@ceph:common and I@nova:compute ${extra_tgt}")) {
-        salt.enforceState(master, "I@ceph:common and I@nova:compute ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
-        salt.runSaltProcessStep(master, "I@ceph:common and I@nova:compute ${extra_tgt}", 'saltutil.sync_grains')
         salt.enforceState(master, "I@ceph:common and I@nova:compute ${extra_tgt}", ['nova'])
         salt.runSaltProcessStep(master, "I@ceph:common and I@nova:compute ${extra_tgt}", 'service.restart', ['nova-compute'])
     }
+    if (salt.testTarget(master, "I@ceph:common and I@gnocchi:server ${extra_tgt}")) {
+        salt.enforceState(master, "I@ceph:common and I@gnocchi:server:role:primary ${extra_tgt}", 'gnocchi.server')
+        salt.enforceState(master, "I@ceph:common and I@gnocchi:server ${extra_tgt}", 'gnocchi.server')
+    }
 }
 
 def installOssInfra(master, extra_tgt = '') {
   def common = new com.mirantis.mk.Common()
   def salt = new com.mirantis.mk.Salt()
 
-  if (salt.testTarget(master, "I@devops_portal:config ${extra_tgt}")) {
-    salt.enforceState(master, "I@devops_portal:config ${extra_tgt}", 'devops_portal.config')
-    salt.enforceState(master, "I@rundeck:client ${extra_tgt}", ['linux.system.user', 'openssh'])
-    salt.enforceState(master, "I@rundeck:server ${extra_tgt}", 'rundeck.server')
-  }
+  salt.enforceStateWithTest(master, "I@devops_portal:config ${extra_tgt}", 'devops_portal.config', )
+  salt.enforceStateWithTest(master, "I@rundeck:client ${extra_tgt}", ['linux.system.user', 'openssh'], "I@devops_portal:config ${extra_tgt}")
+  salt.enforceStateWithTest(master, "I@rundeck:server ${extra_tgt}", 'rundeck.server', "I@devops_portal:config ${extra_tgt}")
 }
 
 def installOss(master, extra_tgt = '') {
@@ -1325,4 +1267,4 @@
     else {
       common.infoMsg("No applications found for orchestration")
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/mirantis/mk/Salt.groovy b/src/com/mirantis/mk/Salt.groovy
index e2f4189..ca209be 100644
--- a/src/com/mirantis/mk/Salt.groovy
+++ b/src/com/mirantis/mk/Salt.groovy
@@ -189,9 +189,10 @@
         return enforceState(saltId, target, state, output, failOnError, batch, false, read_timeout, retries, queue, saltArgs)
     } else {
         if (!optional) {
-                throw new Exception("No Minions matched the target matcher: ${testTargetMatcher}.")
+                common.infoMsg("No Minions matched the target matcher: ${testTargetMatcher}, and 'optional' param was set to false. - This may signify missing pillar definition!!")
+//              throw new Exception("No Minions matched the target matcher: ${testTargetMatcher}.") TODO: Change the infoMsg to Error once the methods are changed to Use named params and optional param will be set globally
             } else {
-                common.infoMsg("No Minions matched the target given, but 'optional' param was set to true - Pipeline continues. ")
+                common.infoMsg("No Minions matched the target matcher: ${testTargetMatcher}, but 'optional' param was set to true - Pipeline continues. ")
             }
     }
 }
@@ -1003,8 +1004,8 @@
  * @param file      File path to read (/etc/hosts for example)
  */
 
-def getFileContent(saltId, target, file) {
-    result = cmdRun(saltId, target, "cat ${file}")
+def getFileContent(saltId, target, file, checkResponse = true, batch=null, output = true, saltArgs = []) {
+    result = cmdRun(saltId, target, "cat ${file}", checkResponse, batch, output, saltArgs)
     return result['return'][0].values()[0].replaceAll('Salt command execution success','')
 }
 
diff --git a/src/com/mirantis/mk/SaltModelTesting.groovy b/src/com/mirantis/mk/SaltModelTesting.groovy
index 239ba69..2d1a888 100644
--- a/src/com/mirantis/mk/SaltModelTesting.groovy
+++ b/src/com/mirantis/mk/SaltModelTesting.groovy
@@ -1,59 +1,378 @@
 package com.mirantis.mk
 
 /**
+ * Setup Docker to run some tests. Returns true/false based on
+ were tests successful or not.
+ * @param config - LinkedHashMap with configuration params:
+ *   dockerHostname - (required) Hostname to use for Docker container.
+ *   formulasRevision - (optional) Revision of packages to use (default proposed).
+ *   runCommands - (optional) Dict with closure structure of body required tests. For example:
+ *     [ '001_Test': { sh("./run-some-test") }, '002_Test': { sh("./run-another-test") } ]
+ *     Before execution runCommands will be sorted by key names. Alpabetical order is preferred.
+ *   runFinally - (optional) Dict with closure structure of body required commands, which should be
+ *     executed in any case of test results. Same format as for runCommands
+ *   updateRepo - (optional) Whether to run common repo update step.
+ *   dockerContainerName - (optional) Docker container name.
+ *   dockerImageName - (optional) Docker image name
+ *   dockerMaxCpus - (optional) Number of CPUS to use in Docker.
+ *   dockerExtraOpts - (optional) Array of Docker extra opts for container
+ *   envOpts - (optional) Array of variables that should be passed as ENV vars to Docker container.
+ * Return true | false
+ */
+
+def setupDockerAndTest(LinkedHashMap config) {
+    def common = new com.mirantis.mk.Common()
+    def TestMarkerResult = false
+    // setup options
+    def defaultContainerName = 'test-' + UUID.randomUUID().toString()
+    def dockerHostname = config.get('dockerHostname', defaultContainerName)
+    def formulasRevision = config.get('formulasRevision', 'proposed')
+    def runCommands = config.get('runCommands', [:])
+    def runFinally = config.get('runFinally', [:])
+    def baseRepoPreConfig = config.get('baseRepoPreConfig', true)
+    def dockerContainerName = config.get('dockerContainerName', defaultContainerName)
+    def dockerImageName = config.get('image', "mirantis/salt:saltstack-ubuntu-xenial-salt-2017.7")
+    def dockerMaxCpus = config.get('dockerMaxCpus', 4)
+    def dockerExtraOpts = config.get('dockerExtraOpts', [])
+    def envOpts = config.get('envOpts', [])
+    envOpts.add("DISTRIB_REVISION=${formulasRevision}")
+    def dockerBaseOpts = [
+        '-u root:root',
+        "--hostname=${dockerHostname}",
+        '--ulimit nofile=4096:8192',
+        "--name=${dockerContainerName}",
+        "--cpus=${dockerMaxCpus}"
+    ]
+
+    def dockerOptsFinal = (dockerBaseOpts + dockerExtraOpts).join(' ')
+    def defaultExtraReposYaml = '''
+---
+distrib_revision: 'nightly'
+aprConfD: |-
+  APT::Get::AllowUnauthenticated 'true';
+  APT::Get::Install-Suggests 'false';
+  APT::Get::Install-Recommends 'false';
+repo:
+  mcp_saltstack:
+    source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/saltstack-2017.7/xenial xenial main"
+    pinning: |-
+        Package: libsodium18
+        Pin: release o=SaltStack
+        Pin-Priority: 50
+
+        Package: *
+        Pin: release o=SaltStack
+        Pin-Priority: 1100
+  mcp_extra:
+    source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/extra/xenial xenial main"
+  ubuntu:
+    source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/ubuntu xenial main restricted universe"
+  ubuntu-upd:
+    source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/ubuntu xenial-updates main restricted universe"
+  ubuntu-sec:
+    source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/ubuntu xenial-security main restricted universe"
+'''
+    def img = docker.image(dockerImageName)
+    def extraReposYaml = config.get('extraReposYaml', defaultExtraReposYaml)
+
+    img.pull()
+
+    try {
+        img.inside(dockerOptsFinal) {
+            withEnv(envOpts) {
+                try {
+                    // Currently, we don't have any other point to install
+                    // runtime dependencies for tests.
+                    if (baseRepoPreConfig) {
+                        // Warning! POssible point of 'allow-downgrades' issue
+                        // Probably, need to add such flag into apt.prefs
+                        sh("""#!/bin/bash -xe
+                            echo "Installing extra-deb dependencies inside docker:"
+                            echo > /etc/apt/sources.list
+                            rm -vf /etc/apt/sources.list.d/* || true
+                        """)
+                        common.debianExtraRepos(extraReposYaml)
+                        sh('''#!/bin/bash -xe
+                            apt-get update
+                            apt-get install -y python-netaddr reclass
+                        ''')
+
+                    }
+                    runCommands.sort().each { command, body ->
+                        common.warningMsg("Running command: ${command}")
+                        // doCall is the closure implementation in groovy, allow to pass arguments to closure
+                        body.call()
+                    }
+                    // If we didn't dropped for now - test has been passed.
+                    TestMarkerResult = true
+                }
+                finally {
+                    runFinally.sort().each { command, body ->
+                        common.warningMsg("Running ${command} command.")
+                        // doCall is the closure implementation in groovy, allow to pass arguments to closure
+                        body.call()
+                    }
+                }
+            }
+        }
+    }
+    catch (Exception er) {
+        common.warningMsg("IgnoreMe:Something wrong with img.Message:\n" + er.toString())
+    }
+
+    try {
+        common.warningMsg("IgnoreMe:Force cleanup slave.Ignore docker-daemon errors")
+        timeout(time: 10, unit: 'SECONDS') {
+            sh(script: "set -x; docker kill ${dockerContainerName} || true", returnStdout: true)
+        }
+        timeout(time: 10, unit: 'SECONDS') {
+            sh(script: "set -x; docker rm --force ${dockerContainerName} || true", returnStdout: true)
+        }
+    }
+    catch (Exception er) {
+        common.warningMsg("IgnoreMe:Timeout to delete test docker container with force!Message:\n" + er.toString())
+    }
+
+    if (TestMarkerResult) {
+        common.infoMsg("Test finished: SUCCESS")
+    } else {
+        common.warningMsg("Test finished: FAILURE")
+    }
+    return TestMarkerResult
+}
+
+/**
+ * Wrapper around setupDockerAndTest, to run checks against new Reclass version
+ * that current model is compatible with new Reclass.
+ *
+ * @param config - LinkedHashMap with configuration params:
+ *   dockerHostname - (required) Hostname to use for Docker container.
+ *   distribRevision - (optional) Revision of packages to use (default proposed).
+ *   extraRepo - (optional) Extra repo to use to install new Reclass version. Has
+ *     high priority on distribRevision
+ *   targetNodes - (required) List nodes to check pillar data.
+ */
+def compareReclassVersions(config) {
+    def common = new com.mirantis.mk.Common()
+    def salt = new com.mirantis.mk.Salt()
+    common.infoMsg("Going to test new reclass for CFG node")
+    def distribRevision = config.get('distribRevision', 'proposed')
+    def venv = config.get('venv')
+    def extraRepo = config.get('extraRepo', '')
+    def extraRepoKey = config.get('extraRepoKey', '')
+    def targetNodes = config.get('targetNodes')
+    sh "rm -rf ${env.WORKSPACE}/old ${env.WORKSPACE}/new"
+    sh "mkdir -p ${env.WORKSPACE}/old ${env.WORKSPACE}/new"
+    def configRun = [
+        'formulasRevision': distribRevision,
+        'dockerExtraOpts' : [
+            "-v /srv/salt/reclass:/srv/salt/reclass:ro",
+            "-v /etc/salt:/etc/salt:ro",
+            "-v /usr/share/salt-formulas/:/usr/share/salt-formulas/:ro"
+        ],
+        'envOpts'         : [
+            "WORKSPACE=${env.WORKSPACE}",
+            "NODES_LIST=${targetNodes.join(' ')}"
+        ],
+        'runCommands'     : [
+            '001_Update_Reclass_package'    : {
+                sh('apt-get update && apt-get install -y reclass')
+            },
+            '002_Test_Reclass_Compatibility': {
+                sh('''
+                reclass-salt -b /srv/salt/reclass -t > ${WORKSPACE}/new/inventory || exit 1
+                for node in $NODES_LIST; do
+                    reclass-salt -b /srv/salt/reclass -p $node > ${WORKSPACE}/new/$node || exit 1
+                done
+              ''')
+            }
+        ]
+    ]
+    if (extraRepo) {
+        // FIXME
+        configRun['runCommands']['0001_Additional_Extra_Repo_Passed'] = {
+            sh("""
+                echo "${extraRepo}" > /etc/apt/sources.list.d/mcp_extra.list
+                [ "${extraRepoKey}" ] && wget -O - ${extraRepoKey} | apt-key add -
+            """)
+        }
+    }
+    if (setupDockerAndTest(configRun)) {
+        common.infoMsg("New reclass version is compatible with current model: SUCCESS")
+        def inventoryOld = salt.cmdRun(venv, "I@salt:master", "reclass-salt -b /srv/salt/reclass -t", true, null, true).get("return")[0].values()[0]
+        // [0..-31] to exclude 'echo Salt command execution success' from output
+        writeFile(file: "${env.WORKSPACE}/old/inventory", text: inventoryOld[0..-31])
+        for (String node in targetNodes) {
+            def nodeOut = salt.cmdRun(venv, "I@salt:master", "reclass-salt -b /srv/salt/reclass -p ${node}", true, null, true).get("return")[0].values()[0]
+            writeFile(file: "${env.WORKSPACE}/old/${node}", text: nodeOut[0..-31])
+        }
+        def reclassDiff = common.comparePillars(env.WORKSPACE, env.BUILD_URL, '')
+        currentBuild.description = reclassDiff
+        if (reclassDiff != '<b>No job changes</b>') {
+            throw new RuntimeException("Pillars with new reclass version has been changed: FAILED")
+        } else {
+            common.infoMsg("Pillars not changed with new reclass version: SUCCESS")
+        }
+    } else {
+        throw new RuntimeException("New reclass version is not compatible with current model: FAILED")
+    }
+}
+
+/**
+ * Wrapper over setupDockerAndTest, to test CC model.
+ *
+ * @param config - dict with params:
+ *   dockerHostname - (required) salt master's name
+ *   clusterName - (optional) model cluster name
+ *   extraFormulas - (optional) extraFormulas to install. DEPRECATED
+ *   formulasSource - (optional) formulas source (git or pkg, default pkg)
+ *   reclassVersion - (optional) Version of used reclass (branch, tag, ...) (optional, default master)
+ *   reclassEnv - (require) directory of model
+ *   ignoreClassNotfound - (optional) Ignore missing classes for reclass model (default false)
+ *   aptRepoUrl - (optional) package repository with salt formulas
+ *   aptRepoGPG - (optional) GPG key for apt repository with formulas
+ *   testContext - (optional) Description of test
+ Return: true\exception
+ */
+
+def testNode(LinkedHashMap config) {
+    def common = new com.mirantis.mk.Common()
+    def result = ''
+    def dockerHostname = config.get('dockerHostname')
+    def reclassEnv = config.get('reclassEnv')
+    def clusterName = config.get('clusterName', "")
+    def formulasSource = config.get('formulasSource', 'pkg')
+    def extraFormulas = config.get('extraFormulas', 'linux')
+    def reclassVersion = config.get('reclassVersion', 'master')
+    def ignoreClassNotfound = config.get('ignoreClassNotfound', false)
+    def aptRepoUrl = config.get('aptRepoUrl', "")
+    def aptRepoGPG = config.get('aptRepoGPG', "")
+    def testContext = config.get('testContext', 'test')
+    config['envOpts'] = [
+        "RECLASS_ENV=${reclassEnv}", "SALT_STOPSTART_WAIT=5",
+        "MASTER_HOSTNAME=${dockerHostname}", "CLUSTER_NAME=${clusterName}",
+        "MINION_ID=${dockerHostname}", "FORMULAS_SOURCE=${formulasSource}",
+        "EXTRA_FORMULAS=${extraFormulas}", "RECLASS_VERSION=${reclassVersion}",
+        "RECLASS_IGNORE_CLASS_NOTFOUND=${ignoreClassNotfound}", "DEBUG=1",
+        "APT_REPOSITORY=${aptRepoUrl}", "APT_REPOSITORY_GPG=${aptRepoGPG}",
+        "EXTRA_FORMULAS_PKG_ALL=true"
+    ]
+
+    config['runCommands'] = [
+        '001_Clone_salt_formulas_scripts': {
+            sh(script: 'git clone https://github.com/salt-formulas/salt-formulas-scripts /srv/salt/scripts', returnStdout: true)
+        },
+
+        '002_Prepare_something'          : {
+            sh('''rsync -ah ${RECLASS_ENV}/* /srv/salt/reclass && echo '127.0.1.2  salt' >> /etc/hosts
+              cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt-mk.mirantis.com/apt.mirantis.net:8085/g' {} \\;
+              cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt.mirantis.com/apt.mirantis.net:8085/g' {} \\;
+            ''')
+        },
+
+        '004_Run_tests'                  : {
+            def testTimeout = 40 * 60
+            timeout(time: testTimeout, unit: 'SECONDS') {
+                sh('''#!/bin/bash
+                source /srv/salt/scripts/bootstrap.sh
+                cd /srv/salt/scripts
+                source_local_envs
+                configure_salt_master
+                configure_salt_minion
+                install_salt_formula_pkg
+                source /srv/salt/scripts/bootstrap.sh
+                cd /srv/salt/scripts
+                saltservice_restart''')
+
+                sh('''#!/bin/bash
+                source /srv/salt/scripts/bootstrap.sh
+                cd /srv/salt/scripts
+                source_local_envs
+                saltmaster_init''')
+
+                sh('''#!/bin/bash
+                source /srv/salt/scripts/bootstrap.sh
+                cd /srv/salt/scripts
+                verify_salt_minions''')
+            }
+        }
+    ]
+    config['runFinally'] = [
+        '001_Archive_artefacts': {
+            sh(script: "cd /tmp; tar -czf ${env.WORKSPACE}/nodesinfo.tar.gz *reclass*", returnStatus: true)
+            archiveArtifacts artifacts: "nodesinfo.tar.gz"
+        }
+    ]
+    testResult = setupDockerAndTest(config)
+    if (testResult) {
+        common.infoMsg("Node test for context: ${testContext} model: ${reclassEnv} finished: SUCCESS")
+    } else {
+        throw new RuntimeException("Node test for context: ${testContext} model: ${reclassEnv} finished: FAILURE")
+    }
+    return testResult
+}
+
+/**
  * setup and test salt-master
  *
- * @param masterName          salt master's name
- * @param clusterName         model cluster name
- * @param extraFormulas       extraFormulas to install
- * @param formulasSource      formulas source (git or pkg)
- * @param reclassVersion      Version of used reclass (branch, tag, ...) (optional, default master)
- * @param testDir             directory of model
- * @param formulasSource      Salt formulas source type (optional, default pkg)
- * @param formulasRevision    APT revision for formulas (optional default stable)
+ * @param masterName salt master's name
+ * @param clusterName model cluster name
+ * @param extraFormulas extraFormulas to install. DEPRECATED
+ * @param formulasSource formulas source (git or pkg)
+ * @param reclassVersion Version of used reclass (branch, tag, ...) (optional, default master)
+ * @param testDir directory of model
+ * @param formulasSource Salt formulas source type (optional, default pkg)
+ * @param formulasRevision APT revision for formulas (optional default stable)
  * @param ignoreClassNotfound Ignore missing classes for reclass model
- * @param dockerMaxCpus       max cpus passed to docker (default 0, disabled)
- * @param legacyTestingMode   do you want to enable legacy testing mode (iterating through the nodes directory definitions instead of reading cluster models)
- * @param aptRepoUrl          package repository with salt formulas
- * @param aptRepoGPG          GPG key for apt repository with formulas
+ * @param dockerMaxCpus max cpus passed to docker (default 0, disabled)
+ * @param legacyTestingMode do you want to enable legacy testing mode (iterating through the nodes directory definitions instead of reading cluster models)
+ * @param aptRepoUrl package repository with salt formulas
+ * @param aptRepoGPG GPG key for apt repository with formulas
  * Return                     true | false
  */
 
-def setupAndTestNode(masterName, clusterName, extraFormulas, testDir, formulasSource = 'pkg',
+def setupAndTestNode(masterName, clusterName, extraFormulas = '*', testDir, formulasSource = 'pkg',
                      formulasRevision = 'stable', reclassVersion = "master", dockerMaxCpus = 0,
                      ignoreClassNotfound = false, legacyTestingMode = false, aptRepoUrl = '', aptRepoGPG = '', dockerContainerName = false) {
-  def common = new com.mirantis.mk.Common()
-  // timeout for test execution (40min)
-  def testTimeout = 40 * 60
-  def TestMarkerResult = false
-  def saltOpts = "--retcode-passthrough --force-color"
-  def workspace = common.getWorkspace()
-  def img = docker.image("mirantis/salt:saltstack-ubuntu-xenial-salt-2017.7")
-  img.pull()
+    def common = new com.mirantis.mk.Common()
+    // TODO
+    common.errorMsg('You are using deprecated function!Please migrate to "setupDockerAndTest".' +
+        'It would be removed after 2018.q4 release!Pushing forced 60s sleep..')
+    sh('sleep 60')
+    // timeout for test execution (40min)
+    def testTimeout = 40 * 60
+    def TestMarkerResult = false
+    def saltOpts = "--retcode-passthrough --force-color"
+    def workspace = common.getWorkspace()
+    def img = docker.image("mirantis/salt:saltstack-ubuntu-xenial-salt-2017.7")
+    img.pull()
 
-  if (!extraFormulas || extraFormulas == "") {
-    extraFormulas = "linux"
-  }
-  if (!dockerContainerName) {
-    dockerContainerName = 'setupAndTestNode' + UUID.randomUUID().toString()
-  }
-  def dockerMaxCpusOpt = "--cpus=4"
-  if (dockerMaxCpus > 0) {
-    dockerMaxCpusOpt = "--cpus=${dockerMaxCpus}"
-  }
-  try {
-    img.inside("-u root:root --hostname=${masterName} --ulimit nofile=4096:8192 ${dockerMaxCpusOpt} --name=${dockerContainerName}") {
-      withEnv(["FORMULAS_SOURCE=${formulasSource}", "EXTRA_FORMULAS=${extraFormulas}",
-               "DISTRIB_REVISION=${formulasRevision}",
-               "DEBUG=1", "MASTER_HOSTNAME=${masterName}",
-               "CLUSTER_NAME=${clusterName}", "MINION_ID=${masterName}",
-               "RECLASS_VERSION=${reclassVersion}", "RECLASS_IGNORE_CLASS_NOTFOUND=${ignoreClassNotfound}",
-               "APT_REPOSITORY=${aptRepoUrl}", "SALT_STOPSTART_WAIT=5",
-               "APT_REPOSITORY_GPG=${aptRepoGPG}"]) {
-        try {
-          // Currently, we don't have any other point to install
-          // runtime dependencies for tests.
-          sh("""#!/bin/bash -xe
+    if (formulasSource == 'pkg') {
+        if (extraFormulas) {
+            common.warningMsg("You have passed deprecated variable:extraFormulas=${extraFormulas}. " +
+                "\n It would be ignored, and all formulas would be installed anyway")
+        }
+    }
+    if (!dockerContainerName) {
+        dockerContainerName = 'setupAndTestNode' + UUID.randomUUID().toString()
+    }
+    def dockerMaxCpusOpt = "--cpus=4"
+    if (dockerMaxCpus > 0) {
+        dockerMaxCpusOpt = "--cpus=${dockerMaxCpus}"
+    }
+    try {
+        img.inside("-u root:root --hostname=${masterName} --ulimit nofile=4096:8192 ${dockerMaxCpusOpt} --name=${dockerContainerName}") {
+            withEnv(["FORMULAS_SOURCE=${formulasSource}", "EXTRA_FORMULAS=${extraFormulas}", "EXTRA_FORMULAS_PKG_ALL=true",
+                     "DISTRIB_REVISION=${formulasRevision}",
+                     "DEBUG=1", "MASTER_HOSTNAME=${masterName}",
+                     "CLUSTER_NAME=${clusterName}", "MINION_ID=${masterName}",
+                     "RECLASS_VERSION=${reclassVersion}", "RECLASS_IGNORE_CLASS_NOTFOUND=${ignoreClassNotfound}",
+                     "APT_REPOSITORY=${aptRepoUrl}", "SALT_STOPSTART_WAIT=5",
+                     "APT_REPOSITORY_GPG=${aptRepoGPG}"]) {
+                try {
+                    // Currently, we don't have any other point to install
+                    // runtime dependencies for tests.
+                    sh("""#!/bin/bash -xe
             echo "Installing extra-deb dependencies inside docker:"
             echo "APT::Get::AllowUnauthenticated 'true';"  > /etc/apt/apt.conf.d/99setupAndTestNode
             echo "APT::Get::Install-Suggests 'false';"  >> /etc/apt/apt.conf.d/99setupAndTestNode
@@ -64,18 +383,18 @@
             apt-get update
             apt-get install -y python-netaddr
             """)
-          sh(script: "git clone https://github.com/salt-formulas/salt-formulas-scripts /srv/salt/scripts", returnStdout: true)
-          sh("""rsync -ah ${testDir}/* /srv/salt/reclass && echo '127.0.1.2  salt' >> /etc/hosts
+                    sh(script: "git clone https://github.com/salt-formulas/salt-formulas-scripts /srv/salt/scripts", returnStdout: true)
+                    sh("""rsync -ah ${testDir}/* /srv/salt/reclass && echo '127.0.1.2  salt' >> /etc/hosts
             cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt-mk.mirantis.com/apt.mirantis.net:8085/g' {} \\;
             cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt.mirantis.com/apt.mirantis.net:8085/g' {} \\;
             """)
-          // FIXME: should be changed to use reclass from mcp_extra_nigtly?
-          sh("""for s in \$(python -c \"import site; print(' '.join(site.getsitepackages()))\"); do
+                    // FIXME: should be changed to use reclass from mcp_extra_nigtly?
+                    sh("""for s in \$(python -c \"import site; print(' '.join(site.getsitepackages()))\"); do
             sudo -H pip install --install-option=\"--prefix=\" --upgrade --force-reinstall -I \
             -t \"\$s\" git+https://github.com/salt-formulas/reclass.git@${reclassVersion};
             done""")
-          timeout(time: testTimeout, unit: 'SECONDS') {
-            sh('''#!/bin/bash
+                    timeout(time: testTimeout, unit: 'SECONDS') {
+                        sh('''#!/bin/bash
               source /srv/salt/scripts/bootstrap.sh
               cd /srv/salt/scripts
               source_local_envs
@@ -85,68 +404,68 @@
               source /srv/salt/scripts/bootstrap.sh
               cd /srv/salt/scripts
               saltservice_restart''')
-            sh('''#!/bin/bash
+                        sh('''#!/bin/bash
               source /srv/salt/scripts/bootstrap.sh
               cd /srv/salt/scripts
               source_local_envs
               saltmaster_init''')
 
-            if (!legacyTestingMode.toBoolean()) {
-              sh('''#!/bin/bash
+                        if (!legacyTestingMode.toBoolean()) {
+                            sh('''#!/bin/bash
                 source /srv/salt/scripts/bootstrap.sh
                 cd /srv/salt/scripts
                 verify_salt_minions
                 ''')
-              }
+                        }
+                    }
+                    // If we didn't dropped for now - test has been passed.
+                    TestMarkerResult = true
+                }
+
+                finally {
+                    // Collect rendered per-node data.Those info could be simply used
+                    // for diff processing. Data was generated via reclass.cli --nodeinfo,
+                    /// during verify_salt_minions.
+                    sh(script: "cd /tmp; tar -czf ${env.WORKSPACE}/nodesinfo.tar.gz *reclass*", returnStatus: true)
+                    archiveArtifacts artifacts: "nodesinfo.tar.gz"
+                }
             }
-            // If we didn't dropped for now - test has been passed.
-            TestMarkerResult = true
-          }
-
-        finally {
-        // Collect rendered per-node data.Those info could be simply used
-        // for diff processing. Data was generated via reclass.cli --nodeinfo,
-        /// during verify_salt_minions.
-        sh(script: "cd /tmp; tar -czf ${env.WORKSPACE}/nodesinfo.tar.gz *reclass*", returnStatus: true)
-        archiveArtifacts artifacts: "nodesinfo.tar.gz"
         }
-      }
     }
-  }
-  catch (Exception er) {
-    common.warningMsg("IgnoreMe:Something wrong with img.Message:\n" + er.toString())
-  }
+    catch (Exception er) {
+        common.warningMsg("IgnoreMe:Something wrong with img.Message:\n" + er.toString())
+    }
 
-  if (legacyTestingMode.toBoolean()) {
-    common.infoMsg("Running legacy mode test for master hostname ${masterName}")
-    def nodes = sh(script: "find /srv/salt/reclass/nodes -name '*.yml' | grep -v 'cfg*.yml'", returnStdout: true)
-    for (minion in nodes.tokenize()) {
-      def basename = sh(script: "set +x;basename ${minion} .yml", returnStdout: true)
-      if (!basename.trim().contains(masterName)) {
-        testMinion(basename.trim())
-      }
+    if (legacyTestingMode.toBoolean()) {
+        common.infoMsg("Running legacy mode test for master hostname ${masterName}")
+        def nodes = sh(script: "find /srv/salt/reclass/nodes -name '*.yml' | grep -v 'cfg*.yml'", returnStdout: true)
+        for (minion in nodes.tokenize()) {
+            def basename = sh(script: "set +x;basename ${minion} .yml", returnStdout: true)
+            if (!basename.trim().contains(masterName)) {
+                testMinion(basename.trim())
+            }
+        }
     }
-  }
 
-  try {
-    common.warningMsg("IgnoreMe:Force cleanup slave.Ignore docker-daemon errors")
-    timeout(time: 10, unit: 'SECONDS') {
-      sh(script: "set -x; docker kill ${dockerContainerName} || true", returnStdout: true)
+    try {
+        common.warningMsg("IgnoreMe:Force cleanup slave.Ignore docker-daemon errors")
+        timeout(time: 10, unit: 'SECONDS') {
+            sh(script: "set -x; docker kill ${dockerContainerName} || true", returnStdout: true)
+        }
+        timeout(time: 10, unit: 'SECONDS') {
+            sh(script: "set -x; docker rm --force ${dockerContainerName} || true", returnStdout: true)
+        }
     }
-    timeout(time: 10, unit: 'SECONDS') {
-      sh(script: "set -x; docker rm --force ${dockerContainerName} || true", returnStdout: true)
+    catch (Exception er) {
+        common.warningMsg("IgnoreMe:Timeout to delete test docker container with force!Message:\n" + er.toString())
     }
-  }
-  catch (Exception er) {
-    common.warningMsg("IgnoreMe:Timeout to delete test docker container with force!Message:\n" + er.toString())
-  }
 
-  if (TestMarkerResult) {
-    common.infoMsg("Test finished: SUCCESS")
-  } else {
-    common.warningMsg("Test finished: FAILURE")
-  }
-  return TestMarkerResult
+    if (TestMarkerResult) {
+        common.infoMsg("Test finished: SUCCESS")
+    } else {
+        common.warningMsg("Test finished: FAILURE")
+    }
+    return TestMarkerResult
 
 }
 
@@ -157,58 +476,60 @@
  */
 
 def testMinion(minionName) {
-  sh(script: "bash -c 'source /srv/salt/scripts/bootstrap.sh; cd /srv/salt/scripts && verify_salt_minion ${minionName}'", returnStdout: true)
+    sh(script: "bash -c 'source /srv/salt/scripts/bootstrap.sh; cd /srv/salt/scripts && verify_salt_minion ${minionName}'", returnStdout: true)
 }
 
-
 /**
  * Wrapper over setupAndTestNode, to test exactly one CC model.
-   Whole workspace and model - should be pre-rendered and passed via MODELS_TARGZ
-   Flow: grab all data, and pass to setupAndTestNode function
-   under-modell will be directly mirrored to `model/{cfg.testReclassEnv}/* /srv/salt/reclass/*`
+ Whole workspace and model - should be pre-rendered and passed via MODELS_TARGZ
+ Flow: grab all data, and pass to setupAndTestNode function
+ under-modell will be directly mirrored to `model/{cfg.testReclassEnv}/* /srv/salt/reclass/*`
  *
  * @param cfg - dict with params:
-  MODELS_TARGZ       http link to arch with (models|contexts|global_reclass)
-  modelFile
-  DockerCName        directly passed to setupAndTestNode
-  EXTRA_FORMULAS     directly passed to setupAndTestNode
-  DISTRIB_REVISION   directly passed to setupAndTestNode
-  reclassVersion     directly passed to setupAndTestNode
+ MODELS_TARGZ       http link to arch with (models|contexts|global_reclass)
+ modelFile
+ DockerCName        directly passed to setupAndTestNode
+ EXTRA_FORMULAS     directly passed to setupAndTestNode
+ DISTRIB_REVISION   directly passed to setupAndTestNode
+ reclassVersion     directly passed to setupAndTestNode
 
-  Return: true\exception
+ Return: true\exception
  */
 
 def testCCModel(cfg) {
-  def common = new com.mirantis.mk.Common()
-  sh(script:  'find . -mindepth 1 -delete || true', returnStatus: true)
-  sh(script: "wget --progress=dot:mega --auth-no-challenge -O models.tar.gz ${cfg.MODELS_TARGZ}")
-  // unpack data
-  sh(script: "tar -xzf models.tar.gz ")
-  common.infoMsg("Going to test exactly one context: ${cfg.modelFile}\n, with params: ${cfg}")
-  content = readFile(file: cfg.modelFile)
-  templateContext = readYaml text: content
-  clusterName = templateContext.default_context.cluster_name
-  clusterDomain = templateContext.default_context.cluster_domain
+    def common = new com.mirantis.mk.Common()
+    common.errorMsg('You are using deprecated function!Please migrate to "testNode".' +
+        'It would be removed after 2018.q4 release!Pushing forced 60s sleep..')
+    sh('sleep 60')
+    sh(script: 'find . -mindepth 1 -delete || true', returnStatus: true)
+    sh(script: "wget --progress=dot:mega --auth-no-challenge -O models.tar.gz ${cfg.MODELS_TARGZ}")
+    // unpack data
+    sh(script: "tar -xzf models.tar.gz ")
+    common.infoMsg("Going to test exactly one context: ${cfg.modelFile}\n, with params: ${cfg}")
+    content = readFile(file: cfg.modelFile)
+    templateContext = readYaml text: content
+    clusterName = templateContext.default_context.cluster_name
+    clusterDomain = templateContext.default_context.cluster_domain
 
-  def testResult = false
-  testResult = setupAndTestNode(
-      "cfg01.${clusterDomain}",
-      clusterName,
-      cfg.EXTRA_FORMULAS,
-      cfg.testReclassEnv, // Sync into image exactly one env
-      'pkg',
-      cfg.DISTRIB_REVISION,
-      cfg.reclassVersion,
-      0,
-      false,
-      false,
-      '',
-      '',
-      cfg.DockerCName)
-  if (testResult) {
-    common.infoMsg("testCCModel for context: ${cfg.modelFile} model: ${cfg.testReclassEnv} finished: SUCCESS")
-  } else {
-    throw new RuntimeException("testCCModel for context: ${cfg.modelFile} model: ${cfg.testReclassEnv} finished: FAILURE")
-  }
-  return testResult
+    def testResult = false
+    testResult = setupAndTestNode(
+        "cfg01.${clusterDomain}",
+        clusterName,
+        '',
+        cfg.testReclassEnv, // Sync into image exactly one env
+        'pkg',
+        cfg.DISTRIB_REVISION,
+        cfg.reclassVersion,
+        0,
+        false,
+        false,
+        '',
+        '',
+        cfg.DockerCName)
+    if (testResult) {
+        common.infoMsg("testCCModel for context: ${cfg.modelFile} model: ${cfg.testReclassEnv} finished: SUCCESS")
+    } else {
+        throw new RuntimeException("testCCModel for context: ${cfg.modelFile} model: ${cfg.testReclassEnv} finished: FAILURE")
+    }
+    return testResult
 }