Merge "move function to ceph class"
diff --git a/src/com/mirantis/mcp/MCPArtifactory.groovy b/src/com/mirantis/mcp/MCPArtifactory.groovy
index 272b1bc..fd01573 100644
--- a/src/com/mirantis/mcp/MCPArtifactory.groovy
+++ b/src/com/mirantis/mcp/MCPArtifactory.groovy
@@ -139,14 +139,8 @@
  */
 def moveItem (String artifactoryURL, String sourcePath, String dstPath, boolean copy = false, boolean dryRun = false) {
     def url = "${artifactoryURL}/api/${copy ? 'copy' : 'move'}/${sourcePath}?to=/${dstPath}&dry=${dryRun ? '1' : '0'}"
-    withCredentials([
-            [$class          : 'UsernamePasswordMultiBinding',
-             credentialsId   : 'artifactory',
-             passwordVariable: 'ARTIFACTORY_PASSWORD',
-             usernameVariable: 'ARTIFACTORY_LOGIN']
-    ]) {
-        sh "bash -c \"curl -X POST -u ${ARTIFACTORY_LOGIN}:${ARTIFACTORY_PASSWORD} \'${url}\'\""
-    }
+    def http = new com.mirantis.mk.Http()
+    return http.doPost(url, 'artifactory')
 }
 
 /**
@@ -522,7 +516,7 @@
             }"""
 
         artifactoryServer.upload(uploadSpec, newBuildInfo())
-        def linkUrl = "${artifactoryServer.getUrl()}/artifactory/${config.get('artifactoryRepo')}"
+        def linkUrl = "${artifactoryServer.getUrl()}/${config.get('artifactoryRepo')}"
         artifactsDescription = "Job artifacts uploaded to Artifactory: <a href=\"${linkUrl}\">${linkUrl}</a>"
     } catch (Exception e) {
         if (e =~ /no artifacts/) {
diff --git a/src/com/mirantis/mk/Artifactory.groovy b/src/com/mirantis/mk/Artifactory.groovy
index 9ac53bc..d126a1b 100644
--- a/src/com/mirantis/mk/Artifactory.groovy
+++ b/src/com/mirantis/mk/Artifactory.groovy
@@ -346,7 +346,7 @@
       "files": [
         {
           "pattern": "${file}",
-          "target": "${art.outRepo}",
+          "target": "artifactory/${art.outRepo}",
           "props": "${props}"
         }
       ]
@@ -477,7 +477,7 @@
                 "files": [
                    {
                        "pattern": "${chartPattern}",
-                       "target": "${repoName}/"
+                       "target": "artifactory/${repoName}/"
                     }
                 ]
             }"""
diff --git a/src/com/mirantis/mk/Common.groovy b/src/com/mirantis/mk/Common.groovy
index 5027041..448935f 100644
--- a/src/com/mirantis/mk/Common.groovy
+++ b/src/com/mirantis/mk/Common.groovy
@@ -3,6 +3,8 @@
 import static groovy.json.JsonOutput.prettyPrint
 import static groovy.json.JsonOutput.toJson
 
+import groovy.time.TimeCategory
+
 import com.cloudbees.groovy.cps.NonCPS
 import groovy.json.JsonSlurperClassic
 
@@ -25,6 +27,31 @@
 }
 
 /**
+ * Return Duration for given datetime period with suffix
+ *
+ * @param input String in format '\d+[smhd]', to convert given number into seconds, minutes, hours
+ *              and days duration respectively. For example: '7d' is for 7 days, '10m' - 10 minutes
+ *              and so on. Return null if input in incorrect format
+ */
+def getDuration(String input) {
+    // Verify input format
+    if (!input.matches('[0-9]+[smhd]')) {
+        errorMsg("Incorrect input data for getDuration(): ${input}")
+        return
+    }
+    switch (input[-1]) {
+        case 's':
+            return TimeCategory.getSeconds(input[0..-2].toInteger())
+        case 'm':
+            return TimeCategory.getMinutes(input[0..-2].toInteger())
+        case 'h':
+            return TimeCategory.getHours(input[0..-2].toInteger())
+        case 'd':
+            return TimeCategory.getDays(input[0..-2].toInteger())
+    }
+}
+
+/**
  * Return workspace.
  * Currently implemented by calling pwd so it won't return relevant result in
  * dir context
diff --git a/src/com/mirantis/mk/DockerImageScanner.groovy b/src/com/mirantis/mk/DockerImageScanner.groovy
index 536de37..8178564 100644
--- a/src/com/mirantis/mk/DockerImageScanner.groovy
+++ b/src/com/mirantis/mk/DockerImageScanner.groovy
@@ -31,7 +31,7 @@
         case ~/^(tungsten|tungsten-operator)\/.*$/:
             team_assignee = 'OpenContrail'
             break
-        case ~/^bm\/.*$/:
+        case ~/^(bm|general)\/.*$/:
             team_assignee = 'BM/OS (KaaS BM)'
             break
         case ~/^openstack\/.*$/:
@@ -92,7 +92,7 @@
     if (!found_key[0] && dict && image_short_name) {
         dict.each { issue_key_name ->
             if (!found_key[0]) {
-                def s = dict[issue_key_name.key]['summary'] =~ /\b${image_short_name}\b/
+                def s = dict[issue_key_name.key]['summary'] =~ /(?<=[\/\[])${image_short_name}(?=\])/
                 if (s) {
                     if (image_full_name) {
                         def d = dict[issue_key_name.key]['description'] =~ /(?m)\b${image_full_name}\b/
@@ -126,7 +126,38 @@
     return found_key
 }
 
-def reportJiraTickets(String reportFileContents, String jiraCredentialsID, String jiraUserID, String jiraNamespace = 'PRODX') {
+def getLatestAffectedVersion(cred, productName, defaultJiraAffectedVersion = 'Backlog') {
+    def filterName = ''
+    if (productName == 'mosk') {
+        filterName = 'MOSK'
+    } else if (productName == 'kaas') {
+        filterName = 'KaaS'
+    } else {
+        return defaultJiraAffectedVersion
+    }
+
+    def search_api_url = "${cred.description}/rest/api/2/issue/createmeta?projectKeys=PRODX&issuetypeNames=Bug&expand=projects.issuetypes.fields"
+    def response = callREST("${search_api_url}", "${cred.username}:${cred.password}", 'GET')
+    def InputJSON = new JsonSlurper().parseText(response["responseText"])
+    def AffectedVersions = InputJSON['projects'][0]['issuetypes'][0]['fields']['versions']['allowedValues']
+
+    def versions = []
+    AffectedVersions.each{
+        if (it.containsKey('released') && it['released']) {
+            if (it.containsKey('name') && it['name'].startsWith(filterName)) {
+                if (it.containsKey('releaseDate') && it['releaseDate']) {
+                    versions.add("${it['releaseDate']}`${it['name']}")
+                }
+            }
+        }
+    }
+    if (versions) {
+        return versions.sort()[-1].split('`')[-1]
+    }
+    return defaultJiraAffectedVersion
+}
+
+def reportJiraTickets(String reportFileContents, String jiraCredentialsID, String jiraUserID, String productName = '', String jiraNamespace = 'PRODX') {
 
     def dict = [:]
 
@@ -181,6 +212,11 @@
             }
     }
 
+    def affectedVersion = ''
+    if (jiraNamespace == 'PRODX') {
+        affectedVersion = getLatestAffectedVersion(cred, productName)
+    }
+
     def jira_summary = ''
     def jira_description = ''
     imageDict.each{
@@ -225,7 +261,7 @@
             ]
             if (jiraNamespace == 'PRODX') {
                 basicIssueJSON['fields']['customfield_19000'] = [value:"${team_assignee}"]
-                basicIssueJSON['fields']['versions'] = [["name": "Backlog"]]
+                basicIssueJSON['fields']['versions'] = [["name": affectedVersion]]
             }
             def post_issue_json = JsonOutput.toJson(basicIssueJSON)
             def jira_comment = jira_description.replaceAll(/\n/, '\\\\n')
@@ -239,7 +275,7 @@
                 def post_comment_response = callREST("${uri}/${jira_key[0]}/comment", auth, 'POST', post_comment_json)
                 if ( post_comment_response['responseCode'] == 201 ) {
                     def issueCommentJSON = new JsonSlurper().parseText(post_comment_response["responseText"])
-                    print "\n\nComment was posted to ${jira_key[0]} for ${image_key} and ${image.key}"
+                    print "\n\nComment was posted to ${jira_key[0]} ${affectedVersion} for ${image_key} and ${image.key}"
                 } else {
                     print "\nComment to ${jira_key[0]} Jira issue was not posted"
                 }
@@ -248,7 +284,7 @@
                 if (post_issue_response['responseCode'] == 201) {
                     def issueJSON = new JsonSlurper().parseText(post_issue_response["responseText"])
                     dict = updateDictionary(issueJSON['key'], dict, uri, auth, jiraUserID)
-                    print "\n\nJira issue was created ${issueJSON['key']} for ${image_key} and ${image.key}"
+                    print "\n\nJira issue was created ${issueJSON['key']} ${affectedVersion} for ${image_key} and ${image.key}"
                 } else {
                     print "\n${image.key} CVE issues were not published\n"
                 }
diff --git a/src/com/mirantis/mk/Gerrit.groovy b/src/com/mirantis/mk/Gerrit.groovy
index 88da957..07c6b5d 100644
--- a/src/com/mirantis/mk/Gerrit.groovy
+++ b/src/com/mirantis/mk/Gerrit.groovy
@@ -320,11 +320,13 @@
  *                          HOST, PORT and USER
  * @param changeParams      Parameters to identify Geriit change e.g.: owner, topic,
  *                          status, branch, project
+ * @param extraFlags        Additional flags for gerrit querry for example
+ *                          '--current-patch-set' or '--comments' as a simple string
  */
-def findGerritChange(credentialsId, LinkedHashMap gerritAuth, LinkedHashMap changeParams) {
+def findGerritChange(credentialsId, LinkedHashMap gerritAuth, LinkedHashMap changeParams, String extraFlags = '') {
     scriptText = """
                  ssh -p ${gerritAuth['PORT']} ${gerritAuth['USER']}@${gerritAuth['HOST']} \
-                 gerrit query \
+                 gerrit query ${extraFlags} \
                  --format JSON \
                  """
     changeParams.each {
diff --git a/src/com/mirantis/mk/Http.groovy b/src/com/mirantis/mk/Http.groovy
index b752b42..a7e0f97 100644
--- a/src/com/mirantis/mk/Http.groovy
+++ b/src/com/mirantis/mk/Http.groovy
@@ -55,7 +55,10 @@
         response = connection.inputStream.text
         try {
             response_content = new groovy.json.JsonSlurperClassic().parseText(response)
-        } catch (groovy.json.JsonException e) {
+        } catch (groovy.json.JsonException|java.lang.NullPointerException e) {
+            if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
+                println("[HTTP] Cought exception while trying parsing response as JSON: ${e}")
+            }
             response_content = response
         }
         if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
diff --git a/src/com/mirantis/mk/KaasUtils.groovy b/src/com/mirantis/mk/KaasUtils.groovy
index 3af9777..5936ec8 100644
--- a/src/com/mirantis/mk/KaasUtils.groovy
+++ b/src/com/mirantis/mk/KaasUtils.groovy
@@ -48,9 +48,11 @@
     def common = new com.mirantis.mk.Common()
 
     // Available triggers and its sane defaults
+    def seedMacOs = env.SEED_MACOS ? env.SEED_MACOS.toBoolean() : false
     def deployChild = env.DEPLOY_CHILD_CLUSTER ? env.DEPLOY_CHILD_CLUSTER.toBoolean() : false
     def upgradeChild = env.UPGRADE_CHILD_CLUSTER ? env.UPGRADE_CHILD_CLUSTER.toBoolean() : false
     def attachBYO = env.ATTACH_BYO ? env.ATTACH_BYO.toBoolean() : false
+    def upgradeBYO = env.UPGRADE_BYO ? env.UPGRADE_BYO.toBoolean() : false
     def upgradeMgmt = env.UPGRADE_MGMT_CLUSTER ? env.UPGRADE_MGMT_CLUSTER.toBoolean() : false
     def runUie2e = env.RUN_UI_E2E ? env.RUN_UI_E2E.toBoolean() : false
     def runMgmtConformance = env.RUN_MGMT_CFM ? env.RUN_MGMT_CFM.toBoolean() : false
@@ -67,10 +69,14 @@
     def awsOnDemandDemo = env.ALLOW_AWS_ON_DEMAND ? env.ALLOW_AWS_ON_DEMAND.toBoolean() : false
     def awsOnRhelDemo = false
     def vsphereOnDemandDemo = env.ALLOW_VSPHERE_ON_DEMAND ? env.ALLOW_VSPHERE_ON_DEMAND.toBoolean() : false
+    def equinixOnAwsDemo = false
     def enableOSDemo = true
     def enableBMDemo = true
 
     def commitMsg = env.GERRIT_CHANGE_COMMIT_MESSAGE ? new String(env.GERRIT_CHANGE_COMMIT_MESSAGE.decodeBase64()) : ''
+    if (commitMsg ==~ /(?s).*\[seed-macos\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*seed-macos.*/) {
+        seedMacOs = true
+    }
     if (commitMsg ==~ /(?s).*\[child-deploy\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*child-deploy.*/ || upgradeChild || runChildConformance) {
         deployChild = true
     }
@@ -81,8 +87,16 @@
     if (commitMsg ==~ /(?s).*\[byo-attach\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*byo-attach.*/) {
         attachBYO = true
     }
-    if (commitMsg ==~ /(?s).*\[mgmt-upgrade\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*mgmt-upgrade.*/) {
+    if (commitMsg ==~ /(?s).*\[byo-upgrade\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*byo-upgrade.*/) {
+        attachBYO = true
+        upgradeBYO = true
+    }
+    if (commitMsg ==~ /(?s).*\[mgmt-upgrade\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*mgmt-upgrade.*/ || upgradeBYO) {
         upgradeMgmt = true
+        if (upgradeBYO) {
+            // TODO (vnaumov) remove such dependency right after we can verify byo upgrade w/o mgmt upgrade
+            common.warningMsg('Forced running kaas mgmt upgrade scenario, due byo demo scenario trigger: \'[byo-upgrade]\' ')
+        }
     }
     if (commitMsg ==~ /(?s).*\[ui-e2e\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*ui-e2e.*/) {
         runUie2e = true
@@ -97,12 +111,14 @@
     if (commitMsg ==~ /(?s).*\[fetch.*binaries\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*fetch.*binaries.*/) {
         fetchServiceBinaries = true
     }
-    if (commitMsg ==~ /(?s).*\[aws-demo\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*aws-demo.*/ || attachBYO) {
+    if (commitMsg ==~ /(?s).*\[equinix-demo\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*equinix-demo.*/) {
+        equinixOnAwsDemo = true
+        common.warningMsg('Forced running child cluster deployment on EQUINIX METAL provider based on AWS management cluster, triggered on patchset using custom keyword: \'[equinix-demo]\' ')
+    }
+    if (commitMsg ==~ /(?s).*\[aws-demo\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*aws-demo.*/ || attachBYO || upgradeBYO || seedMacOs || equinixOnAwsDemo) {
         awsOnDemandDemo = true
-        if (attachBYO) {
-            common.warningMsg('Forced running additional kaas deployment with AWS provider, due byo demo scenario trigger: \'[byo-attach]\' ')
-        } else {
-            common.warningMsg('Forced running additional kaas deployment with AWS provider, triggered on patchset using custom keyword: \'[aws-demo]\' ')
+        if (attachBYO || upgradeBYO || seedMacOs || equinixOnAwsDemo) {
+            common.warningMsg('Forced running additional kaas deployment with AWS provider, due applied trigger cross dependencies, follow docs to clarify info')
         }
     }
     if (commitMsg ==~ /(?s).*\[aws-rhel-demo\].*/) {
@@ -143,12 +159,16 @@
     }
     switch (multiregionalMappings['managementLocation']) {
         case 'aws':
+            common.warningMsg('Forced running additional kaas deployment with AWS provider according multiregional demo request')
             awsOnDemandDemo = true
             if (awsOnRhelDemo) {
                 // Run only one variant: standard AWS deployment (on Ubuntu) or on RHEL
                 awsOnDemandDemo = false
             }
-            common.warningMsg('Forced running additional kaas deployment with AWS provider according multiregional demo request')
+
+            if (multiregionalMappings['regionLocation'] != 'aws' && seedMacOs) { // macstadium seed node has access only to *public* providers
+                error('incompatible triggers: [seed-macos] and multiregional deployment based on *private* regional provider cannot be applied simultaneously')
+            }
             break
         case 'os':
             if (enableOSDemo == false) {
@@ -157,27 +177,36 @@
             break
     }
 
+    // calculate weight of current demo run to manage lockable resources
+    def demoWeight = (deployChild) ? 2 : 1 // management = 1, child = 1
+
     common.infoMsg("""
+        Use MacOS node as seed: ${seedMacOs}
         Child cluster deployment scheduled: ${deployChild}
         Child cluster release upgrade scheduled: ${upgradeChild}
         Child conformance testing scheduled: ${runChildConformance}
         BYO cluster attachment scheduled: ${attachBYO}
+        Attached BYO cluster upgrade test scheduled: ${upgradeBYO}
         Mgmt cluster release upgrade scheduled: ${upgradeMgmt}
         Mgmt conformance testing scheduled: ${runMgmtConformance}
         Mgmt UI e2e testing scheduled: ${runUie2e}
         AWS provider deployment scheduled: ${awsOnDemandDemo}
         AWS provider on RHEL deployment scheduled: ${awsOnRhelDemo}
         VSPHERE provider deployment scheduled: ${vsphereOnDemandDemo}
+        EQUINIX child cluster deployment scheduled: ${equinixOnAwsDemo}
         OS provider deployment scheduled: ${enableOSDemo}
         BM provider deployment scheduled: ${enableBMDemo}
         Multiregional configuration: ${multiregionalMappings}
         Service binaries fetching scheduled: ${fetchServiceBinaries}
+        Current weight of the demo run: ${demoWeight} (Used to manage lockable resources)
         Triggers: https://docs.google.com/document/d/1SSPD8ZdljbqmNl_FEAvTHUTow9Ki8NIMu82IcAVhzXw/""")
     return [
+        useMacOsSeedNode           : seedMacOs,
         deployChildEnabled         : deployChild,
         upgradeChildEnabled        : upgradeChild,
         runChildConformanceEnabled : runChildConformance,
         attachBYOEnabled           : attachBYO,
+        upgradeBYOEnabled          : upgradeBYO,
         upgradeMgmtEnabled         : upgradeMgmt,
         runUie2eEnabled            : runUie2e,
         runMgmtConformanceEnabled  : runMgmtConformance,
@@ -185,9 +214,11 @@
         awsOnDemandDemoEnabled     : awsOnDemandDemo,
         awsOnDemandRhelDemoEnabled : awsOnRhelDemo,
         vsphereOnDemandDemoEnabled : vsphereOnDemandDemo,
+        equinixOnAwsDemoEnabled    : equinixOnAwsDemo,
         bmDemoEnabled              : enableBMDemo,
         osDemoEnabled              : enableOSDemo,
-        multiregionalConfiguration : multiregionalMappings]
+        multiregionalConfiguration : multiregionalMappings,
+        demoWeight                 : demoWeight]
 }
 
 /**
@@ -410,12 +441,14 @@
         string(name: 'SI_TESTS_DOCKER_IMAGE_TAG', value: siRefspec.siTestsDockerImageTag),
         string(name: 'SI_PIPELINES_REFSPEC', value: siRefspec.siPipelines),
         string(name: 'CUSTOM_RELEASE_PATCH_SPEC', value: patchSpec),
+        booleanParam(name: 'SEED_MACOS', value: triggers.useMacOsSeedNode),
         booleanParam(name: 'UPGRADE_MGMT_CLUSTER', value: triggers.upgradeMgmtEnabled),
         booleanParam(name: 'RUN_UI_E2E', value: triggers.runUie2eEnabled),
         booleanParam(name: 'RUN_MGMT_CFM', value: triggers.runMgmtConformanceEnabled),
         booleanParam(name: 'DEPLOY_CHILD_CLUSTER', value: triggers.deployChildEnabled),
         booleanParam(name: 'UPGRADE_CHILD_CLUSTER', value: triggers.upgradeChildEnabled),
         booleanParam(name: 'ATTACH_BYO', value: triggers.attachBYOEnabled),
+        booleanParam(name: 'UPGRADE_BYO', value: triggers.upgradeBYOEnabled),
         booleanParam(name: 'RUN_CHILD_CFM', value: triggers.runChildConformanceEnabled),
         booleanParam(name: 'ALLOW_AWS_ON_DEMAND', value: triggers.awsOnDemandDemoEnabled || triggers.awsOnDemandRhelDemoEnabled),
         booleanParam(name: 'ALLOW_VSPHERE_ON_DEMAND', value: triggers.vsphereOnDemandDemoEnabled),
diff --git a/src/com/mirantis/mk/Ruby.groovy b/src/com/mirantis/mk/Ruby.groovy
index 4681d07..2e1e494 100644
--- a/src/com/mirantis/mk/Ruby.groovy
+++ b/src/com/mirantis/mk/Ruby.groovy
@@ -6,9 +6,9 @@
 
 /**
  * Ensures Ruby environment with given version (install it if necessary)
- * @param rubyVersion target ruby version (optional, default 2.2.3)
+ * @param rubyVersion target ruby version (optional, default 2.6.6)
  */
-def ensureRubyEnv(rubyVersion="2.4.1"){
+def ensureRubyEnv(rubyVersion="2.6.6"){
     if (!fileExists("/var/lib/jenkins/.rbenv/versions/${rubyVersion}/bin/ruby")){
         //XXX: patch ruby-build because debian package is quite old
         sh "git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build || git -C ~/.rbenv/plugins/ruby-build pull origin master"