Merge "Added the update salt master formulas, reclass model and jenkins jobs pipelines"
diff --git a/aptly-promote-pipeline.groovy b/aptly-promote-pipeline.groovy
index 00d41b8..eb10f40 100644
--- a/aptly-promote-pipeline.groovy
+++ b/aptly-promote-pipeline.groovy
@@ -26,8 +26,9 @@
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   } finally {
      common.sendNotification(currentBuild.result,"",["slack"])
   }
-}
\ No newline at end of file
+}
diff --git a/build-debian-packages-jmx-exporter.groovy b/build-debian-packages-jmx-exporter.groovy
index d356f69..71f626e 100644
--- a/build-debian-packages-jmx-exporter.groovy
+++ b/build-debian-packages-jmx-exporter.groovy
@@ -69,6 +69,7 @@
     } catch (Throwable e) {
        // If there was an exception thrown, the build failed
        currentBuild.result = "FAILURE"
+       currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
        throw e
     } finally {
        common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/build-debian-packages-libvirt-exporter.groovy b/build-debian-packages-libvirt-exporter.groovy
index eb109cb..ab83db6 100644
--- a/build-debian-packages-libvirt-exporter.groovy
+++ b/build-debian-packages-libvirt-exporter.groovy
@@ -72,6 +72,7 @@
     } catch (Throwable e) {
        // If there was an exception thrown, the build failed
        currentBuild.result = "FAILURE"
+       currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
        throw e
     } finally {
        common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/build-debian-packages-pipeline.groovy b/build-debian-packages-pipeline.groovy
index bc4ed38..9e40944 100644
--- a/build-debian-packages-pipeline.groovy
+++ b/build-debian-packages-pipeline.groovy
@@ -116,8 +116,9 @@
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   } finally {
      common.sendNotification(currentBuild.result,"",["slack"])
   }
-}
\ No newline at end of file
+}
diff --git a/build-debian-packages-telegraf.groovy b/build-debian-packages-telegraf.groovy
index b946688..dde098e 100644
--- a/build-debian-packages-telegraf.groovy
+++ b/build-debian-packages-telegraf.groovy
@@ -81,6 +81,10 @@
                         aptly.snapshotRepo(APTLY_URL, APTLY_REPO, timestamp)
                         aptly.publish(APTLY_URL)
                     }
+
+                    stage("rebuild docker images") {
+                        build job: "docker-build-images-prometheus", parameters: []
+                    }
                 }
             }
 
@@ -94,6 +98,7 @@
     } catch (Throwable e) {
        // If there was an exception thrown, the build failed
        currentBuild.result = "FAILURE"
+       currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
        throw e
     } finally {
        common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/build-extra-dpdk-pipeline.groovy b/build-extra-dpdk-pipeline.groovy
index 39928dc..357a9ad 100644
--- a/build-extra-dpdk-pipeline.groovy
+++ b/build-extra-dpdk-pipeline.groovy
@@ -59,8 +59,9 @@
      } catch (Throwable e) {
        // If there was an error or exception thrown, the build failed
        currentBuild.result = "FAILURE"
+       currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
        throw e
     } finally {
        common.sendNotification(currentBuild.result,"",["slack"])
     }
-}
\ No newline at end of file
+}
diff --git a/ceph-enforce-weights.groovy b/ceph-enforce-weights.groovy
new file mode 100644
index 0000000..4e06322
--- /dev/null
+++ b/ceph-enforce-weights.groovy
@@ -0,0 +1,60 @@
+/**
+ *
+ * Enforce OSD weights from model
+ *
+ * Requred parameters:
+ *  SALT_MASTER_URL             URL of Salt master
+ *  SALT_MASTER_CREDENTIALS     Credentials to the Salt API
+ *
+ *  ADMIN_HOST                  Host (minion id) with admin keyring
+ *
+ */
+
+common = new com.mirantis.mk.Common()
+salt = new com.mirantis.mk.Salt()
+
+// configure global variables
+def saltMaster
+
+def runCephCommand(master, cmd) {
+    return salt.cmdRun(master, ADMIN_HOST, cmd)
+}
+
+def grains
+
+node("python") {
+
+    stage('Load cluster information') {
+        // create connection to salt master
+        saltMaster = salt.connection(SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
+
+        // get list of disk from grains
+        grains = salt.getGrain(saltMaster, 'I@ceph:osd')['return'][0]
+        common.prettyPrint(grains)
+
+    }
+
+    stage('Enforce weights on OSDs') {
+
+        for (host in grains) {
+            // parse grains
+            def hostGrains = host.value
+            common.prettyPrint(hostGrains)
+
+            def hostname = hostGrains.host
+            def salt_id = hostGrains.id
+            def ceph_host_id = hostGrains.ceph_osd_host_id
+
+            common.infoMsg("Setting weights on host ${hostname} (${salt_id}), ceph_id ${ceph_host_id}")
+            for (disk in hostGrains.ceph_osd_disk) {
+                def osd_id = ceph_host_id + disk.key
+                print(osd_id)
+                print(disk.value)
+                print(disk.key)
+                def cmd = "ceph osd crush set ${osd_id} ${disk.value.weight} host=${hostname}"
+                print(runCephCommand(saltMaster, cmd))
+            }
+        }
+
+    }
+}
diff --git a/ceph-remove-osd.groovy b/ceph-remove-osd.groovy
index 9f6934e..ac102eb 100644
--- a/ceph-remove-osd.groovy
+++ b/ceph-remove-osd.groovy
@@ -40,14 +40,21 @@
 
     // get list of disk at the osd
     def pillar_disks = salt.getPillar(saltMaster, HOST, 'ceph:osd:disk')['return'][0].values()[0]
-    def hostname = salt.getPillar(saltMaster, HOST, 'linux:system:name')['return'][0].values()[0]
-    def hostname_id = hostname.replaceAll('osd', '')
+    def hostname_id = salt.getPillar(saltMaster, HOST, 'ceph:osd:host_id')['return'][0].values()[0]
     def osd_ids = []
 
-    for (i in pillar_disks.keySet()) {
-        def osd_id = (hostname_id + i).toInteger()
+    print("host_id is ${hostname_id}")
+    print("osds:")
+    print(osds)
+
+    for (i in pillar_disks) {
+        def osd_id = (hostname_id + i.key).toInteger().toString()
+        print("Evaluating ${osd_id}")
         if (osd_id in osds || OSD == '*') {
             osd_ids.add('osd.' + osd_id)
+            print("Will delete " + osd_id)
+        } else {
+            print("Skipping " + osd_id)
         }
     }
 
diff --git a/cicd-lab-pipeline.groovy b/cicd-lab-pipeline.groovy
index 15252c9..8902e1f 100644
--- a/cicd-lab-pipeline.groovy
+++ b/cicd-lab-pipeline.groovy
@@ -337,6 +337,7 @@
     } catch (Throwable e) {
         // If there was an error or exception thrown, the build failed
         currentBuild.result = "FAILURE"
+        currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
         throw e
     } finally {
         // Cleanup
diff --git a/cloud-deploy-pipeline.groovy b/cloud-deploy-pipeline.groovy
index b8ac3b0..9c7698b 100644
--- a/cloud-deploy-pipeline.groovy
+++ b/cloud-deploy-pipeline.groovy
@@ -127,28 +127,26 @@
                 }
                 // launch stack
                 if (STACK_REUSE.toBoolean() == false) {
-                    stage('Launch new Heat stack') {
-                        envParams = [
-                            'cluster_zone': HEAT_STACK_ZONE,
-                            'cluster_public_net': HEAT_STACK_PUBLIC_NET
-                        ]
+                    envParams = [
+                        'cluster_zone': HEAT_STACK_ZONE,
+                        'cluster_public_net': HEAT_STACK_PUBLIC_NET
+                    ]
 
-                        // set reclass repo in heat env
-                        try {
-                            envParams.put('cfg_reclass_branch', STACK_RECLASS_BRANCH)
-                            envParams.put('cfg_reclass_address', STACK_RECLASS_ADDRESS)
-                        } catch (MissingPropertyException e) {
-                            common.infoMsg("Property STACK_RECLASS_BRANCH or STACK_RECLASS_ADDRESS not found! Using default values from template.")
-                        }
-
-                        def legacy_env = false;
-                        //FIXME:
-                        if (false && STACK_TEMPLATE.startsWith('virtual_') && !STACK_TEMPLATE.contains('aio')) {
-                            legacy_env = true;
-                        }
-
-                        openstack.createHeatStack(openstackCloud, STACK_NAME, STACK_TEMPLATE, envParams, HEAT_STACK_ENVIRONMENT, venv, legacy_env)
+                    // set reclass repo in heat env
+                    try {
+                        envParams.put('cfg_reclass_branch', STACK_RECLASS_BRANCH)
+                        envParams.put('cfg_reclass_address', STACK_RECLASS_ADDRESS)
+                    } catch (MissingPropertyException e) {
+                        common.infoMsg("Property STACK_RECLASS_BRANCH or STACK_RECLASS_ADDRESS not found! Using default values from template.")
                     }
+
+                    def legacy_env = false;
+                    //FIXME:
+                    if (false && STACK_TEMPLATE.startsWith('virtual_') && !STACK_TEMPLATE.contains('aio')) {
+                        legacy_env = true;
+                    }
+
+                    openstack.createHeatStack(openstackCloud, STACK_NAME, STACK_TEMPLATE, envParams, HEAT_STACK_ENVIRONMENT, venv, legacy_env)
                 }
 
                 // get SALT_MASTER_URL
@@ -242,8 +240,13 @@
 
         // install ceph
         if (common.checkContains('STACK_INSTALL', 'ceph')) {
-            orchestrate.installCephMon(saltMaster)
-            orchestrate.installCephOsd(saltMaster)
+            stage('Install Ceph MONs') {
+                orchestrate.installCephMon(saltMaster)
+            }
+
+            stage('Install Ceph OSDs') {
+                orchestrate.installCephOsd(saltMaster)
+            }
         }
 
         // install k8s
@@ -358,25 +361,8 @@
         def artifacts_dir = '_artifacts/'
 
         if (common.checkContains('STACK_TEST', 'k8s')) {
-            stage('Run k8s bootstrap tests') {
-                def image = 'tomkukral/k8s-scripts'
-                def output_file = image.replaceAll('/', '-') + '.output'
-
-                // run image
-                test.runConformanceTests(saltMaster, TEST_K8S_API_SERVER, image)
-
-                // collect output
-                sh "mkdir -p ${artifacts_dir}"
-                file_content = salt.getFileContent(saltMaster, 'ctl01*', '/tmp/' + output_file)
-                writeFile file: "${artifacts_dir}${output_file}", text: file_content
-                sh "cat ${artifacts_dir}${output_file}"
-
-                // collect artifacts
-                archiveArtifacts artifacts: "${artifacts_dir}${output_file}"
-            }
-
             stage('Run k8s conformance e2e tests') {
-                def image = K8S_CONFORMANCE_IMAGE
+                def image = TEST_K8S_CONFORMANCE_IMAGE
                 def output_file = image.replaceAll('/', '-') + '.output'
 
                 // run image
@@ -410,6 +396,17 @@
             }
         }
 
+
+        if (common.checkContains('STACK_TEST', 'ceph')) {
+            stage('Run infra tests') {
+                def cmd = "apt-get install -y python-pip && pip install -r /usr/share/salt-formulas/env/ceph/files/testinfra/requirements.txt && python -m pytest --junitxml=/root/report.xml /usr/share/salt-formulas/env/ceph/files/testinfra/"
+                salt.cmdRun(saltMaster, 'I@salt:master', cmd)
+                writeFile(file: 'report.xml', text: salt.getFileContent(saltMaster, 'I@salt:master', '/root/report.xml'))
+                junit(keepLongStdio: true, testResults: 'report.xml')
+            }
+        }
+
+
         if (common.checkContains('STACK_INSTALL', 'finalize')) {
             stage('Finalize') {
                 salt.runSaltProcessStep(saltMaster, '*', 'state.apply', [], null, true)
diff --git a/docker-build-image-pipeline.groovy b/docker-build-image-pipeline.groovy
index a22439a..c23f1c3 100644
--- a/docker-build-image-pipeline.groovy
+++ b/docker-build-image-pipeline.groovy
@@ -48,6 +48,7 @@
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   } finally {
      common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/gating-pipeline.groovy b/gating-pipeline.groovy
index c113d28..1b62b53 100644
--- a/gating-pipeline.groovy
+++ b/gating-pipeline.groovy
@@ -68,6 +68,7 @@
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   }
 }
diff --git a/generate-cookiecutter-products.groovy b/generate-cookiecutter-products.groovy
index f6090c0..1d8845d 100644
--- a/generate-cookiecutter-products.groovy
+++ b/generate-cookiecutter-products.groovy
@@ -184,6 +184,7 @@
     } catch (Throwable e) {
          // If there was an error or exception thrown, the build failed
          currentBuild.result = "FAILURE"
+         currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
          throw e
     } finally {
         stage ('Clean workspace directories') {
diff --git a/git-merge-branches-pipeline.groovy b/git-merge-branches-pipeline.groovy
index cff1db5..8293f87 100644
--- a/git-merge-branches-pipeline.groovy
+++ b/git-merge-branches-pipeline.groovy
@@ -22,6 +22,7 @@
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   }
 }
diff --git a/git-mirror-2way-pipeline.groovy b/git-mirror-2way-pipeline.groovy
index c1c808c..c20af8f 100644
--- a/git-mirror-2way-pipeline.groovy
+++ b/git-mirror-2way-pipeline.groovy
@@ -38,6 +38,7 @@
     } catch (Throwable e) {
        // If there was an error or exception thrown, the build failed
        currentBuild.result = "FAILURE"
+       currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
        throw e
     } finally {
        common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/git-mirror-pipeline.groovy b/git-mirror-pipeline.groovy
index 10fa9a0..5035fe6 100644
--- a/git-mirror-pipeline.groovy
+++ b/git-mirror-pipeline.groovy
@@ -17,6 +17,7 @@
     } catch (Throwable e) {
        // If there was an error or exception thrown, the build failed
        currentBuild.result = "FAILURE"
+       currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
        throw e
     } finally {
        common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/opencontrail-upgrade.groovy b/opencontrail-upgrade.groovy
index 6bcb788..00b0e7f 100644
--- a/opencontrail-upgrade.groovy
+++ b/opencontrail-upgrade.groovy
@@ -273,6 +273,7 @@
         } catch (Throwable e) {
             // If there was an error or exception thrown, the build failed
             currentBuild.result = "FAILURE"
+            currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
             throw e
         }
     }
@@ -484,6 +485,7 @@
         } catch (Throwable e) {
             // If there was an error or exception thrown, the build failed
             currentBuild.result = "FAILURE"
+            currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
             throw e
         }
     }
diff --git a/openstack-compute-install.groovy b/openstack-compute-install.groovy
index 8e53396..535cde0 100644
--- a/openstack-compute-install.groovy
+++ b/openstack-compute-install.groovy
@@ -86,6 +86,7 @@
     } catch (Throwable e) {
         // If there was an error or exception thrown, the build failed
         currentBuild.result = "FAILURE"
+        currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
         throw e
     }
 }
diff --git a/openstack-compute-upgrade.groovy b/openstack-compute-upgrade.groovy
index 4a04531..095697d 100644
--- a/openstack-compute-upgrade.groovy
+++ b/openstack-compute-upgrade.groovy
@@ -199,6 +199,7 @@
     } catch (Throwable e) {
         // If there was an error or exception thrown, the build failed
         currentBuild.result = "FAILURE"
+        currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
         throw e
     }
 }
diff --git a/ovs-gateway-upgrade.groovy b/ovs-gateway-upgrade.groovy
index 70037a4..9cfa215 100644
--- a/ovs-gateway-upgrade.groovy
+++ b/ovs-gateway-upgrade.groovy
@@ -148,6 +148,7 @@
     } catch (Throwable e) {
         // If there was an error or exception thrown, the build failed
         currentBuild.result = "FAILURE"
+        currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
         throw e
     }
 }
diff --git a/release-salt-formulas-pipeline.groovy b/release-salt-formulas-pipeline.groovy
index 7660636..4aaaec9 100644
--- a/release-salt-formulas-pipeline.groovy
+++ b/release-salt-formulas-pipeline.groovy
@@ -24,6 +24,7 @@
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   } finally {
      common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/test-cookiecutter-reclass.groovy b/test-cookiecutter-reclass.groovy
index 4b93751..4da0842 100644
--- a/test-cookiecutter-reclass.groovy
+++ b/test-cookiecutter-reclass.groovy
@@ -80,9 +80,10 @@
     def templateEnv = "${env.WORKSPACE}"
     def content = readFile(file: "${templateEnv}/contexts/${modelFile}.yml")
     def templateContext = readYaml text: content
+    def clusterName = templateContext.default_context.cluster_name
     def clusterDomain = templateContext.default_context.cluster_domain
     git.checkoutGitRepository("${testEnv}/classes/system", RECLASS_MODEL_URL, RECLASS_MODEL_BRANCH, CREDENTIALS_ID)
-    saltModelTesting.setupAndTestNode("cfg01.${clusterDomain}", EXTRA_FORMULAS, testEnv)
+    saltModelTesting.setupAndTestNode("cfg01.${clusterDomain}", clusterName, EXTRA_FORMULAS, testEnv)
 }
 
 def gerritRef
@@ -164,6 +165,7 @@
 
     } catch (Throwable e) {
          currentBuild.result = "FAILURE"
+         currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
          throw e
     } finally {
          def dummy = "dummy"
diff --git a/test-salt-formulas-pipeline.groovy b/test-salt-formulas-pipeline.groovy
index 8ad5f1c..1fb62ad 100644
--- a/test-salt-formulas-pipeline.groovy
+++ b/test-salt-formulas-pipeline.groovy
@@ -117,6 +117,7 @@
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      ruby.runKitchenCommand("destroy")
      throw e
   } finally {
diff --git a/test-salt-model-node.groovy b/test-salt-model-node.groovy
index a2e6923..bcc0c8b 100644
--- a/test-salt-model-node.groovy
+++ b/test-salt-model-node.groovy
@@ -5,6 +5,7 @@
  *  DEFAULT_GIT_URL
  *  CREDENTIALS_ID
  *  EXTRA_FORMULAS
+ *  CLUSTER_NAME
  *  NODE_TARGET
  *  SYSTEM_GIT_URL
  *  SYSTEM_GIT_REF
@@ -53,12 +54,13 @@
     stage("test node") {
       if (checkouted) {
         def workspace = common.getWorkspace()
-        saltModelTesting.setupAndTestNode(NODE_TARGET, EXTRA_FORMULAS, workspace, FORMULAS_SOURCE, FORMULAS_REVISION, MAX_CPU_PER_JOB.toInteger())
+        saltModelTesting.setupAndTestNode(NODE_TARGET, CLUSTER_NAME, EXTRA_FORMULAS, workspace, FORMULAS_SOURCE, FORMULAS_REVISION, MAX_CPU_PER_JOB.toInteger())
       }
     }
   } catch (Throwable e) {
      // If there was an error or exception thrown, the build failed
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   }
 }
diff --git a/test-salt-models-pipeline.groovy b/test-salt-models-pipeline.groovy
index 5b97b5d..32ef43c 100644
--- a/test-salt-models-pipeline.groovy
+++ b/test-salt-models-pipeline.groovy
@@ -85,6 +85,7 @@
         def acc = 0
         for (int i = 0; i < nodes.size(); i++) {
           def testTarget = sh(script: "basename ${nodes[i]} .yml", returnStdout: true).trim()
+          def clusterName = testTarget.substring(testTarget.indexOf(".") + 1, testTarget.lastIndexOf("."))
           if (acc >= PARALLEL_NODE_GROUP_SIZE.toInteger()) {
             parallel branches
             branches = [:]
@@ -95,6 +96,7 @@
             build job: "test-salt-model-node", parameters: [
               [$class: 'StringParameterValue', name: 'DEFAULT_GIT_URL', value: defaultGitUrl],
               [$class: 'StringParameterValue', name: 'DEFAULT_GIT_REF', value: defaultGitRef],
+              [$class: 'StringParameterValue', name: 'CLUSTER_NAME', value: clusterName],
               [$class: 'StringParameterValue', name: 'NODE_TARGET', value: testTarget],
               [$class: 'StringParameterValue', name: 'FORMULAS_SOURCE', value: formulasSource],
               [$class: 'StringParameterValue', name: 'EXTRA_FORMULAS', value: EXTRA_FORMULAS],
@@ -113,6 +115,7 @@
     }
   } catch (Throwable e) {
      currentBuild.result = "FAILURE"
+     currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
      throw e
   } finally {
      common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/test-service.groovy b/test-service.groovy
index 29c88b2..f7cdd64 100644
--- a/test-service.groovy
+++ b/test-service.groovy
@@ -87,18 +87,10 @@
 
             writeFile(file: 'report.xml', text: salt.getFileContent(saltMaster, TEST_TEMPEST_TARGET, '/root/report.xml'))
             junit(keepLongStdio: true, testResults: 'report.xml', healthScaleFactor:  Double.parseDouble(TEST_JUNIT_RATIO))
-        }
-        stage("Approve test results"){
-          try {
-            timeout(time: 1, unit: 'HOURS'){
-              userInput = input message: 'Do you want to approve test results?'
+            def testResults = test.collectJUnitResults(currentBuild.rawBuild.getAction(hudson.tasks.test.AbstractTestResultAction.class))
+            if(testResults){
+                currentBuild.desc = String.format("result: %s", testResults["failed"] / testResults["total"])
             }
-            common.successMsg("Test results approved")
-            currentBuild.desc = "result: true"
-          } catch(err) { // timeout reached or input false
-            common.errorMsg("Test results not approved")
-            currentBuild.desc = "result: false"
-          }
         }
     } catch (Throwable e) {
         currentBuild.result = 'FAILURE'
diff --git a/test-system-reclass-pipeline.groovy b/test-system-reclass-pipeline.groovy
index 8a8fbac..411edfc 100644
--- a/test-system-reclass-pipeline.groovy
+++ b/test-system-reclass-pipeline.groovy
@@ -76,6 +76,7 @@
 } catch (Throwable e) {
     // If there was an error or exception thrown, the build failed
     currentBuild.result = "FAILURE"
+    currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
     throw e
 } finally {
     common.sendNotification(currentBuild.result,"",["slack"])
diff --git a/update-package.groovy b/update-package.groovy
index ea2259c..c946123 100644
--- a/update-package.groovy
+++ b/update-package.groovy
@@ -53,7 +53,11 @@
         }
 
         stage("List package upgrades") {
+            common.infoMsg("Listing all the packages that have a new update available on test nodes: ${targetTestSubset}")
             salt.runSaltProcessStep(saltMaster, targetTestSubset, 'pkg.list_upgrades', [], null, true)
+            if(TARGET_PACKAGES != "" && TARGET_PACKAGES != "*"){
+                common.infoMsg("Note that only the ${TARGET_PACKAGES} would be installed from the above list of available updates on the ${targetTestSubset}")
+            }
         }
 
         stage('Confirm live package upgrades on sample') {
@@ -102,6 +106,7 @@
     } catch (Throwable e) {
         // If there was an error or exception thrown, the build failed
         currentBuild.result = "FAILURE"
+        currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
         throw e
     }
 }
diff --git a/validate-cloud.groovy b/validate-cloud.groovy
index 6c25071..f2720c1 100644
--- a/validate-cloud.groovy
+++ b/validate-cloud.groovy
@@ -11,11 +11,15 @@
  *   TEMPEST_TEST_SET            If not false, run tests matched to pattern only
  *   RUN_TEMPEST_TESTS           If not false, run Tempest tests
  *   RUN_RALLY_TESTS             If not false, run Rally tests
+ *   RUN_K8S_TESTS               If not false, run Kubernetes tests
+ *   TEST_K8S_API_SERVER         Kubernetes API address
+ *   TEST_K8S_CONFORMANCE_IMAGE  Path to docker image with conformance e2e tests
  *
  */
 
 common = new com.mirantis.mk.Common()
 salt = new com.mirantis.mk.Salt()
+test = new com.mirantis.mk.Test()
 validate = new com.mirantis.mcp.Validate()
 
 def saltMaster
@@ -48,12 +52,46 @@
                 common.infoMsg("Skipping Rally tests")
             }
         }
+
+        stage('Run k8s bootstrap tests') {
+            if (RUN_K8S_TESTS.toBoolean() == true) {
+                def image = 'tomkukral/k8s-scripts'
+                def output_file = image.replaceAll('/', '-') + '.output'
+
+                // run image
+                test.runConformanceTests(saltMaster, TEST_K8S_API_SERVER, image)
+
+                // collect output
+                def file_content = salt.getFileContent(saltMaster, 'ctl01*', '/tmp/' + output_file)
+                writeFile file: "${artifacts_dir}${output_file}", text: file_content
+            } else {
+                common.infoMsg("Skipping k8s bootstrap tests")
+            }
+        }
+
+        stage('Run k8s conformance e2e tests') {
+            if (RUN_K8S_TESTS.toBoolean() == true) {
+                def image = TEST_K8S_CONFORMANCE_IMAGE
+                def output_file = image.replaceAll('/', '-') + '.output'
+
+                // run image
+                test.runConformanceTests(saltMaster, TEST_K8S_API_SERVER, image)
+
+                // collect output
+                def file_content = salt.getFileContent(saltMaster, 'ctl01*', '/tmp/' + output_file)
+                writeFile file: "${artifacts_dir}${output_file}", text: file_content
+            } else {
+                common.infoMsg("Skipping k8s conformance e2e tests")
+            }
+        }
+
         stage('Collect results') {
             archiveArtifacts artifacts: "${artifacts_dir}/*"
         }
     } catch (Throwable e) {
         // If there was an error or exception thrown, the build failed
         currentBuild.result = "FAILURE"
+        currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
         throw e
     } finally {
         validate.runCleanup(saltMaster, TARGET_NODE, artifacts_dir)