blob: 930a27db4afb6316664157c8a3c1053cf9827e1e [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)
Petr Lomakine700ffd2017-08-01 10:53:15 -070039 */
40
41common = new com.mirantis.mk.Common()
Petr Lomakine700ffd2017-08-01 10:53:15 -070042validate = new com.mirantis.mcp.Validate()
Oleg Basov382613a2019-04-02 19:01:15 +020043salt = new com.mirantis.mk.Salt()
44salt_testing = new com.mirantis.mk.SaltModelTesting()
Petr Lomakine700ffd2017-08-01 10:53:15 -070045
Oleg Basovd4fa3862019-03-05 21:49:12 +010046def VALIDATE_PARAMS = readYaml(text: env.getProperty('VALIDATE_PARAMS')) ?: [:]
47if (! VALIDATE_PARAMS) {
48 throw new Exception("VALIDATE_PARAMS yaml is empty.")
49}
Oleg Basov382613a2019-04-02 19:01:15 +020050def TEST_IMAGE = env.getProperty('TEST_IMAGE') ?: 'xrally-openstack:1.4.0'
51def JOB_TIMEOUT = env.getProperty('JOB_TIMEOUT').toInteger() ?: 12
52def SLAVE_NODE = env.getProperty('SLAVE_NODE') ?: 'docker'
53def rally = VALIDATE_PARAMS.get('rally') ?: [:]
54def scenariosRepo = rally.get('RALLY_CONFIG_REPO') ?: 'https://review.gerrithub.io/Mirantis/scale-scenarios'
55def scenariosBranch = rally.get('RALLY_CONFIG_BRANCH') ?: 'master'
56def pluginsRepo = rally.get('RALLY_PLUGINS_REPO') ?: 'https://github.com/Mirantis/rally-plugins'
57def pluginsBranch = rally.get('RALLY_PLUGINS_BRANCH') ?: 'master'
58def tags = rally.get('RALLY_TAGS') ?: []
Oleg Basovd4fa3862019-03-05 21:49:12 +010059
Oleg Basov382613a2019-04-02 19:01:15 +020060// contrainer working dir vars
61def rallyWorkdir = '/home/rally'
62def rallyPluginsDir = "${rallyWorkdir}/rally-plugins"
63def rallyScenariosDir = "${rallyWorkdir}/rally-scenarios"
64def rallyResultsDir = "${rallyWorkdir}/test_results"
65def rallySecrets = "${rallyWorkdir}/secrets"
Oleg Basov3d93f552019-03-27 01:01:20 +010066
Oleg Basov382613a2019-04-02 19:01:15 +020067// env vars
68def env_vars = []
69def platform = [
70 type: 'unknown',
71 stacklight: [enabled: false, grafanaPass: ''],
72]
73def cmp_count
74
75// test results vars
76def testResult
77def tasksParallel = [:]
78def parallelResults = [:]
79def configRun = [:]
80
81timeout(time: JOB_TIMEOUT, unit: 'HOURS') {
82 node (SLAVE_NODE) {
83
84 // local dir vars
85 def workDir = "${env.WORKSPACE}/rally"
86 def pluginsDir = "${workDir}/rally-plugins"
87 def scenariosDir = "${workDir}/rally-scenarios"
88 def secrets = "${workDir}/secrets"
89 def artifacts = "${workDir}/validation_artifacts"
90
91 stage('Configure env') {
92
93 def master = salt.connection(SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
94
95 // create local directories
96 sh "rm -rf ${workDir} || true"
97 sh "mkdir -p ${artifacts} ${secrets}"
98 writeFile file: "${workDir}/entrypoint.sh", text: '''#!/bin/bash
99set -xe
100exec "$@"
101'''
102 sh "chmod 755 ${workDir}/entrypoint.sh"
103
104 // clone repo with Rally plugins and checkout refs/branch
105 checkout([
106 $class : 'GitSCM',
107 branches : [[name: 'FETCH_HEAD']],
108 extensions : [[$class: 'RelativeTargetDirectory', relativeTargetDir: pluginsDir]],
109 userRemoteConfigs: [[url: pluginsRepo, refspec: pluginsBranch]],
110 ])
111
112 // clone scenarios repo and switch branch / fetch refspecs
113 checkout([
114 $class : 'GitSCM',
115 branches : [[name: 'FETCH_HEAD']],
116 extensions : [[$class: 'RelativeTargetDirectory', relativeTargetDir: scenariosDir]],
117 userRemoteConfigs: [[url: scenariosRepo, refspec: scenariosBranch]],
118 ])
119
120 // get number of computes in the cluster
121 platform['cluster_name'] = salt.getPillar(
122 master, 'I@salt:master', '_param:cluster_name'
123 )['return'][0].values()[0]
124 def rcs_str_node = salt.getPillar(
125 master, 'I@salt:master', 'reclass:storage:node'
126 )['return'][0].values()[0]
127
128 // set up Openstack env variables
129 if (rally.get('K8S_RALLY').toBoolean() == false) {
130
131 platform['type'] = 'openstack'
132 platform['cmp_count'] = rcs_str_node.openstack_compute_rack01['repeat']['count']
133 def rally_variables = [
134 "floating_network=${rally.FLOATING_NETWORK}",
135 "rally_image=${rally.RALLY_IMAGE}",
136 "rally_flavor=${rally.RALLY_FLAVOR}",
137 "availability_zone=${rally.AVAILABILITY_ZONE}",
138 ]
139
140 env_vars = validate._get_keystone_creds_v3(master)
141 if (!env_vars) {
142 env_vars = validate._get_keystone_creds_v2(master)
143 }
144 env_vars = env_vars + rally_variables
145
146 } else {
147 // set up Kubernetes env variables get required secrets
148 platform['type'] = 'k8s'
149 platform['cmp_count'] = rcs_str_node.kubernetes_compute_rack01['repeat']['count']
150
151 def kubernetes = salt.getPillar(
152 master, 'I@kubernetes:master and *01*', 'kubernetes:master'
153 )['return'][0].values()[0]
154
155 env_vars = [
156 "KUBERNETES_HOST=http://${kubernetes.apiserver.vip_address}" +
157 ":${kubernetes.apiserver.insecure_port}",
158 "KUBERNETES_CERT_AUTH=${rallySecrets}/k8s-ca.crt",
159 "KUBERNETES_CLIENT_KEY=${rallySecrets}/k8s-client.key",
160 "KUBERNETES_CLIENT_CERT=${rallySecrets}/k8s-client.crt",
161 ]
162
163 // get K8S certificates to manage cluster
164 def k8s_ca = salt.getFileContent(
165 master, 'I@kubernetes:master and *01*', '/etc/kubernetes/ssl/ca-kubernetes.crt'
166 )
167 def k8s_client_key = salt.getFileContent(
168 master, 'I@kubernetes:master and *01*', '/etc/kubernetes/ssl/kubelet-client.key'
169 )
170 def k8s_client_crt = salt.getFileContent(
171 master, 'I@kubernetes:master and *01*', '/etc/kubernetes/ssl/kubelet-client.crt'
172 )
173 writeFile file: "${secrets}/k8s-ca.crt", text: k8s_ca
174 writeFile file: "${secrets}/k8s-client.key", text: k8s_client_key
175 writeFile file: "${secrets}/k8s-client.crt", text: k8s_client_crt
176
Tetiana Korchakefa4f782017-08-25 10:22:29 -0700177 }
Petr Lomakine700ffd2017-08-01 10:53:15 -0700178
Oleg Basov382613a2019-04-02 19:01:15 +0200179 // get Stacklight data
180 if (rally.STACKLIGHT_RALLY.toBoolean() == true) {
181 platform['stacklight']['enabled'] = true
182
183 def grafana = salt.getPillar(
184 master, 'I@grafana:client', 'grafana:client:server'
185 )['return'][0].values()[0]
186
187 platform['stacklight']['grafanaPass'] = grafana['password']
Petr Lomakine700ffd2017-08-01 10:53:15 -0700188 }
Petr Lomakine700ffd2017-08-01 10:53:15 -0700189
Oleg Basov382613a2019-04-02 19:01:15 +0200190 if (! rally.PARALLEL_PERFORMANCE.toBoolean()) {
Dmitrii Kabanov6b9343e2017-08-30 15:30:21 -0700191
Oleg Basov382613a2019-04-02 19:01:15 +0200192 // Define map with docker commands
193 def commands = validate.runRallyTests(
194 platform, rally.RALLY_SCENARIOS,
Oleg Basov3d93f552019-03-27 01:01:20 +0100195 rally.RALLY_SL_SCENARIOS, rally.RALLY_TASK_ARGS_FILE,
196 rally.RALLY_DB_CONN_STRING, tags,
Oleg Basov382613a2019-04-02 19:01:15 +0200197 rally.RALLY_TRENDS.toBoolean(), rally.SKIP_LIST
Oleg Basov3d93f552019-03-27 01:01:20 +0100198 )
Oleg Basov382613a2019-04-02 19:01:15 +0200199 def commands_list = commands.collectEntries{ [ (it.key) : { sh("${it.value}") } ] }
Dmitrii Kabanova67e5a52017-08-14 16:31:11 -0700200
Oleg Basov382613a2019-04-02 19:01:15 +0200201 configRun = [
202 'image': TEST_IMAGE,
203 'baseRepoPreConfig': false,
204 'dockerMaxCpus': 2,
205 'dockerHostname': 'localhost',
206 'dockerExtraOpts': [
207 "--network=host",
208 "--entrypoint=/entrypoint.sh",
209 "-w ${rallyWorkdir}",
210 "-v ${workDir}/entrypoint.sh:/entrypoint.sh",
211 "-v ${pluginsDir}/:${rallyPluginsDir}",
212 "-v ${scenariosDir}/:${rallyScenariosDir}",
213 "-v ${artifacts}/:${rallyResultsDir}",
214 "-v ${secrets}/:${rallySecrets}",
215 ],
216 'envOpts' : env_vars,
217 'runCommands' : commands_list,
218 ]
219 common.infoMsg('Docker config:')
220 println configRun
221 common.infoMsg('Docker commands list:')
222 println commands
Oleg Basov3d93f552019-03-27 01:01:20 +0100223
Oleg Basov382613a2019-04-02 19:01:15 +0200224 } else {
225
226 // Perform parallel testing of the components with Rally
227 def components = [
228 Common: [],
229 Stacklight: [],
230 ]
231
232 // get list of directories inside scenarios path
233 def scenPath = "${scenariosDir}/${rally.RALLY_SCENARIOS}"
234 def mainComponents = sh(
235 script: "find ${scenPath} -maxdepth 1 -mindepth 1 -type d -exec basename {} \\;",
236 returnStdout: true,
237 ).trim()
238 if (! mainComponents) {
239 error(
240 "No directories found inside RALLY_SCENARIOS ${rally.RALLY_SCENARIOS}\n" +
241 "Either set PARALLEL_PERFORMANCE=false or populate ${rally.RALLY_SCENARIOS} " +
242 "with component directories which include corresponding scenarios"
243 )
244 }
245 components['Common'].addAll(mainComponents.split('\n'))
246 common.infoMsg( "Adding for parallel execution sub dirs found in " +
247 "RALLY_SCENARIOS (${rally.RALLY_SCENARIOS}):"
248 )
249 print mainComponents
250
251 if (rally.STACKLIGHT_RALLY.toBoolean() == true) {
252 def slScenPath = "${scenariosDir}/${rally.RALLY_SL_SCENARIOS}"
253 def slComponents = sh(
254 script: "find ${slScenPath} -maxdepth 1 -mindepth 1 -type d -exec basename {} \\;",
255 returnStdout: true,
256 ).trim()
257 if (! slComponents) {
258 error(
259 "No directories found inside RALLY_SCENARIOS ${rally.RALLY_SL_SCENARIOS}\n" +
260 "Either set PARALLEL_PERFORMANCE=false or populate ${rally.RALLY_SL_SCENARIOS} " +
261 "with component directories which include corresponding scenarios"
262 )
263 }
264 components['Stacklight'].addAll(slComponents.split('\n'))
265 common.infoMsg( "Adding for parallel execution sub dirs found in " +
266 "RALLY_SL_SCENARIOS (${rally.RALLY_SL_SCENARIOS}):"
267 )
268 print slComponents
269 }
270
271 // build up a map with tasks for parallel execution
272 def allComponents = components.values().flatten()
273 for (int i=0; i < allComponents.size(); i++) {
274 // randomize run so we don't bump each other at the startup
275 // also we need to let first thread create rally deployment
276 // so all the rest rally threads can use it after
277 def sleepSeconds = 15 * i
278
279 def task = allComponents[i]
280 def task_name = 'rally_' + task
281 def curComponent = components.find { task in it.value }.key
282 // inherit platform common data
283 def curPlatform = platform
284
285 // setup scenarios and stacklight switch per component
286 def commonScens = "${rally.RALLY_SCENARIOS}/${task}"
287 def stacklightScens = "${rally.RALLY_SL_SCENARIOS}/${task}"
288
289 switch (curComponent) {
290 case 'Common':
291 stacklightScens = ''
292 curPlatform['stacklight']['enabled'] = false
293 break
294 case 'Stacklight':
295 commonScens = ''
296 curPlatform['stacklight']['enabled'] = true
297 break
298 }
299
300 def curCommands = validate.runRallyTests(
301 curPlatform, commonScens,
302 stacklightScens, rally.RALLY_TASK_ARGS_FILE,
303 rally.RALLY_DB_CONN_STRING, tags,
304 rally.RALLY_TRENDS.toBoolean(), rally.SKIP_LIST
305 )
306
307 // copy required files for the current task
308 def taskWorkDir = "${env.WORKSPACE}/rally_" + task
309 def taskPluginsDir = "${taskWorkDir}/rally-plugins"
310 def taskScenariosDir = "${taskWorkDir}/rally-scenarios"
311 def taskArtifacts = "${taskWorkDir}/validation_artifacts"
312 def taskSecrets = "${taskWorkDir}/secrets"
313 sh "rm -rf ${taskWorkDir} || true"
314 sh "cp -ra ${workDir} ${taskWorkDir}"
315
316 def curCommandsList = curCommands.collectEntries{ [ (it.key) : { sh("${it.value}") } ] }
317 def curConfigRun = [
318 'image': TEST_IMAGE,
319 'baseRepoPreConfig': false,
320 'dockerMaxCpus': 2,
321 'dockerHostname': 'localhost',
322 'dockerExtraOpts': [
323 "--network=host",
324 "--entrypoint=/entrypoint.sh",
325 "-w ${rallyWorkdir}",
326 "-v ${taskWorkDir}/entrypoint.sh:/entrypoint.sh",
327 "-v ${taskPluginsDir}/:${rallyPluginsDir}",
328 "-v ${taskScenariosDir}/:${rallyScenariosDir}",
329 "-v ${taskArtifacts}/:${rallyResultsDir}",
330 "-v ${taskSecrets}/:${rallySecrets}",
331 ],
332 'envOpts' : env_vars,
333 'runCommands' : curCommandsList,
334 ]
335
336 tasksParallel['rally_' + task] = {
337 sleep sleepSeconds
338 common.infoMsg("Docker config for task $task")
339 println curConfigRun
340 common.infoMsg("Docker commands list for task $task")
341 println curCommands
342 parallelResults[task_name] = salt_testing.setupDockerAndTest(curConfigRun)
343 }
344 }
345 }
346 }
347
348 stage('Run Rally tests') {
349
350 def dockerStatuses = [:]
351
352 // start tests in Docker
353 if (! rally.PARALLEL_PERFORMANCE.toBoolean()) {
354 testResult = salt_testing.setupDockerAndTest(configRun)
355 dockerStatuses['rally'] = (testResult) ? 'OK' : 'FAILED'
356 } else {
357 common.infoMsg('Jobs to run in threads: ' + tasksParallel.keySet().join(' '))
358 parallel tasksParallel
359 parallelResults.each { task ->
360 dockerStatuses[task.key] = (task.value) ? 'OK' : 'FAILED'
361 }
362 }
363 // safely archiving all possible results
364 dockerStatuses.each { task ->
365 print "Collecting results for ${task.key} (docker status = '${task.value}')"
366 try {
367 archiveArtifacts artifacts: "${task.key}/validation_artifacts/*"
368 } catch (Throwable e) {
369 print 'failed to get artifacts'
370 }
371 }
372 // setting final job status
373 def failed = dockerStatuses.findAll { it.value == 'FAILED' }
374 if (failed.size() == dockerStatuses.size()) {
375 currentBuild.result = 'FAILURE'
376 } else if (dockerStatuses.find { it.value != 'OK' }) {
377 currentBuild.result = 'UNSTABLE'
378 }
379 }
380
381 stage('Clean env') {
382 // remove secrets
383 sh 'find ./ -type d -name secrets -exec rm -rf \\\"{}\\\" \\; || true'
Tetiana Korchakefa4f782017-08-25 10:22:29 -0700384 }
Petr Lomakine700ffd2017-08-01 10:53:15 -0700385 }
386}