blob: 74a1ce9381474af39853045deed640ec5198a806 [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 *
Aleksey Zvyagintsevde345a92019-07-30 11:32:45 +000021 * @param path Path where virtualenv is created
22 * @param version Version of the OpenStack clients
Sergey Kolekonovba203982016-12-21 18:32:17 +040023 */
24
Vasyl Saienko9a2bd372020-01-13 10:00:04 +020025def setupOpenstackVirtualenv(path, version = 'latest', python="python2") {
26 def pythonLib = new com.mirantis.mk.Python()
27 pythonLib.setupDocutilsVirtualenv(path)
Sergey Kolekonovba203982016-12-21 18:32:17 +040028
Aleksey Zvyagintsevde345a92019-07-30 11:32:45 +000029 def openstack_kilo_packages = [
30 //XXX: hack to fix https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1635463
31 'cliff==2.8',
32 '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',
39 'python-openstackclient>=1.7.0,<1.8.0',
40 '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',
Denis Egorenko97ef0fb2020-01-13 14:22:49 +040044 'docutils==0.16'
Aleksey Zvyagintsevde345a92019-07-30 11:32:45 +000045 ]
Sergey Kolekonovba203982016-12-21 18:32:17 +040046
Aleksey Zvyagintsevde345a92019-07-30 11:32:45 +000047 def openstack_latest_packages = [
48 //XXX: hack to fix https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1635463
49 'cliff==2.8',
50 // NOTE(vsaienko): cmd2 is dependency for cliff, since we don't using upper-contstraints
51 // we have to pin cmd2 < 0.9.0 as later versions are not compatible with python2.
52 // the same for warlock package due: https://github.com/bcwaldon/warlock/commit/4241a7a9fbccfce7eb3298c2abdf00ca2dede64a
53 // TODO(vsaienko): use upper-constraints here, as in requirements we set only lowest library
54 // versions.
55 'cmd2<0.9.0;python_version=="2.7"',
56 'cmd2>=0.9.1;python_version=="3.4"',
57 'cmd2>=0.9.1;python_version=="3.5"',
58 'warlock<=1.3.1;python_version=="2.7"',
59 'warlock>1.3.1;python_version=="3.4"',
60 'warlock>1.3.1;python_version=="3.5"',
Denis Egorenko97ef0fb2020-01-13 14:22:49 +040061 // NOTE: pin client packages to current latest to prevent
62 // downloading packages which are not support Python 2.7
Vladimir Khlyunevdf7dd342022-11-24 23:29:47 +040063 'keystoneauth1<=5.0.0',
Denis Egorenko97ef0fb2020-01-13 14:22:49 +040064 'python-openstackclient==4.0.0',
Vasyl Saienkoefd68d92021-09-21 11:56:52 +030065 'python-ironicclient==3.1.2',
Mykyta Karpin4ed4a812020-03-26 16:31:26 +020066 'openstacksdk<0.44.0',
Denis Egorenko97ef0fb2020-01-13 14:22:49 +040067 'python-octaviaclient==1.11.0',
Vasyl Saienkof5df2022020-01-13 18:25:44 +020068 'python-heatclient==1.18.0',
Mykyta Karpine7c78612020-09-09 11:24:41 +030069 'docutils==0.16',
70 'pyrsistent<0.17.1',
Mykyta Karpin1bdfbd22021-04-02 10:28:55 +030071 'decorator<5.0.0',
Aleksey Zvyagintsevde345a92019-07-30 11:32:45 +000072 ]
Sergey Kolekonovba203982016-12-21 18:32:17 +040073
Aleksey Zvyagintsevde345a92019-07-30 11:32:45 +000074 if (version == 'kilo') {
75 requirements = openstack_kilo_packages
76 } else if (version == 'liberty') {
77 requirements = openstack_kilo_packages
78 } else if (version == 'mitaka') {
79 requirements = openstack_kilo_packages
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020080 } else {
Aleksey Zvyagintsevde345a92019-07-30 11:32:45 +000081 requirements = openstack_latest_packages
Sergey Kolekonovba203982016-12-21 18:32:17 +040082 }
Vasyl Saienko9a2bd372020-01-13 10:00:04 +020083 pythonLib.setupVirtualenv(path, python, requirements, null, true)
Sergey Kolekonovba203982016-12-21 18:32:17 +040084}
85
86/**
87 * create connection to OpenStack API endpoint
88 *
Jakub Josef6c963762018-01-18 16:02:22 +010089 * @param path Path to created venv
Sergey Kolekonovba203982016-12-21 18:32:17 +040090 * @param url OpenStack API endpoint address
91 * @param credentialsId Credentials to the OpenStack API
92 * @param project OpenStack project to connect to
93 */
Jakub Josef6c963762018-01-18 16:02:22 +010094def createOpenstackEnv(path, url, credentialsId, project, project_domain="default",
Tomáš Kukrál381a8c92017-06-21 09:01:52 +020095 project_id="", user_domain="default", api_ver="2", cacert="/etc/ssl/certs/ca-certificates.crt") {
iberezovskiyd4240b52017-02-20 17:18:28 +040096 def common = new com.mirantis.mk.Common()
Jakub Josef6c963762018-01-18 16:02:22 +010097 rcFile = "${path}/keystonerc"
Sergey Kolekonovba203982016-12-21 18:32:17 +040098 creds = common.getPasswordCredentials(credentialsId)
Alexander Tivelkovf89a1882017-01-11 13:29:35 +030099 rc = """set +x
100export OS_USERNAME=${creds.username}
Ales Komarek0e558ee2016-12-23 13:02:55 +0100101export OS_PASSWORD=${creds.password.toString()}
102export OS_TENANT_NAME=${project}
103export OS_AUTH_URL=${url}
104export OS_AUTH_STRATEGY=keystone
kairat_kushaev0a26bf72017-05-18 13:20:09 +0400105export OS_PROJECT_NAME=${project}
Jakub Josefbd927322017-05-30 13:20:27 +0000106export OS_PROJECT_ID=${project_id}
kairat_kushaev0a26bf72017-05-18 13:20:09 +0400107export OS_PROJECT_DOMAIN_ID=${project_domain}
Jakub Josefbd927322017-05-30 13:20:27 +0000108export OS_USER_DOMAIN_NAME=${user_domain}
Kirill Mashchenko234708f2017-07-20 17:00:01 +0300109export OS_IDENTITY_API_VERSION=${api_ver}
Tomáš Kukrál381a8c92017-06-21 09:01:52 +0200110export OS_CACERT=${cacert}
Alexander Tivelkovf89a1882017-01-11 13:29:35 +0300111set -x
Ales Komarek0e558ee2016-12-23 13:02:55 +0100112"""
113 writeFile file: rcFile, text: rc
114 return rcFile
Sergey Kolekonovba203982016-12-21 18:32:17 +0400115}
116
117/**
118 * Run command with OpenStack env params and optional python env
119 *
120 * @param cmd Command to be executed
121 * @param env Environmental parameters with endpoint credentials
122 * @param path Optional path to virtualenv with specific clients
123 */
124def runOpenstackCommand(cmd, venv, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400125 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400126 openstackCmd = ". ${venv}; ${cmd}"
127 if (path) {
128 output = python.runVirtualenvCommand(path, openstackCmd)
129 }
130 else {
131 echo("[Command]: ${openstackCmd}")
132 output = sh (
133 script: openstackCmd,
134 returnStdout: true
135 ).trim()
136 }
137 return output
138}
139
140/**
141 * Get OpenStack Keystone token for current credentials
142 *
143 * @param env Connection parameters for OpenStack API endpoint
144 * @param path Optional path to the custom virtualenv
145 */
146def getKeystoneToken(client, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400147 def python = new com.mirantis.mk.Python()
Jakub Josefbd927322017-05-30 13:20:27 +0000148 cmd = "openstack token issue"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400149 outputTable = runOpenstackCommand(cmd, client, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100150 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400151 return output
152}
153
154/**
Ales Komarek51b7b152017-06-27 11:14:50 +0200155 * Create OpenStack environment file
Sergey Kolekonovba203982016-12-21 18:32:17 +0400156 *
157 * @param env Connection parameters for OpenStack API endpoint
158 * @param path Optional path to the custom virtualenv
159 */
160def createHeatEnv(file, environment = [], original_file = null) {
161 if (original_file) {
162 envString = readFile file: original_file
Tomáš Kukrál03029442017-02-21 17:14:29 +0100163 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400164 envString = "parameters:\n"
165 }
Tomáš Kukrál03029442017-02-21 17:14:29 +0100166
Tomáš Kukrálc3964e52017-02-22 14:07:37 +0100167 p = entries(environment)
Tomáš Kukrálb1fe9642017-02-22 11:21:17 +0100168 for (int i = 0; i < p.size(); i++) {
169 envString = "${envString} ${p.get(i)[0]}: ${p.get(i)[1]}\n"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400170 }
Tomáš Kukrál03029442017-02-21 17:14:29 +0100171
Tomáš Kukrále19ddea2017-02-21 11:09:40 +0100172 echo("writing to env file:\n${envString}")
Sergey Kolekonovba203982016-12-21 18:32:17 +0400173 writeFile file: file, text: envString
174}
175
176/**
Vasyl Saienko0adc34b2019-01-23 15:52:37 +0200177 * Create new OpenStack Heat stack. Will wait for action to be complited in
178 * specified amount of time (by default 120min)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400179 *
180 * @param env Connection parameters for OpenStack API endpoint
181 * @param template HOT template for the new Heat stack
182 * @param environment Environmentale parameters of the new Heat stack
183 * @param name Name of the new Heat stack
184 * @param path Optional path to the custom virtualenv
Vasyl Saienko0adc34b2019-01-23 15:52:37 +0200185 * @param timeout Optional number in minutes to wait for stack action is applied.
Sergey Kolekonovba203982016-12-21 18:32:17 +0400186 */
Vasyl Saienko0adc34b2019-01-23 15:52:37 +0200187def createHeatStack(client, name, template, params = [], environment = null, path = null, action="create", timeout=120) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400188 def python = new com.mirantis.mk.Python()
Jakub Josef0a898762017-08-11 16:27:44 +0200189 def templateFile = "${env.WORKSPACE}/template/template/${template}.hot"
190 def envFile
191 def envSource
Sergey Kolekonovba203982016-12-21 18:32:17 +0400192 if (environment) {
Tomáš Kukrála1152742017-08-22 16:21:50 +0200193 envFile = "${env.WORKSPACE}/template/env/${name}.env"
194 if (environment.contains("/")) {
195 //init() returns all elements but the last in a collection.
196 def envPath = environment.tokenize("/").init().join("/")
197 if (envPath) {
198 envFile = "${env.WORKSPACE}/template/env/${envPath}/${name}.env"
199 }
Ales Komarek51b7b152017-06-27 11:14:50 +0200200 }
Tomáš Kukrála1152742017-08-22 16:21:50 +0200201 envSource = "${env.WORKSPACE}/template/env/${environment}.env"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400202 createHeatEnv(envFile, params, envSource)
Jakub Josef9a59aeb2017-08-11 15:50:20 +0200203 } else {
Sergey Kolekonovba203982016-12-21 18:32:17 +0400204 envFile = "${env.WORKSPACE}/template/${name}.env"
205 createHeatEnv(envFile, params)
206 }
Tomáš Kukrála1152742017-08-22 16:21:50 +0200207
Mykyta Karpincf44f812017-08-28 14:45:21 +0300208 def cmd
Vasyl Saienkob91df802019-01-23 17:22:57 +0200209 def cmd_args = "-t ${templateFile} -e ${envFile} --timeout ${timeout} --wait ${name}"
Mykyta Karpincf44f812017-08-28 14:45:21 +0300210
Tomáš Kukrála1152742017-08-22 16:21:50 +0200211 if (action == "create") {
Vasyl Saienko0adc34b2019-01-23 15:52:37 +0200212 cmd = "openstack stack create ${cmd_args}"
Tomáš Kukrála1152742017-08-22 16:21:50 +0200213 } else {
Vasyl Saienko0adc34b2019-01-23 15:52:37 +0200214 cmd = "openstack stack update ${cmd_args}"
Tomáš Kukrála1152742017-08-22 16:21:50 +0200215 }
216
Sergey Kolekonovba203982016-12-21 18:32:17 +0400217 dir("${env.WORKSPACE}/template/template") {
Vasyl Saienkod4254192019-01-23 18:02:01 +0200218 def out = runOpenstackCommand(cmd, client, path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400219 }
Sergey Kolekonovba203982016-12-21 18:32:17 +0400220}
221
222/**
Jakub Josefdb4baf22017-05-10 15:16:09 +0200223 * Returns list of stacks for stack name filter
224 *
225 * @param client Connection parameters for OpenStack API endpoint
226 * @param filter Stack name filter
227 * @param path Optional path to the custom virtualenv
228 */
229def getStacksForNameContains(client, filter, path = null){
Jakub Josef6465fca2017-05-10 16:09:20 +0200230 cmd = 'heat stack-list | awk \'NR>3 {print $4}\' | sed \'$ d\' | grep ' + filter + '|| true'
Jakub Josefdb4baf22017-05-10 15:16:09 +0200231 return runOpenstackCommand(cmd, client, path).trim().tokenize("\n")
232}
233
234
235/**
Jakub Josef5e238a22017-04-19 16:35:15 +0200236 * Get list of stack names with given stack status
237 *
Jakub Josefdb4baf22017-05-10 15:16:09 +0200238 * @param client Connection parameters for OpenStack API endpoint
Jakub Josef5e238a22017-04-19 16:35:15 +0200239 * @param status Stack status
240 * @param path Optional path to the custom virtualenv
241 */
242 def getStacksWithStatus(client, status, path = null) {
243 cmd = 'heat stack-list -f stack_status='+status+' | awk \'NR>3 {print $4}\' | sed \'$ d\''
244 return runOpenstackCommand(cmd, client, path).trim().tokenize("\n")
245 }
246
247/**
Sergey Kolekonovba203982016-12-21 18:32:17 +0400248 * Get life cycle status for existing OpenStack Heat stack
249 *
250 * @param env Connection parameters for OpenStack API endpoint
251 * @param name Name of the managed Heat stack instance
252 * @param path Optional path to the custom virtualenv
253 */
254def getHeatStackStatus(client, name, path = null) {
255 cmd = 'heat stack-list | awk -v stack='+name+' \'{if ($4==stack) print $6}\''
256 return runOpenstackCommand(cmd, client, path)
257}
258
259/**
260 * Get info about existing OpenStack Heat stack
261 *
262 * @param env Connection parameters for OpenStack API endpoint
263 * @param name Name of the managed Heat stack instance
264 * @param path Optional path to the custom virtualenv
265 */
266def getHeatStackInfo(env, name, path = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400267 def python = new com.mirantis.mk.Python()
Sergey Kolekonovba203982016-12-21 18:32:17 +0400268 cmd = "heat stack-show ${name}"
269 outputTable = runOpenstackCommand(cmd, env, path)
Ales Komareke11e8792016-12-28 09:42:25 +0100270 output = python.parseTextTable(outputTable, 'item', 'prettytable', path)
Sergey Kolekonovba203982016-12-21 18:32:17 +0400271 return output
272}
273
274/**
275 * Get existing OpenStack Heat stack output parameter
276 *
277 * @param env Connection parameters for OpenStack API endpoint
278 * @param name Name of the managed Heat stack
279 * @param parameter Name of the output parameter
280 * @param path Optional path to the custom virtualenv
281 */
282def getHeatStackOutputParam(env, name, outputParam, path = null) {
Vasyl Saienkoea4b2812017-07-10 10:36:03 +0000283 cmd = "heat output-show ${name} ${outputParam}"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400284 output = runOpenstackCommand(cmd, env, path)
Ales Komarekeedc2222017-01-03 10:10:03 +0100285 echo("${cmd}: ${output}")
Vasyl Saienko2a1c2de2017-07-11 11:41:53 +0300286 // NOTE(vsaienko) heatclient 1.5.1 returns output in "", while later
287 // versions returns string without "".
288 // TODO Use openstack 'stack output show' when all jobs using at least Mitaka heatclient
289 return "${output}".replaceAll('"', '')
Sergey Kolekonovba203982016-12-21 18:32:17 +0400290}
291
292/**
293 * List all resources from existing OpenStack Heat stack
294 *
295 * @param env Connection parameters for OpenStack API endpoint
296 * @param name Name of the managed Heat stack instance
297 * @param path Optional path to the custom virtualenv
Mykyta Karpin72306362018-02-08 16:40:43 +0200298 * @param depth Optional depth of stack for listing resources,
299 * 0 - do not list nested resources
Sergey Kolekonovba203982016-12-21 18:32:17 +0400300 */
Mykyta Karpin72306362018-02-08 16:40:43 +0200301def getHeatStackResources(env, name, path = null, depth = 0) {
iberezovskiyd4240b52017-02-20 17:18:28 +0400302 def python = new com.mirantis.mk.Python()
Mykyta Karpin72306362018-02-08 16:40:43 +0200303 cmd = "heat resource-list --nested-depth ${depth} ${name}"
Sergey Kolekonovba203982016-12-21 18:32:17 +0400304 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/**
Mykyta Karpin72306362018-02-08 16:40:43 +0200352 * Return hashmap of hashes server_id:server_name of servers from OpenStack Heat stack
Sergey Kolekonovba203982016-12-21 18:32:17 +0400353 *
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) {
Mykyta Karpin72306362018-02-08 16:40:43 +0200359 // set depth to 1000 to ensure all nested resources are shown
360 resources = getHeatStackResources(env, name, path, 1000)
361 servers = [:]
Sergey Kolekonovba203982016-12-21 18:32:17 +0400362 for (resource in resources) {
363 if (resource.resource_type == 'OS::Nova::Server') {
Mykyta Karpin67978112018-02-22 11:16:45 +0200364 server = getHeatStackResourceInfo(env, resource.stack_name, resource.resource_name, path)
Mykyta Karpin72306362018-02-08 16:40:43 +0200365 servers[server.attributes.id] = server.attributes.name
Sergey Kolekonovba203982016-12-21 18:32:17 +0400366 }
367 }
368 echo("[Stack ${name}] Servers: ${servers}")
369 return servers
370}
Jiri Broulikf8f96942018-02-15 10:03:42 +0100371
372/**
Mykyta Karpin8306a9d2018-07-27 11:34:10 +0300373 * Delete nova key pair
374 *
375 * @param env Connection parameters for OpenStack API endpoint
376 * @param name Name of the key pair to delete
377 * @param path Optional path to the custom virtualenv
378 */
379def deleteKeyPair(env, name, path = null) {
380 def common = new com.mirantis.mk.Common()
381 common.infoMsg("Removing key pair ${name}")
382 def cmd = "openstack keypair delete ${name}"
383 runOpenstackCommand(cmd, env, path)
384}
385
386/**
Oleksii Grudev69382ce2020-01-03 15:31:57 +0200387 * Check if Nova keypair exists and delete it.
388 *
389 * @param env Connection parameters for OpenStack API endpoint
390 * @param name Name of the key pair to delete
391 * @param path Path to virtualenv
392**/
393def ensureKeyPairRemoved(String name, env, path) {
394 def common = new com.mirantis.mk.Common()
395 def keypairs = runOpenstackCommand("openstack keypair list -f value -c Name", env, path).tokenize('\n')
396 if (name in keypairs) {
397 deleteKeyPair(env, name, path)
398 common.infoMsg("Keypair ${name} has been deleted")
399 } else {
400 common.warningMsg("Keypair ${name} not found")
401 }
402}
403
404/**
Mykyta Karpin8306a9d2018-07-27 11:34:10 +0300405 * Get nova key pair
406 *
407 * @param env Connection parameters for OpenStack API endpoint
408 * @param name Name of the key pair to show
409 * @param path Optional path to the custom virtualenv
410 */
411
412def getKeyPair(env, name, path = null) {
413 def common = new com.mirantis.mk.Common()
414 def cmd = "openstack keypair show ${name}"
415 def outputTable
416 try {
417 outputTable = runOpenstackCommand(cmd, env, path)
418 } catch (Exception e) {
419 common.infoMsg("Key pair ${name} not found")
420 }
421 return outputTable
422}
423
424/**
Jiri Broulikf8f96942018-02-15 10:03:42 +0100425 * Stops all services that contain specific string (for example nova,heat, etc.)
426 * @param env Salt Connection object or pepperEnv
427 * @param probe single node on which to list service names
428 * @param target all targeted nodes
429 * @param services lists of type of services to be stopped
Jiri Broulikf6daac62018-03-08 13:17:53 +0100430 * @param confirm enable/disable manual service stop confirmation
Jiri Broulikf8f96942018-02-15 10:03:42 +0100431 * @return output of salt commands
432 */
Jiri Broulik27e83052018-03-06 11:37:29 +0100433def stopServices(env, probe, target, services=[], confirm=false) {
Jiri Broulikf8f96942018-02-15 10:03:42 +0100434 def salt = new com.mirantis.mk.Salt()
Jiri Broulikf6daac62018-03-08 13:17:53 +0100435 def common = new com.mirantis.mk.Common()
Jiri Broulikf8f96942018-02-15 10:03:42 +0100436 for (s in services) {
Dmitry Ukovd72cd2a2018-09-04 17:31:46 +0400437 def outputServicesStr = salt.getReturnValues(salt.cmdRun(env, probe, "service --status-all | grep ${s} | awk \'{print \$4}\'"))
Jiri Broulikf6daac62018-03-08 13:17:53 +0100438 def servicesList = outputServicesStr.tokenize("\n").init()
Jiri Broulik27e83052018-03-06 11:37:29 +0100439 if (confirm) {
Jiri Broulikf6daac62018-03-08 13:17:53 +0100440 if (servicesList) {
441 try {
442 input message: "Click PROCEED to stop ${servicesList}. Otherwise click ABORT to skip stopping them."
443 for (name in servicesList) {
444 if (!name.contains('Salt command')) {
Dmitry Ukovd72cd2a2018-09-04 17:31:46 +0400445 salt.runSaltProcessStep(env, target, 'service.stop', ["${name}"])
Jiri Broulikf6daac62018-03-08 13:17:53 +0100446 }
447 }
448 } catch (Exception er) {
449 common.infoMsg("skipping stopping ${servicesList} services")
450 }
451 }
452 } else {
453 if (servicesList) {
Jiri Broulik27e83052018-03-06 11:37:29 +0100454 for (name in servicesList) {
455 if (!name.contains('Salt command')) {
Dmitry Ukovd72cd2a2018-09-04 17:31:46 +0400456 salt.runSaltProcessStep(env, target, 'service.stop', ["${name}"])
Jiri Broulik27e83052018-03-06 11:37:29 +0100457 }
458 }
Jiri Broulikf8f96942018-02-15 10:03:42 +0100459 }
460 }
461 }
462}
463
464/**
Vasyl Saienko4129e102018-09-03 10:15:52 +0300465 * Return intersection of globally installed services and those are
466 * defined on specific target according to theirs priorities.
Oleksii Grudev3aaadc22019-03-14 10:54:58 +0200467 * By default services are added to the result list only if
468 * <service>.upgrade.enabled pillar is set to "True". However if it
469 * is needed to obtain list of upgrade services regardless of
470 * <service>.upgrade.enabled pillar value it is needed to set
471 * "upgrade_condition" param to "False".
Vasyl Saienko4129e102018-09-03 10:15:52 +0300472 *
473 * @param env Salt Connection object or env
Oleksii Grudev3aaadc22019-03-14 10:54:58 +0200474 * @param target The target node to get list of apps for
475 * @param upgrade_condition Whether to take "upgrade:enabled"
476 * service pillar into consideration
477 * when obtaining list of upgrade services
Vasyl Saienko4129e102018-09-03 10:15:52 +0300478**/
Oleksii Grudev3aaadc22019-03-14 10:54:58 +0200479def getOpenStackUpgradeServices(env, target, upgrade_condition=true){
Vasyl Saienko4129e102018-09-03 10:15:52 +0300480 def salt = new com.mirantis.mk.Salt()
481 def common = new com.mirantis.mk.Common()
482
483 def global_apps = salt.getConfig(env, 'I@salt:master:enabled:true', 'orchestration.upgrade.applications')
484 def node_apps = salt.getPillar(env, target, '__reclass__:applications')['return'][0].values()[0]
Oleksii Grudev3aaadc22019-03-14 10:54:58 +0200485 if (upgrade_condition) {
486 node_pillar = salt.getPillar(env, target)
487 }
Vasyl Saienko4129e102018-09-03 10:15:52 +0300488 def node_sorted_apps = []
489 if ( !global_apps['return'][0].values()[0].isEmpty() ) {
490 Map<String,Integer> _sorted_apps = [:]
491 for (k in global_apps['return'][0].values()[0].keySet()) {
492 if (k in node_apps) {
Oleksii Grudev3aaadc22019-03-14 10:54:58 +0200493 if (upgrade_condition) {
494 if (node_pillar['return'][0].values()[k]['upgrade']['enabled'][0] != null) {
495 if (node_pillar['return'][0].values()[k]['upgrade']['enabled'][0].toBoolean()) {
496 _sorted_apps[k] = global_apps['return'][0].values()[0][k].values()[0].toInteger()
497 }
Oleksii Grudev3116a732019-02-14 18:16:05 +0200498 }
Oleksii Grudev3aaadc22019-03-14 10:54:58 +0200499 } else {
500 _sorted_apps[k] = global_apps['return'][0].values()[0][k].values()[0].toInteger()
Oleksii Grudev3116a732019-02-14 18:16:05 +0200501 }
Vasyl Saienko4129e102018-09-03 10:15:52 +0300502 }
503 }
504 node_sorted_apps = common.SortMapByValueAsc(_sorted_apps).keySet()
505 common.infoMsg("Applications are placed in following order:"+node_sorted_apps)
506 } else {
507 common.errorMsg("No applications found.")
508 }
509
510 return node_sorted_apps
511}
512
Vasyl Saienko4129e102018-09-03 10:15:52 +0300513/**
514 * Run specified upgrade phase for all services on given node.
515 *
516 * @param env Salt Connection object or env
517 * @param target The target node to run states on.
518 * @param phase The phase name to run.
519**/
520def runOpenStackUpgradePhase(env, target, phase){
521 def salt = new com.mirantis.mk.Salt()
522 def common = new com.mirantis.mk.Common()
523
524 services = getOpenStackUpgradeServices(env, target)
525 def st
526
527 for (service in services){
528 st = "${service}.upgrade.${phase}".trim()
529 common.infoMsg("Running ${phase} for service ${st} on ${target}")
530 salt.enforceState(env, target, st)
531 }
532}
533
534
535/**
536 * Run OpenStack states on specified node.
537 *
538 * @param env Salt Connection object or env
539 * @param target The target node to run states on.
540**/
541def applyOpenstackAppsStates(env, target){
542 def salt = new com.mirantis.mk.Salt()
543 def common = new com.mirantis.mk.Common()
544
545 services = getOpenStackUpgradeServices(env, target)
546 def st
547
548 for (service in services){
549 st = "${service}".trim()
550 common.infoMsg("Running ${st} on ${target}")
551 salt.enforceState(env, target, st)
552 }
553}
554
Martin Polreich232ad902019-01-21 14:31:00 +0100555def verifyGaleraStatus(env, slave=false, checkTimeSync=false) {
Martin Polreich65864b02018-12-05 10:42:50 +0100556 def common = new com.mirantis.mk.Common()
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100557 def galera = new com.mirantis.mk.Galera()
558 common.warningMsg("verifyGaleraStatus method was moved to Galera class. Please change your calls accordingly.")
559 return galera.verifyGaleraStatus(env, slave, checkTimeSync)
Martin Polreich65864b02018-12-05 10:42:50 +0100560}
561
Martin Polreich9a5d6682018-12-21 16:42:06 +0100562def validateAndPrintGaleraStatusReport(env, out, minion) {
Martin Polreich65864b02018-12-05 10:42:50 +0100563 def common = new com.mirantis.mk.Common()
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100564 def galera = new com.mirantis.mk.Galera()
565 common.warningMsg("validateAndPrintGaleraStatusReport method was moved to Galera class. Please change your calls accordingly.")
566 return galera.validateAndPrintGaleraStatusReport(env, out, minion)
Martin Polreich65864b02018-12-05 10:42:50 +0100567}
568
Martin Polreich9a5d6682018-12-21 16:42:06 +0100569def getGaleraLastShutdownNode(env) {
Martin Polreich9a5d6682018-12-21 16:42:06 +0100570 def common = new com.mirantis.mk.Common()
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100571 def galera = new com.mirantis.mk.Galera()
572 common.warningMsg("getGaleraLastShutdownNode method was moved to Galera class. Please change your calls accordingly.")
573 return galera.getGaleraLastShutdownNode(env)
Martin Polreich9a5d6682018-12-21 16:42:06 +0100574}
575
Ivan Berezovskiy004cac22019-02-01 17:03:28 +0400576def restoreGaleraDb(env) {
Jiri Broulikf8f96942018-02-15 10:03:42 +0100577 def common = new com.mirantis.mk.Common()
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100578 def galera = new com.mirantis.mk.Galera()
579 common.warningMsg("restoreGaleraDb method was moved to Galera class. Please change your calls accordingly.")
580 return galera.restoreGaleraDb(env)
Oleksii Grudev3aaadc22019-03-14 10:54:58 +0200581}
Oleksandr Kononenkod8fe24c2021-11-19 14:21:08 +0200582
583/**
584 * create connection to OpenStack API endpoint via Docker
585 *
586 * @param authUrl OpenStack API endpoint address
587 * @param credentialsId Credentials to the OpenStack API
588 * @param project OpenStack project to connect to
589 */
590def createOpenstackEnvInDocker(authUrl, credentialsId, project, project_domain="default", project_id="", user_domain="default", cacert="/etc/ssl/certs/ca-certificates.crt") {
591 def common = new com.mirantis.mk.Common()
592 creds = common.getPasswordCredentials(credentialsId)
Oleksandr Kononenko984d7b92021-12-14 16:34:47 +0200593 def env = ["OS_USERNAME=${creds.username}", "OS_PASSWORD=${creds.password.toString()}", "OS_TENANT_NAME=${project}", "OS_AUTH_URL=${authUrl}", "OS_AUTH_STRATEGY=keystone -e OS_PROJECT_NAME=${project}", "OS_PROJECT_ID=${project_id}", "OS_PROJECT_DOMAIN_ID=${project_domain}", "OS_USER_DOMAIN_NAME=${user_domain}", "OS_CACERT=${cacert}"]
Oleksandr Kononenkod8fe24c2021-11-19 14:21:08 +0200594 return env
595}
596
597/**
598 * prepare Docker docker image for OpenstackEnv
599 *
600 * @param credentials Credentials for access to artifact-metadata repo
601 * @param release Optional openstack release for image
602 * @param img Optional URL for Docker image
603 */
604def prepareOpenstackDockerImage(credentialsId, release=null, img=null) {
605 def common = new com.mirantis.mk.Common()
606 Map getMetadataParams = ['metadataCredentialsId': credentialsId]
607 if (!img) {
608 if (!release) {
609 release = 'ussuri'
610 }
611 def releaseWorkflow = new com.mirantis.mk.ReleaseWorkflow()
612 img = releaseWorkflow.getReleaseMetadataValue("images:openstack:${release}:heat:url", getMetadataParams).replace('"', '')
613 }
614 common.infoMsg("Use image ${img}")
615 return img
616}
617
Oleksandr Kononenkod8fe24c2021-11-19 14:21:08 +0200618/**
619 * create connection to OpenStack API endpoint in the Docker container
620 *
621 * @param cmd Command to be executed
622 * @param env Environmental parameters with endpoint credentials (from createOpenstackEnvInDocker)
623 * @param img Docker image for container (from prepareOpenstackDockerImage)
624 */
625def runOpenstackCommandInDocker(cmd, env, img) {
626 def dockerImg = docker.image(img)
627 def result
Oleksandr Kononenko984d7b92021-12-14 16:34:47 +0200628 dockerImg.inside() {
629 withEnv(env) {
630 result = sh(script: "${cmd}", returnStdout: true).trim()
631 }
Oleksandr Kononenkod8fe24c2021-11-19 14:21:08 +0200632 }
633 return result
634}
635
636/**
637 * Delete nova key pair
638 *
639 * @param env Connection parameters for OpenStack API endpoint
640 * @param name Name of the key pair to delete
641 */
642def deleteKeyPairInDocker(env, name, img) {
643 def common = new com.mirantis.mk.Common()
644 common.infoMsg("Removing key pair ${name}")
645 def cmd = "openstack keypair delete ${name}"
646 runOpenstackCommandInDocker(cmd, env, img)
647}
648
649/**
650 * Check if Nova keypair exists and delete it.
651 *
652 * @param env Connection parameters for OpenStack API endpoint
653 * @param name Name of the key pair to delete
654**/
655def ensureKeyPairRemovedInDocker(String name, env, img) {
656 def common = new com.mirantis.mk.Common()
657 def keypairs = runOpenstackCommandInDocker("openstack keypair list -f value -c Name", env, img).tokenize('\n')
658 if (name in keypairs) {
659 deleteKeyPairInDocker(env, name, img)
660 common.infoMsg("Keypair ${name} has been deleted")
661 } else {
662 common.warningMsg("Keypair ${name} not found")
663 }
664}
665
666/**
667 * Get nova key pair
668 *
669 * @param env Connection parameters for OpenStack API endpoint
670 * @param name Name of the key pair to show
671 */
672def getKeyPairInDocker(env, name, img) {
673 def common = new com.mirantis.mk.Common()
674 def cmd = "openstack keypair show ${name}"
675 def outputTable
676 try {
677 outputTable = runOpenstackCommandInDocker(cmd, env, img)
678 } catch (Exception e) {
679 common.infoMsg("Key pair ${name} not found")
680 }
681 return outputTable
682}