blob: b895b8b5363f94d16aa8467bb898454078f7eece [file] [log] [blame]
azvyagintsev9df82e52018-09-06 19:17:18 +03001/*
2Able to be triggered from Gerrit if :
3Variators:
4Modes:
51) manual run via job-build , possible to pass refspec
6 - for CC
7 - Reclass
8 TODO: currently impossible to use custom COOKIECUTTER_TEMPLATE_URL| RECLASS_SYSTEM_URL Gerrit-one always used.
9 - gerrit trigget.
10 Automatically switches if GERRIT_PROJECT variable detected
11 Always test GERRIT_REFSPEC VS GERRIT_BRANCH-master version of opposite project
12 */
13
chnydae80bb922017-05-29 17:48:40 +020014common = new com.mirantis.mk.Common()
chnydabc63c9a2017-05-30 15:37:54 +020015gerrit = new com.mirantis.mk.Gerrit()
chnydae80bb922017-05-29 17:48:40 +020016git = new com.mirantis.mk.Git()
17python = new com.mirantis.mk.Python()
chnydae80bb922017-05-29 17:48:40 +020018
azvyagintsev9df82e52018-09-06 19:17:18 +030019slaveNode = env.SLAVE_NODE ?: 'python&&docker'
azvyagintsev5c0313d2018-08-13 17:13:35 +030020
azvyagintsev9df82e52018-09-06 19:17:18 +030021// Global var's
22alreadyMerged = false
23gerritConData = [credentialsId : env.CREDENTIALS_ID,
24 gerritName : env.GERRIT_NAME ?: 'mcp-jenkins',
25 gerritHost : env.GERRIT_HOST ?: 'gerrit.mcp.mirantis.net',
26 gerritRefSpec : null,
27 gerritProject : null,
28 withWipeOut : true,
29 GERRIT_CHANGE_NUMBER: null]
30//
31//ccTemplatesRepo = env.COOKIECUTTER_TEMPLATE_URL ?: 'ssh://mcp-jenkins@gerrit.mcp.mirantis.net:29418/mk/cookiecutter-templates'
32gerritDataCC = [:]
33gerritDataCC << gerritConData
34gerritDataCC['gerritBranch'] = env.COOKIECUTTER_TEMPLATE_BRANCH ?: 'master'
35gerritDataCC['gerritProject'] = 'mk/cookiecutter-templates'
36//
37//reclassSystemRepo = env.RECLASS_SYSTEM_URL ?: 'ssh://mcp-jenkins@gerrit.mcp.mirantis.net:29418/salt-models/reclass-system'
38gerritDataRS = [:]
39gerritDataRS << gerritConData
40gerritDataRS['gerritBranch'] = env.RECLASS_MODEL_BRANCH ?: 'master'
41gerritDataRS['gerritProject'] = 'salt-models/reclass-system'
42
43// version of debRepos, aka formulas\reclass
azvyagintsev10e24012018-09-10 21:36:32 +030044testDistribRevision = env.DISTRIB_REVISION ?: 'nightly'
45reclassVersion = 'v1.5.4'
azvyagintsev9df82e52018-09-06 19:17:18 +030046if (common.validInputParam(env.RECLASS_VERSION)) {
47 reclassVersion = env.RECLASS_VERSION
Vasyl Saienko772e1232018-07-23 14:42:24 +030048}
49
chnyda467f10f2017-05-30 17:25:07 +020050def generateSaltMaster(modEnv, clusterDomain, clusterName) {
azvyagintsev5c0313d2018-08-13 17:13:35 +030051 def nodeFile = "${modEnv}/nodes/cfg01.${clusterDomain}.yml"
52 def nodeString = """classes:
chnydae80bb922017-05-29 17:48:40 +020053- cluster.${clusterName}.infra.config
54parameters:
55 _param:
56 linux_system_codename: xenial
57 reclass_data_revision: master
58 linux:
59 system:
60 name: cfg01
61 domain: ${clusterDomain}
62"""
azvyagintsev5c0313d2018-08-13 17:13:35 +030063 sh "mkdir -p ${modEnv}/nodes/"
64 println "Create file ${nodeFile}"
65 writeFile(file: nodeFile, text: nodeString)
azvyagintsev87985532018-07-10 20:49:38 +030066}
67
azvyagintsev30bc82e2018-08-22 12:26:06 +030068/**
69 *
70 * @param contextFile - path to `contexts/XXX.yaml file`
azvyagintsev9df82e52018-09-06 19:17:18 +030071 * @param virtualenv - pyvenv with CC and dep's
azvyagintsev30bc82e2018-08-22 12:26:06 +030072 * @param templateEnvDir - root of CookieCutter
73 * @return
74 */
75
azvyagintsev5c0313d2018-08-13 17:13:35 +030076def generateModel(contextFile, virtualenv, templateEnvDir) {
77 def modelEnv = "${templateEnvDir}/model"
78 def basename = common.GetBaseName(contextFile, '.yml')
79 def generatedModel = "${modelEnv}/${basename}"
80 def content = readFile(file: "${templateEnvDir}/contexts/${contextFile}")
81 def templateContext = readYaml text: content
82 def clusterDomain = templateContext.default_context.cluster_domain
83 def clusterName = templateContext.default_context.cluster_name
84 def outputDestination = "${generatedModel}/classes/cluster/${clusterName}"
85 def templateBaseDir = templateEnvDir
86 def templateDir = "${templateEnvDir}/dir"
87 def templateOutputDir = templateBaseDir
azvyagintsev30bc82e2018-08-22 12:26:06 +030088 dir(templateEnvDir) {
89 sh(script: "rm -rf ${generatedModel} || true")
90 common.infoMsg("Generating model from context ${contextFile}")
91 def productList = ["infra", "cicd", "opencontrail", "kubernetes", "openstack", "oss", "stacklight", "ceph"]
92 for (product in productList) {
chnydae80bb922017-05-29 17:48:40 +020093
azvyagintsev30bc82e2018-08-22 12:26:06 +030094 // get templateOutputDir and productDir
95 if (product.startsWith("stacklight")) {
96 templateOutputDir = "${templateEnvDir}/output/stacklight"
97 try {
98 productDir = "stacklight" + templateContext.default_context['stacklight_version']
99 } catch (Throwable e) {
100 productDir = "stacklight1"
101 }
102 } else {
103 templateOutputDir = "${templateEnvDir}/output/${product}"
104 productDir = product
azvyagintsev5c0313d2018-08-13 17:13:35 +0300105 }
azvyagintsev30bc82e2018-08-22 12:26:06 +0300106
107 if (product == "infra" || (templateContext.default_context["${product}_enabled"]
108 && templateContext.default_context["${product}_enabled"].toBoolean())) {
109
110 templateDir = "${templateEnvDir}/cluster_product/${productDir}"
111 common.infoMsg("Generating product " + product + " from " + templateDir + " to " + templateOutputDir)
112
113 sh "rm -rf ${templateOutputDir} || true"
114 sh "mkdir -p ${templateOutputDir}"
115 sh "mkdir -p ${outputDestination}"
116
117 python.buildCookiecutterTemplate(templateDir, content, templateOutputDir, virtualenv, templateBaseDir)
118 sh "mv -v ${templateOutputDir}/${clusterName}/* ${outputDestination}"
119 } else {
120 common.warningMsg("Product " + product + " is disabled")
121 }
azvyagintsev5c0313d2018-08-13 17:13:35 +0300122 }
azvyagintsev30bc82e2018-08-22 12:26:06 +0300123 generateSaltMaster(generatedModel, clusterDomain, clusterName)
chnydae80bb922017-05-29 17:48:40 +0200124 }
chnydae80bb922017-05-29 17:48:40 +0200125}
126
azvyagintsev87985532018-07-10 20:49:38 +0300127
azvyagintsev5c0313d2018-08-13 17:13:35 +0300128def testModel(modelFile, reclassVersion = 'v1.5.4') {
129 // modelFile - `modelfiname` from model/modelfiname/modelfiname.yaml
130 //* Grub all models and send it to check in paralell - by one in thread.
chnyda7dd8cd92017-12-18 10:19:25 +0100131
azvyagintsev5c0313d2018-08-13 17:13:35 +0300132 _values_string = """
azvyagintsev87985532018-07-10 20:49:38 +0300133 ---
azvyagintsev5c0313d2018-08-13 17:13:35 +0300134 MODELS_TARGZ: "${env.BUILD_URL}/artifact/patched_reclass.tar.gz"
azvyagintsev87985532018-07-10 20:49:38 +0300135 DockerCName: "${env.JOB_NAME.toLowerCase()}_${env.BUILD_TAG.toLowerCase()}_${modelFile.toLowerCase()}"
136 testReclassEnv: "model/${modelFile}/"
137 modelFile: "contexts/${modelFile}.yml"
azvyagintsev9df82e52018-09-06 19:17:18 +0300138 DISTRIB_REVISION: "${testDistribRevision}"
azvyagintsev87985532018-07-10 20:49:38 +0300139 EXTRA_FORMULAS: "${env.EXTRA_FORMULAS}"
140 reclassVersion: "${reclassVersion}"
141 """
azvyagintsev5c0313d2018-08-13 17:13:35 +0300142 build job: "test-mk-cookiecutter-templates-chunk", parameters: [
143 [$class: 'StringParameterValue', name: 'EXTRA_VARIABLES_YAML',
azvyagintsev30bc82e2018-08-22 12:26:06 +0300144 value : _values_string.stripIndent()],
azvyagintsev5c0313d2018-08-13 17:13:35 +0300145 ]
chnydabc63c9a2017-05-30 15:37:54 +0200146}
147
azvyagintsev5c0313d2018-08-13 17:13:35 +0300148def StepTestModel(basename) {
149 // We need to wrap what we return in a Groovy closure, or else it's invoked
150 // when this method is called, not when we pass it to parallel.
151 // To do this, you need to wrap the code below in { }, and either return
152 // that explicitly, or use { -> } syntax.
153 // return node object
154 return {
155 node(slaveNode) {
156 testModel(basename)
157 }
chnydae80bb922017-05-29 17:48:40 +0200158 }
azvyagintsev87985532018-07-10 20:49:38 +0300159}
160
azvyagintsev9df82e52018-09-06 19:17:18 +0300161def StepPrepareGit(templateEnvFolder, gerrit_data) {
azvyagintsev5c0313d2018-08-13 17:13:35 +0300162 // return git clone object
163 return {
azvyagintsev9df82e52018-09-06 19:17:18 +0300164 def checkouted = false
165 common.infoMsg("StepPrepareGit: ${gerrit_data}")
azvyagintsev5c0313d2018-08-13 17:13:35 +0300166 // fetch needed sources
167 dir(templateEnvFolder) {
azvyagintsev9df82e52018-09-06 19:17:18 +0300168 if (gerrit_data['gerritRefSpec']) {
169 // Those part might be not work,in case manual var's pass
170 def gerritChange = gerrit.getGerritChange(gerrit_data['gerritName'], gerrit_data['gerritHost'],
171 gerrit_data['GERRIT_CHANGE_NUMBER'], gerrit_data['credentialsId'])
azvyagintsev5c0313d2018-08-13 17:13:35 +0300172 merged = gerritChange.status == "MERGED"
173 if (!merged) {
azvyagintsev9df82e52018-09-06 19:17:18 +0300174 checkouted = gerrit.gerritPatchsetCheckout(gerrit_data)
azvyagintsev5c0313d2018-08-13 17:13:35 +0300175 } else {
azvyagintsev9df82e52018-09-06 19:17:18 +0300176 // update global variable for pretty return from pipeline
177 alreadyMerged = true
178 common.successMsg("Change ${gerrit_data['GERRIT_CHANGE_NUMBER']} is already merged, no need to gate them")
179 error('change already merged')
azvyagintsev87985532018-07-10 20:49:38 +0300180 }
azvyagintsev5c0313d2018-08-13 17:13:35 +0300181 } else {
azvyagintsev9df82e52018-09-06 19:17:18 +0300182 // Get clean HEAD
183 gerrit_data['useGerritTriggerBuildChooser'] = false
184 checkouted = gerrit.gerritPatchsetCheckout(gerrit_data)
185 if (!checkouted) {
186 error("Failed to get repo:${gerrit_data}")
187 }
azvyagintsev87985532018-07-10 20:49:38 +0300188 }
azvyagintsev5c0313d2018-08-13 17:13:35 +0300189 }
azvyagintsev87985532018-07-10 20:49:38 +0300190 }
azvyagintsev5c0313d2018-08-13 17:13:35 +0300191}
192
193def StepGenerateModels(_contextFileList, _virtualenv, _templateEnvDir) {
194 return {
195 for (contextFile in _contextFileList) {
196 generateModel(contextFile, _virtualenv, _templateEnvDir)
197 }
198 }
199}
200
azvyagintsev9df82e52018-09-06 19:17:18 +0300201def globalVariatorsUpdate() {
202 // Simple function, to check and define branch-around variables
203 // In general, simply make transition updates for non-master branch
204 // based on magic logic
205 def message = ''
206 if (!common.validInputParam(env.GERRIT_PROJECT)) {
207 if (!['nightly', 'testing', 'stable', 'proposed', 'master'].contains(env.GERRIT_BRANCH)) {
208 gerritDataCC['gerritBranch'] = env.GERRIT_BRANCH
209 gerritDataRS['gerritBranch'] = env.GERRIT_BRANCH
210 // 'binary' branch logic w\o 'release/' prefix
211 testDistribRevision = env.GERRIT_BRANCH.split('/')[-1]
212 // Check if we are going to test bleeding-edge release, which doesn't have binary release yet
213 if (!common.checkRemoteBinary([apt_mk_version: testDistribRevision]).linux_system_repo_url) {
214 common.errorMsg("Binary release: ${testDistribRevision} not exist. Fallback to 'proposed'! ")
215 testDistribRevision = 'proposed'
216 }
217 }
218 // Identify, who triggered. To whom we should pass refspec
219 if (env.GERRIT_PROJECT == 'salt-models/reclass-system') {
220 gerritDataRS['gerritRefSpec'] = env.GERRIT_REFSPEC
221 gerritDataRS['GERRIT_CHANGE_NUMBER'] = env.GERRIT_CHANGE_NUMBER
222 message = "<br/>RECLASS_SYSTEM_GIT_REF =>${gerritDataRS['gerritRefSpec']}"
223 } else if (env.GERRIT_PROJECT == 'mk/cookiecutter-templates') {
224 gerritDataCC['gerritRefSpec'] = env.GERRIT_REFSPEC
225 gerritDataCC['GERRIT_CHANGE_NUMBER'] = env.GERRIT_CHANGE_NUMBER
226 message = "<br/>COOKIECUTTER_TEMPLATE_REF =>${gerritDataCC['gerritRefSpec']}"
227 } else {
228 error("Unsuported gerrit-project triggered:${env.GERRIT_PROJECT}")
229 }
230
231 message = "<font color='red'>GerritTrigger detected! We are in auto-mode:</font>" +
232 "<br/>Test env variables has been changed:" +
233 "<br/>COOKIECUTTER_TEMPLATE_BRANCH => ${gerritDataCC['gerritBranch']}" +
234 "<br/>DISTRIB_REVISION =>${testDistribRevision}" +
235 "<br/>RECLASS_MODEL_BRANCH=> ${gerritDataRS['gerritBranch']}" + message
236 common.warningMsg(message)
237 currentBuild.description = currentBuild.description ? message + "<br/>" + currentBuild.description : message
238 } else {
239 // Check for passed variables:
240 if (common.validInputParam(env.RECLASS_SYSTEM_GIT_REF)) {
241 gerritDataRS['gerritRefSpec'] = RECLASS_SYSTEM_GIT_REF
242 }
243 if (common.validInputParam(env.COOKIECUTTER_TEMPLATE_REF)) {
244 gerritDataCC['gerritRefSpec'] = COOKIECUTTER_TEMPLATE_REF
245 }
246 message = "<font color='red'>Manual run detected!</font>" + "<br/>"
247 currentBuild.description = currentBuild.description ? message + "<br/>" + currentBuild.description : message
248 }
249
250}
251
azvyagintsev5c0313d2018-08-13 17:13:35 +0300252timeout(time: 1, unit: 'HOURS') {
253 node(slaveNode) {
azvyagintsev9df82e52018-09-06 19:17:18 +0300254 globalVariatorsUpdate()
255 def gerritDataCCHEAD = [:]
azvyagintsev30bc82e2018-08-22 12:26:06 +0300256 def templateEnvHead = "${env.WORKSPACE}/EnvHead/"
257 def templateEnvPatched = "${env.WORKSPACE}/EnvPatched/"
azvyagintsev5c0313d2018-08-13 17:13:35 +0300258 def contextFileListHead = []
259 def contextFileListPatched = []
260 def vEnv = "${env.WORKSPACE}/venv"
261
262 try {
263 sh(script: 'find . -mindepth 1 -delete > /dev/null || true')
264 stage('Download and prepare CC env') {
265 // Prepare 2 env - for patchset, and for HEAD
azvyagintsev9df82e52018-09-06 19:17:18 +0300266 def paralellEnvs = [:]
azvyagintsev5c0313d2018-08-13 17:13:35 +0300267 paralellEnvs.failFast = true
azvyagintsev9df82e52018-09-06 19:17:18 +0300268 paralellEnvs['downloadEnvPatched'] = StepPrepareGit(templateEnvPatched, gerritDataCC)
269 gerritDataCCHEAD << gerritDataCC
270 gerritDataCCHEAD['gerritRefSpec'] = null; gerritDataCCHEAD['GERRIT_CHANGE_NUMBER'] = null
271 paralellEnvs['downloadEnvHead'] = StepPrepareGit(templateEnvHead, gerritDataCCHEAD)
azvyagintsev5c0313d2018-08-13 17:13:35 +0300272 parallel paralellEnvs
273 }
274 stage("Check workflow_definition") {
275 // Check only for patchset
276 python.setupVirtualenv(vEnv, 'python2', [], "${templateEnvPatched}/requirements.txt")
277 common.infoMsg(python.runVirtualenvCommand(vEnv, "python ${templateEnvPatched}/workflow_definition_test.py"))
278 }
279
280 stage("generate models") {
281 dir("${templateEnvHead}/contexts") {
282 for (String x : findFiles(glob: "*.yml")) {
283 contextFileListHead.add(x)
284 }
285 }
286 dir("${templateEnvPatched}/contexts") {
287 for (String x : findFiles(glob: "*.yml")) {
288 contextFileListPatched.add(x)
289 }
290 }
291 // Generate over 2env's - for patchset, and for HEAD
azvyagintsev9df82e52018-09-06 19:17:18 +0300292 def paralellEnvs = [:]
azvyagintsev5c0313d2018-08-13 17:13:35 +0300293 paralellEnvs.failFast = true
azvyagintsev30bc82e2018-08-22 12:26:06 +0300294 paralellEnvs['GenerateEnvPatched'] = StepGenerateModels(contextFileListPatched, vEnv, templateEnvPatched)
295 paralellEnvs['GenerateEnvHead'] = StepGenerateModels(contextFileListHead, vEnv, templateEnvHead)
azvyagintsev5c0313d2018-08-13 17:13:35 +0300296 parallel paralellEnvs
297
298 // Collect artifacts
299 dir(templateEnvPatched) {
300 // Collect only models. For backward comparability - who know, probably someone use it..
301 sh(script: "tar -czf model.tar.gz -C model ../contexts .", returnStatus: true)
302 archiveArtifacts artifacts: "model.tar.gz"
303 }
304
305 // to be able share reclass for all subenvs
306 // Also, makes artifact test more solid - use one reclass for all of sub-models.
307 // Archive Structure will be:
308 // tar.gz
309 // ├── contexts
310 // │   └── ceph.yml
311 // ├── global_reclass <<< reclass system
312 // ├── model
313 // │   └── ceph <<< from `context basename`
314 // │   ├── classes
315 // │   │   ├── cluster
316 // │   │   └── system -> ../../../global_reclass
317 // │   └── nodes
318 // │   └── cfg01.ceph-cluster-domain.local.yml
azvyagintsev9df82e52018-09-06 19:17:18 +0300319 StepPrepareGit("${env.WORKSPACE}/global_reclass/", gerritDataRS).call()
azvyagintsev5c0313d2018-08-13 17:13:35 +0300320 // link all models, to use one global reclass
321 // For HEAD
322 dir(templateEnvHead) {
323 for (String context : contextFileListHead) {
324 def basename = common.GetBaseName(context, '.yml')
325 dir("${templateEnvHead}/model/${basename}") {
326 sh(script: 'mkdir -p classes/; ln -sfv ../../../../global_reclass classes/system ')
327 }
328 }
329 // Save all models and all contexts. Warning! `h` flag must be used.
azvyagintsev9df82e52018-09-06 19:17:18 +0300330 sh(script: "set -ex;tar -chzf head_reclass.tar.gz --exclude='*@tmp' model contexts")
azvyagintsev5c0313d2018-08-13 17:13:35 +0300331 archiveArtifacts artifacts: "head_reclass.tar.gz"
332 // move for "Compare Pillars" stage
333 sh(script: "mv -v head_reclass.tar.gz ${env.WORKSPACE}")
334 }
335 // For patched
336 dir(templateEnvPatched) {
337 for (String context : contextFileListPatched) {
338 def basename = common.GetBaseName(context, '.yml')
339 dir("${templateEnvPatched}/model/${basename}") {
340 sh(script: 'mkdir -p classes/; ln -sfv ../../../../global_reclass classes/system ')
341 }
342 }
343 // Save all models and all contexts. Warning! `h` flag must be used.
azvyagintsev9df82e52018-09-06 19:17:18 +0300344 sh(script: "set -ex;tar -chzf patched_reclass.tar.gz --exclude='*@tmp' model contexts")
azvyagintsev5c0313d2018-08-13 17:13:35 +0300345 archiveArtifacts artifacts: "patched_reclass.tar.gz"
346 // move for "Compare Pillars" stage
347 sh(script: "mv -v patched_reclass.tar.gz ${env.WORKSPACE}")
348 }
349 }
350
azvyagintsev9df82e52018-09-06 19:17:18 +0300351 stage("Compare Cluster lvl models") {
azvyagintsev5c0313d2018-08-13 17:13:35 +0300352 // Compare patched and HEAD reclass pillars
353 compareRoot = "${env.WORKSPACE}/test_compare/"
354 sh(script: """
355 mkdir -pv ${compareRoot}/new ${compareRoot}/old
356 tar -xzf patched_reclass.tar.gz --directory ${compareRoot}/new
357 tar -xzf head_reclass.tar.gz --directory ${compareRoot}/old
358 """)
359 common.warningMsg('infra/secrets.yml has been skipped from compare!')
azvyagintsev30bc82e2018-08-22 12:26:06 +0300360 rezult = common.comparePillars(compareRoot, env.BUILD_URL, "-Ev \'infra/secrets.yml\'")
azvyagintsev9df82e52018-09-06 19:17:18 +0300361 currentBuild.description = currentBuild.description + '\n' + rezult
azvyagintsev5c0313d2018-08-13 17:13:35 +0300362 }
363 stage("test-contexts") {
364 // Test contexts for patched only
365 stepsForParallel = [:]
366 common.infoMsg("Found: ${contextFileListPatched.size()} patched contexts to test.")
367 for (String context : contextFileListPatched) {
368 def basename = common.GetBaseName(context, '.yml')
369 stepsForParallel.put("ContextPatchTest:${basename}", StepTestModel(basename))
370 }
371 parallel stepsForParallel
372 common.infoMsg('All tests done')
373 }
azvyagintsev5c0313d2018-08-13 17:13:35 +0300374 sh(script: 'find . -mindepth 1 -delete > /dev/null || true')
375
376 } catch (Throwable e) {
azvyagintsev9df82e52018-09-06 19:17:18 +0300377 if (alreadyMerged) {
378 currentBuild.result = 'ABORTED'
379 currentBuild.description = "Change ${GERRIT_CHANGE_NUMBER} is already merged, no need to gate them"
380 return
381 }
azvyagintsev5c0313d2018-08-13 17:13:35 +0300382 currentBuild.result = "FAILURE"
383 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
384 throw e
385 } finally {
386 def dummy = "dummy"
azvyagintsev5c0313d2018-08-13 17:13:35 +0300387 }
388 }
chnydae80bb922017-05-29 17:48:40 +0200389}