Merge "Add possibility to generate junit report for k8s tests."
diff --git a/build-mirror-image.groovy b/build-mirror-image.groovy
index a3c3122..822d398 100644
--- a/build-mirror-image.groovy
+++ b/build-mirror-image.groovy
@@ -34,7 +34,7 @@
 def openstack = new com.mirantis.mk.Openstack()
 def date = new Date()
 def dateTime = date.format("ddMMyyyy-HHmmss")
-def venvPepper = "venvPepper"
+def venvPepper = ""
 def privateKey = ""
 def floatingIP = ""
 def openstackServer = ""
@@ -62,8 +62,9 @@
     node("python&&disk-xl") {
         try {
             def workspace = common.getWorkspace()
-            rcFile = openstack.createOpenstackEnv(OS_URL, OS_CREDENTIALS_ID, OS_PROJECT, "default", "", "default", "2", "")
             openstackEnv = String.format("%s/venv", workspace)
+            venvPepper = String.format("%s/venvPepper", workspace)
+            rcFile = openstack.createOpenstackEnv(openstackEnv, OS_URL, OS_CREDENTIALS_ID, OS_PROJECT, "default", "", "default", "2", "")
             def openstackVersion = OS_VERSION
 
             VM_IP_DELAY = VM_IP_DELAY as Integer
@@ -227,4 +228,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/ceph-remove-osd.groovy b/ceph-remove-osd.groovy
index 04a176b..71946b7 100644
--- a/ceph-remove-osd.groovy
+++ b/ceph-remove-osd.groovy
@@ -75,7 +75,11 @@
 
         // get list of osd disks of the host
         salt.runSaltProcessStep(pepperEnv, HOST, 'saltutil.sync_grains', [], null, true, 5)
-        def ceph_disks = salt.getGrain(pepperEnv, HOST, 'ceph')['return'][0].values()[0].values()[0]['ceph_disk']
+        def cephGrain = salt.getGrain(pepperEnv, HOST, 'ceph')['return']
+        if(cephGrain['return'].isEmpty()){
+            throw new Exception("Ceph salt grain cannot be found!")
+        }
+        def ceph_disks = cephGrain['return'][0].values()[0].values()[0]['ceph_disk']
         common.prettyPrint(ceph_disks)
 
         for (i in ceph_disks) {
@@ -89,7 +93,7 @@
         }
 
         // wait for healthy cluster
-        if (WAIT_FOR_HEALTHY.toBoolean() == true) {
+        if (WAIT_FOR_HEALTHY.toBoolean()) {
             waitForHealthy(pepperEnv)
         }
 
@@ -99,7 +103,7 @@
         }
 
         // wait for healthy cluster
-        if (WAIT_FOR_HEALTHY.toBoolean() == true) {
+        if (WAIT_FOR_HEALTHY.toBoolean()) {
             sleep(5)
             waitForHealthy(pepperEnv)
         }
diff --git a/cicd-lab-pipeline.groovy b/cicd-lab-pipeline.groovy
index d985b3f..6236f2a 100644
--- a/cicd-lab-pipeline.groovy
+++ b/cicd-lab-pipeline.groovy
@@ -80,7 +80,7 @@
             }
 
             stage('Connect to OpenStack cloud') {
-                openstackCloud = openstack.createOpenstackEnv(
+                openstackCloud = openstack.createOpenstackEnv(openstackEnv,
                     OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS,
                     OPENSTACK_API_PROJECT, OPENSTACK_API_PROJECT_DOMAIN,
                     OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN,
diff --git a/cleanup-pipeline.groovy b/cleanup-pipeline.groovy
index ec3e19d..001e6c0 100644
--- a/cleanup-pipeline.groovy
+++ b/cleanup-pipeline.groovy
@@ -28,8 +28,8 @@
 salt = new com.mirantis.mk.Salt()
 timeout(time: 12, unit: 'HOURS') {
     node {
-
-        def venv_path = "${env.WORKSPACE}/venv"
+        def workspace = common.getWorkspace()
+        def venv_path = "${workspace}/venv"
         def env_vars
 
         // default STACK_TYPE is heat
@@ -56,7 +56,7 @@
 
         stage('Delete stack') {
             if (STACK_TYPE == 'heat') {
-                def openstackCloud = openstack.createOpenstackEnv(
+                def openstackCloud = openstack.createOpenstackEnv(venv_path,
                     OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS,
                     OPENSTACK_API_PROJECT,OPENSTACK_API_PROJECT_DOMAIN,
                     OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN,
diff --git a/cloud-deploy-pipeline.groovy b/cloud-deploy-pipeline.groovy
index 47fcc36..41c08ab 100644
--- a/cloud-deploy-pipeline.groovy
+++ b/cloud-deploy-pipeline.groovy
@@ -50,6 +50,13 @@
  *   TEST_TEMPEST_IMAGE           Tempest image link
  *   TEST_TEMPEST_PATTERN         If not false, run tests matched to pattern only
  *   TEST_TEMPEST_TARGET          Salt target for tempest node
+ *   TESTRAIL_REPORT              Whether upload results to testrail or not
+ *   TESTRAIL_REPORTER_IMAGE      Docker image for testrail reporter
+ *   TESTRAIL_QA_CREDENTIALS      Credentials for upload to testrail
+ *   TESTRAIL_MILESTONE           Product version for tests
+ *   TESTRAIL_PLAN                Testrail test plan
+ *   TESTRAIL_GROUP               Testrail test group
+ *   TESTRAIL_SUITE               Testrail test suite
  *
  * optional parameters for overwriting soft params
  *   SALT_OVERRIDES              YAML with overrides for Salt deployment
@@ -91,8 +98,9 @@
     node(slave_node) {
         try {
             // Set build-specific variables
-            venv = "${env.WORKSPACE}/venv"
-            venvPepper = "${env.WORKSPACE}/venvPepper"
+            def workspace = common.getWorkspace()
+            venv = "${workspace}/venv"
+            venvPepper = "${workspace}/venvPepper"
 
             //
             // Prepare machines
@@ -136,7 +144,7 @@
 
                     // create openstack env
                     openstack.setupOpenstackVirtualenv(venv, OPENSTACK_API_CLIENT)
-                    openstackCloud = openstack.createOpenstackEnv(
+                    openstackCloud = openstack.createOpenstackEnv(venv,
                         OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS,
                         OPENSTACK_API_PROJECT, OPENSTACK_API_PROJECT_DOMAIN,
                         OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN,
@@ -504,6 +512,19 @@
                 stage('Archive rally artifacts') {
                     test.archiveRallyArtifacts(venvPepper, TEST_TEMPEST_TARGET)
                 }
+
+                if (common.validInputParam('TESTRAIL_REPORT') && TESTRAIL_REPORT.toBoolean()) {
+                    stage('Upload test results to TestRail') {
+                        def date = sh(script: 'date +%Y-%m-%d', returnStdout: true).trim()
+                        def plan = TESTRAIL_PLAN ?: "[${TESTRAIL_MILESTONE}]System-Devcloud-${date}"
+                        def group = TESTRAIL_GROUP ?: STACK_TEMPLATE
+
+                        salt.cmdRun(venvPepper, TEST_TEMPEST_TARGET, "cd /root/rally_reports && cp \$(ls -t *xml | head -n1) report.xml")
+                        test.uploadResultsTestrail("/root/rally_reports/report.xml",
+                                TESTRAIL_REPORTER_IMAGE, group, TESTRAIL_QA_CREDENTIALS,
+                                plan, TESTRAIL_MILESTONE, TESTRAIL_SUITE)
+                    }
+                }
             }
 
 
diff --git a/create-debmirror-package.groovy b/create-debmirror-package.groovy
new file mode 100644
index 0000000..7911d37
--- /dev/null
+++ b/create-debmirror-package.groovy
@@ -0,0 +1,52 @@
+/**
+ *
+ * Create debmirror package pipeline
+ *
+ * Expected parameters:
+ * MIRROR_NAME - Name of the mirror
+ * MIRROR_URL - URL of mirror
+ * ROOT - Root directory of the upstream location
+ * METHOD - rsync or http
+ * DEBMIRROR_ARGS - args for debmirror comand
+ * UPLOAD_URL - URL to upload TAR to
+ */
+
+// Load shared libs
+def common = new com.mirantis.mk.Common()
+
+timeout(time: 12, unit: 'HOURS') {
+    node("python&&disk-xl") {
+        try {
+            def workspace = common.getWorkspace()
+            if(METHOD == "rsync"){
+                ROOT = ":mirror/${ROOT}"
+            }
+            stage("Create mirror"){
+                def mirrordir="${workspace}/mirror"
+                def debmlog="${workspace}/mirror_${MIRROR_NAME}_log"
+
+                sh "debmirror --verbose --method=${METHOD} --progress --host=${MIRROR_URL} --root=${ROOT} ${DEBMIRROR_ARGS} ${mirrordir}/${MIRROR_NAME} 2>&1 | tee -a ${debmlog}"
+
+                sh "tar -czvf ${workspace}/${MIRROR_NAME}.tar.gz -C ${mirrordir}/${MIRROR_NAME} ."
+            }
+
+            stage("Upload mirror"){
+                common.retry(3, 5, {
+                    uploadImageStatus = sh(script: "curl -f -T ${workspace}/${MIRROR_NAME}.tar.gz ${UPLOAD_URL}", returnStatus: true)
+                    if(uploadImageStatus!=0){
+                        throw new Exception("Image upload failed")
+                    }
+                })
+            }
+
+        } catch (Throwable e) {
+            // If there was an error or exception thrown, the build failed
+            currentBuild.result = "FAILURE"
+            throw e
+        }finally {
+            stage("Cleanup"){
+                sh "rm -rf ${workspace}/*"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/delete-broken-stacks-pipeline.groovy b/delete-broken-stacks-pipeline.groovy
index c68fe9e..df938ed 100644
--- a/delete-broken-stacks-pipeline.groovy
+++ b/delete-broken-stacks-pipeline.groovy
@@ -22,14 +22,15 @@
         def openstackCloud
         // value defaults
         def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : 'liberty'
-        def openstackEnv = "${env.WORKSPACE}/venv"
+        def workspace = common.getWorkspace()
+        def openstackEnv = "${workspace}/venv"
 
         stage('Install OpenStack env') {
             openstack.setupOpenstackVirtualenv(openstackEnv, openstackVersion)
         }
 
         stage('Connect to OpenStack cloud') {
-            openstackCloud = openstack.createOpenstackEnv(OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
+            openstackCloud = openstack.createOpenstackEnv(openstackEnv, OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
             openstack.getKeystoneToken(openstackCloud, openstackEnv)
         }
 
diff --git a/ironic-node-provision-pipeline.groovy b/ironic-node-provision-pipeline.groovy
index 1826100..3d2717b 100644
--- a/ironic-node-provision-pipeline.groovy
+++ b/ironic-node-provision-pipeline.groovy
@@ -40,7 +40,7 @@
 test = new com.mirantis.mk.Test()
 def python = new com.mirantis.mk.Python()
 
-def pepperEnv = "pepperEnv"
+def pepperEnv
 def venv
 def outputs = [:]
 
@@ -76,7 +76,9 @@
     node("python") {
         try {
             // Set build-specific variables
-            venv = "${env.WORKSPACE}/venv"
+            def workspace = common.getWorkspace()
+            venv = "${workspace}/venv"
+            venvPepper = "${workspace}/venvPepper"
 
             def required_params = ['IRONIC_AUTHORIZATION_PROFILE', 'IRONIC_DEPLOY_NODES']
             def missed_params = []
@@ -112,7 +114,7 @@
 
                         // create openstack env
                         openstack.setupOpenstackVirtualenv(venv, OPENSTACK_API_CLIENT)
-                        openstackCloud = openstack.createOpenstackEnv(
+                        openstackCloud = openstack.createOpenstackEnv(venv,
                             OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS,
                             OPENSTACK_API_PROJECT, OPENSTACK_API_PROJECT_DOMAIN,
                             OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN,
diff --git a/mk-k8s-cleanup-pipeline.groovy b/mk-k8s-cleanup-pipeline.groovy
index db5aa8a..b907709 100644
--- a/mk-k8s-cleanup-pipeline.groovy
+++ b/mk-k8s-cleanup-pipeline.groovy
@@ -25,14 +25,15 @@
 
         // value defaults
         def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : 'liberty'
-        def openstackEnv = "${env.WORKSPACE}/venv"
+        def workspace = common.getWorkspace()
+        def openstackEnv = "${workspace}/venv"
 
         stage('Install OpenStack env') {
             openstack.setupOpenstackVirtualenv(openstackEnv, openstackVersion)
         }
 
         stage('Connect to OpenStack cloud') {
-            openstackCloud = openstack.createOpenstackEnv(OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
+            openstackCloud = openstack.createOpenstackEnv(openstackEnv, OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
             openstack.getKeystoneToken(openstackCloud, openstackEnv)
         }
 
diff --git a/mk-k8s-simple-deploy-pipeline.groovy b/mk-k8s-simple-deploy-pipeline.groovy
index 39ddc9c..2a3653c 100644
--- a/mk-k8s-simple-deploy-pipeline.groovy
+++ b/mk-k8s-simple-deploy-pipeline.groovy
@@ -31,7 +31,6 @@
 test = new com.mirantis.mk.Test()
 def python = new com.mirantis.mk.Python()
 
-def pepperEnv = "pepperEnv"
 artifacts_dir = "_artifacts"
 timeout(time: 12, unit: 'HOURS') {
     node {
@@ -41,8 +40,9 @@
 
         // value defaults
         def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : 'liberty'
-        def openstackEnv = "${env.WORKSPACE}/venv"
-
+        def workspace = common.getWorkspace()
+        def openstackEnv = "${workspace}/venv"
+        def pepperEnv = "${workspace}/pepperEnv"
         if (HEAT_STACK_NAME == "") {
             HEAT_STACK_NAME = BUILD_TAG
         }
@@ -56,7 +56,7 @@
         }
 
         stage('Connect to OpenStack cloud') {
-            openstackCloud = openstack.createOpenstackEnv(OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT,
+            openstackCloud = openstack.createOpenstackEnv(openstackEnv, OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT,
             "", OPENSTACK_API_PROJECT_DOMAIN_ID, OPENSTACK_API_USER_DOMAIN_ID, OPENSTACK_API_VERSION)
             openstack.getKeystoneToken(openstackCloud, openstackEnv)
         }
diff --git a/mk-maaas-deploy-pipeline.groovy b/mk-maaas-deploy-pipeline.groovy
index 924019e..9a2244e 100644
--- a/mk-maaas-deploy-pipeline.groovy
+++ b/mk-maaas-deploy-pipeline.groovy
@@ -32,8 +32,9 @@
         def saltMaster
 
         // value defaults
+        def workspace = common.getWorkspace()
         def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : "liberty"
-        def openstackEnv = "${env.WORKSPACE}/venv"
+        def openstackEnv = "${workspace}/venv"
 
         stage ('Download Heat templates') {
             git.checkoutGitRepository('template', HEAT_TEMPLATE_URL, HEAT_TEMPLATE_BRANCH, HEAT_TEMPLATE_CREDENTIALS)
@@ -44,7 +45,7 @@
         }
 
         stage('Connect to OpenStack cloud') {
-            openstackCloud = openstack.createOpenstackEnv(OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
+            openstackCloud = openstack.createOpenstackEnv(openstackEnv, OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
             openstack.getKeystoneToken(openstackCloud, openstackEnv)
         }
 
diff --git a/test-salt-formula-docs-pipeline.groovy b/test-salt-formula-docs-pipeline.groovy
new file mode 100644
index 0000000..27afd22
--- /dev/null
+++ b/test-salt-formula-docs-pipeline.groovy
@@ -0,0 +1,72 @@
+/**
+ * Pipeline for generating and testing sphinx generated documentation
+ *
+ * Parameters:
+ *   SALT_MASTER_URL
+ *   SALT_MASTER_CREDENTIALS
+ *
+ */
+
+def gerritRef
+try {
+  gerritRef = GERRIT_REFSPEC
+} catch (MissingPropertyException e) {
+  gerritRef = null
+}
+
+common = new com.mirantis.mk.Common()
+gerrit = new com.mirantis.mk.Gerrit()
+python = new com.mirantis.mk.Python()
+salt = new com.mirantis.mk.Salt()
+
+timeout(time: 12, unit: 'HOURS') {
+  node("python") {
+    try {
+       def masterName = "cfg01.test-salt-formulas-docs.lab"
+       def img = docker.image("tcpcloud/salt-models-testing:latest")
+       img.pull()
+       img.inside("-u root:root --hostname ${masterName}--ulimit nofile=4096:8192 --cpus=2") {
+           stage("Prepare salt env") {
+              withEnv(["MASTER_HOSTNAME=${masterName}", "CLUSTER_NAME=test-salt-formulas-docs-cluster", "MINION_ID=${masterName}"]){
+                    //TODO: we need to have some simple model or maybe not, bootstrap.sh script generates test model
+                    //sh("cp -r ${testDir}/* /srv/salt/reclass && echo '127.0.1.2  salt' >> /etc/hosts")
+                    sh("echo '127.0.1.2  salt' >> /etc/hosts")
+                    // sedding apt to internal -  should be not necessary
+                    sh("cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt-mk.mirantis.com/apt.mirantis.net:8085/g' {} \\;")
+                    sh("cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt.mirantis.com/apt.mirantis.net:8085/g' {} \\;")
+                    sh("""bash -c 'source /srv/salt/scripts/bootstrap.sh; cd /srv/salt/scripts \
+                          && source_local_envs \
+                          && configure_salt_master \
+                          && configure_salt_minion \
+                          && install_salt_formula_pkg; \
+                          saltservice_restart; \
+                          saltmaster_init'""")
+              }
+           }
+           stage("Install all formulas"){
+              sh("apt update && apt install -y salt-formula-*")
+           }
+           stage("Checkout formula review"){
+              if(gerritRef){
+                //TODO: checkout gerrit review and replace formula content in directory
+                // gerrit.gerritPatchsetCheckout([credentialsId: CREDENTIALS_ID])
+              }else{
+                common.successMsg("Test triggered manually, so skipping checkout formula review stage")
+              }
+           }
+           stage("Generate documentation"){
+               def pepperEnv = common.getWorkspace() + "/venvPepper"
+               python.setupPepperVirtualenv(venvPepper, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
+               salt.enforceState(venvPepper, masterName , 'sphinx' , true)
+           }
+       }
+    } 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-salt-formulas-env.groovy b/test-salt-formulas-env.groovy
index 8f60727..b36d4d7 100644
--- a/test-salt-formulas-env.groovy
+++ b/test-salt-formulas-env.groovy
@@ -20,7 +20,7 @@
 def checkouted = false
 
 throttle(['test-formula']) {
-  timeout(time: 12, unit: 'HOURS') {
+  timeout(time: 1, unit: 'HOURS') {
     node("python") {
       try {
         stage("checkout") {
diff --git a/test-salt-formulas-pipeline.groovy b/test-salt-formulas-pipeline.groovy
index af22dc8..56e9bb9 100644
--- a/test-salt-formulas-pipeline.groovy
+++ b/test-salt-formulas-pipeline.groovy
@@ -43,8 +43,13 @@
         try {
           triggerTestFormulaJob(currentFormula, defaultGitRef, defaultGitUrl)
         } catch (Exception e) {
-          failedFormulas << currentFormula
-          common.warningMsg("Test of ${currentFormula} failed :  ${e}")
+          if (e.getMessage().contains("completed with status ABORTED")) {
+            common.warningMsg("Test of ${currentFormula} was aborted and will be retriggered")
+            futureFormulas << currentFormula
+          } else {
+            failedFormulas << currentFormula
+            common.warningMsg("Test of ${currentFormula} failed :  ${e}")
+          }
         }
       }
     }
diff --git a/test-salt-model-node.groovy b/test-salt-model-node.groovy
index e96bc98..5ff8ff3 100644
--- a/test-salt-model-node.groovy
+++ b/test-salt-model-node.groovy
@@ -29,7 +29,7 @@
 def checkouted = false
 
 throttle(['test-model']) {
-  timeout(time: 12, unit: 'HOURS') {
+  timeout(time: 1, unit: 'HOURS') {
     node("python") {
       try{
         stage("checkout") {
diff --git a/test-salt-models-pipeline.groovy b/test-salt-models-pipeline.groovy
index 3ac1275..fa784ea 100644
--- a/test-salt-models-pipeline.groovy
+++ b/test-salt-models-pipeline.groovy
@@ -158,12 +158,9 @@
       stage("test-nodes") {
         if(checkouted) {
           def modifiedClusters = null
-
-          if (gerritRef) {
-            checkChange = sh(script: "git diff-tree --no-commit-id --name-only -r HEAD | grep -v classes/cluster", returnStatus: true)
-            if (checkChange == 1) {
-              modifiedClusters = sh(script: "git diff-tree --no-commit-id --name-only -r HEAD | grep classes/cluster/ | awk -F/ '{print \$3}' | uniq", returnStdout: true).tokenize()
-            }
+          def checkChange = sh(script: "git diff-tree --no-commit-id --name-only -r HEAD | grep -v classes/cluster", returnStatus: true)
+          if (checkChange == 1) {
+            modifiedClusters = sh(script: "git diff-tree --no-commit-id --name-only -r HEAD | grep classes/cluster/ | awk -F/ '{print \$3}' | uniq", returnStdout: true).tokenize()
           }
 
           def infraYMLs = sh(script: "find ./classes/ -regex '.*cluster/[-_a-zA-Z0-9]*/[infra/]*init\\.yml' -exec grep -il 'cluster_name' {} \\;", returnStdout: true).tokenize()