blob: 8aa569063d128b512dac9f446d6d35cc6192b59e [file] [log] [blame]
Sergey Kolekonovba203982016-12-21 18:32:17 +04001package com.mirantis.mk
2
3/**
4 *
5 * Openstack functions
6 *
7 */
8
9/**
Tomáš Kukrálc3964e52017-02-22 14:07:37 +010010 * Convert maps
11 *
12 */
13
14@NonCPS def entries(m) {
15 return m.collect {k, v -> [k, v]}
16}
17
18/**
Sergey Kolekonovba203982016-12-21 18:32:17 +040019 * Install OpenStack service clients in isolated environment
20 *
21 * @param path Path where virtualenv is created
22 * @param version Version of the OpenStack clients
23 */
24
Tomáš Kukrálbee0b992017-08-10 16:50:40 +020025def setupOpenstackVirtualenv(path, version = 'latest') {
iberezovskiyd4240b52017-02-20 17:18:28 +040026 def python = new com.mirantis.mk.Python()
Vasyl Saienko030fc182017-07-12 14:54:42 +030027 python.setupDocutilsVirtualenv(path)
Sergey Kolekonovba203982016-12-21 18:32:17 +040028
29 def openstack_kilo_packages = [
Jakub Josef268bc842017-10-10 14:36:17 +020030 //XXX: hack to fix https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1635463
31 'cliff==2.8',
Sergey Kolekonovba203982016-12-21 18:32:17 +040032 'python-cinderclient>=1.3.1,<1.4.0',
33 'python-glanceclient>=0.19.0,<0.20.0',
34 'python-heatclient>=0.6.0,<0.7.0',
35 'python-keystoneclient>=1.6.0,<1.7.0',
36 'python-neutronclient>=2.2.6,<2.3.0',
37 'python-novaclient>=2.19.0,<2.20.0',
38 'python-swiftclient>=2.5.0,<2.6.0',
Jakub Josefbd927322017-05-30 13:20:27 +000039 'python-openstackclient>=1.7.0,<1.8.0',
Sergey Kolekonovba203982016-12-21 18:32:17 +040040 'oslo.config>=2.2.0,<2.3.0',
41 'oslo.i18n>=2.3.0,<2.4.0',
42 'oslo.serialization>=1.8.0,<1.9.0',
43 'oslo.utils>=1.4.0,<1.5.0',
Jakub Josef60280212017-08-10 19:01:19 +020044 'docutils'
Sergey Kolekonovba203982016-12-21 18:32:17 +040045 ]
46
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020047 def openstack_latest_packages = [
Jakub Josef268bc842017-10-10 14:36:17 +020048 //XXX: hack to fix https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1635463
49 'cliff==2.8',
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020050 'python-openstackclient',
51 'python-heatclient',
Jakub Josef60280212017-08-10 19:01:19 +020052 'docutils'
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020053 ]
Sergey Kolekonovba203982016-12-21 18:32:17 +040054
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020055 if (version == 'kilo') {
Sergey Kolekonovba203982016-12-21 18:32:17 +040056 requirements = openstack_kilo_packages
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020057 } else if (version == 'liberty') {
Sergey Kolekonovba203982016-12-21 18:32:17 +040058 requirements = openstack_kilo_packages
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020059 } else if (version == 'mitaka') {
Sergey Kolekonovba203982016-12-21 18:32:17 +040060 requirements = openstack_kilo_packages
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020061 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +040062 requirements = openstack_latest_packages
63 }
Tomáš Kukrálbee0b992017-08-10 16:50:40 +020064 python.setupVirtualenv(path, 'python2', requirements, null, true)
Sergey Kolekonovba203982016-12-21 18:32:17 +040065}
66
67/**
68 * create connection to OpenStack API endpoint
69 *
70 * @param url OpenStack API endpoint address
71 * @param credentialsId Credentials to the OpenStack API
72 * @param project OpenStack project to connect to
73 */
kairat_kushaev0a26bf72017-05-18 13:20:09 +040074def createOpenstackEnv(url, credentialsId, project, project_domain="default",
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020075 project_id="", user_domain="default", api_ver="2", cacert="/etc/ssl/certs/ca-certificates.crt") {
iberezovskiyd4240b52017-02-20 17:18:28 +040076 def common = new com.mirantis.mk.Common()
Ales Komarek0e558ee2016-12-23 13:02:55 +010077 rcFile = "${env.WORKSPACE}/keystonerc"
Sergey Kolekonovba203982016-12-21 18:32:17 +040078 creds = common.getPasswordCredentials(credentialsId)
Alexander Tivelkovf89a1882017-01-11 13:29:35 +030079 rc = """set +x
80export OS_USERNAME=${creds.username}
Ales Komarek0e558ee2016-12-23 13:02:55 +010081export OS_PASSWORD=${creds.password.toString()}
82export OS_TENANT_NAME=${project}
83export OS_AUTH_URL=${url}
84export OS_AUTH_STRATEGY=keystone
kairat_kushaev0a26bf72017-05-18 13:20:09 +040085export OS_PROJECT_NAME=${project}
Jakub Josefbd927322017-05-30 13:20:27 +000086export OS_PROJECT_ID=${project_id}
kairat_kushaev0a26bf72017-05-18 13:20:09 +040087export OS_PROJECT_DOMAIN_ID=${project_domain}
Jakub Josefbd927322017-05-30 13:20:27 +000088export OS_USER_DOMAIN_NAME=${user_domain}
Kirill Mashchenko234708f2017-07-20 17:00:01 +030089export OS_IDENTITY_API_VERSION=${api_ver}
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020090export OS_CACERT=${cacert}
Alexander Tivelkovf89a1882017-01-11 13:29:35 +030091set -x
Ales Komarek0e558ee2016-12-23 13:02:55 +010092"""
93 writeFile file: rcFile, text: rc
94 return rcFile
Sergey Kolekonovba203982016-12-21 18:32:17 +040095}
96
97/**
98 * Run command with OpenStack env params and optional python env
99 *
100 * @param cmd Command to be executed
101 * @param env Environmental parameters with endpoint credentials
102 * @param path Optional path to virtualenv with specific clients
103 */
104def runOpenstackCommand(cmd, venv, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400105 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400106 openstackCmd = ". ${venv}; ${cmd}"
107 if (path) {
108 output = python.runVirtualenvCommand(path, openstackCmd)
109 }
110 else {
111 echo("[Command]: ${openstackCmd}")
112 output = sh (
113 script: openstackCmd,
114 returnStdout: true
115 ).trim()
116 }
117 return output
118}
119
120/**
121 * Get OpenStack Keystone token for current credentials
122 *
123 * @param env Connection parameters for OpenStack API endpoint
124 * @param path Optional path to the custom virtualenv
125 */
126def getKeystoneToken(client, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400127 def python = new com.mirantis.mk.Python()
Jakub Josefbd927322017-05-30 13:20:27 +0000128 cmd = "openstack token issue"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400129 outputTable = runOpenstackCommand(cmd, client, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100130 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400131 return output
132}
133
134/**
Ales Komarek51b7b152017-06-27 11:14:50 +0200135 * Create OpenStack environment file
Sergey Kolekonovba203982016-12-21 18:32:17 +0400136 *
137 * @param env Connection parameters for OpenStack API endpoint
138 * @param path Optional path to the custom virtualenv
139 */
140def createHeatEnv(file, environment = [], original_file = null) {
141 if (original_file) {
142 envString = readFile file: original_file
Tomáš Kukrál03029442017-02-21 17:14:29 +0100143 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400144 envString = "parameters:\n"
145 }
Tomáš Kukrál03029442017-02-21 17:14:29 +0100146
Tomáš Kukrálc3964e52017-02-22 14:07:37 +0100147 p = entries(environment)
Tomáš Kukrálb1fe9642017-02-22 11:21:17 +0100148 for (int i = 0; i < p.size(); i++) {
149 envString = "${envString} ${p.get(i)[0]}: ${p.get(i)[1]}\n"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400150 }
Tomáš Kukrál03029442017-02-21 17:14:29 +0100151
Tomáš Kukrále19ddea2017-02-21 11:09:40 +0100152 echo("writing to env file:\n${envString}")
Sergey Kolekonovba203982016-12-21 18:32:17 +0400153 writeFile file: file, text: envString
154}
155
156/**
157 * Create new OpenStack Heat stack
158 *
159 * @param env Connection parameters for OpenStack API endpoint
160 * @param template HOT template for the new Heat stack
161 * @param environment Environmentale parameters of the new Heat stack
162 * @param name Name of the new Heat stack
163 * @param path Optional path to the custom virtualenv
164 */
Tomáš Kukrála1152742017-08-22 16:21:50 +0200165def createHeatStack(client, name, template, params = [], environment = null, path = null, action="create") {
iberezovskiyd4240b52017-02-20 17:18:28 +0400166 def python = new com.mirantis.mk.Python()
Jakub Josef0a898762017-08-11 16:27:44 +0200167 def templateFile = "${env.WORKSPACE}/template/template/${template}.hot"
168 def envFile
169 def envSource
Sergey Kolekonovba203982016-12-21 18:32:17 +0400170 if (environment) {
Tomáš Kukrála1152742017-08-22 16:21:50 +0200171 envFile = "${env.WORKSPACE}/template/env/${name}.env"
172 if (environment.contains("/")) {
173 //init() returns all elements but the last in a collection.
174 def envPath = environment.tokenize("/").init().join("/")
175 if (envPath) {
176 envFile = "${env.WORKSPACE}/template/env/${envPath}/${name}.env"
177 }
Ales Komarek51b7b152017-06-27 11:14:50 +0200178 }
Tomáš Kukrála1152742017-08-22 16:21:50 +0200179 envSource = "${env.WORKSPACE}/template/env/${environment}.env"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400180 createHeatEnv(envFile, params, envSource)
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200181 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400182 envFile = "${env.WORKSPACE}/template/${name}.env"
183 createHeatEnv(envFile, params)
184 }
Tomáš Kukrála1152742017-08-22 16:21:50 +0200185
Mykyta Karpincf44f812017-08-28 14:45:21 +0300186 def cmd
187 def waitState
188
Tomáš Kukrála1152742017-08-22 16:21:50 +0200189 if (action == "create") {
Mykyta Karpincf44f812017-08-28 14:45:21 +0300190 cmd = "heat stack-create -f ${templateFile} -e ${envFile} ${name}"
191 waitState = "CREATE_COMPLETE"
Tomáš Kukrála1152742017-08-22 16:21:50 +0200192 } else {
Mykyta Karpincf44f812017-08-28 14:45:21 +0300193 cmd = "heat stack-update -f ${templateFile} -e ${envFile} ${name}"
194 waitState = "UPDATE_COMPLETE"
Tomáš Kukrála1152742017-08-22 16:21:50 +0200195 }
196
Sergey Kolekonovba203982016-12-21 18:32:17 +0400197 dir("${env.WORKSPACE}/template/template") {
198 outputTable = runOpenstackCommand(cmd, client, path)
199 }
Tomáš Kukrála1152742017-08-22 16:21:50 +0200200
Ales Komareke11e8792016-12-28 09:42:25 +0100201 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400202
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200203 def heatStatusCheckerCount = 1
Jakub Josefe0337272017-09-12 14:15:27 +0200204 while (heatStatusCheckerCount <= 250) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400205 status = getHeatStackStatus(client, name, path)
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200206 echo("[Heat Stack] Status: ${status}, Check: ${heatStatusCheckerCount}")
Ales Komarekdce8b472016-12-30 16:56:07 +0100207
Tomáš Kukrála1152742017-08-22 16:21:50 +0200208 if (status.contains('CREATE_FAILED')) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400209 info = getHeatStackInfo(client, name, path)
210 throw new Exception(info.stack_status_reason)
Tomáš Kukrála1152742017-08-22 16:21:50 +0200211
212 } else if (status.contains(waitState)) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400213 info = getHeatStackInfo(client, name, path)
214 echo(info.stack_status_reason)
215 break
216 }
Tomáš Kukrála1152742017-08-22 16:21:50 +0200217
Jakub Josefe0337272017-09-12 14:15:27 +0200218 sleep(30)
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200219 heatStatusCheckerCount++
Sergey Kolekonovba203982016-12-21 18:32:17 +0400220 }
221 echo("[Heat Stack] Status: ${status}")
222}
223
224/**
Jakub Josefdb4baf22017-05-10 15:16:09 +0200225 * Returns list of stacks for stack name filter
226 *
227 * @param client Connection parameters for OpenStack API endpoint
228 * @param filter Stack name filter
229 * @param path Optional path to the custom virtualenv
230 */
231def getStacksForNameContains(client, filter, path = null){
Jakub Josef6465fca2017-05-10 16:09:20 +0200232 cmd = 'heat stack-list | awk \'NR>3 {print $4}\' | sed \'$ d\' | grep ' + filter + '|| true'
Jakub Josefdb4baf22017-05-10 15:16:09 +0200233 return runOpenstackCommand(cmd, client, path).trim().tokenize("\n")
234}
235
236
237/**
Jakub Josef5e238a22017-04-19 16:35:15 +0200238 * Get list of stack names with given stack status
239 *
Jakub Josefdb4baf22017-05-10 15:16:09 +0200240 * @param client Connection parameters for OpenStack API endpoint
Jakub Josef5e238a22017-04-19 16:35:15 +0200241 * @param status Stack status
242 * @param path Optional path to the custom virtualenv
243 */
244 def getStacksWithStatus(client, status, path = null) {
245 cmd = 'heat stack-list -f stack_status='+status+' | awk \'NR>3 {print $4}\' | sed \'$ d\''
246 return runOpenstackCommand(cmd, client, path).trim().tokenize("\n")
247 }
248
249/**
Sergey Kolekonovba203982016-12-21 18:32:17 +0400250 * Get life cycle status for existing OpenStack Heat stack
251 *
252 * @param env Connection parameters for OpenStack API endpoint
253 * @param name Name of the managed Heat stack instance
254 * @param path Optional path to the custom virtualenv
255 */
256def getHeatStackStatus(client, name, path = null) {
257 cmd = 'heat stack-list | awk -v stack='+name+' \'{if ($4==stack) print $6}\''
258 return runOpenstackCommand(cmd, client, path)
259}
260
261/**
262 * Get info about existing OpenStack Heat stack
263 *
264 * @param env Connection parameters for OpenStack API endpoint
265 * @param name Name of the managed Heat stack instance
266 * @param path Optional path to the custom virtualenv
267 */
268def getHeatStackInfo(env, name, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400269 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400270 cmd = "heat stack-show ${name}"
271 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100272 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400273 return output
274}
275
276/**
277 * Get existing OpenStack Heat stack output parameter
278 *
279 * @param env Connection parameters for OpenStack API endpoint
280 * @param name Name of the managed Heat stack
281 * @param parameter Name of the output parameter
282 * @param path Optional path to the custom virtualenv
283 */
284def getHeatStackOutputParam(env, name, outputParam, path = null) {
Vasyl Saienkoea4b2812017-07-10 10:36:03 +0000285 cmd = "heat output-show ${name} ${outputParam}"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400286 output = runOpenstackCommand(cmd, env, path)
Ales Komarekeedc2222017-01-03 10:10:03 +0100287 echo("${cmd}: ${output}")
Vasyl Saienko2a1c2de2017-07-11 11:41:53 +0300288 // NOTE(vsaienko) heatclient 1.5.1 returns output in "", while later
289 // versions returns string without "".
290 // TODO Use openstack 'stack output show' when all jobs using at least Mitaka heatclient
291 return "${output}".replaceAll('"', '')
Sergey Kolekonovba203982016-12-21 18:32:17 +0400292}
293
294/**
295 * List all resources from existing OpenStack Heat stack
296 *
297 * @param env Connection parameters for OpenStack API endpoint
298 * @param name Name of the managed Heat stack instance
299 * @param path Optional path to the custom virtualenv
300 */
301def getHeatStackResources(env, name, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400302 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400303 cmd = "heat resource-list ${name}"
304 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100305 output = python.parseTextTable(outputTable, 'list', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400306 return output
307}
308
309/**
310 * Get info about resource from existing OpenStack Heat stack
311 *
312 * @param env Connection parameters for OpenStack API endpoint
313 * @param name Name of the managed Heat stack instance
314 * @param path Optional path to the custom virtualenv
315 */
316def getHeatStackResourceInfo(env, name, resource, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400317 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400318 cmd = "heat resource-show ${name} ${resource}"
319 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100320 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400321 return output
322}
323
324/**
325 * Update existing OpenStack Heat stack
326 *
327 * @param env Connection parameters for OpenStack API endpoint
328 * @param name Name of the managed Heat stack instance
329 * @param path Optional path to the custom virtualenv
330 */
331def updateHeatStack(env, name, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400332 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400333 cmd = "heat stack-update ${name}"
334 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100335 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400336 return output
337}
338
339/**
340 * Delete existing OpenStack Heat stack
341 *
342 * @param env Connection parameters for OpenStack API endpoint
343 * @param name Name of the managed Heat stack instance
344 * @param path Optional path to the custom virtualenv
345 */
346def deleteHeatStack(env, name, path = null) {
347 cmd = "heat stack-delete ${name}"
348 outputTable = runOpenstackCommand(cmd, env, path)
349}
350
351/**
352 * Return list of servers from OpenStack Heat stack
353 *
354 * @param env Connection parameters for OpenStack API endpoint
355 * @param name Name of the managed Heat stack instance
356 * @param path Optional path to the custom virtualenv
357 */
358def getHeatStackServers(env, name, path = null) {
359 resources = heatGetStackResources(env, name, path)
360 servers = []
361 for (resource in resources) {
362 if (resource.resource_type == 'OS::Nova::Server') {
363 resourceName = resource.resource_name
364 server = heatGetStackResourceInfo(env, name, resourceName, path)
365 servers.add(server.attributes.name)
366 }
367 }
368 echo("[Stack ${name}] Servers: ${servers}")
369 return servers
370}