Merge "Changed default gerrit checkout branch, fixed gating"
diff --git a/cicd-lab-pipeline.groovy b/cicd-lab-pipeline.groovy
index 972ab81..adaedd0 100644
--- a/cicd-lab-pipeline.groovy
+++ b/cicd-lab-pipeline.groovy
@@ -142,31 +142,31 @@
 
             stage("Deploy Docker services") {
                 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.client')
-
-                // XXX: Hack to fix dependency of gerrit on mysql
-                print common.prettyPrint(salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', "docker service rm gerrit; sleep 5; rm -rf /srv/volumes/gerrit/*"))
-
-                timeout(10) {
-                    salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', 'apt-get install -y mysql-client')
-                    println "Waiting for MySQL to come up.."
-                    salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', 'while true; do mysql -h172.16.10.254 -ppassword -e"show status;" >/dev/null && break; done')
-                }
-                salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.client')
-                // ---- cut here (end of hack) ----
             }
 
             stage("Configure CI/CD services") {
                 salt.syncAll(saltMaster, '*')
 
                 // Aptly
+                timeout(10) {
+                    println "Waiting for Aptly to come up.."
+                    salt.cmdRun(saltMaster, 'I@aptly:server', 'while true; do curl -svf http://172.16.10.254:8084/api/version >/dev/null && break; done')
+                }
                 salt.enforceState(saltMaster, 'I@aptly:server', 'aptly', true)
 
+                // OpenLDAP
+                timeout(10) {
+                    println "Waiting for OpenLDAP to come up.."
+                    salt.cmdRun(saltMaster, 'I@openldap:client', 'while true; do curl -svf ldap://172.16.10.254 >/dev/null && break; done')
+                }
+                salt.enforceState(saltMaster, 'I@openldap:client', 'openldap', true)
+
                 // Gerrit
                 timeout(10) {
                     println "Waiting for Gerrit to come up.."
                     salt.cmdRun(saltMaster, 'I@gerrit:client', 'while true; do curl -svf 172.16.10.254:8080 >/dev/null && break; done')
                 }
-                retry(2) {
+                retry(3) {
                     // Needs to run twice to pass __virtual__ method of gerrit module
                     // after installation of dependencies
                     try {
@@ -262,6 +262,7 @@
         9600    haproxy stats
         8080    gerrit
         8081    jenkins
+        8089    LDAP administration
         8091    Docker swarm visualizer
         8090    Reclass-generated documentation
 
diff --git a/generate-cookiecutter-products.groovy b/generate-cookiecutter-products.groovy
new file mode 100644
index 0000000..2fd65bc
--- /dev/null
+++ b/generate-cookiecutter-products.groovy
@@ -0,0 +1,146 @@
+/**
+ * Generate cookiecutter cluster by individual products
+ *
+ * Expected parameters:
+ *   COOKIECUTTER_TEMPLATE_CREDENTIALS  Credentials to the Cookiecutter template repo.
+ *   COOKIECUTTER_TEMPLATE_URL          Cookiecutter template repo address.
+ *   COOKIECUTTER_TEMPLATE_BRANCH       Branch for the template.
+ *   COOKIECUTTER_TEMPLATE_CONTEXT      Context parameters for the template generation.
+ *   COOKIECUTTER_INSTALL_CICD          Whether to install CI/CD stack.
+ *   COOKIECUTTER_INSTALL_CONTRAIL      Whether to install OpenContrail SDN.
+ *   COOKIECUTTER_INSTALL_KUBERNETES    Whether to install Kubernetes.
+ *   COOKIECUTTER_INSTALL_OPENSTACK     Whether to install OpenStack cloud.
+ *   COOKIECUTTER_INSTALL_STACKLIGHT    Whether to install StackLight monitoring.
+ *   RECLASS_MODEL_URL                  Reclass model repo address
+ *   RECLASS_MODEL_CREDENTIALS          Credentials to the Reclass model repo.
+ *   RECLASS_MODEL_BRANCH               Branch for the template to push to model.
+ *
+**/
+
+common = new com.mirantis.mk.Common()
+git = new com.mirantis.mk.Git()
+python = new com.mirantis.mk.Python()
+
+timestamps {
+    node() {
+        def templateEnv = "${env.WORKSPACE}/template"
+        def modelEnv = "${env.WORKSPACE}/model"
+
+        try {
+            def templateContext = python.loadJson(COOKIECUTTER_TEMPLATE_CONTEXT)
+            def templateDir = "${templateEnv}/template/dir"
+            def templateOutputDir = "${env.WORKSPACE}/template"
+            def cutterEnv = "${env.WORKSPACE}/cutter"
+            def jinjaEnv = "${env.WORKSPACE}/jinja"
+            def clusterName = templateContext.cluster_name
+            def clusterDomain = templateContext.cluster_domain
+            def targetBranch = "feature/${clusterName}"
+
+            stage ('Download Cookiecutter template') {
+                git.checkoutGitRepository(templateEnv, COOKIECUTTER_TEMPLATE_URL, COOKIECUTTER_TEMPLATE_BRANCH, COOKIECUTTER_TEMPLATE_CREDENTIALS)
+            }
+
+            stage ('Download full Reclass model') {
+                git.checkoutGitRepository(modelEnv, RECLASS_MODEL_URL, RECLASS_MODEL_BRANCH, RECLASS_MODEL_CREDENTIALS)
+            }
+
+            stage('Generate base infrastructure') {
+                templateDir = "${templateEnv}/cluster_product/infra"
+                templateOutputDir = "${env.WORKSPACE}/template/output_infra"
+                sh "mkdir -p ${templateOutputDir}"
+                python.setupCookiecutterVirtualenv(cutterEnv)
+                python.buildCookiecutterTemplate(templateDir, templateContext, templateOutputDir, cutterEnv)
+            }
+
+            stage('Generate product CI/CD') {
+                if (COOKIECUTTER_INSTALL_CICD.toBoolean()) {
+                    templateDir = "${templateEnv}/cluster_product/cicd"
+                    templateOutputDir = "${env.WORKSPACE}/template/output_cicd"
+                    sh "mkdir -p ${templateOutputDir}"
+                    python.setupCookiecutterVirtualenv(cutterEnv)
+                    python.buildCookiecutterTemplate(templateDir, templateContext, templateOutputDir, cutterEnv)
+                }
+            }
+
+            stage('Generate product OpenContrail') {
+                if (COOKIECUTTER_INSTALL_CONTRAIL.toBoolean()) {
+                    templateDir = "${templateEnv}/cluster_product/contrail"
+                    templateOutputDir = "${env.WORKSPACE}/template/output_contrail"
+                    sh "mkdir -p ${templateOutputDir}"
+                    python.setupCookiecutterVirtualenv(cutterEnv)
+                    python.buildCookiecutterTemplate(templateDir, templateContext, templateOutputDir, cutterEnv)
+                }
+            }
+
+            stage('Generate product Kubernetes') {
+                if (COOKIECUTTER_INSTALL_KUBERNETES.toBoolean()) {
+                    templateDir = "${templateEnv}/cluster_product/kubernetes"
+                    templateOutputDir = "${env.WORKSPACE}/template/output_kubernetes"
+                    sh "mkdir -p ${templateOutputDir}"
+                    python.setupCookiecutterVirtualenv(cutterEnv)
+                    python.buildCookiecutterTemplate(templateDir, templateContext, templateOutputDir, cutterEnv)
+                }
+            }
+
+            stage('Generate product OpenStack') {
+                if (COOKIECUTTER_INSTALL_OPENSTACK.toBoolean()) {
+                    templateDir = "${templateEnv}/cluster_product/openstack"
+                    templateOutputDir = "${env.WORKSPACE}/template/output_openstack"
+                    sh "mkdir -p ${templateOutputDir}"
+                    python.setupCookiecutterVirtualenv(cutterEnv)
+                    python.buildCookiecutterTemplate(templateDir, templateContext, templateOutputDir, cutterEnv)
+                }
+            }
+
+            stage('Generate product StackLight') {
+                if (COOKIECUTTER_INSTALL_STACKLIGHT.toBoolean()) {
+                    templateDir = "${templateEnv}/cluster_product/stacklight"
+                    templateOutputDir = "${env.WORKSPACE}/template/output_stacklight"
+                    sh "mkdir -p ${templateOutputDir}"
+                    python.setupCookiecutterVirtualenv(cutterEnv)
+                    python.buildCookiecutterTemplate(templateDir, templateContext, templateOutputDir, cutterEnv)
+                }
+            }
+
+            stage('Generate new SaltMaster node') {
+                def nodeFile = "${modelEnv}/nodes/cfg01.${clusterDomain}.yml"
+                def nodeString = """classes:
+- cluster.${clusterName}.infra.config
+parameters:
+    _param:
+        linux_system_codename: xenial
+        reclass_data_revision: master
+    linux:
+        system:
+            name: cfg01
+            domain: ${clusterDomain}
+"""
+                writeFile(file: nodeFile, text: nodeString)
+            }
+
+            stage('Inject changes to Reclass model') {
+                git.changeGitBranch(modelEnv, targetBranch)
+                def outputSource = "${templateOutputDir}/${clusterName}"
+                def outputDestination = "${modelEnv}/classes/cluster/${clusterName}"
+                sh(returnStdout: true, script: "cp ${outputSource} ${outputDestination} -r")
+                git.commitGitChanges(modelEnv, "Added new cluster ${clusterName}")
+                archiveArtifacts artifacts: modelEnv
+            }
+
+            stage ('Push changes to Reclass model') {
+                git.pushGitChanges(modelEnv, targetBranch, 'origin', RECLASS_MODEL_CREDENTIALS)
+            }
+
+        } catch (Throwable e) {
+             // If there was an error or exception thrown, the build failed
+             currentBuild.result = "FAILURE"
+             throw e
+        } finally {
+            stage ('Clean workspace directories') {
+                sh(returnStdout: true, script: "rm ${templateEnv} -rf")
+                sh(returnStdout: true, script: "rm ${modelEnv} -rf")
+            }
+             // common.sendNotification(currentBuild.result,"",["slack"])
+        }
+    }
+}
diff --git a/lab-pipeline.groovy b/lab-pipeline.groovy
index a6efaca..5b7cc11 100644
--- a/lab-pipeline.groovy
+++ b/lab-pipeline.groovy
@@ -41,7 +41,7 @@
 openstack = new com.mirantis.mk.Openstack()
 salt = new com.mirantis.mk.Salt()
 common = new com.mirantis.mk.Common()
-
+test = new com.mirantis.mk.Test()
 
 timestamps {
     node {
@@ -50,6 +50,7 @@
             // Prepare machines
             //
             stage ('Create infrastructure') {
+
                 if (STACK_TYPE == 'heat') {
                     // value defaults
                     def openstackCloud
@@ -99,7 +100,7 @@
                     saltMasterHost = openstack.getHeatStackOutputParam(openstackCloud, HEAT_STACK_NAME, 'salt_master_ip', openstackEnv)
                     currentBuild.description = "${HEAT_STACK_NAME}: ${saltMasterHost}"
 
-                    if (INSTALL.toLowerCase().contains('kvm')) {
+                    if (common.checkContains('INSTALL', 'kvm')) {
                         saltPort = 6969
                     } else {
                         saltPort = 6969
@@ -122,7 +123,7 @@
             // Install
             //
 
-            if (INSTALL.toLowerCase().contains('core')) {
+            if (common.checkContains('INSTALL', 'core')) {
                 stage('Install core infrastructure') {
                     // salt.master, reclass
                     // refresh_pillar
@@ -131,17 +132,19 @@
 
                     //orchestrate.installFoundationInfra(saltMaster)
                     salt.enforceState(saltMaster, 'I@salt:master', ['salt.master', 'reclass'], true)
+                    salt.enforceState(saltMaster, '*', ['linux.system'], true)
+                    salt.enforceState(saltMaster, '*', ['salt.minion'], true)
                     salt.runSaltProcessStep(saltMaster, 'I@linux:system', 'saltutil.refresh_pillar', [], null, true)
                     salt.runSaltProcessStep(saltMaster, 'I@linux:system', 'saltutil.sync_all', [], null, true)
                     salt.enforceState(saltMaster, 'I@linux:system', ['linux', 'openssh', 'salt.minion', 'ntp'], true)
 
 
-                    if (INSTALL.toLowerCase().contains('kvm')) {
+                    if (common.checkContains('INSTALL', 'kvm')) {
                         //orchestrate.installInfraKvm(saltMaster)
-                        salt.runSaltProcessStep(saltMaster, 'I@linux:system', 'saltutil.refresh_pillar', [], null, true)
-                        salt.runSaltProcessStep(saltMaster, 'I@linux:system', 'saltutil.sync_all', [], null, true)
+                        //salt.runSaltProcessStep(saltMaster, 'I@linux:system', 'saltutil.refresh_pillar', [], null, true)
+                        //salt.runSaltProcessStep(saltMaster, 'I@linux:system', 'saltutil.sync_all', [], null, true)
 
-                        salt.enforceState(saltMaster, 'I@salt:control', ['salt.minion', 'linux.system', 'linux.network', 'ntp'], true)
+                        //salt.enforceState(saltMaster, 'I@salt:control', ['salt.minion', 'linux.system', 'linux.network', 'ntp'], true)
                         salt.enforceState(saltMaster, 'I@salt:control', 'libvirt', true)
                         salt.enforceState(saltMaster, 'I@salt:control', 'salt.control', true)
 
@@ -167,7 +170,7 @@
             }
 
             // install k8s
-            if (INSTALL.toLowerCase().contains('k8s')) {
+            if (common.checkContains('INSTALL', 'k8s')) {
                 stage('Install Kubernetes infra') {
                     //orchestrate.installOpenstackMcpInfra(saltMaster)
 
@@ -235,7 +238,7 @@
             }
 
             // install openstack
-            if (INSTALL.toLowerCase().contains('openstack')) {
+            if (common.checkContains('INSTALL', 'openstack')) {
                 // install Infra and control, tests, ...
 
                 stage('Install OpenStack infra') {
@@ -258,14 +261,19 @@
                     salt.runSaltProcessStep(saltMaster, 'I@glusterfs:server', 'cmd.run', ['gluster volume status'], null, true)
 
                     // Install rabbitmq
-                    salt.enforceState(saltMaster, 'I@rabbitmq:server', 'rabbitmq', true, false)
-
+                    withEnv(['ASK_ON_ERROR=false']){
+                        retry(2) {
+                            salt.enforceState(saltMaster, 'I@rabbitmq:server', 'rabbitmq', true)
+                        }
+                    }
                     // Check the rabbitmq status
                     salt.runSaltProcessStep(saltMaster, 'I@rabbitmq:server', 'cmd.run', ['rabbitmqctl cluster_status'])
 
                     // Install galera
-                    retry(2) {
-                        salt.enforceState(saltMaster, 'I@galera:master', 'galera', true)
+                    withEnv(['ASK_ON_ERROR=false']){
+                        retry(2) {
+                            salt.enforceState(saltMaster, 'I@galera:master', 'galera', true)
+                        }
                     }
                     salt.enforceState(saltMaster, 'I@galera:slave', 'galera', true)
 
@@ -348,7 +356,7 @@
                 stage('Install OpenStack network') {
                     //orchestrate.installOpenstackMkNetwork(saltMaster, physical)
 
-                    if (INSTALL.toLowerCase().contains('contrail')) {
+                    if (common.checkContains('INSTALL', 'contrail')) {
                         // Install opencontrail database services
                         //runSaltProcessStep(saltMaster, 'I@opencontrail:database', 'state.sls', ['opencontrail.database'], 1)
                         try {
@@ -371,7 +379,7 @@
 
                         // Test opencontrail
                         salt.runSaltProcessStep(saltMaster, 'I@opencontrail:control', 'cmd.run', ['contrail-status'], null, true)
-                    } else if (INSTALL.toLowerCase().contains('ovs')) {
+                    } else if (common.checkContains('INSTALL', 'ovs')) {
                         // Apply gateway
                         salt.runSaltProcessStep(saltMaster, 'I@neutron:gateway', 'state.apply', [], null, true)
                     }
@@ -388,7 +396,7 @@
                         salt.runSaltProcessStep(saltMaster, 'I@nova:compute', 'state.apply', [], null, true)
                     }
 
-                    if (INSTALL.toLowerCase().contains('contrail')) {
+                    if (common.checkContains('INSTALL', 'contrail')) {
                         // Provision opencontrail control services
                         salt.enforceState(saltMaster, 'I@opencontrail:database:id:1', 'opencontrail.client', true)
                         // Provision opencontrail virtual routers
@@ -402,7 +410,7 @@
             }
 
 
-            if (INSTALL.toLowerCase().contains('stacklight')) {
+            if (common.checkContains('INSTALL', 'stacklight')) {
                 stage('Install StackLight') {
                     // infra install
                     // Install the StackLight backends
@@ -506,7 +514,7 @@
             // Test
             //
 
-            if (TEST.toLowerCase().contains('k8s')) {
+            if (common.checkContains('TEST', 'k8s')) {
                 stage('Run k8s bootstrap tests') {
                     orchestrate.runConformanceTests(saltMaster, K8S_API_SERVER, 'tomkukral/k8s-scripts')
                 }
@@ -516,7 +524,7 @@
                 }
             }
 
-            if (TEST.toLowerCase().contains('openstack')) {
+            if (common.checkContains('TEST', 'openstack')) {
                 stage('Run OpenStack tests') {
                     test.runTempestTests(saltMaster, TEMPEST_IMAGE_LINK)
                 }
@@ -538,14 +546,15 @@
             throw e
         } finally {
 
-            // send notification
-            common.sendNotification(currentBuild.result,HEAT_STACK_NAME,["slack"])
 
             //
             // Clean
             //
 
             if (STACK_TYPE == 'heat') {
+                // send notification
+                common.sendNotification(currentBuild.result, HEAT_STACK_NAME, ["slack"])
+
                 if (HEAT_STACK_DELETE.toBoolean() == true) {
                     common.errorMsg('Heat job cleanup triggered')
                     stage('Trigger cleanup job') {
diff --git a/mk-k8s-simple-deploy-pipeline.groovy b/mk-k8s-simple-deploy-pipeline.groovy
index c82771f..02f7709 100644
--- a/mk-k8s-simple-deploy-pipeline.groovy
+++ b/mk-k8s-simple-deploy-pipeline.groovy
@@ -86,7 +86,7 @@
     }
 
     if (RUN_TESTS == "1") {
-        sleep(30000)
+        sleep(30)
         stage('Run k8s bootstrap tests') {
             test.runConformanceTests(saltMaster, K8S_API_SERVER, 'tomkukral/k8s-scripts')
         }
diff --git a/test-nodejs-pipeline.groovy b/test-nodejs-pipeline.groovy
new file mode 100644
index 0000000..35d8317
--- /dev/null
+++ b/test-nodejs-pipeline.groovy
@@ -0,0 +1,52 @@
+/**
+* JS testing pipeline
+* CREDENTIALS_ID - gerrit credentials id
+* NODE_IMAGE - NodeJS with NPM Docker image name
+* COMMANDS - a list of command(s) to run
+**/
+
+gerrit = new com.mirantis.mk.Gerrit()
+common = new com.mirantis.mk.Common()
+
+node("docker") {
+    def containerID
+    try {
+        stage ('Checkout source code') {
+            gerrit.gerritPatchsetCheckout ([
+              credentialsId : CREDENTIALS_ID,
+              withWipeOut : true,
+            ])
+        }
+        stage ('Start container') {
+           def workspace = common.getWorkspace()
+           containerID = sh(
+               script: "docker run -d -v ${workspace}:/opt/workspace:rw ${NODE_IMAGE}",
+               returnStdout: true,
+           ).trim()
+        }
+        stage ('Execute commands') {
+            assert containerID != null
+            def cmds = COMMANDS.tokenize('\n')
+            for (int i = 0; i < cmds.size(); i++) {
+               def cmd = cmds[i]
+               def output = sh(
+                   script: "docker exec ${containerID} ${cmd}",
+                   returnStdout: true,
+               ).trim()
+               common.infoMsg(output)
+            }
+        }
+    } catch (Throwable e) {
+        currentBuild.result = 'FAILURE'
+        common.errorMsg("Build failed due to some commands failed.")
+        throw e
+    } finally {
+        common.sendNotification(currentBuild.result, "" ,["slack"])
+        stage ('Remove container') {
+            if (containerID != null) {
+                sh "docker stop -t 0 ${containerID}"
+                sh "docker rm ${containerID}"
+            }
+        }
+    }
+}
diff --git a/update-package.groovy b/update-package.groovy
index 6c31c95..b37fe22 100644
--- a/update-package.groovy
+++ b/update-package.groovy
@@ -24,6 +24,7 @@
 def result
 def packages
 def command
+def commandKwargs
 
 node() {
     try {
@@ -70,15 +71,17 @@
         }
 
         if (TARGET_PACKAGES != "") {
-            command = "pkg.install";
+            command = "pkg.install"
             packages = TARGET_PACKAGES.tokenize(' ')
+            commandKwargs = ['only_upgrade': 'true']
         }else {
             command = "pkg.upgrade"
             packages = null
         }
 
         stage('Apply package upgrades on sample') {
-            salt.runSaltProcessStep(saltMaster, targetLiveSubset, command, packages, null, true)
+            out = salt.runSaltCommand(saltMaster, 'local', ['expression': targetLiveSubset, 'type': 'compound'], command, null, packages, commandKwargs)
+            salt.printSaltCommandResult(out)
         }
 
         stage('Confirm package upgrades on all nodes') {
@@ -88,7 +91,8 @@
         }
 
         stage('Apply package upgrades on all nodes') {
-            salt.runSaltProcessStep(saltMaster, targetLiveAll, command, packages, null, true)
+            out = salt.runSaltCommand(saltMaster, 'local', ['expression': targetLiveAll, 'type': 'compound'], command, null, packages, commandKwargs)
+            salt.printSaltCommandResult(out)
         }
 
     } catch (Throwable e) {