Merge "add backupninja backup pipeline"
diff --git a/cloud-deploy-pipeline.groovy b/cloud-deploy-pipeline.groovy
index 1412a76..1de45ee 100644
--- a/cloud-deploy-pipeline.groovy
+++ b/cloud-deploy-pipeline.groovy
@@ -376,7 +376,7 @@
             // install k8s
             if (common.checkContains('STACK_INSTALL', 'k8s')) {
                 extra_tgt_bckp = extra_tgt
-                extra_tgt = 'and not kdt* ' + extra_tgt_bckp
+                extra_tgt = 'and not kdt* and not cfg* ' + extra_tgt_bckp
                 stage('Install Kubernetes infra') {
                     if (STACK_TYPE == 'aws') {
                         // configure kubernetes_control_address - save loadbalancer
@@ -460,8 +460,10 @@
 
                 stage('Install Kubernetes control for kdt') {
                     salt.enforceStateWithTest([saltId: venvPepper, target: "I@kubernetes:master ${extra_tgt}", state: 'kubernetes.master.kube-addons'])
+                    salt.enforceStateWithTest([saltId: venvPepper, target: "I@kubernetes:master ${extra_tgt}", state: 'kubernetes.pool.images'])
                     orchestrate.installKubernetesControl(venvPepper, extra_tgt)
 
+                    salt.enforceStateWithTest([saltId: venvPepper, target: "I@kubernetes:master ${extra_tgt}", state: 'nginx.server'])
                     // collect artifacts (kubeconfig)
                     writeFile(file: 'kubeconfig-kdt', text: salt.getFileContent(venvPepper, "I@kubernetes:master and *01* ${extra_tgt}", '/etc/kubernetes/admin-kube-config'))
                     archiveArtifacts(artifacts: 'kubeconfig-kdt')
diff --git a/generate-cookiecutter-products.groovy b/generate-cookiecutter-products.groovy
index b672c4e..ac6b918 100644
--- a/generate-cookiecutter-products.groovy
+++ b/generate-cookiecutter-products.groovy
@@ -242,7 +242,10 @@
                     if (outdateGeneration) {
                         args.add('--ssh-key failsafe-ssh-key.pub')
                     } else {
-                        args.add('--ssh-keys failsafe-ssh-key.pub')
+                        if (context.get('cfg_failsafe_user')) {
+                            args.add('--ssh-keys failsafe-ssh-key.pub')
+                            args.add("--cloud-user-name ${context.get('cfg_failsafe_user')}")
+                        }
                     }
                 }
                 // load data from model
@@ -367,6 +370,19 @@
                 sh(script: 'find . -mindepth 1 -delete > /dev/null || true')
             }
             // common.sendNotification(currentBuild.result,"",["slack"])
+            stage('Save artifacts to Artifactory') {
+                def artifactory = new com.mirantis.mcp.MCPArtifactory()
+                def buildProps = [ "context=${context['cluster_name']}" ]
+                if (RequesterEmail != '' && !RequesterEmail.contains('example')) {
+                    buildProps.add("emailTo=${RequesterEmail}")
+                }
+                def artifactoryLink = artifactory.uploadJobArtifactsToArtifactory([
+                    'artifactory': 'mcp-ci',
+                    'artifactoryRepo': "drivetrain-local/${JOB_NAME}/${context['cluster_name']}-${BUILD_NUMBER}",
+                    'buildProps': buildProps,
+                ])
+                currentBuild.description += "<br/>${artifactoryLink}"
+            }
         }
     }
 }
diff --git a/git-mirror-pipeline.groovy b/git-mirror-pipeline.groovy
index 8bfe467..8766678 100644
--- a/git-mirror-pipeline.groovy
+++ b/git-mirror-pipeline.groovy
@@ -4,7 +4,11 @@
   timeout(time: 12, unit: 'HOURS') {
     node() {
       try{
-        def branches = BRANCHES.tokenize(',')
+        if (BRANCHES.equals("*") || BRANCHES.contains('*')) {
+          branches = git.getBranchesForGitRepo(SOURCE_URL, BRANCHES)
+        } else {
+          branches = BRANCHES.tokenize(',')
+        }
         def pollBranches = []
         for (i=0; i < branches.size(); i++) {
             pollBranches.add([name:branches[i]])
diff --git a/test-cookiecutter-reclass-chunk.groovy b/test-cookiecutter-reclass-chunk.groovy
index e0c9710..8c804a1 100644
--- a/test-cookiecutter-reclass-chunk.groovy
+++ b/test-cookiecutter-reclass-chunk.groovy
@@ -1,5 +1,3 @@
-package com.mirantis.mk
-
 def common = new com.mirantis.mk.Common()
 def saltModelTesting = new com.mirantis.mk.SaltModelTesting()
 
@@ -14,8 +12,8 @@
 timeout(time: 1, unit: 'HOURS') {
     node(slaveNode) {
         stage("RunTest") {
+            extraVars = readYaml text: EXTRA_VARIABLES_YAML
             try {
-                extraVars = readYaml text: EXTRA_VARIABLES_YAML
                 currentBuild.description = extraVars.modelFile
                 sh(script:  'find . -mindepth 1 -delete || true', returnStatus: true)
                 sh(script: """
@@ -36,6 +34,9 @@
                     'testContext': extraVars.modelFile,
                     'dockerExtraOpts': [ '--memory=3g' ]
                 ]
+                if (extraVars.DISTRIB_REVISION == 'nightly') {
+                    config['nodegenerator'] = true
+                }
                 if (extraVars.useExtraRepos) {
                     config['extraRepos'] = extraVars.extraRepos ? extraVars.extraRepos : [:]
                     config['extraRepoMergeStrategy'] = extraVars.extraRepoMergeStrategy ? extraVars.extraRepoMergeStrategy : ''
@@ -46,6 +47,17 @@
                 currentBuild.result = "FAILURE"
                 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
                 throw e
+            } finally {
+                stage('Save artifacts to Artifactory') {
+                    def artifactory = new com.mirantis.mcp.MCPArtifactory()
+                    def envGerritVars = [ "GERRIT_PROJECT=${extraVars.get('GERRIT_PROJECT', '')}", "GERRIT_CHANGE_NUMBER=${extraVars.get('GERRIT_CHANGE_NUMBER', '')}",
+                                          "GERRIT_PATCHSET_NUMBER=${extraVars.get('GERRIT_PATCHSET_NUMBER', '')}", "GERRIT_CHANGE_ID=${extraVars.get('GERRIT_CHANGE_ID', '')}",
+                                          "GERRIT_PATCHSET_REVISION=${extraVars.get('GERRIT_PATCHSET_REVISION', '')}" ]
+                    withEnv(envGerritVars) {
+                        def artifactoryLink = artifactory.uploadJobArtifactsToArtifactory(['artifactory': 'mcp-ci', 'artifactoryRepo': "drivetrain-local/${JOB_NAME}/${BUILD_NUMBER}"])
+                        currentBuild.description += "<br/>${artifactoryLink}"
+                    }
+                }
             }
         }
     }
diff --git a/test-cookiecutter-reclass.groovy b/test-cookiecutter-reclass.groovy
index bd5ec1e..e530bdc 100644
--- a/test-cookiecutter-reclass.groovy
+++ b/test-cookiecutter-reclass.groovy
@@ -458,7 +458,11 @@
             currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
             throw e
         } finally {
-            def dummy = "dummy"
+            stage('Save artifacts to Artifactory') {
+                def artifactory = new com.mirantis.mcp.MCPArtifactory()
+                def artifactoryLink = artifactory.uploadJobArtifactsToArtifactory(['artifactory': 'mcp-ci', 'artifactoryRepo': "drivetrain-local/${JOB_NAME}/${BUILD_NUMBER}"])
+                currentBuild.description += "<br/>${artifactoryLink}"
+            }
         }
     }
 }
diff --git a/test-salt-model-wrapper.groovy b/test-salt-model-wrapper.groovy
index 42aa4e9..118431a 100644
--- a/test-salt-model-wrapper.groovy
+++ b/test-salt-model-wrapper.groovy
@@ -216,8 +216,13 @@
                     branchJobName = 'oscore-test-cookiecutter-models'
                     branches[branchJobName] = runTests(branchJobName, yamlJobParameters(buildTestParams))
                 }
+                if (env['GERRIT_EVENT_COMMENT_TEXT'] && new String(env['GERRIT_EVENT_COMMENT_TEXT'].decodeBase64()) =~ /\ntest_schemas.*/) {
+                    if (gerritProject == reclassSystemRepo) {
+                       branchJobName = 'oscore-test-cookiecutter-models'
+                       branches[branchJobName] = runTests(branchJobName, yamlJobParameters(buildTestParams))
+                    }
+                }
             }
-
             branches.keySet().each { key ->
                 if (branches[key] instanceof Closure) {
                     jobResultComments[key] = ['url': job_env.get('BUILD_URL'), 'status': 'WAITING']
diff --git a/upgrade-mcp-release.groovy b/upgrade-mcp-release.groovy
index 77c0a26..de24a41 100644
--- a/upgrade-mcp-release.groovy
+++ b/upgrade-mcp-release.groovy
@@ -170,7 +170,7 @@
                 if (updateClusterModel) {
                     common.infoMsg('Perform: UPDATE_CLUSTER_MODEL')
                     def dateTime = common.getDatetime()
-                    salt.cmdRun(venvPepper, 'I@salt:master', "cd /srv/salt/reclass/ && git submodule update")
+                    salt.cmdRun(venvPepper, 'I@salt:master', "cd /srv/salt/reclass/ && git submodule foreach git fetch")
                     salt.cmdRun(venvPepper, 'I@salt:master', "cd /srv/salt/reclass/classes/cluster/$cluster_name && " +
                         "grep -r --exclude-dir=aptly -l 'mcp_version: .*' * | xargs --no-run-if-empty sed -i 's|mcp_version: .*|mcp_version: \"$targetMcpVersion\"|g'")
                     // Do the same, for deprecated variable-duplicate
diff --git a/validate-cloud.groovy b/validate-cloud.groovy
index fa9a7a6..000c34c 100644
--- a/validate-cloud.groovy
+++ b/validate-cloud.groovy
@@ -3,28 +3,25 @@
  * Launch validation of the cloud
  *
  * Expected parameters:
+ *
+ *   ACCUMULATE_RESULTS          If true, results from the previous build will be used
+ *   JOB_TIMEOUT                 Job timeout in hours
+ *   RUN_RALLY_TESTS             If not false, run Rally tests
+ *   RUN_SPT_TESTS               If not false, run SPT tests
+ *   RUN_TEMPEST_TESTS           If not false, run Tempest tests
+ *   TEST_IMAGE                  Docker image link
+ *   TARGET_NODE                 Salt target for tempest node
  *   SALT_MASTER_URL             URL of Salt master
  *   SALT_MASTER_CREDENTIALS     Credentials to the Salt API
  *
- *   TEST_IMAGE                  Docker image link
- *   TARGET_NODE                 Salt target for tempest node
- *   TEMPEST_TEST_SET            If not false, run tests matched to pattern only
- *   TEMPEST_CONFIG_REPO         Git repository with configuration files for Tempest
- *   TEMPEST_CONFIG_BRANCH       Git branch which will be used during the checkout
- *   TEMPEST_REPO                Git repository with Tempest
- *   TEMPEST_VERSION             Version of Tempest (tag, branch or commit)
- *   RUN_TEMPEST_TESTS           If not false, run Tempest tests
- *   RUN_RALLY_TESTS             If not false, run Rally tests
- *   K8S_RALLY                   If not false, run Kubernetes Rally tests
- *   STACKLIGHT_RALLY            If not false, run additional Stacklight tests
- *   RUN_K8S_TESTS               If not false, run Kubernetes e2e/conformance tests
- *   RUN_SPT_TESTS               If not false, run SPT tests
- *   SPT_SSH_USER                The name of the user which should be used for ssh to nodes
- *   SPT_IMAGE                   The name of the image for SPT tests
- *   SPT_IMAGE_USER              The name of the user for SPT image
- *   SPT_FLAVOR                  The name of the flavor for SPT image
+ *   Additional validate job YAML params:
+ *
+ *   Rally
+ *
  *   AVAILABILITY_ZONE           The name of availability zone
  *   FLOATING_NETWORK            The name of the external(floating) network
+ *   K8S_RALLY                   Use Kubernetes Rally plugin for testing K8S cluster
+ *   STACKLIGHT_RALLY            Use Stacklight Rally plugin for testing Stacklight
  *   RALLY_IMAGE                 The name of the image for Rally tests
  *   RALLY_FLAVOR                The name of the flavor for Rally image
  *   RALLY_PLUGINS_REPO          Git repository with Rally plugins
@@ -34,14 +31,35 @@
  *   RALLY_SCENARIOS             Path to file or directory with rally scenarios
  *   RALLY_SL_SCENARIOS          Path to file or directory with stacklight rally scenarios
  *   RALLY_TASK_ARGS_FILE        Path to file with rally tests arguments
- *   REPORT_DIR                  Path for reports outside docker image
- *   TEST_K8S_API_SERVER         Kubernetes API address
- *   TEST_K8S_CONFORMANCE_IMAGE  Path to docker image with conformance e2e tests
- *   TEST_K8S_NODE               Kubernetes node to run tests from
- *   GENERATE_REPORT             If not false, run report generation command
- *   ACCUMULATE_RESULTS          If true, results from the previous build will be used
- *   JOB_TIMEOUT                 Job timeout in hours
+ *   RALLY_DB_CONN_STRING        Rally-compliant DB connection string for long-term storing
+                                 results to external DB
+ *   RALLY_TAGS                  List of tags for marking Rally tasks. Can be used when
+                                 generating Rally trends based on particular group of tasks
+ *   RALLY_TRENDS                If enabled, generate Rally trends report. Requires external DB
+                                 connection string to be set. If RALLY_TAGS was set, trends will
+                                 be generated based on finished tasks with these tags, otherwise
+                                 on all the finished tasks available in DB
  *   SKIP_LIST                   List of the Rally scenarios which should be skipped
+ *   REPORT_DIR                  Path for reports outside docker image
+ *
+ *   Tempest
+ *
+ *   TEMPEST_TEST_SET            If not false, run tests matched to pattern only
+ *   TEMPEST_CONFIG_REPO         Git repository with configuration files for Tempest
+ *   TEMPEST_CONFIG_BRANCH       Git branch which will be used during the checkout
+ *   TEMPEST_REPO                Git repository with Tempest
+ *   TEMPEST_VERSION             Version of Tempest (tag, branch or commit)
+ *   GENERATE_REPORT             If not false, run report generation command
+ *
+ *   SPT
+ *
+ *   AVAILABILITY_ZONE           The name of availability zone
+ *   FLOATING_NETWORK            The name of the external(floating) network
+ *   SPT_SSH_USER                The name of the user which should be used for ssh to nodes
+ *   SPT_IMAGE                   The name of the image for SPT tests
+ *   SPT_IMAGE_USER              The name of the user for SPT image
+ *   SPT_FLAVOR                  The name of the flavor for SPT image
+ *   GENERATE_REPORT             If not false, run report generation command
  *
  */
 
@@ -52,6 +70,11 @@
 
 def pepperEnv = "pepperEnv"
 def artifacts_dir = 'validation_artifacts/'
+def VALIDATE_PARAMS = readYaml(text: env.getProperty('VALIDATE_PARAMS')) ?: [:]
+if (! VALIDATE_PARAMS) {
+    throw new Exception("VALIDATE_PARAMS yaml is empty.")
+}
+
 if (env.JOB_TIMEOUT == ''){
     job_timeout = 12
 } else {
@@ -74,7 +97,17 @@
 
             stage('Run Tempest tests') {
                 if (RUN_TEMPEST_TESTS.toBoolean() == true) {
-                    validate.runTempestTests(pepperEnv, TARGET_NODE, TEST_IMAGE, artifacts_dir, TEMPEST_CONFIG_REPO, TEMPEST_CONFIG_BRANCH, TEMPEST_REPO, TEMPEST_VERSION, TEMPEST_TEST_SET)
+                    def tempest = VALIDATE_PARAMS.get('tempest') ?: []
+                    validate.runTempestTests(
+                        pepperEnv, TARGET_NODE, TEST_IMAGE,
+                        artifacts_dir, tempest.TEMPEST_CONFIG_REPO,
+                        tempest.TEMPEST_CONFIG_BRANCH, tempest.TEMPEST_REPO,
+                        tempest.TEMPEST_VERSION, tempest.TEMPEST_TEST_SET
+                    )
+                    if (tempest.GENERATE_REPORT.toBoolean() == true) {
+                        common.infoMsg("Generating html test report ...")
+                        validate.generateTestReport(pepperEnv, TARGET_NODE, TEST_IMAGE, artifacts_dir)
+                    }
                 } else {
                     common.infoMsg("Skipping Tempest tests")
                 }
@@ -82,22 +115,33 @@
 
             stage('Run Rally tests') {
                 if (RUN_RALLY_TESTS.toBoolean() == true) {
-                    def report_dir = env.REPORT_DIR ?: '/root/qa_results'
+                    def rally = VALIDATE_PARAMS.get('rally') ?: []
+                    def tags = rally.get('RALLY_TAGS') ?: []
+                    def report_dir = rally.REPORT_DIR ?: '/root/qa_results'
                     def platform = ["type":"unknown", "stacklight_enabled":false]
                     def rally_variables = []
-                    if (K8S_RALLY.toBoolean() == false) {
+                    if (rally.K8S_RALLY.toBoolean() == false) {
                       platform['type'] = 'openstack'
-                      rally_variables = ["floating_network=${FLOATING_NETWORK}",
-                                         "rally_image=${RALLY_IMAGE}",
-                                         "rally_flavor=${RALLY_FLAVOR}",
-                                         "availability_zone=${AVAILABILITY_ZONE}"]
+                      rally_variables = ["floating_network=${rally.FLOATING_NETWORK}",
+                                         "rally_image=${rally.RALLY_IMAGE}",
+                                         "rally_flavor=${rally.RALLY_FLAVOR}",
+                                         "availability_zone=${rally.AVAILABILITY_ZONE}"]
                     } else {
                       platform['type'] = 'k8s'
                     }
-                    if (STACKLIGHT_RALLY.toBoolean() == true) {
+                    if (rally.STACKLIGHT_RALLY.toBoolean() == true) {
                       platform['stacklight_enabled'] = true
                     }
-                    validate.runRallyTests(pepperEnv, TARGET_NODE, TEST_IMAGE, platform, artifacts_dir, RALLY_CONFIG_REPO, RALLY_CONFIG_BRANCH, RALLY_PLUGINS_REPO, RALLY_PLUGINS_BRANCH, RALLY_SCENARIOS, RALLY_SL_SCENARIOS, RALLY_TASK_ARGS_FILE, rally_variables, report_dir, SKIP_LIST)
+                    validate.runRallyTests(
+                        pepperEnv, TARGET_NODE, TEST_IMAGE,
+                        platform, artifacts_dir, rally.RALLY_CONFIG_REPO,
+                        rally.RALLY_CONFIG_BRANCH, rally.RALLY_PLUGINS_REPO,
+                        rally.RALLY_PLUGINS_BRANCH, rally.RALLY_SCENARIOS,
+                        rally.RALLY_SL_SCENARIOS, rally.RALLY_TASK_ARGS_FILE,
+                        rally.RALLY_DB_CONN_STRING, tags,
+                        rally.RALLY_TRENDS, rally_variables,
+                        report_dir, rally.SKIP_LIST
+                    )
                 } else {
                     common.infoMsg("Skipping Rally tests")
                 }
@@ -105,53 +149,24 @@
 
             stage('Run SPT tests') {
                 if (RUN_SPT_TESTS.toBoolean() == true) {
-                    def spt_variables = ["spt_ssh_user=${SPT_SSH_USER}",
-                                         "spt_floating_network=${FLOATING_NETWORK}",
-                                         "spt_image=${SPT_IMAGE}",
-                                         "spt_user=${SPT_IMAGE_USER}",
-                                         "spt_flavor=${SPT_FLAVOR}",
-                                         "spt_availability_zone=${AVAILABILITY_ZONE}"]
+                    def spt = VALIDATE_PARAMS.get('spt') ?: []
+                    def spt_variables = ["spt_ssh_user=${spt.SPT_SSH_USER}",
+                                         "spt_floating_network=${spt.FLOATING_NETWORK}",
+                                         "spt_image=${spt.SPT_IMAGE}",
+                                         "spt_user=${spt.SPT_IMAGE_USER}",
+                                         "spt_flavor=${spt.SPT_FLAVOR}",
+                                         "spt_availability_zone=${spt.AVAILABILITY_ZONE}"]
                     validate.runSptTests(pepperEnv, TARGET_NODE, TEST_IMAGE, artifacts_dir, spt_variables)
+
+                    if (spt.GENERATE_REPORT.toBoolean() == true) {
+                        common.infoMsg("Generating html test report ...")
+                        validate.generateTestReport(pepperEnv, TARGET_NODE, TEST_IMAGE, artifacts_dir)
+                    }
                 } else {
                     common.infoMsg("Skipping SPT tests")
                 }
             }
 
-            stage('Run K8S bootstrap tests') {
-                if (RUN_K8S_TESTS.toBoolean() == true) {
-                    def image = 'tomkukral/k8s-scripts'
-                    def output_file = 'k8s-bootstrap-tests.txt'
-                    def outfile = "/tmp/" + image.replaceAll('/', '-') + '.output'
-                    test.runConformanceTests(pepperEnv, TEST_K8S_NODE, TEST_K8S_API_SERVER, image)
-
-                    def file_content = validate.getFileContent(pepperEnv, TEST_K8S_NODE, outfile)
-                    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 = 'report-k8s-e2e-tests.txt'
-                    def outfile = "/tmp/" + image.replaceAll('/', '-') + '.output'
-                    test.runConformanceTests(pepperEnv, TEST_K8S_NODE, TEST_K8S_API_SERVER, image)
-
-                    def file_content = validate.getFileContent(pepperEnv, TEST_K8S_NODE, outfile)
-                    writeFile file: "${artifacts_dir}${output_file}", text: file_content
-                } else {
-                    common.infoMsg("Skipping k8s conformance e2e tests")
-                }
-            }
-            stage('Generate report') {
-                if (GENERATE_REPORT.toBoolean() == true) {
-                    common.infoMsg("Generating html test report ...")
-                    validate.generateTestReport(pepperEnv, TARGET_NODE, TEST_IMAGE, artifacts_dir)
-                } else {
-                    common.infoMsg("Skipping report generation")
-                }
-            }
             stage('Collect results') {
                 archiveArtifacts artifacts: "${artifacts_dir}/*"
             }