Merge "[artifactory-replicator] Add DOCKER_OVERWRITE_TAG parameter"
diff --git a/src/com/mirantis/mk/KaasUtils.groovy b/src/com/mirantis/mk/KaasUtils.groovy
index bbc26c0..68ae547 100644
--- a/src/com/mirantis/mk/KaasUtils.groovy
+++ b/src/com/mirantis/mk/KaasUtils.groovy
@@ -94,6 +94,7 @@
     def runMkeCustomCertTest = env.RUN_MKE_CUSTOM_CERT_TEST ? env.RUN_MKE_CUSTOM_CERT_TEST.toBoolean() : false
     def runCustomHostnames = env.RUN_CUSTOM_HOSTNAMES ? env.RUN_CUSTOM_HOSTNAMES.toBoolean() : false
     def slLatest = env.SL_LATEST ? env.SL_LATEST.toBoolean() : false
+    def lcmAnsibleLatest = env.LCM_ANSIBLE_LATEST ? env.LCM_ANSIBLE_LATEST.toBoolean() : false
     def coreKeycloakLdap = env.CORE_KEYCLOAK_LDAP_ENABLED ? env.CORE_KEYCLOAK_LDAP_ENABLED.toBoolean() : false
     def configureInternalNTP = env.CORE_KAAS_NTP_ENABLED ? env.CORE_KAAS_NTP_ENABLED.toBoolean() : false
     def disableKubeApiAudit = env.DISABLE_KUBE_API_AUDIT ? env.DISABLE_KUBE_API_AUDIT.toBoolean() : false
@@ -109,6 +110,7 @@
     def runRuntimeMigrateAndRollbackTestChild = env.RUN_CHILD_RUNTIME_MIGRATE_AND_ROLLBACK_TEST ? env.RUN_CHILD_RUNTIME_MIGRATE_AND_ROLLBACK_TEST.toBoolean() : false
     def upgradeChildPlanSeq = env.UPGRADE_CHILD_PLAN_SEQ ? env.UPGRADE_CHILD_PLAN_SEQ.toBoolean() : false
     def upgradeChildPlanBulk = env.UPGRADE_CHILD_PLAN_BULK ? env.UPGRADE_CHILD_PLAN_BULK.toBoolean() : false
+    def upgradeRestartChecker = env.ENABLE_RESTART_CHECKER_FOR_CHILD_UPGRADE ? env.ENABLE_RESTART_CHECKER_FOR_CHILD_UPGRADE.toBoolean() : false
     // multiregion configuration from env variable: comma-separated string in form $mgmt_provider,$regional_provider
     def multiregionalMappings = env.MULTIREGION_SETUP ? multiregionWorkflowParser(env.MULTIREGION_SETUP) : [
         enabled: false,
@@ -135,6 +137,8 @@
     def enableBMDemo = true
     def enablebmCoreDemo = env.ALLOW_BM_CORE_ON_DEMAND ? env.ALLOW_BM_CORE_ON_DEMAND.toBoolean() : false
     def bmCoreCleanup = env.BM_CORE_CLEANUP ? env.BM_CORE_CLEANUP.toBoolean() : true
+    def airGapped = env.ALLOW_AIRGAP ? env.ALLOW_AIRGAP.toBoolean() : false
+    def airGappedCDN = env.AIRGAP_CDN ? env.AIRGAP_CDN.toString() : 'internal-ci'
     def enableArtifactsBuild = true
     def bmDeployType = env.BM_DEPLOY_TYPE ? env.BM_DEPLOY_TYPE.toString() : 'virtual'
     def openstackIMC = env.OPENSTACK_CLOUD_LOCATION ? env.OPENSTACK_CLOUD_LOCATION : 'us'
@@ -194,6 +198,12 @@
         upgradeChild = true
         upgradeChildPlanBulk = true
     }
+    if (commitMsg ==~ /(?s).*\[check-runtime-restart-upgrade\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*check-runtime-restart-upgrade.*/) {
+        upgradeRestartChecker = true
+        deployChild = true
+        upgradeChild = true
+        common.warningMsg('Runtime restart checker enabled for child upgrade. Child deployment and upgrade will be enforced.')
+    }
     if ((upgradeMgmt || autoUpgradeMgmt) && deployChild) {
         upgradeChild = true
         common.warningMsg('child upgrade is automatically enabled as mgmt upgrade and child deploy are enabled')
@@ -404,6 +414,25 @@
         bmCoreCleanup = false
     }
 
+    if (commitMsg ==~ /(?s).*\[air-gapped\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*air-gapped\\.*/) {
+        airGapped = true
+    }
+
+    if (commitMsg ==~ /(?s).*\[airgap-cdn-(eu|us|public-ci)\].*/) {
+        def parsedStr = commitMsg =~ /\[airgap-cdn-(eu|us|public-ci)\]/
+        switch (parsedStr[0][1]) {
+            case 'eu':
+                airGappedCDN = 'internal-eu'
+                break
+            case 'us':
+                airGappedCDN = 'internal-ci'
+                break
+            case 'public-ci':
+                airGappedCDN = 'public-ci'
+                break
+        }
+    }
+
     if (commitMsg ==~ /(?s).*\[disable-vsphere-demo\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*disable-vsphere-demo\.*/) {
         enableVsphereDemo = false
         common.errorMsg('vSphere demo deployment will be aborted, VF -1 will be set')
@@ -474,6 +503,11 @@
         common.warningMsg('All clusters will be deployed with Stacklight version from artifact-metadata')
     }
 
+    if (commitMsg ==~ /(?s).*\[lcm-ansible-latest\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*lcm-ansible-latest\.*/) {
+        lcmAnsibleLatest = true
+        common.warningMsg('All clusters will be deployed with latest available lcm-ansible version')
+    }
+
     if (commitMsg ==~ /(?s).*\[keycloak-ldap\].*/ || env.GERRIT_EVENT_COMMENT_TEXT ==~ /(?s).*keycloak-ldap\.*/) {
         coreKeycloakLdap = true
         common.warningMsg('Management cluster will be deployed with LDAP integration enabled and after-deployment checks will be executed')
@@ -704,6 +738,7 @@
         MKE custom cert test for mgmt/region: ${runMkeCustomCertTest}
         Custom hostnames for all clisuers: ${runCustomHostnames}
         Stacklight templates enchanced with latest version from artifact-metadata: ${slLatest}
+        Latest lcm-ansible tarball is used: ${lcmAnsibleLatest}
         Disable Kubernetes API audit: ${disableKubeApiAudit}
         Enable Auditd : ${auditd}
         AWS provider deployment scheduled: ${awsOnDemandDemo}
@@ -720,6 +755,8 @@
         BM Core type deplyment: ${bmDeployType}
         BM Core cleanup: ${bmCoreCleanup}
         BM provider deployment scheduled: ${enableBMDemo}
+        airGapped deployment: ${airGapped}
+        airGap CDN region: ${airGappedCDN}
         Ubuntu on vSphere scheduled: ${enableVsphereUbuntu}
         RHEL on vSphere scheduled: ${enableVsphereRHEL}
         Artifacts build scheduled: ${enableArtifactsBuild}
@@ -749,6 +786,7 @@
         Child runtime migration (extended) with rollback enabled: ${runRuntimeMigrateAndRollbackTestChild}
         Child Upgrade via update plan with sequental steps enabled: ${upgradeChildPlanSeq}
         Child Upgrade via update plan with bulk steps enabled: ${upgradeChildPlanBulk}
+        Runtime restart checker for child upgrade enabled: ${upgradeRestartChecker}
         Triggers: https://gerrit.mcp.mirantis.com/plugins/gitiles/kaas/core/+/refs/heads/master/hack/ci-gerrit-keywords.md""")
     return [
         osCloudLocation                          : openstackIMC,
@@ -790,6 +828,7 @@
         runMkeCustomCertTestEnabled              : runMkeCustomCertTest,
         runCustomHostnamesEnabled                : runCustomHostnames,
         slLatestEnabled                          : slLatest,
+        lcmAnsibleLatestEnabled                  : lcmAnsibleLatest,
         runByoChildCustomCertTestEnabled         : runByoChildCustomCertTest,
         runChildMachineDeletionPolicyTestEnabled : runChildMachineDeletionPolicyTest,
         runLMATestEnabled                        : runLMATest,
@@ -808,6 +847,8 @@
         bmCoreDemoEnabled                        : enablebmCoreDemo,
         bmCoreCleanup                            : bmCoreCleanup,
         bmDeployType                             : bmDeployType,
+        airGapped                                : airGapped,
+        airGappedCDN                             : airGappedCDN,
         osDemoEnabled                            : enableOSDemo,
         vsphereUbuntuEnabled                     : enableVsphereUbuntu,
         vsphereRHELEnabled                       : enableVsphereRHEL,
@@ -840,6 +881,7 @@
         runtimeMigrateChildAndRollbackEnabled    : runRuntimeMigrateAndRollbackTestChild,
         upgradeChildPlanSeqEnabled               : upgradeChildPlanSeq,
         upgradeChildPlanBulkEnabled              : upgradeChildPlanBulk,
+        upgradeRestartCheckerEnabled             : upgradeRestartChecker,
     ]
 }
 
@@ -1110,6 +1152,7 @@
         string(name: 'OPENSTACK_CLOUD_LOCATION', value: triggers.osCloudLocation),
         string(name: 'SLACK_CHANNEL_NOTIFY', value: triggers.customSlackChannelEnabled),
         string(name: 'BM_DEPLOY_TYPE', value: triggers.bmDeployType),
+        string(name: 'AIRGAP_CDN', value: airGappedCDN),
         booleanParam(name: 'OFFLINE_MGMT_CLUSTER', value: triggers.proxyConfig['mgmtOffline']),
         booleanParam(name: 'OFFLINE_CHILD_CLUSTER', value: triggers.proxyConfig['childOffline']),
         booleanParam(name: 'PROXY_CHILD_CLUSTER', value: triggers.proxyConfig['childProxy']),
@@ -1133,6 +1176,7 @@
         booleanParam(name: 'RUN_MKE_CUSTOM_CERT_TEST', value: triggers.runMkeCustomCertTestEnabled),
         booleanParam(name: 'RUN_CUSTOM_HOSTNAMES', value: triggers.runCustomHostnamesEnabled),
         booleanParam(name: 'SL_LATEST', value: triggers.slLatestEnabled),
+        booleanParam(name: 'LCM_ANSIBLE_LATEST', value: triggers.lcmAnsibleLatestEnabled),
         booleanParam(name: 'RUN_BYO_CHILD_CUSTOM_CERT_TEST', value: triggers.runByoChildCustomCertTestEnabled),
         booleanParam(name: 'RUN_CHILD_MACHINE_DELETION_POLICY_TEST', value: triggers.runChildMachineDeletionPolicyTestEnabled),
         booleanParam(name: 'RUN_LMA_TEST', value: triggers.runLMATestEnabled),
@@ -1163,6 +1207,7 @@
         booleanParam(name: 'AIO_CLUSTER', value: triggers.aioCluster),
         booleanParam(name: 'DOCKER_SERVICES_CHECK_SKIP', value: triggers.dockerServicesCheckSkip),
         booleanParam(name: 'BM_CORE_CLEANUP', value: triggers.bmCoreCleanup),
+        booleanParam(name: 'ALLOW_AIRGAP', value: triggers.airGapped),
         booleanParam(name: 'DISABLE_KUBE_API_AUDIT', value: triggers.disableKubeApiAudit),
         booleanParam(name: "AUDITD_ENABLE", value: triggers.auditdEnabled),
         booleanParam(name: 'CORE_KEYCLOAK_LDAP_ENABLED', value: triggers.coreKeycloakLdapEnabled),
@@ -1177,6 +1222,7 @@
         booleanParam(name: 'RUN_CHILD_RUNTIME_MIGRATE_AND_ROLLBACK_TEST', value: triggers.runtimeMigrateChildAndRollbackEnabled),
         booleanParam(name: 'UPGRADE_CHILD_PLAN_SEQ', value: triggers.upgradeChildPlanSeqEnabled),
         booleanParam(name: 'UPGRADE_CHILD_PLAN_BULK', value: triggers.upgradeChildPlanBulkEnabled),
+        booleanParam(name: 'ENABLE_RESTART_CHECKER_FOR_CHILD_UPGRADE', value: triggers.upgradeRestartCheckerEnabled),
     ]
 
     // customize multiregional demo
diff --git a/src/com/mirantis/mk/Openstack.groovy b/src/com/mirantis/mk/Openstack.groovy
index 74a1ce9..db54e4b 100644
--- a/src/com/mirantis/mk/Openstack.groovy
+++ b/src/com/mirantis/mk/Openstack.groovy
@@ -590,7 +590,7 @@
 def createOpenstackEnvInDocker(authUrl, credentialsId, project, project_domain="default", project_id="", user_domain="default", cacert="/etc/ssl/certs/ca-certificates.crt") {
     def common = new com.mirantis.mk.Common()
     creds = common.getPasswordCredentials(credentialsId)
-    def env = ["OS_USERNAME=${creds.username}", "OS_PASSWORD=${creds.password.toString()}", "OS_TENANT_NAME=${project}", "OS_AUTH_URL=${authUrl}", "OS_AUTH_STRATEGY=keystone -e OS_PROJECT_NAME=${project}", "OS_PROJECT_ID=${project_id}", "OS_PROJECT_DOMAIN_ID=${project_domain}", "OS_USER_DOMAIN_NAME=${user_domain}", "OS_CACERT=${cacert}"]
+    def env = ["OS_USERNAME=${creds.username}", "OS_PASSWORD=${creds.password.toString()}", "OS_TENANT_NAME=${project}", "OS_AUTH_URL=${authUrl}", "OS_AUTH_STRATEGY=keystone", "OS_PROJECT_NAME=${project}", "OS_PROJECT_ID=${project_id}", "OS_PROJECT_DOMAIN_ID=${project_domain}", "OS_USER_DOMAIN_NAME=${user_domain}", "OS_CACERT=${cacert}"]
     return env
 }
 
@@ -680,3 +680,33 @@
     }
     return outputTable
 }
+
+/**
+ * Delete application credential
+ *
+ * @param env          Connection parameters for OpenStack API endpoint
+ * @param name         Name of the application credential to delete
+ */
+def deleteAppCredentialInDocker(env, name, img) {
+    def common = new com.mirantis.mk.Common()
+    common.infoMsg("Removing application credential ${name}")
+    def cmd = "openstack application credential delete ${name}"
+    runOpenstackCommandInDocker(cmd, env, img)
+}
+
+/**
+ * Check if application credential exists and delete it.
+ *
+ * @param env          Connection parameters for OpenStack API endpoint
+ * @param name         Name of the application credential to delete
+ **/
+def ensureAppCredentialRemovedInDocker(String name, env, img) {
+    def common = new com.mirantis.mk.Common()
+    def appCreds = runOpenstackCommandInDocker("openstack application credential list -f value -c Name", env, img).tokenize('\n')
+    if (name in appCreds) {
+        deleteAppCredentialInDocker(env, name, img)
+        common.infoMsg("Application credential ${name} has been deleted")
+    } else {
+        common.warningMsg("Application credential ${name} not found")
+    }
+}
diff --git a/src/com/mirantis/mk/Workflow.groovy b/src/com/mirantis/mk/Workflow.groovy
index 224ae77..61e9b33 100644
--- a/src/com/mirantis/mk/Workflow.groovy
+++ b/src/com/mirantis/mk/Workflow.groovy
@@ -241,6 +241,14 @@
 def runJob(job_name, job_parameters, global_variables, Boolean propagate = false) {
 
     def parameters = generateParameters(job_parameters, global_variables)
+    // Inject hidden random parameter (is not showed in jjb) to be sure we are triggering unique downstream job.
+    // Most actual case - parallel run for same jobs( but with different params)
+    // WARNING: dont move hack to generateParameters:
+    // PRODX-48965 - it will conflict with si_run_steps logic and will be copy-paste to sub.jobs
+    String rand_value = "${env.JOB_NAME.toLowerCase()}-${env.BUILD_NUMBER}-${UUID.randomUUID().toString().split('-')[0]}"
+    parameters.add([$class: "StringParameterValue",
+                    name  : "RANDOM_SEED_STRING",
+                    value : rand_value])
     // Build the job
     def job_info = build job: "${job_name}", parameters: parameters, propagate: propagate
     return job_info
@@ -662,6 +670,7 @@
     //   max_concurrent_interval: 300    # how many seconds should be passed between checking for an available concurrency
     //   check_failed_concurrent: false  # stop waiting for available concurrent executors if count of failed jobs >= max_concurrent,
     //                                   # which means that all available shared resources are occupied by the failed jobs
+    //   abort_on_parallel_fail: false                 # pass parallel.fail_fast option. force your parallel stages to all be aborted when any one of them fails
     def common = new com.mirantis.mk.Common()
 
     def sourceText = ""
@@ -693,6 +702,7 @@
     def check_failed_concurrent = (step['check_failed_concurrent'] ?: false).toBoolean()
 
     def jobs = [:]
+    jobs.failFast = (step['abort_on_parallel_fail'] ?: false).toBoolean()
     def nested_step_id = 0
     def free_concurrent = max_concurrent
     def failed_concurrent = []
@@ -756,7 +766,6 @@
         // Run parallel iterations
         try {
             common.infoMsg("${prefixMsg} Run steps in parallel")
-
             parallel jobs
 
             parallelSummary['nested_result'] = 'SUCCESS'