blob: 962c4edb443458660ff8004c394c47e6bf9ef999 [file] [log] [blame]
Petr Lomakine700ffd2017-08-01 10:53:15 -07001/**
2 *
Oleg Basov3d93f552019-03-27 01:01:20 +01003 * Launch validation of the cloud with Rally
Petr Lomakine700ffd2017-08-01 10:53:15 -07004 *
5 * Expected parameters:
Oleg Basovd4fa3862019-03-05 21:49:12 +01006 *
Oleg Basovd4fa3862019-03-05 21:49:12 +01007 * JOB_TIMEOUT Job timeout in hours
Petr Lomakine700ffd2017-08-01 10:53:15 -07008 * SALT_MASTER_URL URL of Salt master
9 * SALT_MASTER_CREDENTIALS Credentials to the Salt API
Oleg Basov382613a2019-04-02 19:01:15 +020010 * VALIDATE_PARAMS Validate job YAML params (see below)
Petr Lomakine700ffd2017-08-01 10:53:15 -070011 *
Oleg Basov382613a2019-04-02 19:01:15 +020012 * Rally - map with parameters for starting Rally tests
Oleg Basovd4fa3862019-03-05 21:49:12 +010013 *
Dmitrii Kabanov9f3b7ed2017-09-29 10:47:36 -070014 * AVAILABILITY_ZONE The name of availability zone
15 * FLOATING_NETWORK The name of the external(floating) network
Oleg Basovd4fa3862019-03-05 21:49:12 +010016 * K8S_RALLY Use Kubernetes Rally plugin for testing K8S cluster
17 * STACKLIGHT_RALLY Use Stacklight Rally plugin for testing Stacklight
Dmitrii Kabanov9f3b7ed2017-09-29 10:47:36 -070018 * RALLY_IMAGE The name of the image for Rally tests
19 * RALLY_FLAVOR The name of the flavor for Rally image
Oleg Basov41c4fe72018-06-10 01:16:58 +020020 * RALLY_PLUGINS_REPO Git repository with Rally plugins
21 * RALLY_PLUGINS_BRANCH Git branch which will be used during the checkout
Dmitrii Kabanovb2f60ee2017-11-10 00:31:50 -080022 * RALLY_CONFIG_REPO Git repository with files for Rally
23 * RALLY_CONFIG_BRANCH Git branch which will be used during the checkout
Sergey Galkin8991e822017-11-29 19:10:46 +040024 * RALLY_SCENARIOS Path to file or directory with rally scenarios
Oleg Basovbf860322018-09-04 20:54:36 +020025 * RALLY_SL_SCENARIOS Path to file or directory with stacklight rally scenarios
Sergey Galkin8991e822017-11-29 19:10:46 +040026 * RALLY_TASK_ARGS_FILE Path to file with rally tests arguments
Oleg Basovd4fa3862019-03-05 21:49:12 +010027 * RALLY_DB_CONN_STRING Rally-compliant DB connection string for long-term storing
Oleg Basov382613a2019-04-02 19:01:15 +020028 * results to external DB
Oleg Basovd4fa3862019-03-05 21:49:12 +010029 * RALLY_TAGS List of tags for marking Rally tasks. Can be used when
Oleg Basov382613a2019-04-02 19:01:15 +020030 * generating Rally trends based on particular group of tasks
Oleg Basovd4fa3862019-03-05 21:49:12 +010031 * RALLY_TRENDS If enabled, generate Rally trends report. Requires external DB
Oleg Basov382613a2019-04-02 19:01:15 +020032 * connection string to be set. If RALLY_TAGS was set, trends will
33 * be generated based on finished tasks with these tags, otherwise
34 * on all the finished tasks available in DB
mkraynovda6b6982018-08-06 17:48:24 +040035 * SKIP_LIST List of the Rally scenarios which should be skipped
Oleg Basovd4fa3862019-03-05 21:49:12 +010036 *
Oleg Basov382613a2019-04-02 19:01:15 +020037 * PARALLEL_PERFORMANCE If enabled, run Rally tests separately in parallel for each sub directory found
38 * inside RALLY_SCENARIOS and RALLY_SL_SCENARIOS (if STACKLIGHT_RALLY is enabled)
Oleg Basov2d291f72019-05-28 11:44:15 +020039 * GENERATE_REPORT Set this to false if you are running longevity tests on a cicd node with less than
40 * 21GB memory. Rally consumes lots of memory when generating reports sourcing week
41 * amounts of data (BUG PROD-30433)
Petr Lomakine700ffd2017-08-01 10:53:15 -070042 */
43
44common = new com.mirantis.mk.Common()
Petr Lomakine700ffd2017-08-01 10:53:15 -070045validate = new com.mirantis.mcp.Validate()
Oleg Basov382613a2019-04-02 19:01:15 +020046salt = new com.mirantis.mk.Salt()
47salt_testing = new com.mirantis.mk.SaltModelTesting()
Petr Lomakine700ffd2017-08-01 10:53:15 -070048
Oleg Basovd4fa3862019-03-05 21:49:12 +010049def VALIDATE_PARAMS = readYaml(text: env.getProperty('VALIDATE_PARAMS')) ?: [:]
50if (! VALIDATE_PARAMS) {
51 throw new Exception("VALIDATE_PARAMS yaml is empty.")
52}
Oleg Basov382613a2019-04-02 19:01:15 +020053def TEST_IMAGE = env.getProperty('TEST_IMAGE') ?: 'xrally-openstack:1.4.0'
54def JOB_TIMEOUT = env.getProperty('JOB_TIMEOUT').toInteger() ?: 12
55def SLAVE_NODE = env.getProperty('SLAVE_NODE') ?: 'docker'
56def rally = VALIDATE_PARAMS.get('rally') ?: [:]
57def scenariosRepo = rally.get('RALLY_CONFIG_REPO') ?: 'https://review.gerrithub.io/Mirantis/scale-scenarios'
58def scenariosBranch = rally.get('RALLY_CONFIG_BRANCH') ?: 'master'
59def pluginsRepo = rally.get('RALLY_PLUGINS_REPO') ?: 'https://github.com/Mirantis/rally-plugins'
60def pluginsBranch = rally.get('RALLY_PLUGINS_BRANCH') ?: 'master'
61def tags = rally.get('RALLY_TAGS') ?: []
Oleg Basov2d291f72019-05-28 11:44:15 +020062def generateReport = rally.get('GENERATE_REPORT', true).toBoolean()
Oleg Basovd4fa3862019-03-05 21:49:12 +010063
Oleg Basov382613a2019-04-02 19:01:15 +020064// contrainer working dir vars
65def rallyWorkdir = '/home/rally'
66def rallyPluginsDir = "${rallyWorkdir}/rally-plugins"
67def rallyScenariosDir = "${rallyWorkdir}/rally-scenarios"
68def rallyResultsDir = "${rallyWorkdir}/test_results"
69def rallySecrets = "${rallyWorkdir}/secrets"
Oleg Basov3d93f552019-03-27 01:01:20 +010070
Oleg Basov382613a2019-04-02 19:01:15 +020071// env vars
72def env_vars = []
73def platform = [
74 type: 'unknown',
75 stacklight: [enabled: false, grafanaPass: ''],
76]
77def cmp_count
78
79// test results vars
80def testResult
81def tasksParallel = [:]
82def parallelResults = [:]
83def configRun = [:]
84
85timeout(time: JOB_TIMEOUT, unit: 'HOURS') {
86 node (SLAVE_NODE) {
87
88 // local dir vars
89 def workDir = "${env.WORKSPACE}/rally"
90 def pluginsDir = "${workDir}/rally-plugins"
91 def scenariosDir = "${workDir}/rally-scenarios"
92 def secrets = "${workDir}/secrets"
93 def artifacts = "${workDir}/validation_artifacts"
94
95 stage('Configure env') {
96
97 def master = salt.connection(SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
98
99 // create local directories
100 sh "rm -rf ${workDir} || true"
101 sh "mkdir -p ${artifacts} ${secrets}"
102 writeFile file: "${workDir}/entrypoint.sh", text: '''#!/bin/bash
103set -xe
104exec "$@"
105'''
106 sh "chmod 755 ${workDir}/entrypoint.sh"
107
108 // clone repo with Rally plugins and checkout refs/branch
109 checkout([
110 $class : 'GitSCM',
111 branches : [[name: 'FETCH_HEAD']],
112 extensions : [[$class: 'RelativeTargetDirectory', relativeTargetDir: pluginsDir]],
113 userRemoteConfigs: [[url: pluginsRepo, refspec: pluginsBranch]],
114 ])
115
116 // clone scenarios repo and switch branch / fetch refspecs
117 checkout([
118 $class : 'GitSCM',
119 branches : [[name: 'FETCH_HEAD']],
120 extensions : [[$class: 'RelativeTargetDirectory', relativeTargetDir: scenariosDir]],
121 userRemoteConfigs: [[url: scenariosRepo, refspec: scenariosBranch]],
122 ])
123
124 // get number of computes in the cluster
125 platform['cluster_name'] = salt.getPillar(
126 master, 'I@salt:master', '_param:cluster_name'
127 )['return'][0].values()[0]
128 def rcs_str_node = salt.getPillar(
129 master, 'I@salt:master', 'reclass:storage:node'
130 )['return'][0].values()[0]
131
132 // set up Openstack env variables
133 if (rally.get('K8S_RALLY').toBoolean() == false) {
134
135 platform['type'] = 'openstack'
136 platform['cmp_count'] = rcs_str_node.openstack_compute_rack01['repeat']['count']
137 def rally_variables = [
138 "floating_network=${rally.FLOATING_NETWORK}",
139 "rally_image=${rally.RALLY_IMAGE}",
140 "rally_flavor=${rally.RALLY_FLAVOR}",
141 "availability_zone=${rally.AVAILABILITY_ZONE}",
142 ]
143
144 env_vars = validate._get_keystone_creds_v3(master)
145 if (!env_vars) {
146 env_vars = validate._get_keystone_creds_v2(master)
147 }
148 env_vars = env_vars + rally_variables
149
150 } else {
151 // set up Kubernetes env variables get required secrets
152 platform['type'] = 'k8s'
153 platform['cmp_count'] = rcs_str_node.kubernetes_compute_rack01['repeat']['count']
154
155 def kubernetes = salt.getPillar(
156 master, 'I@kubernetes:master and *01*', 'kubernetes:master'
157 )['return'][0].values()[0]
158
159 env_vars = [
160 "KUBERNETES_HOST=http://${kubernetes.apiserver.vip_address}" +
161 ":${kubernetes.apiserver.insecure_port}",
162 "KUBERNETES_CERT_AUTH=${rallySecrets}/k8s-ca.crt",
163 "KUBERNETES_CLIENT_KEY=${rallySecrets}/k8s-client.key",
164 "KUBERNETES_CLIENT_CERT=${rallySecrets}/k8s-client.crt",
165 ]
166
167 // get K8S certificates to manage cluster
168 def k8s_ca = salt.getFileContent(
169 master, 'I@kubernetes:master and *01*', '/etc/kubernetes/ssl/ca-kubernetes.crt'
170 )
171 def k8s_client_key = salt.getFileContent(
172 master, 'I@kubernetes:master and *01*', '/etc/kubernetes/ssl/kubelet-client.key'
173 )
174 def k8s_client_crt = salt.getFileContent(
175 master, 'I@kubernetes:master and *01*', '/etc/kubernetes/ssl/kubelet-client.crt'
176 )
177 writeFile file: "${secrets}/k8s-ca.crt", text: k8s_ca
178 writeFile file: "${secrets}/k8s-client.key", text: k8s_client_key
179 writeFile file: "${secrets}/k8s-client.crt", text: k8s_client_crt
180
Tetiana Korchakefa4f782017-08-25 10:22:29 -0700181 }
Petr Lomakine700ffd2017-08-01 10:53:15 -0700182
Oleg Basov382613a2019-04-02 19:01:15 +0200183 // get Stacklight data
184 if (rally.STACKLIGHT_RALLY.toBoolean() == true) {
185 platform['stacklight']['enabled'] = true
186
187 def grafana = salt.getPillar(
188 master, 'I@grafana:client', 'grafana:client:server'
189 )['return'][0].values()[0]
190
191 platform['stacklight']['grafanaPass'] = grafana['password']
Petr Lomakine700ffd2017-08-01 10:53:15 -0700192 }
Petr Lomakine700ffd2017-08-01 10:53:15 -0700193
Oleg Basov382613a2019-04-02 19:01:15 +0200194 if (! rally.PARALLEL_PERFORMANCE.toBoolean()) {
Dmitrii Kabanov6b9343e2017-08-30 15:30:21 -0700195
Oleg Basov382613a2019-04-02 19:01:15 +0200196 // Define map with docker commands
197 def commands = validate.runRallyTests(
198 platform, rally.RALLY_SCENARIOS,
Oleg Basov3d93f552019-03-27 01:01:20 +0100199 rally.RALLY_SL_SCENARIOS, rally.RALLY_TASK_ARGS_FILE,
200 rally.RALLY_DB_CONN_STRING, tags,
Oleg Basov2d291f72019-05-28 11:44:15 +0200201 rally.RALLY_TRENDS.toBoolean(), rally.SKIP_LIST, generateReport
Oleg Basov3d93f552019-03-27 01:01:20 +0100202 )
Oleg Basov382613a2019-04-02 19:01:15 +0200203 def commands_list = commands.collectEntries{ [ (it.key) : { sh("${it.value}") } ] }
Dmitrii Kabanova67e5a52017-08-14 16:31:11 -0700204
Oleg Basov382613a2019-04-02 19:01:15 +0200205 configRun = [
206 'image': TEST_IMAGE,
207 'baseRepoPreConfig': false,
208 'dockerMaxCpus': 2,
209 'dockerHostname': 'localhost',
210 'dockerExtraOpts': [
211 "--network=host",
212 "--entrypoint=/entrypoint.sh",
213 "-w ${rallyWorkdir}",
214 "-v ${workDir}/entrypoint.sh:/entrypoint.sh",
215 "-v ${pluginsDir}/:${rallyPluginsDir}",
216 "-v ${scenariosDir}/:${rallyScenariosDir}",
217 "-v ${artifacts}/:${rallyResultsDir}",
218 "-v ${secrets}/:${rallySecrets}",
219 ],
220 'envOpts' : env_vars,
221 'runCommands' : commands_list,
222 ]
223 common.infoMsg('Docker config:')
224 println configRun
225 common.infoMsg('Docker commands list:')
226 println commands
Oleg Basov3d93f552019-03-27 01:01:20 +0100227
Oleg Basov382613a2019-04-02 19:01:15 +0200228 } else {
229
230 // Perform parallel testing of the components with Rally
231 def components = [
232 Common: [],
233 Stacklight: [],
234 ]
235
236 // get list of directories inside scenarios path
237 def scenPath = "${scenariosDir}/${rally.RALLY_SCENARIOS}"
238 def mainComponents = sh(
239 script: "find ${scenPath} -maxdepth 1 -mindepth 1 -type d -exec basename {} \\;",
240 returnStdout: true,
241 ).trim()
242 if (! mainComponents) {
243 error(
244 "No directories found inside RALLY_SCENARIOS ${rally.RALLY_SCENARIOS}\n" +
245 "Either set PARALLEL_PERFORMANCE=false or populate ${rally.RALLY_SCENARIOS} " +
246 "with component directories which include corresponding scenarios"
247 )
248 }
249 components['Common'].addAll(mainComponents.split('\n'))
250 common.infoMsg( "Adding for parallel execution sub dirs found in " +
251 "RALLY_SCENARIOS (${rally.RALLY_SCENARIOS}):"
252 )
253 print mainComponents
254
255 if (rally.STACKLIGHT_RALLY.toBoolean() == true) {
256 def slScenPath = "${scenariosDir}/${rally.RALLY_SL_SCENARIOS}"
257 def slComponents = sh(
258 script: "find ${slScenPath} -maxdepth 1 -mindepth 1 -type d -exec basename {} \\;",
259 returnStdout: true,
260 ).trim()
261 if (! slComponents) {
262 error(
263 "No directories found inside RALLY_SCENARIOS ${rally.RALLY_SL_SCENARIOS}\n" +
264 "Either set PARALLEL_PERFORMANCE=false or populate ${rally.RALLY_SL_SCENARIOS} " +
265 "with component directories which include corresponding scenarios"
266 )
267 }
268 components['Stacklight'].addAll(slComponents.split('\n'))
269 common.infoMsg( "Adding for parallel execution sub dirs found in " +
270 "RALLY_SL_SCENARIOS (${rally.RALLY_SL_SCENARIOS}):"
271 )
272 print slComponents
273 }
274
275 // build up a map with tasks for parallel execution
276 def allComponents = components.values().flatten()
277 for (int i=0; i < allComponents.size(); i++) {
278 // randomize run so we don't bump each other at the startup
279 // also we need to let first thread create rally deployment
280 // so all the rest rally threads can use it after
281 def sleepSeconds = 15 * i
282
283 def task = allComponents[i]
284 def task_name = 'rally_' + task
285 def curComponent = components.find { task in it.value }.key
286 // inherit platform common data
287 def curPlatform = platform
288
289 // setup scenarios and stacklight switch per component
290 def commonScens = "${rally.RALLY_SCENARIOS}/${task}"
291 def stacklightScens = "${rally.RALLY_SL_SCENARIOS}/${task}"
292
293 switch (curComponent) {
294 case 'Common':
295 stacklightScens = ''
296 curPlatform['stacklight']['enabled'] = false
297 break
298 case 'Stacklight':
299 commonScens = ''
300 curPlatform['stacklight']['enabled'] = true
301 break
302 }
303
304 def curCommands = validate.runRallyTests(
305 curPlatform, commonScens,
306 stacklightScens, rally.RALLY_TASK_ARGS_FILE,
307 rally.RALLY_DB_CONN_STRING, tags,
Oleg Basov2d291f72019-05-28 11:44:15 +0200308 rally.RALLY_TRENDS.toBoolean(), rally.SKIP_LIST,
309 generateReport
Oleg Basov382613a2019-04-02 19:01:15 +0200310 )
311
312 // copy required files for the current task
313 def taskWorkDir = "${env.WORKSPACE}/rally_" + task
314 def taskPluginsDir = "${taskWorkDir}/rally-plugins"
315 def taskScenariosDir = "${taskWorkDir}/rally-scenarios"
316 def taskArtifacts = "${taskWorkDir}/validation_artifacts"
317 def taskSecrets = "${taskWorkDir}/secrets"
318 sh "rm -rf ${taskWorkDir} || true"
319 sh "cp -ra ${workDir} ${taskWorkDir}"
320
321 def curCommandsList = curCommands.collectEntries{ [ (it.key) : { sh("${it.value}") } ] }
322 def curConfigRun = [
323 'image': TEST_IMAGE,
324 'baseRepoPreConfig': false,
325 'dockerMaxCpus': 2,
326 'dockerHostname': 'localhost',
327 'dockerExtraOpts': [
328 "--network=host",
329 "--entrypoint=/entrypoint.sh",
330 "-w ${rallyWorkdir}",
331 "-v ${taskWorkDir}/entrypoint.sh:/entrypoint.sh",
332 "-v ${taskPluginsDir}/:${rallyPluginsDir}",
333 "-v ${taskScenariosDir}/:${rallyScenariosDir}",
334 "-v ${taskArtifacts}/:${rallyResultsDir}",
335 "-v ${taskSecrets}/:${rallySecrets}",
336 ],
337 'envOpts' : env_vars,
338 'runCommands' : curCommandsList,
339 ]
340
341 tasksParallel['rally_' + task] = {
342 sleep sleepSeconds
343 common.infoMsg("Docker config for task $task")
344 println curConfigRun
345 common.infoMsg("Docker commands list for task $task")
346 println curCommands
347 parallelResults[task_name] = salt_testing.setupDockerAndTest(curConfigRun)
348 }
349 }
350 }
351 }
352
353 stage('Run Rally tests') {
354
355 def dockerStatuses = [:]
356
357 // start tests in Docker
358 if (! rally.PARALLEL_PERFORMANCE.toBoolean()) {
359 testResult = salt_testing.setupDockerAndTest(configRun)
360 dockerStatuses['rally'] = (testResult) ? 'OK' : 'FAILED'
361 } else {
362 common.infoMsg('Jobs to run in threads: ' + tasksParallel.keySet().join(' '))
363 parallel tasksParallel
364 parallelResults.each { task ->
365 dockerStatuses[task.key] = (task.value) ? 'OK' : 'FAILED'
366 }
367 }
368 // safely archiving all possible results
369 dockerStatuses.each { task ->
370 print "Collecting results for ${task.key} (docker status = '${task.value}')"
371 try {
372 archiveArtifacts artifacts: "${task.key}/validation_artifacts/*"
373 } catch (Throwable e) {
374 print 'failed to get artifacts'
375 }
376 }
377 // setting final job status
378 def failed = dockerStatuses.findAll { it.value == 'FAILED' }
379 if (failed.size() == dockerStatuses.size()) {
380 currentBuild.result = 'FAILURE'
381 } else if (dockerStatuses.find { it.value != 'OK' }) {
382 currentBuild.result = 'UNSTABLE'
383 }
384 }
385
386 stage('Clean env') {
387 // remove secrets
388 sh 'find ./ -type d -name secrets -exec rm -rf \\\"{}\\\" \\; || true'
Tetiana Korchakefa4f782017-08-25 10:22:29 -0700389 }
Petr Lomakine700ffd2017-08-01 10:53:15 -0700390 }
391}