Merge "Run dogtag:server in single thread"
diff --git a/src/com/mirantis/mk/Openstack.groovy b/src/com/mirantis/mk/Openstack.groovy
index acbf5f1..c195c2e 100644
--- a/src/com/mirantis/mk/Openstack.groovy
+++ b/src/com/mirantis/mk/Openstack.groovy
@@ -511,9 +511,11 @@
  *      of Salt mysql.status function. The result is then parsed, validated and outputed to the user.
  *
  * @param env           Salt Connection object or pepperEnv
+ * @param slave         Boolean value to enable slave checking (if master in unreachable)
+ * @param checkTimeSync Boolean value to enable time sync check
  * @return resultCode   int values used to determine exit status in the calling function
  */
-def verifyGaleraStatus(env, slave=false) {
+def verifyGaleraStatus(env, slave=false, checkTimeSync=false) {
     def salt = new com.mirantis.mk.Salt()
     def common = new com.mirantis.mk.Common()
     def out = ""
@@ -551,6 +553,10 @@
         common.errorMsg("No Galera slave was reachable.")
         return 130
     }
+    if (checkTimeSync && !salt.checkClusterTimeSync(env, "I@galera:master or I@galera:slave")) {
+        common.errorMsg("Time in cluster is desynchronized or it couldn't be detemined. You should fix this issue manually before proceeding.")
+        return 131
+    }
     try {
         out = salt.cmdRun(env, "I@salt:master", "salt -C '${testNode}' mysql.status")
     } catch (Exception e) {
@@ -714,7 +720,7 @@
  * @param env Salt Connection object or pepperEnv
  * @return output of salt commands
  */
-def restoreGaleraDb(env) {
+def restoreGaleraDb(env, type) {
     def salt = new com.mirantis.mk.Salt()
     def common = new com.mirantis.mk.Common()
     try {
diff --git a/src/com/mirantis/mk/Orchestrate.groovy b/src/com/mirantis/mk/Orchestrate.groovy
index 99d9643..a62503f 100644
--- a/src/com/mirantis/mk/Orchestrate.groovy
+++ b/src/com/mirantis/mk/Orchestrate.groovy
@@ -561,11 +561,18 @@
     salt.enforceStateWithExclude([saltId: master, target: "I@opencontrail:collector ${extra_tgt}", state: "opencontrail", excludedStates: "opencontrail.client"])
 
     salt.enforceStateWithTest([saltId: master, target: "( I@opencontrail:control or I@opencontrail:collector ) ${extra_tgt}", state: 'docker.client', testTargetMatcher: "I@docker:client and I@opencontrail:control ${extra_tgt}"])
+}
 
-    // Waiting until Contrail API is started
-    def apiCheckResult = salt.getReturnValues(salt.runSaltProcessStep(master, "I@opencontrail:control:role:primary", 'contrail_health.get_api_status', ['wait_for=300', 'tries=20']))
+
+def checkContrailApiReadiness(master, extra_tgt = '') {
+    def common = new com.mirantis.mk.Common()
+    def salt = new com.mirantis.mk.Salt()
+
+    def apiCheckResult = salt.getReturnValues(salt.runSaltProcessStep(master, "I@opencontrail:control:role:primary ${extra_tgt}", 'contrail_health.get_api_status', ['wait_for=900', 'tries=50']))
     if (!apiCheckResult){
         throw new Exception("Contrail is not working after deployment: contrail-api service is not in healthy state")
+    } else {
+        common.infoMsg('Contrail API is ready to service requests')
     }
 }
 
diff --git a/src/com/mirantis/mk/Salt.groovy b/src/com/mirantis/mk/Salt.groovy
index d126964..12195f4 100644
--- a/src/com/mirantis/mk/Salt.groovy
+++ b/src/com/mirantis/mk/Salt.groovy
@@ -1119,3 +1119,46 @@
     }
     return outputObj
 }
+
+/**
+* Check time settings on defined nodes, compares them
+* and evaluates the results
+*
+* @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method)
+* @param target Targeted nodes to be checked
+* @param diff   Maximum time difference (in seconds) to be accepted during time sync check
+* @return bool  Return true if time difference is <= diff and returns false if time difference is > diff
+*/
+
+def checkClusterTimeSync(saltId, target) {
+    def common = new com.mirantis.mk.Common()
+    def salt = new com.mirantis.mk.Salt()
+
+    times = []
+    try {
+        diff = salt.getReturnValues(salt.getPillar(saltId, 'I@salt:master', 'linux:system:time_diff'))
+        if (diff != null && diff != "" && diff.isInteger()) {
+            diff = diff.toInteger()
+        } else {
+            diff = 5
+        }
+        out = salt.runSaltProcessStep(saltId, target, 'status.time', '%s')
+        outParsed = out['return'][0]
+        def outKeySet = outParsed.keySet()
+        for (key in outKeySet) {
+            def time = outParsed[key].readLines().get(0)
+            common.infoMsg(time)
+            if (time.isInteger()) {
+                times.add(time.toInteger())
+            }
+        }
+        if ((times.max() - times.min()) <= diff) {
+            return true
+        } else {
+            return false
+        }
+    } catch(Exception e) {
+        common.errorMsg("Could not check cluster time sync.")
+        return false
+    }
+}
diff --git a/src/com/mirantis/mk/Test.groovy b/src/com/mirantis/mk/Test.groovy
index 8c663e5..d9de536 100644
--- a/src/com/mirantis/mk/Test.groovy
+++ b/src/com/mirantis/mk/Test.groovy
@@ -411,7 +411,7 @@
     def salt = new com.mirantis.mk.Salt()
     def dockerPackagesPillar = salt.getPillar(master, target, 'docker:host:pkgs')
     def dockerPackages = salt.getReturnValues(dockerPackagesPillar) ?: ['docker.io']
-    salt.runSaltProcessStep(master, target, 'pkg.install', dockerPackages)
+    salt.runSaltProcessStep(master, target, 'pkg.install', [dockerPackages.join(',')])
 }