Merge "Use py3 for latest setupOpenstackVirtualenv"
diff --git a/src/com/mirantis/mk/Artifactory.groovy b/src/com/mirantis/mk/Artifactory.groovy
index 2463fe4..84eb143 100644
--- a/src/com/mirantis/mk/Artifactory.groovy
+++ b/src/com/mirantis/mk/Artifactory.groovy
@@ -85,18 +85,6 @@
 }
 
 /**
- * Make PUT request using Artifactory REST API for helm charts
- *
- * @param art       Artifactory connection object
- * @param uri       URI which will be appended to artifactory server base URL
- * @param data      JSON Data to PUT
- * @param prefix    Default prefix "/api" (empty values should be for this way)
- */
-def restPut2(art, uri, data) {
-    return restCall(art, uri, 'PUT', data, ['Accept': '*/*'], '')
-}
-
-/**
  * Make DELETE request using Artifactory REST API
  *
  * @param art   Artifactory connection object
@@ -414,8 +402,20 @@
  * @param art           Artifactory connection object
  * @param repoName      Chart repository name
  */
-def getArtifactoryProjectByName(art, repoName){
-    return restGet(art, "/repositories/${repoName}")
+def getArtifactoryHelmChartRepoByName(art, repoName){
+    def res
+    def common = new com.mirantis.mk.Common()
+    try {
+        res = restGet(art, "/repositories/${repoName}")
+    } catch (IOException e) {
+        def error = e.message.tokenize(':')[1]
+        if (error.contains(' 400 for URL') || error.contains(' 404 for URL')) {
+            common.warningMsg("No projects found for the pattern ${repoName} error code ${error}")
+        }else {
+            throw e
+        }
+    }
+    return res
 }
 
 /**
@@ -424,7 +424,7 @@
  * @param art           Artifactory connection object
  * @param packageType   Repository package type
  */
-def getArtifactoryProjectByPackageType(art, repoName){
+def getArtifactoryRepoByPackageType(art, repoName){
     return restGet(art, "/repositories?${packageType}")
 }
 
@@ -450,14 +450,22 @@
 }
 
 /**
- * Create Helm repo for Artifactory
+ * Publish Helm chart to Artifactory
  *
- * @param art           Artifactory connection object
+ * @param art           Artifactory connection object from artifactory jenkins plugin
  * @param repoName      Repository Chart name
- * @param chartName     Chart name
+ * @param chartPattern  Chart pattern for publishing
  */
-def publishArtifactoryHelmChart(art, repoName, chartName){
-    return restPut2(art, "/${repoName}", "/${chartName}")
+def publishArtifactoryHelmChart(art, repoName, chartPattern){
+    def uploadSpec = """{
+                "files": [
+                   {
+                       "pattern": "${chartPattern}",
+                       "target": "${repoName}/"
+                    }
+                ]
+            }"""
+    art.upload spec: uploadSpec
 }
 
 /**
diff --git a/src/com/mirantis/mk/Git.groovy b/src/com/mirantis/mk/Git.groovy
index 5743a61..36292aa 100644
--- a/src/com/mirantis/mk/Git.groovy
+++ b/src/com/mirantis/mk/Git.groovy
@@ -16,8 +16,9 @@
  * @param poll            Enable git polling (default true)
  * @param timeout         Set checkout timeout (default 10)
  * @param depth           Git depth param (default 0 means no depth)
+ * @param reference       Git reference param to checkout (default empyt, i.e. no reference)
  */
-def checkoutGitRepository(path, url, branch, credentialsId = null, poll = true, timeout = 10, depth = 0){
+def checkoutGitRepository(path, url, branch, credentialsId = null, poll = true, timeout = 10, depth = 0, reference = ''){
     dir(path) {
         checkout(
             changelog:true,
@@ -28,7 +29,7 @@
             doGenerateSubmoduleConfigurations: false,
             extensions: [
                 [$class: 'CheckoutOption', timeout: timeout],
-                [$class: 'CloneOption', depth: depth, noTags: false, reference: '', shallow: depth > 0, timeout: timeout]],
+                [$class: 'CloneOption', depth: depth, noTags: false, reference: reference, shallow: depth > 0, timeout: timeout]],
             submoduleCfg: [],
             userRemoteConfigs: [[url: url, credentialsId: credentialsId]]]
         )
diff --git a/src/com/mirantis/mk/Orchestrate.groovy b/src/com/mirantis/mk/Orchestrate.groovy
index dd897bb..509fe87 100644
--- a/src/com/mirantis/mk/Orchestrate.groovy
+++ b/src/com/mirantis/mk/Orchestrate.groovy
@@ -411,8 +411,9 @@
     // races, apply on the first node initially
     if (salt.testTarget(master, "I@gnocchi:client ${extra_tgt}")) {
         first_target = salt.getFirstMinion(master, "I@gnocchi:client ${extra_tgt}")
-        salt.enforceState([saltId: master, target: "${first_target} ${extra_tgt}", state: 'gnocchi.client'])
-        salt.enforceState([saltId: master, target: "I@gnocchi:client ${extra_tgt}", state: 'gnocchi.client'])
+        // TODO(vsaienko) remove retries when they are moved to gnocchiv1 salt module. Related-Prod: PROD-32186
+        salt.enforceState([saltId: master, target: "${first_target} ${extra_tgt}", state: 'gnocchi.client', retries: 3])
+        salt.enforceState([saltId: master, target: "I@gnocchi:client ${extra_tgt}", state: 'gnocchi.client', retries: 3])
     }
 
     // Install gnocchi statsd
diff --git a/src/com/mirantis/mk/ReleaseWorkflow.groovy b/src/com/mirantis/mk/ReleaseWorkflow.groovy
index a81f0d1..7da212d 100644
--- a/src/com/mirantis/mk/ReleaseWorkflow.groovy
+++ b/src/com/mirantis/mk/ReleaseWorkflow.groovy
@@ -1,8 +1,35 @@
 package com.mirantis.mk
 /**
- * ReleaseWorkflow functions
+ * Checkout release metadata repo with clone or without, if cloneRepo parameter is set
  *
+ * @param params map with expected parameters:
+ *    - metadataCredentialsId
+ *    - metadataGitRepoUrl
+ *    - metadataGitRepoBranch
+ *    - repoDir
+ *    - cloneRepo
  */
+def checkoutReleaseMetadataRepo(Map params = [:]) {
+    def git = new com.mirantis.mk.Git()
+    Boolean cloneRepo       = params.get('cloneRepo', true)
+    String gitCredentialsId = params.get('metadataCredentialsId', 'mcp-ci-gerrit')
+    String gitUrl           = params.get('metadataGitRepoUrl', "ssh://${gitCredentialsId}@gerrit.mcp.mirantis.net:29418/mcp/release-metadata")
+    String gitBranch        = params.get('metadataGitRepoBranch', 'master')
+    String gitRef           = params.get('metadataGitRepoRef', '')
+    String repoDir          = params.get('repoDir', 'release-metadata')
+    if (cloneRepo) {
+        stage('Cleanup repo dir') {
+            dir(repoDir) {
+                deleteDir()
+            }
+        }
+        stage('Cloning release-metadata repository') {
+            git.checkoutGitRepository(repoDir, gitUrl, gitBranch, gitCredentialsId, true, 10, 0, gitRef)
+        }
+    } else {
+        git.changeGitBranch(repoDir, gitRef ?: gitBranch)
+    }
+}
 
 /**
  * Get release metadata value for given key
@@ -24,24 +51,31 @@
     String gitBranch        = params.get('metadataGitRepoBranch', 'master')
     String repoDir          = params.get('repoDir', 'release-metadata')
     String outputFormat     = params.get('outputFormat', 'json')
+    Boolean cloneRepo       = params.get('cloneRepo', true)
 
     // Libs
     def git = new com.mirantis.mk.Git()
+    def common = new com.mirantis.mk.Common()
 
     String opts = ''
     if (outputFormat && !outputFormat.isEmpty()) {
         opts += " --${outputFormat}"
     }
-    // TODO cache it somehow to not checkout it all the time
-    git.checkoutGitRepository(repoDir, gitUrl, gitBranch, gitCredentialsId, true, 10, 0)
+
+    checkoutReleaseMetadataRepo(params)
+
     docker.image(toxDockerImage).inside {
         result = sh(script: "cd ${repoDir} && tox -qq -e metadata -- ${opts} get --key ${key}", returnStdout: true).trim()
     }
+    common.infoMsg("""
+    Release metadata key ${key} has value:
+        ${result}
+    """)
     return result
 }
 
 /**
- * Update release metadata after image build
+ * Update release metadata value and upload CR to release metadata repository
  *
  * @param key metadata key
  * @param value metadata value
@@ -54,6 +88,7 @@
     metadataGerritBranch = params['metadataGerritBranch'] ?: "master"
     comment = params['comment'] ?: ""
     crTopic = params['crTopic'] ?: ""
+    Boolean cloneRepo = params.get('cloneRepo', true)
     def common = new com.mirantis.mk.Common()
     def python = new com.mirantis.mk.Python()
     def gerrit = new com.mirantis.mk.Gerrit()
@@ -67,7 +102,7 @@
     def gerritPort = metadataRepoUrl.tokenize(':')[-1].tokenize('/')[0]
     def workspace = common.getWorkspace()
     def venvDir = "${workspace}/gitreview-venv"
-    def repoDir = "${venvDir}/repo"
+    def repoDir = params.get('repoDir', "${venvDir}/repo")
     def metadataDir = "${repoDir}/metadata"
     def ChangeId
     def commitMessage
@@ -75,21 +110,19 @@
     stage("Installing virtualenv") {
         python.setupVirtualenv(venvDir, 'python3', ['git-review', 'PyYaml'])
     }
-    stage('Cleanup repo dir') {
-        dir(repoDir) {
-            deleteDir()
-        }
+    checkoutReleaseMetadataRepo(['metadataCredentialsId': credentialsID,
+                                 'metadataGitRepoBranch': metadataGerritBranch,
+                                 'metadataGitRepoUrl': metadataRepoUrl,
+                                 'repoDir': repoDir,
+                                 'cloneRepo': cloneRepo])
+    dir(repoDir) {
+        gitRemote = sh(
+            script:
+                'git remote -v | head -n1 | cut -f1',
+                returnStdout: true,
+        ).trim()
     }
-    stage('Cloning release-metadata repository') {
-        git.checkoutGitRepository(repoDir, metadataRepoUrl, metadataGerritBranch, credentialsID, true, 10, 0)
-        dir(repoDir) {
-            gitRemote = sh(
-                    script:
-                            'git remote -v | head -n1 | cut -f1',
-                    returnStdout: true,
-            ).trim()
-        }
-    }
+
     stage('Creating CR') {
         def gerritAuth = ['PORT': gerritPort, 'USER': gerritUser, 'HOST': gerritHost]
         def changeParams = ['owner': gerritUser, 'status': 'open', 'project': metadataProject, 'branch': metadataGerritBranch, 'topic': crTopic]
diff --git a/src/com/mirantis/mk/Salt.groovy b/src/com/mirantis/mk/Salt.groovy
index daff9fc..7347205 100644
--- a/src/com/mirantis/mk/Salt.groovy
+++ b/src/com/mirantis/mk/Salt.groovy
@@ -1348,11 +1348,15 @@
 def isPackageInstalled(Map params) {
     def output = params.get('output', true)
     def res = runSaltProcessStep(params.saltId, params.target, "pkg.info_installed", params.packageName, null, output)['return'][0]
-    for (int i = 0; i < res.size(); i++) {
-        def key = res.keySet()[i]
-        if (!(res[key] instanceof Map && res[key].containsKey(params.packageName))) {
-            return false
+    if (res) {
+        for (int i = 0; i < res.size(); i++) {
+            def key = res.keySet()[i]
+            if (!(res[key] instanceof Map && res[key].containsKey(params.packageName))) {
+                return false
+            }
         }
+        return true
+    } else {
+        return false
     }
-    return true
 }