blob: 8ccc80032fa1d74b1aba38ffe81961a44acf51ca [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 = [
30 'python-cinderclient>=1.3.1,<1.4.0',
31 'python-glanceclient>=0.19.0,<0.20.0',
32 'python-heatclient>=0.6.0,<0.7.0',
33 'python-keystoneclient>=1.6.0,<1.7.0',
34 'python-neutronclient>=2.2.6,<2.3.0',
35 'python-novaclient>=2.19.0,<2.20.0',
36 'python-swiftclient>=2.5.0,<2.6.0',
Jakub Josefbd927322017-05-30 13:20:27 +000037 'python-openstackclient>=1.7.0,<1.8.0',
Sergey Kolekonovba203982016-12-21 18:32:17 +040038 'oslo.config>=2.2.0,<2.3.0',
39 'oslo.i18n>=2.3.0,<2.4.0',
40 'oslo.serialization>=1.8.0,<1.9.0',
41 'oslo.utils>=1.4.0,<1.5.0',
Jakub Josef60280212017-08-10 19:01:19 +020042 'docutils'
Sergey Kolekonovba203982016-12-21 18:32:17 +040043 ]
44
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020045 def openstack_latest_packages = [
46 'python-openstackclient',
47 'python-heatclient',
Jakub Josef60280212017-08-10 19:01:19 +020048 'docutils'
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020049 ]
Sergey Kolekonovba203982016-12-21 18:32:17 +040050
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020051 if (version == 'kilo') {
Sergey Kolekonovba203982016-12-21 18:32:17 +040052 requirements = openstack_kilo_packages
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020053 } else if (version == 'liberty') {
Sergey Kolekonovba203982016-12-21 18:32:17 +040054 requirements = openstack_kilo_packages
Tomáš Kukrálc6a94c62017-06-19 14:44:24 +020055 } else if (version == 'mitaka') {
Sergey Kolekonovba203982016-12-21 18:32:17 +040056 requirements = openstack_kilo_packages
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020057 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +040058 requirements = openstack_latest_packages
59 }
Tomáš Kukrálbee0b992017-08-10 16:50:40 +020060 python.setupVirtualenv(path, 'python2', requirements, null, true)
Sergey Kolekonovba203982016-12-21 18:32:17 +040061}
62
63/**
64 * create connection to OpenStack API endpoint
65 *
66 * @param url OpenStack API endpoint address
67 * @param credentialsId Credentials to the OpenStack API
68 * @param project OpenStack project to connect to
69 */
kairat_kushaev0a26bf72017-05-18 13:20:09 +040070def createOpenstackEnv(url, credentialsId, project, project_domain="default",
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020071 project_id="", user_domain="default", api_ver="2", cacert="/etc/ssl/certs/ca-certificates.crt") {
iberezovskiyd4240b52017-02-20 17:18:28 +040072 def common = new com.mirantis.mk.Common()
Ales Komarek0e558ee2016-12-23 13:02:55 +010073 rcFile = "${env.WORKSPACE}/keystonerc"
Sergey Kolekonovba203982016-12-21 18:32:17 +040074 creds = common.getPasswordCredentials(credentialsId)
Alexander Tivelkovf89a1882017-01-11 13:29:35 +030075 rc = """set +x
76export OS_USERNAME=${creds.username}
Ales Komarek0e558ee2016-12-23 13:02:55 +010077export OS_PASSWORD=${creds.password.toString()}
78export OS_TENANT_NAME=${project}
79export OS_AUTH_URL=${url}
80export OS_AUTH_STRATEGY=keystone
kairat_kushaev0a26bf72017-05-18 13:20:09 +040081export OS_PROJECT_NAME=${project}
Jakub Josefbd927322017-05-30 13:20:27 +000082export OS_PROJECT_ID=${project_id}
kairat_kushaev0a26bf72017-05-18 13:20:09 +040083export OS_PROJECT_DOMAIN_ID=${project_domain}
Jakub Josefbd927322017-05-30 13:20:27 +000084export OS_USER_DOMAIN_NAME=${user_domain}
Kirill Mashchenko234708f2017-07-20 17:00:01 +030085export OS_IDENTITY_API_VERSION=${api_ver}
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020086export OS_CACERT=${cacert}
Alexander Tivelkovf89a1882017-01-11 13:29:35 +030087set -x
Ales Komarek0e558ee2016-12-23 13:02:55 +010088"""
89 writeFile file: rcFile, text: rc
90 return rcFile
Sergey Kolekonovba203982016-12-21 18:32:17 +040091}
92
93/**
94 * Run command with OpenStack env params and optional python env
95 *
96 * @param cmd Command to be executed
97 * @param env Environmental parameters with endpoint credentials
98 * @param path Optional path to virtualenv with specific clients
99 */
100def runOpenstackCommand(cmd, venv, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400101 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400102 openstackCmd = ". ${venv}; ${cmd}"
103 if (path) {
104 output = python.runVirtualenvCommand(path, openstackCmd)
105 }
106 else {
107 echo("[Command]: ${openstackCmd}")
108 output = sh (
109 script: openstackCmd,
110 returnStdout: true
111 ).trim()
112 }
113 return output
114}
115
116/**
117 * Get OpenStack Keystone token for current credentials
118 *
119 * @param env Connection parameters for OpenStack API endpoint
120 * @param path Optional path to the custom virtualenv
121 */
122def getKeystoneToken(client, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400123 def python = new com.mirantis.mk.Python()
Jakub Josefbd927322017-05-30 13:20:27 +0000124 cmd = "openstack token issue"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400125 outputTable = runOpenstackCommand(cmd, client, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100126 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400127 return output
128}
129
130/**
Ales Komarek51b7b152017-06-27 11:14:50 +0200131 * Create OpenStack environment file
Sergey Kolekonovba203982016-12-21 18:32:17 +0400132 *
133 * @param env Connection parameters for OpenStack API endpoint
134 * @param path Optional path to the custom virtualenv
135 */
136def createHeatEnv(file, environment = [], original_file = null) {
137 if (original_file) {
138 envString = readFile file: original_file
Tomáš Kukrál03029442017-02-21 17:14:29 +0100139 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400140 envString = "parameters:\n"
141 }
Tomáš Kukrál03029442017-02-21 17:14:29 +0100142
Tomáš Kukrálc3964e52017-02-22 14:07:37 +0100143 p = entries(environment)
Tomáš Kukrálb1fe9642017-02-22 11:21:17 +0100144 for (int i = 0; i < p.size(); i++) {
145 envString = "${envString} ${p.get(i)[0]}: ${p.get(i)[1]}\n"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400146 }
Tomáš Kukrál03029442017-02-21 17:14:29 +0100147
Tomáš Kukrále19ddea2017-02-21 11:09:40 +0100148 echo("writing to env file:\n${envString}")
Sergey Kolekonovba203982016-12-21 18:32:17 +0400149 writeFile file: file, text: envString
150}
151
152/**
153 * Create new OpenStack Heat stack
154 *
155 * @param env Connection parameters for OpenStack API endpoint
156 * @param template HOT template for the new Heat stack
157 * @param environment Environmentale parameters of the new Heat stack
158 * @param name Name of the new Heat stack
159 * @param path Optional path to the custom virtualenv
Ales Komarek51b7b152017-06-27 11:14:50 +0200160 * @param legacy_env Use old path format [env_name/template_name] to
161 * target env file.
Sergey Kolekonovba203982016-12-21 18:32:17 +0400162 */
Ales Komarek51b7b152017-06-27 11:14:50 +0200163def createHeatStack(client, name, template, params = [], environment = null, path = null, legacy_env = true) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400164 def python = new com.mirantis.mk.Python()
Jakub Josef0a898762017-08-11 16:27:44 +0200165 def templateFile = "${env.WORKSPACE}/template/template/${template}.hot"
166 def envFile
167 def envSource
Sergey Kolekonovba203982016-12-21 18:32:17 +0400168 if (environment) {
Ales Komarek51b7b152017-06-27 11:14:50 +0200169 if (legacy_env) {
170 envFile = "${env.WORKSPACE}/template/env/${template}/${name}.env"
171 envSource = "${env.WORKSPACE}/template/env/${template}/${environment}.env"
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200172 } else {
Jakub Josef0a898762017-08-11 16:27:44 +0200173 envFile = "${env.WORKSPACE}/template/env/${name}.env"
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200174 if(environment.contains("/")){
175 //init() returns all elements but the last in a collection.
Jakub Josef3192a252017-08-11 16:13:06 +0200176 def envPath = environment.tokenize("/").init().join("/")
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200177 if(envPath){
Jakub Josef0a898762017-08-11 16:27:44 +0200178 envFile = "${env.WORKSPACE}/template/env/${envPath}/${name}.env"
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200179 }
180 }
Ales Komarek51b7b152017-06-27 11:14:50 +0200181 envSource = "${env.WORKSPACE}/template/env/${environment}.env"
182 }
Sergey Kolekonovba203982016-12-21 18:32:17 +0400183 createHeatEnv(envFile, params, envSource)
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200184 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400185 envFile = "${env.WORKSPACE}/template/${name}.env"
186 createHeatEnv(envFile, params)
187 }
188 cmd = "heat stack-create -f ${templateFile} -e ${envFile} ${name}"
189 dir("${env.WORKSPACE}/template/template") {
190 outputTable = runOpenstackCommand(cmd, client, path)
191 }
Ales Komareke11e8792016-12-28 09:42:25 +0100192 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400193
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200194 def heatStatusCheckerCount = 1
195 while (heatStatusCheckerCount <= 500) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400196 status = getHeatStackStatus(client, name, path)
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200197 echo("[Heat Stack] Status: ${status}, Check: ${heatStatusCheckerCount}")
Ales Komarekdce8b472016-12-30 16:56:07 +0100198
199 if (status.indexOf('CREATE_FAILED') != -1) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400200 info = getHeatStackInfo(client, name, path)
201 throw new Exception(info.stack_status_reason)
202 }
Ales Komarekdce8b472016-12-30 16:56:07 +0100203 else if (status.indexOf('CREATE_COMPLETE') != -1) {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400204 info = getHeatStackInfo(client, name, path)
205 echo(info.stack_status_reason)
206 break
207 }
208 sh('sleep 5s')
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200209 heatStatusCheckerCount++
Sergey Kolekonovba203982016-12-21 18:32:17 +0400210 }
211 echo("[Heat Stack] Status: ${status}")
212}
213
214/**
Jakub Josefdb4baf22017-05-10 15:16:09 +0200215 * Returns list of stacks for stack name filter
216 *
217 * @param client Connection parameters for OpenStack API endpoint
218 * @param filter Stack name filter
219 * @param path Optional path to the custom virtualenv
220 */
221def getStacksForNameContains(client, filter, path = null){
Jakub Josef6465fca2017-05-10 16:09:20 +0200222 cmd = 'heat stack-list | awk \'NR>3 {print $4}\' | sed \'$ d\' | grep ' + filter + '|| true'
Jakub Josefdb4baf22017-05-10 15:16:09 +0200223 return runOpenstackCommand(cmd, client, path).trim().tokenize("\n")
224}
225
226
227/**
Jakub Josef5e238a22017-04-19 16:35:15 +0200228 * Get list of stack names with given stack status
229 *
Jakub Josefdb4baf22017-05-10 15:16:09 +0200230 * @param client Connection parameters for OpenStack API endpoint
Jakub Josef5e238a22017-04-19 16:35:15 +0200231 * @param status Stack status
232 * @param path Optional path to the custom virtualenv
233 */
234 def getStacksWithStatus(client, status, path = null) {
235 cmd = 'heat stack-list -f stack_status='+status+' | awk \'NR>3 {print $4}\' | sed \'$ d\''
236 return runOpenstackCommand(cmd, client, path).trim().tokenize("\n")
237 }
238
239/**
Sergey Kolekonovba203982016-12-21 18:32:17 +0400240 * Get life cycle status for existing OpenStack Heat stack
241 *
242 * @param env Connection parameters for OpenStack API endpoint
243 * @param name Name of the managed Heat stack instance
244 * @param path Optional path to the custom virtualenv
245 */
246def getHeatStackStatus(client, name, path = null) {
247 cmd = 'heat stack-list | awk -v stack='+name+' \'{if ($4==stack) print $6}\''
248 return runOpenstackCommand(cmd, client, path)
249}
250
251/**
252 * Get info about existing OpenStack Heat stack
253 *
254 * @param env Connection parameters for OpenStack API endpoint
255 * @param name Name of the managed Heat stack instance
256 * @param path Optional path to the custom virtualenv
257 */
258def getHeatStackInfo(env, name, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400259 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400260 cmd = "heat stack-show ${name}"
261 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100262 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400263 return output
264}
265
266/**
267 * Get existing OpenStack Heat stack output parameter
268 *
269 * @param env Connection parameters for OpenStack API endpoint
270 * @param name Name of the managed Heat stack
271 * @param parameter Name of the output parameter
272 * @param path Optional path to the custom virtualenv
273 */
274def getHeatStackOutputParam(env, name, outputParam, path = null) {
Vasyl Saienkoea4b2812017-07-10 10:36:03 +0000275 cmd = "heat output-show ${name} ${outputParam}"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400276 output = runOpenstackCommand(cmd, env, path)
Ales Komarekeedc2222017-01-03 10:10:03 +0100277 echo("${cmd}: ${output}")
Vasyl Saienko2a1c2de2017-07-11 11:41:53 +0300278 // NOTE(vsaienko) heatclient 1.5.1 returns output in "", while later
279 // versions returns string without "".
280 // TODO Use openstack 'stack output show' when all jobs using at least Mitaka heatclient
281 return "${output}".replaceAll('"', '')
Sergey Kolekonovba203982016-12-21 18:32:17 +0400282}
283
284/**
285 * List all resources from existing OpenStack Heat stack
286 *
287 * @param env Connection parameters for OpenStack API endpoint
288 * @param name Name of the managed Heat stack instance
289 * @param path Optional path to the custom virtualenv
290 */
291def getHeatStackResources(env, name, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400292 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400293 cmd = "heat resource-list ${name}"
294 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100295 output = python.parseTextTable(outputTable, 'list', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400296 return output
297}
298
299/**
300 * Get info about resource from existing OpenStack Heat stack
301 *
302 * @param env Connection parameters for OpenStack API endpoint
303 * @param name Name of the managed Heat stack instance
304 * @param path Optional path to the custom virtualenv
305 */
306def getHeatStackResourceInfo(env, name, resource, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400307 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400308 cmd = "heat resource-show ${name} ${resource}"
309 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100310 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400311 return output
312}
313
314/**
315 * Update existing OpenStack Heat stack
316 *
317 * @param env Connection parameters for OpenStack API endpoint
318 * @param name Name of the managed Heat stack instance
319 * @param path Optional path to the custom virtualenv
320 */
321def updateHeatStack(env, name, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400322 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400323 cmd = "heat stack-update ${name}"
324 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100325 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400326 return output
327}
328
329/**
330 * Delete existing OpenStack Heat stack
331 *
332 * @param env Connection parameters for OpenStack API endpoint
333 * @param name Name of the managed Heat stack instance
334 * @param path Optional path to the custom virtualenv
335 */
336def deleteHeatStack(env, name, path = null) {
337 cmd = "heat stack-delete ${name}"
338 outputTable = runOpenstackCommand(cmd, env, path)
339}
340
341/**
342 * Return list of servers from OpenStack Heat stack
343 *
344 * @param env Connection parameters for OpenStack API endpoint
345 * @param name Name of the managed Heat stack instance
346 * @param path Optional path to the custom virtualenv
347 */
348def getHeatStackServers(env, name, path = null) {
349 resources = heatGetStackResources(env, name, path)
350 servers = []
351 for (resource in resources) {
352 if (resource.resource_type == 'OS::Nova::Server') {
353 resourceName = resource.resource_name
354 server = heatGetStackResourceInfo(env, name, resourceName, path)
355 servers.add(server.attributes.name)
356 }
357 }
358 echo("[Stack ${name}] Servers: ${servers}")
359 return servers
360}