| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 1 | package com.mirantis.mk | 
 | 2 |  | 
| Jakub Josef | b77c081 | 2017-03-27 14:11:01 +0200 | [diff] [blame] | 3 | import java.util.stream.Collectors | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 4 | /** | 
 | 5 |  * Salt functions | 
 | 6 |  * | 
 | 7 | */ | 
 | 8 |  | 
 | 9 | /** | 
 | 10 |  * Salt connection and context parameters | 
 | 11 |  * | 
 | 12 |  * @param url                 Salt API server URL | 
 | 13 |  * @param credentialsID       ID of credentials store entry | 
 | 14 |  */ | 
 | 15 | def connection(url, credentialsId = "salt") { | 
| Tomáš Kukrál | 6c04bd0 | 2017-03-01 22:18:52 +0100 | [diff] [blame] | 16 |     def common = new com.mirantis.mk.Common() | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 17 |     params = [ | 
 | 18 |         "url": url, | 
 | 19 |         "credentialsId": credentialsId, | 
 | 20 |         "authToken": null, | 
 | 21 |         "creds": common.getCredentials(credentialsId) | 
 | 22 |     ] | 
 | 23 |     params["authToken"] = saltLogin(params) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 24 |     return params | 
 | 25 | } | 
 | 26 |  | 
 | 27 | /** | 
 | 28 |  * Login to Salt API, return auth token | 
 | 29 |  * | 
 | 30 |  * @param master   Salt connection object | 
 | 31 |  */ | 
 | 32 | def saltLogin(master) { | 
| Tomáš Kukrál | 7bec053 | 2017-02-20 15:39:31 +0100 | [diff] [blame] | 33 |     def http = new com.mirantis.mk.Http() | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 34 |     data = [ | 
 | 35 |         'username': master.creds.username, | 
 | 36 |         'password': master.creds.password.toString(), | 
 | 37 |         'eauth': 'pam' | 
 | 38 |     ] | 
| Tomáš Kukrál | 7bec053 | 2017-02-20 15:39:31 +0100 | [diff] [blame] | 39 |     authToken = http.restGet(master, '/login', data)['return'][0]['token'] | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 40 |     return authToken | 
 | 41 | } | 
 | 42 |  | 
 | 43 | /** | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 44 |  * Run action using Salt API (using plain HTTP request from Jenkins master) or Pepper (from slave shell) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 45 |  * | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 46 |  * @param saltId   Salt Connection object or pepperEnv (the command will be sent using the selected method) (determines if command will be sent with Pepper of Salt API ) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 47 |  * @param client   Client type | 
 | 48 |  * @param target   Target specification, eg. for compound matches by Pillar | 
 | 49 |  *                 data: ['expression': 'I@openssh:server', 'type': 'compound']) | 
 | 50 |  * @param function Function to execute (eg. "state.sls") | 
| Jakub Josef | 2f25cf2 | 2017-03-28 13:34:57 +0200 | [diff] [blame] | 51 |  * @param batch    Batch param to salt (integer or string with percents) | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 52 |  *                      - null   - automatic decision (based on number of worker threads env var or not use batch at all) | 
 | 53 |  *                      - int    - fixed size of batch | 
 | 54 |  *                      - 'str%' - percantage of the requests in one batch | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 55 |  * @param args     Additional arguments to function | 
 | 56 |  * @param kwargs   Additional key-value arguments to function | 
| Jiri Broulik | 48544be | 2017-06-14 18:33:54 +0200 | [diff] [blame] | 57 |  * @param timeout  Additional argument salt api timeout | 
| Vasyl Saienko | e36ab7c | 2017-07-17 14:35:48 +0300 | [diff] [blame] | 58 |  * @param read_timeout http session read timeout | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 59 |  */ | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 60 |  | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 61 | def runSaltCommand(saltId, client, target, function, batch = null, args = null, kwargs = null, timeout = -1, read_timeout = -1) { | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 62 |     data = [ | 
 | 63 |         'tgt': target.expression, | 
 | 64 |         'fun': function, | 
 | 65 |         'client': client, | 
 | 66 |         'expr_form': target.type, | 
 | 67 |     ] | 
| Richard Felkl | d9476ac | 2018-07-12 19:01:33 +0200 | [diff] [blame] | 68 |  | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 69 |     if (batch) { | 
| Richard Felkl | d9476ac | 2018-07-12 19:01:33 +0200 | [diff] [blame] | 70 |         batch = batch.toString() | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 71 |     } else if (env.getEnvironment().containsKey('SALT_MASTER_OPT_WORKER_THREADS')) { | 
 | 72 |         batch = env['SALT_MASTER_OPT_WORKER_THREADS'].toString() | 
 | 73 |     } | 
 | 74 |  | 
 | 75 |     if (batch instanceof String) { | 
 | 76 |         if ((batch.isInteger() && batch.toInteger() > 0) || (batch.matches(/(\d){1,2}%/))){ | 
| Richard Felkl | d9476ac | 2018-07-12 19:01:33 +0200 | [diff] [blame] | 77 |             data['client']= "local_batch" | 
 | 78 |             data['batch'] = batch | 
 | 79 |         } | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 80 |     } | 
 | 81 |  | 
 | 82 |     if (args) { | 
 | 83 |         data['arg'] = args | 
 | 84 |     } | 
 | 85 |  | 
 | 86 |     if (kwargs) { | 
 | 87 |         data['kwarg'] = kwargs | 
 | 88 |     } | 
 | 89 |  | 
| Jiri Broulik | 48544be | 2017-06-14 18:33:54 +0200 | [diff] [blame] | 90 |     if (timeout != -1) { | 
 | 91 |         data['timeout'] = timeout | 
 | 92 |     } | 
 | 93 |  | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 94 |     def result = [:] | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 95 |     // Command will be sent using HttpRequest | 
 | 96 |     if (saltId instanceof HashMap && saltId.containsKey("authToken") ) { | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 97 |  | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 98 |         def headers = [ | 
 | 99 |           'X-Auth-Token': "${saltId.authToken}" | 
 | 100 |         ] | 
 | 101 |  | 
 | 102 |         def http = new com.mirantis.mk.Http() | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 103 |         result = http.sendHttpPostRequest("${saltId.url}/", data, headers, read_timeout) | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 104 |     } else if (saltId instanceof HashMap) { | 
 | 105 |         throw new Exception("Invalid saltId") | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 106 |     } else { | 
 | 107 |         // Command will be sent using Pepper | 
 | 108 |         result = runPepperCommand(data, saltId) | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 109 |     } | 
 | 110 |  | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 111 |     // Convert returned Object to the same structure as from 'local' client to keep compatibility | 
 | 112 |     if (data['client'].equals('local_batch')) { | 
 | 113 |         def resultMap = ['return': [[:]]] | 
 | 114 |         result['return'].each { it -> resultMap['return'][0] = it + resultMap['return'][0] } | 
 | 115 |         return resultMap | 
 | 116 |     } else { | 
 | 117 |         return result | 
 | 118 |     } | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 119 | } | 
 | 120 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 121 | /** | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 122 |  * Return pillar for given saltId and target | 
 | 123 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 124 |  * @param target Get pillar target | 
 | 125 |  * @param pillar pillar name (optional) | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 126 |  * @param batch  Batch param to salt (integer or string with percents) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 127 |  * @return output of salt command | 
 | 128 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 129 | def getPillar(saltId, target, pillar = null, batch = null) { | 
| Tomáš Kukrál | d258970 | 2017-03-10 16:30:46 +0100 | [diff] [blame] | 130 |     if (pillar != null) { | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 131 |         return runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'pillar.get', batch, [pillar.replace('.', ':')]) | 
| Tomáš Kukrál | d258970 | 2017-03-10 16:30:46 +0100 | [diff] [blame] | 132 |     } else { | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 133 |         return runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'pillar.data', batch) | 
| Ales Komarek | a3c7e50 | 2017-03-13 11:20:44 +0100 | [diff] [blame] | 134 |     } | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 135 | } | 
 | 136 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 137 | /** | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 138 |  * Return grain for given saltId and target | 
 | 139 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 140 |  * @param target Get grain target | 
 | 141 |  * @param grain grain name (optional) | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 142 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 143 |  * @return output of salt command | 
 | 144 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 145 | def getGrain(saltId, target, grain = null, batch = null) { | 
| Ales Komarek | cec24d4 | 2017-03-08 10:25:45 +0100 | [diff] [blame] | 146 |     if(grain != null) { | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 147 |         return runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'grains.item', batch, [grain]) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 148 |     } else { | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 149 |         return runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'grains.items', batch) | 
| Ales Komarek | cec24d4 | 2017-03-08 10:25:45 +0100 | [diff] [blame] | 150 |     } | 
| Ales Komarek | cec24d4 | 2017-03-08 10:25:45 +0100 | [diff] [blame] | 151 | } | 
 | 152 |  | 
| Oleksii Grudev | 9e1d97a | 2018-06-29 16:04:30 +0300 | [diff] [blame] | 153 | /** | 
 | 154 |  * Return config items for given saltId and target | 
 | 155 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 156 |  * @param target Get grain target | 
 | 157 |  * @param config grain name (optional) | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 158 |  * @param batch Batch param to salt (integer or string with percents) | 
| Oleksii Grudev | 9e1d97a | 2018-06-29 16:04:30 +0300 | [diff] [blame] | 159 |  * @return output of salt command | 
 | 160 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 161 | def getConfig(saltId, target, config, batch = null) { | 
 | 162 |     return runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'config.get', batch, [config.replace('.', ':')], '--out=json') | 
| Oleksii Grudev | 9e1d97a | 2018-06-29 16:04:30 +0300 | [diff] [blame] | 163 | } | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 164 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 165 | /** | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 166 |  * Enforces state on given saltId and target | 
 | 167 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 168 |  * @param target State enforcing target | 
 | 169 |  * @param state Salt state | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 170 |  * @param excludedStates states which will be excluded from main state (default empty string) | 
 | 171 |  * @param output print output (optional, default true) | 
 | 172 |  * @param failOnError throw exception on salt state result:false (optional, default true) | 
 | 173 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
 | 174 |  * @param optional Optional flag (if true pipeline will continue even if no minions for target found) | 
 | 175 |  * @param read_timeout http session read timeout (optional, default -1 - disabled) | 
 | 176 |  * @param retries Retry count for salt state. (optional, default -1 - no retries) | 
 | 177 |  * @param queue salt queue parameter for state.sls calls (optional, default true) - CANNOT BE USED WITH BATCH | 
 | 178 |  * @param saltArgs additional salt args eq. ["runas=aptly"] | 
 | 179 |  * @return output of salt command | 
 | 180 |  */ | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 181 | def enforceStateWithExclude(Map params) { | 
 | 182 |     //Set defaults | 
 | 183 |     defaults = ["excludedStates": "", "output": true, "failOnError": true, "batch": null, "optional": false, | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 184 |                 "read_timeout": -1, "retries": -1, "retries_wait": 5, "queue": true, "saltArgs": []] | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 185 |     params = defaults + params | 
 | 186 |     params.saltArgs << "exclude=${params.excludedStates}" | 
 | 187 |     params.remove('excludedStates') | 
 | 188 |     return enforceState(params) | 
 | 189 | } | 
 | 190 |  | 
 | 191 |  | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 192 | def enforceStateWithExclude(saltId, target, state, excludedStates = "", output = true, failOnError = true, batch = null, optional = false, read_timeout=-1, retries=-1, queue=true, saltArgs=[], retries_wait=5) { | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 193 | // Deprecated, convert state to use Map as input parameter | 
 | 194 |     def common = new com.mirantis.mk.Common() | 
| Martin Polreich | 4928869 | 2018-12-20 15:41:04 +0100 | [diff] [blame] | 195 |     common.infoMsg("This method will be deprecated. Convert you method call to use Map as input parameter") | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 196 |     // Convert to Map | 
 | 197 |     params = ['saltId': saltId, 'target': target, 'state': state, 'excludedStates': excludedStates, 'output': output, | 
 | 198 |                 'failOnError': failOnError, 'batch': batch, 'optional': optional, 'read_timeout': read_timeout, | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 199 |                 'retries': retries, 'retries_wait': retries_wait, 'queue': queue, 'saltArgs': saltArgs] | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 200 |     // Call new method with Map as parameter | 
 | 201 |     return enforceStateWithExclude(params) | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 202 | } | 
 | 203 |  | 
| Martin Polreich | 75e4064 | 2018-08-13 16:05:08 +0200 | [diff] [blame] | 204 | /** | 
 | 205 |  * Allows to test the given target for reachability and if reachable enforces the state | 
 | 206 | * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 207 |  * @param target State enforcing target | 
 | 208 |  * @param state Salt state | 
 | 209 |  * @param testTargetMatcher Salt compound matcher to be tested (default is empty string). If empty string, param `target` will be used for tests | 
 | 210 |  * @param output print output (optional, default true) | 
 | 211 |  * @param failOnError throw exception on salt state result:false (optional, default true) | 
 | 212 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
 | 213 |  * @param optional Optional flag (if true pipeline will continue even if no minions for target found) | 
 | 214 |  * @param read_timeout http session read timeout (optional, default -1 - disabled) | 
 | 215 |  * @param retries Retry count for salt state. (optional, default -1 - no retries) | 
 | 216 |  * @param queue salt queue parameter for state.sls calls (optional, default true) - CANNOT BE USED WITH BATCH | 
 | 217 |  * @param saltArgs additional salt args eq. ["runas=aptly"] | 
 | 218 |  * @return output of salt command | 
 | 219 |  */ | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 220 | def enforceStateWithTest(Map params) { | 
| Martin Polreich | 75e4064 | 2018-08-13 16:05:08 +0200 | [diff] [blame] | 221 |     def common = new com.mirantis.mk.Common() | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 222 |     //Set defaults | 
 | 223 |     defaults = ["testTargetMatcher": "", "output": true, "failOnError": true, "batch": null, "optional": false, | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 224 |                 "read_timeout": -1, "retries": -1, "retries_wait": 5, "queue": true, "saltArgs":[]] | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 225 |     params = defaults + params | 
 | 226 |     if (!params.testTargetMatcher) { | 
 | 227 |         params.testTargetMatcher = params.target | 
| Martin Polreich | 75e4064 | 2018-08-13 16:05:08 +0200 | [diff] [blame] | 228 |     } | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 229 |     if (testTarget(params.saltId, params.testTargetMatcher, params.batch)) { | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 230 |         return enforceState(params) | 
| Martin Polreich | 75e4064 | 2018-08-13 16:05:08 +0200 | [diff] [blame] | 231 |     } else { | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 232 |         if (!params.optional) { | 
 | 233 |                 common.infoMsg("No Minions matched the target matcher: ${params.testTargetMatcher}, and 'optional' param was set to false. - This may signify missing pillar definition!!") | 
| Martin Polreich | 92db3e1 | 2018-09-27 15:29:23 +0200 | [diff] [blame] | 234 | //              throw new Exception("No Minions matched the target matcher: ${testTargetMatcher}.") TODO: Change the infoMsg to Error once the methods are changed to Use named params and optional param will be set globally | 
| Martin Polreich | 75e4064 | 2018-08-13 16:05:08 +0200 | [diff] [blame] | 235 |             } else { | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 236 |                 common.infoMsg("No Minions matched the target matcher: ${params.testTargetMatcher}, but 'optional' param was set to true - Pipeline continues. ") | 
| Martin Polreich | 75e4064 | 2018-08-13 16:05:08 +0200 | [diff] [blame] | 237 |             } | 
 | 238 |     } | 
 | 239 | } | 
 | 240 |  | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 241 |  | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 242 | def enforceStateWithTest(saltId, target, state, testTargetMatcher = "", output = true, failOnError = true, batch = null, optional = false, read_timeout=-1, retries=-1, queue=true, saltArgs=[], retries_wait=5) { | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 243 | // Deprecated, convert state to use Map as input parameter | 
 | 244 |     def common = new com.mirantis.mk.Common() | 
| Martin Polreich | 4928869 | 2018-12-20 15:41:04 +0100 | [diff] [blame] | 245 |     common.infoMsg("This method will be deprecated. Convert you method call to use Map as input parameter") | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 246 |     // Convert to Map | 
 | 247 |     params = ['saltId': saltId, 'target': target, 'state': state, 'testTargetMatcher': testTargetMatcher, 'output': output, | 
 | 248 |                 'failOnError': failOnError, 'batch': batch, 'optional': optional, 'read_timeout': read_timeout, | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 249 |                 'retries': retries, 'retries_wait': retries_wait, 'queue': queue, 'saltArgs': saltArgs] | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 250 |     // Call new method with Map as parameter | 
 | 251 |     return enforceStateWithTest(params) | 
 | 252 | } | 
 | 253 |  | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 254 | /* Enforces state on given saltId and target | 
 | 255 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 256 |  * @param target State enforcing target | 
 | 257 |  * @param state Salt state | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 258 |  * @param output print output (optional, default true) | 
 | 259 |  * @param failOnError throw exception on salt state result:false (optional, default true) | 
| Jakub Josef | 2f25cf2 | 2017-03-28 13:34:57 +0200 | [diff] [blame] | 260 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 261 |  * @param optional Optional flag (if true pipeline will continue even if no minions for target found) | 
| Petr Michalec | de0ff32 | 2017-10-04 09:32:14 +0200 | [diff] [blame] | 262 |  * @param read_timeout http session read timeout (optional, default -1 - disabled) | 
 | 263 |  * @param retries Retry count for salt state. (optional, default -1 - no retries) | 
 | 264 |  * @param queue salt queue parameter for state.sls calls (optional, default true) - CANNOT BE USED WITH BATCH | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 265 |  * @param saltArgs additional salt args eq. ["runas=aptly", exclude="opencontrail.database"] | 
| Vasyl Saienko | 6a39621 | 2018-06-08 09:20:08 +0300 | [diff] [blame] | 266 |  * @param minionRestartWaitTimeout specifies timeout that we should wait after minion restart. | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 267 |  * @return output of salt command | 
 | 268 |  */ | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 269 | def enforceState(Map params) { | 
| Tomáš Kukrál | 6c04bd0 | 2017-03-01 22:18:52 +0100 | [diff] [blame] | 270 |     def common = new com.mirantis.mk.Common() | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 271 |     //Set defaults | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 272 |     defaults = ["output": true, "failOnError": true, "batch": null, "optional": false, "read_timeout": -1, | 
 | 273 |                 "retries": -1, "retries_wait": 5, "queue": true, "saltArgs": [], "minionRestartWaitTimeout": 10] | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 274 |     params = defaults + params | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 275 |     // add state to salt args | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 276 |     if (params.state instanceof String) { | 
 | 277 |         params.saltArgs << params.state | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 278 |     } else { | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 279 |         params.saltArgs << params.state.join(',') | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 280 |     } | 
 | 281 |  | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 282 |     common.infoMsg("Running state ${params.state} on ${params.target}") | 
| Vasyl Saienko | e36ab7c | 2017-07-17 14:35:48 +0300 | [diff] [blame] | 283 |     def out | 
| Petr Michalec | de0ff32 | 2017-10-04 09:32:14 +0200 | [diff] [blame] | 284 |     def kwargs = [:] | 
 | 285 |  | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 286 |     if (params.queue && params.batch == null) { | 
| Petr Michalec | de0ff32 | 2017-10-04 09:32:14 +0200 | [diff] [blame] | 287 |       kwargs["queue"] = true | 
 | 288 |     } | 
| Vasyl Saienko | e36ab7c | 2017-07-17 14:35:48 +0300 | [diff] [blame] | 289 |  | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 290 |     if (params.optional == false || testTarget(params.saltId, params.target, params.batch)){ | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 291 |         if (params.retries > 0){ | 
| Jakub Josef | 962ba91 | 2018-04-04 17:39:19 +0200 | [diff] [blame] | 292 |             def retriesCounter = 0 | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 293 |             retry(params.retries){ | 
| Jakub Josef | 962ba91 | 2018-04-04 17:39:19 +0200 | [diff] [blame] | 294 |                 retriesCounter++ | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 295 |                 // we have to reverse order in saltArgs because salt state have to be first | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 296 |                 out = runSaltCommand(params.saltId, 'local', ['expression': params.target, 'type': 'compound'], 'state.sls', params.batch, params.saltArgs.reverse(), kwargs, -1, params.read_timeout) | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 297 |                 // failOnError should be passed as true because we need to throw exception for retry block handler | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 298 |                 checkResult(out, true, params.output, true, retriesCounter < params.retries) //disable ask on error for every interation except last one | 
| Ivan Berezovskiy | 3e7656b | 2019-02-11 20:28:40 +0400 | [diff] [blame] | 299 |                 sleep(params['retries_wait']) | 
| Vasyl Saienko | e36ab7c | 2017-07-17 14:35:48 +0300 | [diff] [blame] | 300 |             } | 
| Petr Michalec | de0ff32 | 2017-10-04 09:32:14 +0200 | [diff] [blame] | 301 |         } else { | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 302 |             // we have to reverse order in saltArgs because salt state have to be first | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 303 |             out = runSaltCommand(params.saltId, 'local', ['expression': params.target, 'type': 'compound'], 'state.sls', params.batch, params.saltArgs.reverse(), kwargs, -1, params.read_timeout) | 
 | 304 |             checkResult(out, params.failOnError, params.output) | 
| Vasyl Saienko | e36ab7c | 2017-07-17 14:35:48 +0300 | [diff] [blame] | 305 |         } | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 306 |         waitForMinion(out, params.minionRestartWaitTimeout) | 
| Martin Polreich | 1c77afa | 2017-07-18 11:27:02 +0200 | [diff] [blame] | 307 |         return out | 
| Martin Polreich | 1c77afa | 2017-07-18 11:27:02 +0200 | [diff] [blame] | 308 |     } else { | 
 | 309 |         common.infoMsg("No Minions matched the target given, but 'optional' param was set to true - Pipeline continues. ") | 
 | 310 |     } | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 311 | } | 
 | 312 |  | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 313 | def enforceState(saltId, target, state, output = true, failOnError = true, batch = null, optional = false, read_timeout=-1, retries=-1, queue=true, saltArgs = [], minionRestartWaitTimeout=10, retries_wait=5) { | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 314 | // Deprecated, convert state to use Map as input parameter | 
 | 315 |     def common = new com.mirantis.mk.Common() | 
| Martin Polreich | 4928869 | 2018-12-20 15:41:04 +0100 | [diff] [blame] | 316 |     common.infoMsg("This method will be deprecated. Convert you method call to use Map as input parameter") | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 317 |     // Convert to Map | 
| Martin Polreich | f67b39a | 2019-02-08 10:06:34 +0100 | [diff] [blame] | 318 |     params = ['saltId': saltId, 'target': target, 'state': state, 'output': output, 'failOnError': failOnError, | 
 | 319 |                 'batch': batch, 'optional': optional, 'read_timeout': read_timeout, 'retries': retries, | 
 | 320 |                 'retries_wait': retries_wait, 'queue': queue, 'saltArgs': saltArgs, 'minionRestartWaitTimeout': minionRestartWaitTimeout] | 
| Martin Polreich | c19e66b | 2018-10-22 12:22:03 +0200 | [diff] [blame] | 321 |     // Call new method with Map as parameter | 
 | 322 |     return enforceState(params) | 
 | 323 | } | 
 | 324 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 325 | /** | 
 | 326 |  * Run command on salt minion (salt cmd.run wrapper) | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 327 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 328 |  * @param target Get pillar target | 
 | 329 |  * @param cmd command | 
| Jakub Josef | 053df39 | 2017-05-03 15:51:05 +0200 | [diff] [blame] | 330 |  * @param checkResponse test command success execution (default true) | 
| Jakub Josef | 2f25cf2 | 2017-03-28 13:34:57 +0200 | [diff] [blame] | 331 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 332 |  * @param output do you want to print output | 
| chnyda | 205a92b | 2018-01-11 17:07:32 +0100 | [diff] [blame] | 333 |  * @param saltArgs additional salt args eq. ["runas=aptly"] | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 334 |  * @return output of salt command | 
 | 335 |  */ | 
| chnyda | 205a92b | 2018-01-11 17:07:32 +0100 | [diff] [blame] | 336 | def cmdRun(saltId, target, cmd, checkResponse = true, batch=null, output = true, saltArgs = []) { | 
| Tomáš Kukrál | 6c04bd0 | 2017-03-01 22:18:52 +0100 | [diff] [blame] | 337 |     def common = new com.mirantis.mk.Common() | 
| Jakub Josef | 053df39 | 2017-05-03 15:51:05 +0200 | [diff] [blame] | 338 |     def originalCmd = cmd | 
| Tomáš Kukrál | dfd4b49 | 2017-03-02 12:08:50 +0100 | [diff] [blame] | 339 |     common.infoMsg("Running command ${cmd} on ${target}") | 
| Jakub Josef | 053df39 | 2017-05-03 15:51:05 +0200 | [diff] [blame] | 340 |     if (checkResponse) { | 
 | 341 |       cmd = cmd + " && echo Salt command execution success" | 
 | 342 |     } | 
| chnyda | 205a92b | 2018-01-11 17:07:32 +0100 | [diff] [blame] | 343 |  | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 344 |     // add cmd name to salt args list | 
| chnyda | 205a92b | 2018-01-11 17:07:32 +0100 | [diff] [blame] | 345 |     saltArgs << cmd | 
 | 346 |  | 
 | 347 |     def out = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'cmd.run', batch, saltArgs.reverse()) | 
| Jakub Josef | 053df39 | 2017-05-03 15:51:05 +0200 | [diff] [blame] | 348 |     if (checkResponse) { | 
 | 349 |         // iterate over all affected nodes and check success return code | 
| Jiri Broulik | 16e9ce7 | 2017-05-17 13:28:31 +0200 | [diff] [blame] | 350 |         if (out["return"]){ | 
 | 351 |             for(int i=0;i<out["return"].size();i++){ | 
 | 352 |                 def node = out["return"][i]; | 
| Jakub Josef | 053df39 | 2017-05-03 15:51:05 +0200 | [diff] [blame] | 353 |                 for(int j=0;j<node.size();j++){ | 
 | 354 |                     def nodeKey = node.keySet()[j] | 
| Martin Polreich | a2effb8 | 2018-08-01 11:35:11 +0200 | [diff] [blame] | 355 |                     if (node[nodeKey] instanceof String) { | 
 | 356 |                         if (!node[nodeKey].contains("Salt command execution success")) { | 
 | 357 |                             throw new Exception("Execution of cmd ${originalCmd} failed. Server returns: ${node[nodeKey]}") | 
 | 358 |                         } | 
 | 359 |                     } else if (node[nodeKey] instanceof Boolean) { | 
 | 360 |                         if (!node[nodeKey]) { | 
 | 361 |                             throw new Exception("Execution of cmd ${originalCmd} failed. Server returns: ${node[nodeKey]}") | 
 | 362 |                         } | 
 | 363 |                     } else { | 
 | 364 |                         throw new Exception("Execution of cmd ${originalCmd} failed. Server returns unexpected data type: ${node[nodeKey]}") | 
| Jakub Josef | 053df39 | 2017-05-03 15:51:05 +0200 | [diff] [blame] | 365 |                     } | 
 | 366 |                 } | 
 | 367 |             } | 
| Martin Polreich | a2effb8 | 2018-08-01 11:35:11 +0200 | [diff] [blame] | 368 |         } else { | 
| Jakub Josef | 053df39 | 2017-05-03 15:51:05 +0200 | [diff] [blame] | 369 |             throw new Exception("Salt Api response doesn't have return param!") | 
 | 370 |         } | 
 | 371 |     } | 
| Jiri Broulik | 16e9ce7 | 2017-05-17 13:28:31 +0200 | [diff] [blame] | 372 |     if (output == true) { | 
 | 373 |         printSaltCommandResult(out) | 
 | 374 |     } | 
 | 375 |     return out | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 376 | } | 
 | 377 |  | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 378 | /** | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 379 |  * Checks if salt minion is in a list of salt master's accepted keys | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 380 |  * @usage minionPresent(saltId, 'I@salt:master', 'ntw', true, null, true, 200, 3) | 
 | 381 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 382 |  * @param target Get pillar target | 
 | 383 |  * @param minion_name unique identification of a minion in salt-key command output | 
 | 384 |  * @param waitUntilPresent return after the minion becomes present (default true) | 
 | 385 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
 | 386 |  * @param output print salt command (default true) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 387 |  * @param maxRetries finite number of iterations to check status of a command (default 200) | 
 | 388 |  * @param answers how many minions should return (optional, default 1) | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 389 |  * @return output of salt command | 
 | 390 |  */ | 
| lmercl | 9418927 | 2018-06-01 11:03:46 +0200 | [diff] [blame] | 391 | def minionPresent(saltId, target, minion_name, waitUntilPresent = true, batch=null, output = true, maxRetries = 180, answers = 1) { | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 392 |     minion_name = minion_name.replace("*", "") | 
 | 393 |     def common = new com.mirantis.mk.Common() | 
| Dzmitry Stremkouski | 8a8d56b | 2018-05-01 12:20:04 +0200 | [diff] [blame] | 394 |     common.infoMsg("Looking for minion: " + minion_name) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 395 |     def cmd = 'salt-key | grep ' + minion_name | 
 | 396 |     if (waitUntilPresent){ | 
 | 397 |         def count = 0 | 
 | 398 |         while(count < maxRetries) { | 
| Dzmitry Stremkouski | 5425d23 | 2018-06-07 00:46:00 +0200 | [diff] [blame] | 399 |             try { | 
 | 400 |                 def out = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'cmd.shell', batch, [cmd], null, 5) | 
 | 401 |                 if (output) { | 
 | 402 |                     printSaltCommandResult(out) | 
 | 403 |                 } | 
 | 404 |                 def valueMap = out["return"][0] | 
 | 405 |                 def result = valueMap.get(valueMap.keySet()[0]) | 
 | 406 |                 def resultsArray = result.tokenize("\n") | 
 | 407 |                 def size = resultsArray.size() | 
 | 408 |                 if (size >= answers) { | 
 | 409 |                     return out | 
 | 410 |                 } | 
 | 411 |                 count++ | 
 | 412 |                 sleep(time: 1000, unit: 'MILLISECONDS') | 
 | 413 |                 common.infoMsg("Waiting for ${cmd} on ${target} to be in correct state") | 
 | 414 |             } catch (Exception er) { | 
 | 415 |                 common.infoMsg('[WARNING]: runSaltCommand command read timeout within 5 seconds. You have very slow or broken environment') | 
 | 416 |             } | 
 | 417 |         } | 
 | 418 |     } else { | 
 | 419 |         try { | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 420 |             def out = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'cmd.shell', batch, [cmd], null, 5) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 421 |             if (output) { | 
 | 422 |                 printSaltCommandResult(out) | 
 | 423 |             } | 
| Dzmitry Stremkouski | 5425d23 | 2018-06-07 00:46:00 +0200 | [diff] [blame] | 424 |             return out | 
 | 425 |         } catch (Exception er) { | 
 | 426 |             common.infoMsg('[WARNING]: runSaltCommand command read timeout within 5 seconds. You have very slow or broken environment') | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 427 |         } | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 428 |     } | 
 | 429 |     // otherwise throw exception | 
 | 430 |     common.errorMsg("Status of command ${cmd} on ${target} failed, please check it.") | 
 | 431 |     throw new Exception("${cmd} signals failure of status check!") | 
 | 432 | } | 
 | 433 |  | 
 | 434 | /** | 
| Dzmitry Stremkouski | 8a8d56b | 2018-05-01 12:20:04 +0200 | [diff] [blame] | 435 |  * Checks if salt minions are in a list of salt master's accepted keys by matching compound | 
 | 436 |  * @usage minionsPresent(saltId, 'I@salt:master', 'I@salt:minion', true, null, true, 200, 3) | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 437 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 438 |  * @param target Performs tests on this target node | 
 | 439 |  * @param target_minions all targeted minions to test (for ex. I@salt:minion) | 
 | 440 |  * @param waitUntilPresent return after the minion becomes present (default true) | 
 | 441 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
 | 442 |  * @param output print salt command (default true) | 
 | 443 |  * @param maxRetries finite number of iterations to check status of a command (default 200) | 
 | 444 |  * @param answers how many minions should return (optional, default 1) | 
 | 445 |  * @return output of salt command | 
 | 446 |  */ | 
 | 447 | def minionsPresent(saltId, target = 'I@salt:master', target_minions = '', waitUntilPresent = true, batch=null, output = true, maxRetries = 200, answers = 1) { | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 448 |     def target_hosts = getMinionsSorted(saltId, target_minions, batch) | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 449 |     for (t in target_hosts) { | 
| Dzmitry Stremkouski | 8a8d56b | 2018-05-01 12:20:04 +0200 | [diff] [blame] | 450 |         def tgt = stripDomainName(t) | 
 | 451 |         minionPresent(saltId, target, tgt, waitUntilPresent, batch, output, maxRetries, answers) | 
 | 452 |     } | 
 | 453 | } | 
 | 454 |  | 
 | 455 | /** | 
 | 456 |  * Checks if salt minions are in a list of salt master's accepted keys by matching a list | 
 | 457 |  * @usage minionsPresentFromList(saltId, 'I@salt:master', ["cfg01.example.com", "bmk01.example.com"], true, null, true, 200, 3) | 
 | 458 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 459 |  * @param target Performs tests on this target node | 
 | 460 |  * @param target_minions list to test (for ex. ["cfg01.example.com", "bmk01.example.com"]) | 
 | 461 |  * @param waitUntilPresent return after the minion becomes present (default true) | 
 | 462 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
 | 463 |  * @param output print salt command (default true) | 
 | 464 |  * @param maxRetries finite number of iterations to check status of a command (default 200) | 
 | 465 |  * @param answers how many minions should return (optional, default 1) | 
 | 466 |  * @return output of salt command | 
 | 467 |  */ | 
 | 468 | def minionsPresentFromList(saltId, target = 'I@salt:master', target_minions = [], waitUntilPresent = true, batch=null, output = true, maxRetries = 200, answers = 1) { | 
 | 469 |     def common = new com.mirantis.mk.Common() | 
 | 470 |     for (tgt in target_minions) { | 
 | 471 |         common.infoMsg("Checking if minion " + tgt + " is present") | 
 | 472 |         minionPresent(saltId, target, tgt, waitUntilPresent, batch, output, maxRetries, answers) | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 473 |     } | 
 | 474 | } | 
 | 475 |  | 
 | 476 | /** | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 477 |  * You can call this function when salt-master already contains salt keys of the target_nodes | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 478 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 479 |  * @param target Should always be salt-master | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 480 |  * @param targetNodes unique identification of a minion or group of salt minions | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 481 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 482 |  * @param cmdTimeout timeout for the salt command if minions do not return (default 10) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 483 |  * @param maxRetries finite number of iterations to check status of a command (default 200) | 
 | 484 |  * @return output of salt command | 
 | 485 |  */ | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 486 |  | 
 | 487 | def minionsReachable(saltId, target, targetNodes, batch=null, cmdTimeout = 10, maxRetries = 200) { | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 488 |     def common = new com.mirantis.mk.Common() | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 489 |     def cmd = "salt -t${cmdTimeout} -C '${targetNodes}' test.ping" | 
 | 490 |     common.infoMsg("Checking if all ${targetNodes} minions are reachable") | 
 | 491 |     def retriesCount = 0 | 
 | 492 |     while(retriesCount < maxRetries) { | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 493 |         Calendar timeout = Calendar.getInstance(); | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 494 |         timeout.add(Calendar.SECOND, cmdTimeout); | 
 | 495 |         def out = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'cmd.shell', batch, [cmd], null, cmdTimeout) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 496 |         Calendar current = Calendar.getInstance(); | 
 | 497 |         if (current.getTime().before(timeout.getTime())) { | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 498 |             common.infoMsg("Successful response received from all targeted nodes.") | 
 | 499 |             printSaltCommandResult(out) | 
 | 500 |             return out | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 501 |         } | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 502 |         def outYaml = readYaml text: getReturnValues(out) | 
 | 503 |         def successfulNodes = [] | 
 | 504 |         def failedNodes = [] | 
 | 505 |         for (node in outYaml.keySet()) { | 
 | 506 |             if (outYaml[node] == true || outYaml[node].toString().toLowerCase() == 'true') { | 
 | 507 |                 successfulNodes.add(node) | 
 | 508 |             } else { | 
 | 509 |                 failedNodes.add(node) | 
 | 510 |             } | 
 | 511 |         } | 
 | 512 |         common.infoMsg("Not all of the targeted minions returned yet. Successful response from ${successfulNodes}. Still waiting for ${failedNodes}.") | 
 | 513 |         retriesCount++ | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 514 |         sleep(time: 500, unit: 'MILLISECONDS') | 
 | 515 |     } | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 516 | } | 
 | 517 |  | 
| Martin Polreich | 712d85c | 2019-07-01 17:21:12 +0200 | [diff] [blame] | 518 |  | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 519 | /** | 
| Denis Egorenko | a1edaba | 2019-02-27 19:19:12 +0400 | [diff] [blame] | 520 |  * You can call this function when need to check that all minions are available, free and ready for command execution | 
 | 521 |  * @param config LinkedHashMap config parameter, which contains next: | 
 | 522 |  *   @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 523 |  *   @param target unique identification of a minion or group of salt minions | 
 | 524 |  *   @param target_reachable unique identification of a minion or group of salt minions to check availability | 
 | 525 |  *   @param wait timeout between retries to check target minions (default 5) | 
 | 526 |  *   @param retries finite number of iterations to check minions (default 10) | 
 | 527 |  *   @param timeout timeout for the salt command if minions do not return (default 5) | 
 | 528 |  *   @param availability check that minions also are available before checking readiness (default true) | 
 | 529 |  */ | 
 | 530 | def checkTargetMinionsReady(LinkedHashMap config) { | 
 | 531 |     def common = new com.mirantis.mk.Common() | 
 | 532 |     def saltId = config.get('saltId') | 
 | 533 |     def target = config.get('target') | 
 | 534 |     def target_reachable = config.get('target_reachable', target) | 
 | 535 |     def wait = config.get('wait', 30) | 
 | 536 |     def retries = config.get('retries', 10) | 
 | 537 |     def timeout = config.get('timeout', 5) | 
 | 538 |     def checkAvailability = config.get('availability', true) | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 539 |     def batch = config.get('batch', null) | 
| Denis Egorenko | a1edaba | 2019-02-27 19:19:12 +0400 | [diff] [blame] | 540 |     common.retry(retries, wait) { | 
 | 541 |         if (checkAvailability) { | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 542 |             minionsReachable(saltId, 'I@salt:master', target_reachable, batch) | 
| Denis Egorenko | a1edaba | 2019-02-27 19:19:12 +0400 | [diff] [blame] | 543 |         } | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 544 |         def running = runSaltProcessStep(saltId, target, 'saltutil.running', [], batch, true, timeout) | 
| Denis Egorenko | a1edaba | 2019-02-27 19:19:12 +0400 | [diff] [blame] | 545 |         for (value in running.get("return")[0].values()) { | 
 | 546 |             if (value != []) { | 
 | 547 |                 throw new Exception("Not all salt-minions are ready for execution") | 
 | 548 |             } | 
 | 549 |         } | 
 | 550 |     } | 
 | 551 | } | 
 | 552 |  | 
 | 553 | /** | 
| Vasyl Saienko | 121f34c | 2019-06-24 23:39:32 +0300 | [diff] [blame] | 554 |  * Restart and wait for salt-minions on target nodes. | 
 | 555 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 556 |  * @param target unique identification of a minion or group of salt minions | 
| Ivan Berezovskiy | 768dabd | 2019-08-01 15:52:44 +0400 | [diff] [blame] | 557 |  * @param wait timeout for the salt command if minions do not return (default 10) | 
 | 558 |  * @param maxRetries finite number of iterations to check status of a command (default 15) | 
 | 559 |  * @param async Run salt minion restart and do not wait for response | 
| Vasyl Saienko | 121f34c | 2019-06-24 23:39:32 +0300 | [diff] [blame] | 560 |  * @return output of salt command | 
 | 561 |  */ | 
| Ivan Berezovskiy | 768dabd | 2019-08-01 15:52:44 +0400 | [diff] [blame] | 562 | def restartSaltMinion(saltId, target, wait = 10, maxRetries = 15, async = true) { | 
| Vasyl Saienko | 121f34c | 2019-06-24 23:39:32 +0300 | [diff] [blame] | 563 |     def common = new com.mirantis.mk.Common() | 
 | 564 |     common.infoMsg("Restarting salt-minion on ${target} and waiting for they are reachable.") | 
| Ivan Berezovskiy | 768dabd | 2019-08-01 15:52:44 +0400 | [diff] [blame] | 565 |     runSaltProcessStep(saltId, target, 'cmd.shell', ['salt-call service.restart salt-minion'], null, true, 60, null, async) | 
 | 566 |     checkTargetMinionsReady(['saltId': saltId, 'target': target, timeout: wait, retries: maxRetries]) | 
| Vasyl Saienko | 121f34c | 2019-06-24 23:39:32 +0300 | [diff] [blame] | 567 |     common.infoMsg("All ${target} minions are alive...") | 
 | 568 | } | 
 | 569 |  | 
 | 570 | /** | 
 | 571 |  * Upgrade package and restart salt minion. | 
 | 572 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 573 |  * @param target unique identification of a minion or group of salt minions | 
 | 574 |  * @param the name of pkg_name to upgrade | 
 | 575 |  * @param wait timeout for the salt command if minions do not return (default 5) | 
 | 576 |  * @param maxRetries finite number of iterations to check status of a command (default 10) | 
 | 577 |  * @return output of salt command | 
 | 578 |  */ | 
 | 579 | def upgradePackageAndRestartSaltMinion(saltId, target, pkg_name, wait = 5, maxRetries = 10) { | 
 | 580 |     def common = new com.mirantis.mk.Common() | 
 | 581 |     def latest_version = getReturnValues(runSaltProcessStep(saltId, target, 'pkg.latest_version', [pkg_name, 'show_installed=True'])).split('\n')[0] | 
 | 582 |     def current_version = getReturnValues(runSaltProcessStep(saltId, target, 'pkg.version', [pkg_name])).split('\n')[0] | 
 | 583 |     if (current_version && latest_version != current_version) { | 
 | 584 |         common.infoMsg("Upgrading current ${pkg_name}: ${current_version} to ${latest_version}") | 
 | 585 |         runSaltProcessStep(saltId, target, 'pkg.install', [pkg_name], 'only_upgrade=True') | 
 | 586 |         restartSaltMinion(saltId, target, wait, maxRetries) | 
 | 587 |     } | 
 | 588 | } | 
 | 589 |  | 
 | 590 | /** | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 591 |  * Run command on salt minion (salt cmd.run wrapper) | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 592 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 593 |  * @param target Get pillar target | 
 | 594 |  * @param cmd name of a service | 
 | 595 |  * @param correct_state string that command must contain if status is in correct state (optional, default 'running') | 
| Jiri Broulik | cf1f233 | 2017-07-25 11:30:03 +0200 | [diff] [blame] | 596 |  * @param find bool value if it is suppose to find some string in the output or the cmd should return empty string (optional, default true) | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 597 |  * @param waitUntilOk return after the minion becomes present (optional, default true) | 
 | 598 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
 | 599 |  * @param output print salt command (default true) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 600 |  * @param maxRetries finite number of iterations to check status of a command (default 200) | 
 | 601 |  * @param answers how many minions should return (optional, default 0) | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 602 |  * @return output of salt command | 
 | 603 |  */ | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 604 | def commandStatus(saltId, target, cmd, correct_state='running', find = true, waitUntilOk = true, batch=null, output = true, maxRetries = 200, answers = 0) { | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 605 |     def common = new com.mirantis.mk.Common() | 
 | 606 |     common.infoMsg("Checking if status of verification command ${cmd} on ${target} is in correct state") | 
 | 607 |     if (waitUntilOk){ | 
 | 608 |         def count = 0 | 
 | 609 |         while(count < maxRetries) { | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 610 |             def out = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'cmd.shell', batch, [cmd], null, 5) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 611 |             if (output) { | 
 | 612 |                 printSaltCommandResult(out) | 
 | 613 |             } | 
| Jakub Josef | 115a78f | 2017-07-18 15:04:00 +0200 | [diff] [blame] | 614 |             def resultMap = out["return"][0] | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 615 |             def success = 0 | 
 | 616 |             if (answers == 0){ | 
 | 617 |                 answers = resultMap.size() | 
 | 618 |             } | 
 | 619 |             for (int i=0;i<answers;i++) { | 
 | 620 |                 result = resultMap.get(resultMap.keySet()[i]) | 
 | 621 |                 // if the goal is to find some string in output of the command | 
 | 622 |                 if (find) { | 
 | 623 |                     if(result == null || result instanceof Boolean || result.isEmpty()) { result='' } | 
 | 624 |                     if (result.toLowerCase().contains(correct_state.toLowerCase())) { | 
 | 625 |                         success++ | 
 | 626 |                         if (success == answers) { | 
 | 627 |                             return out | 
 | 628 |                         } | 
| Jiri Broulik | d0c2757 | 2017-07-24 20:01:10 +0200 | [diff] [blame] | 629 |                     } | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 630 |                 // else the goal is to not find any string in output of the command | 
 | 631 |                 } else { | 
 | 632 |                     if(result instanceof String && result.isEmpty()) { | 
 | 633 |                         success++ | 
 | 634 |                         if (success == answers) { | 
 | 635 |                             return out | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 636 |                         } | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 637 |                     } | 
 | 638 |                 } | 
 | 639 |             } | 
 | 640 |             count++ | 
 | 641 |             sleep(time: 500, unit: 'MILLISECONDS') | 
 | 642 |             common.infoMsg("Waiting for ${cmd} on ${target} to be in correct state") | 
 | 643 |         } | 
 | 644 |     } else { | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 645 |         def out = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'cmd.shell', batch, [cmd], null, 5) | 
| Jiri Broulik | 71512bc | 2017-08-04 10:00:18 +0200 | [diff] [blame] | 646 |         def resultMap = out["return"][0] | 
 | 647 |         if (output) { | 
 | 648 |             printSaltCommandResult(out) | 
 | 649 |         } | 
 | 650 |         for (int i=0;i<resultMap.size();i++) { | 
 | 651 |             result = resultMap.get(resultMap.keySet()[i]) | 
 | 652 |         // if the goal is to find some string in output of the command | 
 | 653 |             if (find) { | 
 | 654 |                 if(result == null || result instanceof Boolean || result.isEmpty()) { result='' } | 
 | 655 |                 if (result.toLowerCase().contains(correct_state.toLowerCase())) { | 
| Jiri Broulik | d0c2757 | 2017-07-24 20:01:10 +0200 | [diff] [blame] | 656 |                     return out | 
 | 657 |                 } | 
 | 658 |  | 
 | 659 |             // else the goal is to not find any string in output of the command | 
 | 660 |             } else { | 
 | 661 |                 if(result instanceof String && result.isEmpty()) { | 
 | 662 |                     return out | 
 | 663 |                 } | 
 | 664 |             } | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 665 |         } | 
 | 666 |     } | 
 | 667 |     // otherwise throw exception | 
| Jiri Broulik | d0c2757 | 2017-07-24 20:01:10 +0200 | [diff] [blame] | 668 |     common.errorMsg("Status of command ${cmd} on ${target} failed, please check it.") | 
| Jiri Broulik | 2c69f3d | 2017-07-18 14:23:58 +0200 | [diff] [blame] | 669 |     throw new Exception("${cmd} signals failure of status check!") | 
 | 670 | } | 
 | 671 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 672 | /** | 
 | 673 |  * Perform complete salt sync between master and target | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 674 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 675 |  * @param target Get pillar target | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 676 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 677 |  * @return output of salt command | 
 | 678 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 679 | def syncAll(saltId, target, batch = null) { | 
 | 680 |     return runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'saltutil.sync_all', batch) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 681 | } | 
 | 682 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 683 | /** | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 684 |  * Perform complete salt refresh between master and target | 
 | 685 |  * Method will call saltutil.refresh_pillar, saltutil.refresh_grains and saltutil.sync_all | 
 | 686 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 687 |  * @param target Get pillar target | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 688 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 689 |  * @return output of salt command | 
 | 690 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 691 | def fullRefresh(saltId, target, batch=20){ | 
 | 692 |     runSaltProcessStep(saltId, target, 'saltutil.refresh_pillar', [], batch, true) | 
 | 693 |     runSaltProcessStep(saltId, target, 'saltutil.refresh_grains', [], batch, true) | 
 | 694 |     runSaltProcessStep(saltId, target, 'saltutil.sync_all', [], batch, true) | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 695 | } | 
 | 696 |  | 
 | 697 | /** | 
 | 698 |  * Enforce highstate on given targets | 
 | 699 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 700 |  * @param target Highstate enforcing target | 
 | 701 |  * @param excludedStates states which will be excluded from main state (default empty string) | 
 | 702 |  * @param output print output (optional, default true) | 
 | 703 |  * @param failOnError throw exception on salt state result:false (optional, default true) | 
 | 704 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
 | 705 |  * @param saltArgs additional salt args eq. ["runas=aptly", exclude="opencontrail.database"] | 
 | 706 |  * @return output of salt command | 
 | 707 |  */ | 
 | 708 | def enforceHighstateWithExclude(saltId, target, excludedStates = "", output = false, failOnError = true, batch = null, saltArgs = []) { | 
 | 709 |     saltArgs << "exclude=${excludedStates}" | 
 | 710 |     return enforceHighstate(saltId, target, output, failOnError, batch, saltArgs) | 
 | 711 | } | 
 | 712 | /** | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 713 |  * Enforce highstate on given targets | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 714 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 715 |  * @param target Highstate enforcing target | 
 | 716 |  * @param output print output (optional, default true) | 
 | 717 |  * @param failOnError throw exception on salt state result:false (optional, default true) | 
| Jakub Josef | 2f25cf2 | 2017-03-28 13:34:57 +0200 | [diff] [blame] | 718 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 719 |  * @return output of salt command | 
 | 720 |  */ | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 721 | def enforceHighstate(saltId, target, output = false, failOnError = true, batch = null, saltArgs = []) { | 
| Petr Jediný | 30be703 | 2018-05-29 18:22:46 +0200 | [diff] [blame] | 722 |     def out = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'state.highstate', batch, saltArgs) | 
| Alexander Noskov | 657ccfc | 2017-07-14 11:35:52 +0000 | [diff] [blame] | 723 |     def common = new com.mirantis.mk.Common() | 
 | 724 |  | 
| Marek Celoud | 6336611 | 2017-07-25 17:27:24 +0200 | [diff] [blame] | 725 |     common.infoMsg("Running state highstate on ${target}") | 
| Alexander Noskov | 657ccfc | 2017-07-14 11:35:52 +0000 | [diff] [blame] | 726 |  | 
| Jakub Josef | 374beb7 | 2017-04-27 15:45:09 +0200 | [diff] [blame] | 727 |     checkResult(out, failOnError, output) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 728 |     return out | 
 | 729 | } | 
 | 730 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 731 | /** | 
| Ales Komarek | 5276ebe | 2017-03-16 08:46:34 +0100 | [diff] [blame] | 732 |  * Get running minions IDs according to the target | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 733 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Ales Komarek | 5276ebe | 2017-03-16 08:46:34 +0100 | [diff] [blame] | 734 |  * @param target Get minions target | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 735 |  * @param batch Batch param to salt (integer or string with percents) | 
| Ales Komarek | 5276ebe | 2017-03-16 08:46:34 +0100 | [diff] [blame] | 736 |  * @return list of active minions fitin | 
 | 737 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 738 | def getMinions(saltId, target, batch = null) { | 
 | 739 |     def minionsRaw = runSaltCommand(saltId, 'local', ['expression': target, 'type': 'compound'], 'test.ping', batch) | 
| Ales Komarek | 5276ebe | 2017-03-16 08:46:34 +0100 | [diff] [blame] | 740 |     return new ArrayList<String>(minionsRaw['return'][0].keySet()) | 
 | 741 | } | 
 | 742 |  | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 743 | /** | 
 | 744 |  * Get sorted running minions IDs according to the target | 
 | 745 |  * @param saltId Salt Connection object or pepperEnv | 
 | 746 |  * @param target Get minions target | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 747 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 748 |  * @return list of sorted active minions fitin | 
 | 749 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 750 | def getMinionsSorted(saltId, target, batch = null) { | 
 | 751 |     return getMinions(saltId, target, batch).sort() | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 752 | } | 
 | 753 |  | 
 | 754 | /** | 
 | 755 |  * Get first out of running minions IDs according to the target | 
 | 756 |  * @param saltId Salt Connection object or pepperEnv | 
 | 757 |  * @param target Get minions target | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 758 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 759 |  * @return first of active minions fitin | 
 | 760 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 761 | def getFirstMinion(saltId, target, batch = null) { | 
 | 762 |     def minionsSorted = getMinionsSorted(saltId, target, batch) | 
| Dmitry Ukov | d72cd2a | 2018-09-04 17:31:46 +0400 | [diff] [blame] | 763 |     return minionsSorted[0] | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 764 | } | 
 | 765 |  | 
 | 766 | /** | 
 | 767 |  * Get running salt minions IDs without it's domain name part and its numbering identifications | 
 | 768 |  * @param saltId Salt Connection object or pepperEnv | 
 | 769 |  * @param target Get minions target | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 770 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 771 |  * @return list of active minions fitin without it's domain name part name numbering | 
 | 772 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 773 | def getMinionsGeneralName(saltId, target, batch = null) { | 
 | 774 |     def minionsSorted = getMinionsSorted(saltId, target, batch) | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 775 |     return stripDomainName(minionsSorted[0]).replaceAll('\\d+$', "") | 
 | 776 | } | 
 | 777 |  | 
 | 778 | /** | 
 | 779 |  * Get domain name of the env | 
 | 780 |  * @param saltId Salt Connection object or pepperEnv | 
 | 781 |  * @return domain name | 
 | 782 |  */ | 
 | 783 | def getDomainName(saltId) { | 
 | 784 |     return getReturnValues(getPillar(saltId, 'I@salt:master', '_param:cluster_domain')) | 
 | 785 | } | 
 | 786 |  | 
 | 787 | /** | 
 | 788 |  * Remove domain name from Salt minion ID | 
 | 789 |  * @param name String of Salt minion ID | 
 | 790 |  * @return Salt minion ID without its domain name | 
 | 791 |  */ | 
 | 792 | def stripDomainName(name) { | 
 | 793 |     return name.split("\\.")[0] | 
 | 794 | } | 
 | 795 |  | 
 | 796 | /** | 
 | 797 |  * Gets return values of a salt command | 
 | 798 |  * @param output String of Salt minion ID | 
 | 799 |  * @return Return values of a salt command | 
 | 800 |  */ | 
 | 801 | def getReturnValues(output) { | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 802 |     if(output && output.containsKey("return") && !output.get("return").isEmpty()) { | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 803 |         return output['return'][0].values()[0] | 
 | 804 |     } | 
 | 805 |     def common = new com.mirantis.mk.Common() | 
 | 806 |     common.errorMsg('output does not contain return key') | 
 | 807 |     return '' | 
 | 808 | } | 
 | 809 |  | 
 | 810 | /** | 
 | 811 |  * Get minion ID of one of KVM nodes | 
 | 812 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 813 |  * @return Salt minion ID of one of KVM nodes in env | 
 | 814 |  */ | 
 | 815 | def getKvmMinionId(saltId) { | 
 | 816 |     return getReturnValues(getGrain(saltId, 'I@salt:control', 'id')).values()[0] | 
 | 817 | } | 
 | 818 |  | 
 | 819 | /** | 
 | 820 |  * Get Salt minion ID of KVM node hosting 'name' VM | 
 | 821 |  * @param saltId Salt Connection object or pepperEnv | 
 | 822 |  * @param name Name of the VM (for ex. ctl01) | 
 | 823 |  * @return Salt minion ID of KVM node hosting 'name' VM | 
 | 824 |  */ | 
| Jiri Broulik | d2a5055 | 2018-04-25 17:17:59 +0200 | [diff] [blame] | 825 | def getNodeProvider(saltId, nodeName) { | 
 | 826 |     def salt = new com.mirantis.mk.Salt() | 
 | 827 |     def common = new com.mirantis.mk.Common() | 
 | 828 |     def kvms = salt.getMinions(saltId, 'I@salt:control') | 
 | 829 |     for (kvm in kvms) { | 
 | 830 |         try { | 
 | 831 |             vms = salt.getReturnValues(salt.runSaltProcessStep(saltId, kvm, 'virt.list_domains', [], null, true)) | 
 | 832 |             if (vms.toString().contains(nodeName)) { | 
 | 833 |                 return kvm | 
 | 834 |             } | 
 | 835 |         } catch (Exception er) { | 
 | 836 |             common.infoMsg("${nodeName} not present on ${kvm}") | 
 | 837 |         } | 
 | 838 |     } | 
| Jiri Broulik | f8f9694 | 2018-02-15 10:03:42 +0100 | [diff] [blame] | 839 | } | 
 | 840 |  | 
| Ales Komarek | 5276ebe | 2017-03-16 08:46:34 +0100 | [diff] [blame] | 841 | /** | 
| Tomáš Kukrál | b12ff9f | 2017-07-12 12:32:34 +0200 | [diff] [blame] | 842 |  * Test if there are any minions to target | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 843 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Tomáš Kukrál | b12ff9f | 2017-07-12 12:32:34 +0200 | [diff] [blame] | 844 |  * @param target Target to test | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 845 |  * @param batch Batch param to salt (integer or string with percents) | 
| vrovachev | 1c4770b | 2017-07-05 13:25:21 +0400 | [diff] [blame] | 846 |  * @return bool indicating if target was succesful | 
| Tomáš Kukrál | b12ff9f | 2017-07-12 12:32:34 +0200 | [diff] [blame] | 847 |  */ | 
 | 848 |  | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 849 | def testTarget(saltId, target, batch = null) { | 
 | 850 |     return getMinions(saltId, target, batch).size() > 0 | 
| Tomáš Kukrál | b12ff9f | 2017-07-12 12:32:34 +0200 | [diff] [blame] | 851 | } | 
 | 852 |  | 
 | 853 | /** | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 854 |  * Generates node key using key.gen_accept call | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 855 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 856 |  * @param target Key generating target | 
 | 857 |  * @param host Key generating host | 
 | 858 |  * @param keysize generated key size (optional, default 4096) | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 859 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 860 |  * @return output of salt command | 
 | 861 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 862 | def generateNodeKey(saltId, target, host, keysize = 4096, batch = null) { | 
 | 863 |     return runSaltCommand(saltId, 'wheel', target, 'key.gen_accept', batch, [host], ['keysize': keysize]) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 864 | } | 
 | 865 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 866 | /** | 
| Jakub Josef | 2f25cf2 | 2017-03-28 13:34:57 +0200 | [diff] [blame] | 867 |  * Generates node reclass metadata | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 868 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 869 |  * @param target Metadata generating target | 
 | 870 |  * @param host Metadata generating host | 
 | 871 |  * @param classes Reclass classes | 
 | 872 |  * @param parameters Reclass parameters | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 873 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 874 |  * @return output of salt command | 
 | 875 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 876 | def generateNodeMetadata(saltId, target, host, classes, parameters, batch = null) { | 
 | 877 |     return runSaltCommand(saltId, 'local', target, 'reclass.node_create', batch, [host, '_generated'], ['classes': classes, 'parameters': parameters]) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 878 | } | 
 | 879 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 880 | /** | 
 | 881 |  * Run salt orchestrate on given targets | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 882 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 883 |  * @param target Orchestration target | 
 | 884 |  * @param orchestrate Salt orchestrate params | 
| Dzmitry Stremkouski | dd020d9 | 2018-07-22 12:01:07 +0200 | [diff] [blame] | 885 |  * @param kwargs Salt orchestrate params | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 886 |  * @param batch Batch param to salt (integer or string with percents) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 887 |  * @return output of salt command | 
 | 888 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 889 | def orchestrateSystem(saltId, target, orchestrate=[], kwargs = null, batch = null) { | 
| Oleksii Grudev | 9e1d97a | 2018-06-29 16:04:30 +0300 | [diff] [blame] | 890 |     //Since the runSaltCommand uses "arg" (singular) for "runner" client this won`t work correctly on old salt 2016 | 
 | 891 |     //cause this version of salt used "args" (plural) for "runner" client, see following link for reference: | 
 | 892 |     //https://github.com/saltstack/salt/pull/32938 | 
| Oleksii Grudev | 0c09838 | 2018-08-08 16:16:38 +0300 | [diff] [blame] | 893 |     def common = new com.mirantis.mk.Common() | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 894 |     def result = runSaltCommand(saltId, 'runner', target, 'state.orchestrate', batch, orchestrate, kwargs, 7200, 7200) | 
| Oleksii Grudev | 0c09838 | 2018-08-08 16:16:38 +0300 | [diff] [blame] | 895 |         if(result != null){ | 
 | 896 |             if(result['return']){ | 
 | 897 |                 def retcode = result['return'][0].get('retcode') | 
 | 898 |                 if (retcode != 0) { | 
 | 899 |                     throw new Exception("Orchestration state failed while running: "+orchestrate) | 
 | 900 |                 }else{ | 
 | 901 |                     common.infoMsg("Orchestrate state "+orchestrate+" succeeded") | 
 | 902 |                 } | 
 | 903 |             }else{ | 
 | 904 |                 common.errorMsg("Salt result has no return attribute! Result: ${result}") | 
 | 905 |             } | 
 | 906 |         }else{ | 
 | 907 |             common.errorMsg("Cannot check salt result, given result is null") | 
 | 908 |         } | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 909 | } | 
 | 910 |  | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 911 | /** | 
| Dzmitry Stremkouski | b544070 | 2018-07-22 13:00:05 +0200 | [diff] [blame] | 912 |  * Run salt pre or post orchestrate tasks | 
 | 913 |  * | 
 | 914 |  * @param  saltId       Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 915 |  * @param  pillar_tree  Reclass pillar that has orchestrate pillar for desired stage | 
 | 916 |  * @param  extra_tgt    Extra targets for compound | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 917 |  * @param batch Batch param to salt (integer or string with percents) | 
| Dzmitry Stremkouski | b544070 | 2018-07-22 13:00:05 +0200 | [diff] [blame] | 918 |  * @return              output of salt command | 
 | 919 |  */ | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 920 | def orchestratePrePost(saltId, pillar_tree, extra_tgt = '', batch = null) { | 
| Dzmitry Stremkouski | b544070 | 2018-07-22 13:00:05 +0200 | [diff] [blame] | 921 |  | 
 | 922 |     def common = new com.mirantis.mk.Common() | 
 | 923 |     def salt = new com.mirantis.mk.Salt() | 
 | 924 |     def compound = 'I@' + pillar_tree + " " + extra_tgt | 
 | 925 |  | 
 | 926 |     common.infoMsg("Refreshing pillars") | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 927 |     runSaltProcessStep(saltId, '*', 'saltutil.refresh_pillar', [], batch, true) | 
| Dzmitry Stremkouski | b544070 | 2018-07-22 13:00:05 +0200 | [diff] [blame] | 928 |  | 
 | 929 |     common.infoMsg("Looking for orchestrate pillars") | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 930 |     if (salt.testTarget(saltId, compound, batch)) { | 
 | 931 |         for ( node in salt.getMinionsSorted(saltId, compound, batch) ) { | 
| Dzmitry Stremkouski | b544070 | 2018-07-22 13:00:05 +0200 | [diff] [blame] | 932 |             def pillar = salt.getPillar(saltId, node, pillar_tree) | 
 | 933 |             if ( !pillar['return'].isEmpty() ) { | 
 | 934 |                 for ( orch_id in pillar['return'][0].values() ) { | 
 | 935 |                     def orchestrator = orch_id.values()['orchestrator'] | 
 | 936 |                     def orch_enabled = orch_id.values()['enabled'] | 
 | 937 |                     if ( orch_enabled ) { | 
 | 938 |                         common.infoMsg("Orchestrating: ${orchestrator}") | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 939 |                         salt.printSaltCommandResult(salt.orchestrateSystem(saltId, ['expression': node], [orchestrator], null, batch)) | 
| Dzmitry Stremkouski | b544070 | 2018-07-22 13:00:05 +0200 | [diff] [blame] | 940 |                     } | 
 | 941 |                 } | 
 | 942 |             } | 
 | 943 |         } | 
 | 944 |     } | 
 | 945 | } | 
 | 946 |  | 
 | 947 | /** | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 948 |  * Run salt process step | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 949 |  * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 950 |  * @param tgt Salt process step target | 
 | 951 |  * @param fun Salt process step function | 
 | 952 |  * @param arg process step arguments (optional, default []) | 
| Ivan Berezovskiy | 768dabd | 2019-08-01 15:52:44 +0400 | [diff] [blame] | 953 |  * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch). Can't be used with async | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 954 |  * @param output print output (optional, default true) | 
| Jiri Broulik | 48544be | 2017-06-14 18:33:54 +0200 | [diff] [blame] | 955 |  * @param timeout  Additional argument salt api timeout | 
| Ivan Berezovskiy | 768dabd | 2019-08-01 15:52:44 +0400 | [diff] [blame] | 956 |  * @param async Run the salt command but don't wait for a reply. Can't be used with batch | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 957 |  * @return output of salt command | 
 | 958 |  */ | 
| Ivan Berezovskiy | 768dabd | 2019-08-01 15:52:44 +0400 | [diff] [blame] | 959 | def runSaltProcessStep(saltId, tgt, fun, arg = [], batch = null, output = true, timeout = -1, kwargs = null, async = false) { | 
| Tomáš Kukrál | 6c04bd0 | 2017-03-01 22:18:52 +0100 | [diff] [blame] | 960 |     def common = new com.mirantis.mk.Common() | 
| Jiri Broulik | 48544be | 2017-06-14 18:33:54 +0200 | [diff] [blame] | 961 |     def salt = new com.mirantis.mk.Salt() | 
| Tomáš Kukrál | adb4ecd | 2017-03-02 10:06:36 +0100 | [diff] [blame] | 962 |     def out | 
 | 963 |  | 
| Marek Celoud | 6336611 | 2017-07-25 17:27:24 +0200 | [diff] [blame] | 964 |     common.infoMsg("Running step ${fun} ${arg} on ${tgt}") | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 965 |     if (async == true) { | 
 | 966 |         out = runSaltCommand(saltId, 'local_async', ['expression': tgt, 'type': 'compound'], fun, null, arg, kwargs, timeout) | 
| Tomáš Kukrál | adb4ecd | 2017-03-02 10:06:36 +0100 | [diff] [blame] | 967 |     } else { | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 968 |         out = runSaltCommand(saltId, 'local', ['expression': tgt, 'type': 'compound'], fun, batch, arg, kwargs, timeout) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 969 |     } | 
| Tomáš Kukrál | adb4ecd | 2017-03-02 10:06:36 +0100 | [diff] [blame] | 970 |  | 
| Tomáš Kukrál | f5dda64 | 2017-03-02 14:22:59 +0100 | [diff] [blame] | 971 |     if (output == true) { | 
| Jiri Broulik | 48544be | 2017-06-14 18:33:54 +0200 | [diff] [blame] | 972 |         salt.printSaltCommandResult(out) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 973 |     } | 
| Jiri Broulik | ae19c26 | 2017-05-16 19:06:52 +0200 | [diff] [blame] | 974 |     return out | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 975 | } | 
 | 976 |  | 
 | 977 | /** | 
 | 978 |  * Check result for errors and throw exception if any found | 
 | 979 |  * | 
 | 980 |  * @param result    Parsed response of Salt API | 
| Jakub Josef | 8021c00 | 2017-03-27 15:41:28 +0200 | [diff] [blame] | 981 |  * @param failOnError Do you want to throw exception if salt-call fails (optional, default true) | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 982 |  * @param printResults Do you want to print salt results (optional, default true) | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 983 |  * @param printOnlyChanges If true (default), print only changed resources | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 984 |  * @param disableAskOnError Flag for disabling ASK_ON_ERROR feature (optional, default false) | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 985 |  */ | 
| Jakub Josef | 432e9d9 | 2018-02-06 18:28:37 +0100 | [diff] [blame] | 986 | def checkResult(result, failOnError = true, printResults = true, printOnlyChanges = true, disableAskOnError = false) { | 
| Jakub Josef | 5ade54c | 2017-03-10 16:14:01 +0100 | [diff] [blame] | 987 |     def common = new com.mirantis.mk.Common() | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 988 |     if(result != null){ | 
 | 989 |         if(result['return']){ | 
 | 990 |             for (int i=0;i<result['return'].size();i++) { | 
 | 991 |                 def entry = result['return'][i] | 
 | 992 |                 if (!entry) { | 
 | 993 |                     if (failOnError) { | 
 | 994 |                         throw new Exception("Salt API returned empty response: ${result}") | 
 | 995 |                     } else { | 
 | 996 |                         common.errorMsg("Salt API returned empty response: ${result}") | 
| Jakub Josef | ece32af | 2017-03-14 19:20:08 +0100 | [diff] [blame] | 997 |                     } | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 998 |                 } | 
 | 999 |                 for (int j=0;j<entry.size();j++) { | 
 | 1000 |                     def nodeKey = entry.keySet()[j] | 
 | 1001 |                     def node=entry[nodeKey] | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1002 |                     def outputResources = [] | 
| Jakub Josef | 4714594 | 2018-04-04 17:30:38 +0200 | [diff] [blame] | 1003 |                     def errorResources = [] | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1004 |                     common.infoMsg("Node ${nodeKey} changes:") | 
 | 1005 |                     if(node instanceof Map || node instanceof List){ | 
 | 1006 |                         for (int k=0;k<node.size();k++) { | 
 | 1007 |                             def resource; | 
 | 1008 |                             def resKey; | 
 | 1009 |                             if(node instanceof Map){ | 
 | 1010 |                                 resKey = node.keySet()[k] | 
| Victor Ryzhenkin | 49d6781 | 2019-01-09 15:28:21 +0400 | [diff] [blame] | 1011 |                                 if (resKey == "retcode") { | 
| Richard Felkl | d9476ac | 2018-07-12 19:01:33 +0200 | [diff] [blame] | 1012 |                                     continue | 
| Victor Ryzhenkin | 49d6781 | 2019-01-09 15:28:21 +0400 | [diff] [blame] | 1013 |                                 } | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1014 |                             }else if(node instanceof List){ | 
 | 1015 |                                 resKey = k | 
 | 1016 |                             } | 
 | 1017 |                             resource = node[resKey] | 
| Jakub Josef | c4c4020 | 2017-04-28 12:04:24 +0200 | [diff] [blame] | 1018 |                            // print | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1019 |                             if(printResults){ | 
 | 1020 |                                 if(resource instanceof Map && resource.keySet().contains("result")){ | 
 | 1021 |                                     //clean unnesaccary fields | 
 | 1022 |                                     if(resource.keySet().contains("__run_num__")){ | 
 | 1023 |                                         resource.remove("__run_num__") | 
 | 1024 |                                     } | 
 | 1025 |                                     if(resource.keySet().contains("__id__")){ | 
 | 1026 |                                         resource.remove("__id__") | 
 | 1027 |                                     } | 
 | 1028 |                                     if(resource.keySet().contains("pchanges")){ | 
 | 1029 |                                         resource.remove("pchanges") | 
 | 1030 |                                     } | 
 | 1031 |                                     if(!resource["result"] || (resource["result"] instanceof String && resource["result"] != "true")){ | 
 | 1032 |                                         if(resource["result"] != null){ | 
| Jakub Josef | bceaa32 | 2017-06-13 18:28:27 +0200 | [diff] [blame] | 1033 |                                             outputResources.add(String.format("Resource: %s\n\u001B[31m%s\u001B[0m", resKey, common.prettify(resource))) | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1034 |                                         }else{ | 
| Jakub Josef | bceaa32 | 2017-06-13 18:28:27 +0200 | [diff] [blame] | 1035 |                                             outputResources.add(String.format("Resource: %s\n\u001B[33m%s\u001B[0m", resKey, common.prettify(resource))) | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1036 |                                         } | 
 | 1037 |                                     }else{ | 
| Ivan Berezovskiy | 65fb637 | 2019-08-09 19:49:55 +0400 | [diff] [blame] | 1038 |                                         if(!printOnlyChanges || (resource.changes && resource.changes.size() > 0)) { | 
| Jakub Josef | bceaa32 | 2017-06-13 18:28:27 +0200 | [diff] [blame] | 1039 |                                             outputResources.add(String.format("Resource: %s\n\u001B[32m%s\u001B[0m", resKey, common.prettify(resource))) | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1040 |                                         } | 
 | 1041 |                                     } | 
 | 1042 |                                 }else{ | 
| Jakub Josef | bceaa32 | 2017-06-13 18:28:27 +0200 | [diff] [blame] | 1043 |                                     outputResources.add(String.format("Resource: %s\n\u001B[36m%s\u001B[0m", resKey, common.prettify(resource))) | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1044 |                                 } | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1045 |                             } | 
| Jakub Josef | c4c4020 | 2017-04-28 12:04:24 +0200 | [diff] [blame] | 1046 |                             common.debugMsg("checkResult: checking resource: ${resource}") | 
 | 1047 |                             if(resource instanceof String || (resource["result"] != null && !resource["result"]) || (resource["result"] instanceof String && resource["result"] == "false")){ | 
| Jakub Josef | 4714594 | 2018-04-04 17:30:38 +0200 | [diff] [blame] | 1048 |                                 errorResources.add(resource) | 
| Jakub Josef | c4c4020 | 2017-04-28 12:04:24 +0200 | [diff] [blame] | 1049 |                             } | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1050 |                         } | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1051 |                     }else if(node!=null && node!=""){ | 
| Jakub Josef | 62f6c84 | 2017-08-04 16:36:35 +0200 | [diff] [blame] | 1052 |                         outputResources.add(String.format("Resource: %s\n\u001B[36m%s\u001B[0m", nodeKey, common.prettify(node))) | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1053 |                     } | 
 | 1054 |                     if(printResults && !outputResources.isEmpty()){ | 
| Jakub Josef | 4714594 | 2018-04-04 17:30:38 +0200 | [diff] [blame] | 1055 |                         println outputResources.stream().collect(Collectors.joining("\n")) | 
 | 1056 |                     } | 
 | 1057 |                     if(!errorResources.isEmpty()){ | 
 | 1058 |                         for(resource in errorResources){ | 
 | 1059 |                             def prettyResource = common.prettify(resource) | 
 | 1060 |                             if (!disableAskOnError && env["ASK_ON_ERROR"] && env["ASK_ON_ERROR"] == "true") { | 
 | 1061 |                                 timeout(time:1, unit:'HOURS') { | 
 | 1062 |                                    input message: "False result on ${nodeKey} found, resource ${prettyResource}. \nDo you want to continue?" | 
 | 1063 |                                 } | 
 | 1064 |                             } else { | 
 | 1065 |                                 def errorMsg = "Salt state on node ${nodeKey} failed. Resource: ${prettyResource}" | 
 | 1066 |                                 if (failOnError) { | 
 | 1067 |                                     throw new Exception(errorMsg) | 
 | 1068 |                                 } else { | 
 | 1069 |                                     common.errorMsg(errorMsg) | 
 | 1070 |                                 } | 
 | 1071 |                             } | 
 | 1072 |                         } | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1073 |                     } | 
 | 1074 |                 } | 
| Jakub Josef | 52f69f7 | 2017-03-14 15:18:08 +0100 | [diff] [blame] | 1075 |             } | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1076 |         }else{ | 
 | 1077 |             common.errorMsg("Salt result hasn't return attribute! Result: ${result}") | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 1078 |         } | 
| Jakub Josef | 52f69f7 | 2017-03-14 15:18:08 +0100 | [diff] [blame] | 1079 |     }else{ | 
| Jakub Josef | a87941c | 2017-04-20 17:14:58 +0200 | [diff] [blame] | 1080 |         common.errorMsg("Cannot check salt result, given result is null") | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 1081 |     } | 
 | 1082 | } | 
 | 1083 |  | 
 | 1084 | /** | 
| Oleg Iurchenko | 7eb2150 | 2017-11-28 18:53:43 +0200 | [diff] [blame] | 1085 | * Parse salt API output to check minion restart and wait some time to be sure minion is up. | 
 | 1086 | * See https://mirantis.jira.com/browse/PROD-16258 for more details | 
 | 1087 | * TODO: change sleep to more tricky procedure. | 
 | 1088 | * | 
 | 1089 | * @param result    Parsed response of Salt API | 
 | 1090 | */ | 
| Vasyl Saienko | 6a39621 | 2018-06-08 09:20:08 +0300 | [diff] [blame] | 1091 | def waitForMinion(result, minionRestartWaitTimeout=10) { | 
| Oleg Iurchenko | 7eb2150 | 2017-11-28 18:53:43 +0200 | [diff] [blame] | 1092 |     def common = new com.mirantis.mk.Common() | 
| Oleg Iurchenko | 3eedc78 | 2017-12-12 11:49:29 +0200 | [diff] [blame] | 1093 |     //In order to prevent multiple sleeps use bool variable to catch restart for any minion. | 
| Oleg Iurchenko | 7eb2150 | 2017-11-28 18:53:43 +0200 | [diff] [blame] | 1094 |     def isMinionRestarted = false | 
| Oleg Iurchenko | 3eedc78 | 2017-12-12 11:49:29 +0200 | [diff] [blame] | 1095 |     if(result != null){ | 
 | 1096 |         if(result['return']){ | 
 | 1097 |             for (int i=0;i<result['return'].size();i++) { | 
 | 1098 |                 def entry = result['return'][i] | 
 | 1099 |                 // exit in case of empty response. | 
 | 1100 |                 if (!entry) { | 
 | 1101 |                     return | 
 | 1102 |                 } | 
 | 1103 |                 // Loop for nodes | 
 | 1104 |                 for (int j=0;j<entry.size();j++) { | 
 | 1105 |                     def nodeKey = entry.keySet()[j] | 
 | 1106 |                     def node=entry[nodeKey] | 
 | 1107 |                     if(node instanceof Map || node instanceof List){ | 
 | 1108 |                         // Loop for node resources | 
 | 1109 |                         for (int k=0;k<node.size();k++) { | 
 | 1110 |                             def resource; | 
 | 1111 |                             def resKey; | 
 | 1112 |                             if(node instanceof Map){ | 
 | 1113 |                                 resKey = node.keySet()[k] | 
 | 1114 |                             }else if(node instanceof List){ | 
 | 1115 |                                 resKey = k | 
 | 1116 |                             } | 
 | 1117 |                             resource = node[resKey] | 
| Jakub Josef | fb9996d | 2018-04-10 14:05:31 +0200 | [diff] [blame] | 1118 |                             // try to find if salt_minion service was restarted | 
 | 1119 |                             if(resKey instanceof String && resKey.contains("salt_minion_service_restart") && resource instanceof Map && resource.keySet().contains("result")){ | 
| Oleg Iurchenko | 3eedc78 | 2017-12-12 11:49:29 +0200 | [diff] [blame] | 1120 |                                 if((resource["result"] instanceof Boolean && resource["result"]) || (resource["result"] instanceof String && resource["result"] == "true")){ | 
 | 1121 |                                     if(resource.changes.size() > 0){ | 
 | 1122 |                                         isMinionRestarted=true | 
 | 1123 |                                     } | 
 | 1124 |                                 } | 
 | 1125 |                             } | 
 | 1126 |                         } | 
 | 1127 |                     } | 
 | 1128 |                 } | 
 | 1129 |             } | 
| Oleg Iurchenko | 7eb2150 | 2017-11-28 18:53:43 +0200 | [diff] [blame] | 1130 |         } | 
 | 1131 |     } | 
| Oleg Iurchenko | 7eb2150 | 2017-11-28 18:53:43 +0200 | [diff] [blame] | 1132 |     if (isMinionRestarted){ | 
| Vasyl Saienko | 6a39621 | 2018-06-08 09:20:08 +0300 | [diff] [blame] | 1133 |       common.infoMsg("Salt minion service restart detected. Sleep ${minionRestartWaitTimeout} seconds to wait minion restart") | 
 | 1134 |         sleep(minionRestartWaitTimeout) | 
| Oleg Iurchenko | 7eb2150 | 2017-11-28 18:53:43 +0200 | [diff] [blame] | 1135 |     } | 
 | 1136 | } | 
 | 1137 |  | 
 | 1138 | /** | 
| Jakub Josef | 7852fe1 | 2017-03-15 16:02:41 +0100 | [diff] [blame] | 1139 |  * Print salt command run results in human-friendly form | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 1140 |  * | 
 | 1141 |  * @param result        Parsed response of Salt API | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 1142 |  */ | 
| Filip Pytloun | d2f1bbe | 2017-02-27 19:03:51 +0100 | [diff] [blame] | 1143 | def printSaltCommandResult(result) { | 
| Jakub Josef | 871bf15 | 2017-03-14 20:13:41 +0100 | [diff] [blame] | 1144 |     def common = new com.mirantis.mk.Common() | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1145 |     if(result != null){ | 
 | 1146 |         if(result['return']){ | 
 | 1147 |             for (int i=0; i<result['return'].size(); i++) { | 
 | 1148 |                 def entry = result['return'][i] | 
 | 1149 |                 for (int j=0; j<entry.size(); j++) { | 
 | 1150 |                     common.debugMsg("printSaltCommandResult: printing salt command entry: ${entry}") | 
 | 1151 |                     def nodeKey = entry.keySet()[j] | 
 | 1152 |                     def node=entry[nodeKey] | 
| Jakub Josef | bceaa32 | 2017-06-13 18:28:27 +0200 | [diff] [blame] | 1153 |                     common.infoMsg(String.format("Node %s changes:\n%s",nodeKey, common.prettify(node))) | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1154 |                 } | 
| Jakub Josef | 8a715bf | 2017-03-14 21:39:01 +0100 | [diff] [blame] | 1155 |             } | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1156 |         }else{ | 
 | 1157 |             common.errorMsg("Salt result hasn't return attribute! Result: ${result}") | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 1158 |         } | 
| Jakub Josef | 8a715bf | 2017-03-14 21:39:01 +0100 | [diff] [blame] | 1159 |     }else{ | 
| Jakub Josef | d9afd0e | 2017-03-15 19:19:23 +0100 | [diff] [blame] | 1160 |         common.errorMsg("Cannot print salt command result, given result is null") | 
| Jakub Josef | 52f69f7 | 2017-03-14 15:18:08 +0100 | [diff] [blame] | 1161 |     } | 
| Jakub Josef | 79ecec3 | 2017-02-17 14:36:28 +0100 | [diff] [blame] | 1162 | } | 
| Tomáš Kukrál | b12eedd | 2017-04-21 10:45:13 +0200 | [diff] [blame] | 1163 |  | 
 | 1164 |  | 
 | 1165 | /** | 
 | 1166 |  * Return content of file target | 
 | 1167 |  * | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 1168 |  * @param saltId    Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Tomáš Kukrál | b12eedd | 2017-04-21 10:45:13 +0200 | [diff] [blame] | 1169 |  * @param target    Compound target (should target only one host) | 
 | 1170 |  * @param file      File path to read (/etc/hosts for example) | 
 | 1171 |  */ | 
 | 1172 |  | 
| Pavlo Shchelokovskyy | 9710007 | 2018-10-04 11:53:44 +0300 | [diff] [blame] | 1173 | def getFileContent(saltId, target, file, checkResponse = true, batch=null, output = true, saltArgs = []) { | 
 | 1174 |     result = cmdRun(saltId, target, "cat ${file}", checkResponse, batch, output, saltArgs) | 
| Tomáš Kukrál | f1a692a | 2017-08-11 13:29:28 +0200 | [diff] [blame] | 1175 |     return result['return'][0].values()[0].replaceAll('Salt command execution success','') | 
| Tomáš Kukrál | b12eedd | 2017-04-21 10:45:13 +0200 | [diff] [blame] | 1176 | } | 
| Matthew Mosesohn | 9e88085 | 2017-07-04 21:17:53 +0300 | [diff] [blame] | 1177 |  | 
 | 1178 | /** | 
 | 1179 |  * Set override parameters in Salt cluster metadata | 
 | 1180 |  * | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 1181 |  * @param saltId         Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
| Matthew Mosesohn | 9e88085 | 2017-07-04 21:17:53 +0300 | [diff] [blame] | 1182 |  * @param salt_overrides YAML formatted string containing key: value, one per line | 
| Matthew Mosesohn | e564684 | 2017-07-19 16:54:57 +0300 | [diff] [blame] | 1183 |  * @param reclass_dir    Directory where Reclass git repo is located | 
| Dzmitry Stremkouski | b544070 | 2018-07-22 13:00:05 +0200 | [diff] [blame] | 1184 |  * @param extra_tgt      Extra targets for compound | 
| Matthew Mosesohn | 9e88085 | 2017-07-04 21:17:53 +0300 | [diff] [blame] | 1185 |  */ | 
 | 1186 |  | 
| Oleh Hryhorov | 5f96e09 | 2018-06-22 18:37:08 +0300 | [diff] [blame] | 1187 | def setSaltOverrides(saltId, salt_overrides, reclass_dir="/srv/salt/reclass", extra_tgt = '') { | 
| Tomáš Kukrál | f178f05 | 2017-07-11 11:31:00 +0200 | [diff] [blame] | 1188 |     def common = new com.mirantis.mk.Common() | 
| Mykyta Karpin | 1c165e2 | 2017-08-22 18:27:01 +0300 | [diff] [blame] | 1189 |     def salt_overrides_map = readYaml text: salt_overrides | 
| Tomáš Kukrál | 243cf84 | 2017-07-11 13:11:56 +0200 | [diff] [blame] | 1190 |     for (entry in common.entries(salt_overrides_map)) { | 
| Matthew Mosesohn | 9e88085 | 2017-07-04 21:17:53 +0300 | [diff] [blame] | 1191 |          def key = entry[0] | 
 | 1192 |          def value = entry[1] | 
 | 1193 |  | 
 | 1194 |          common.debugMsg("Set salt override ${key}=${value}") | 
| Victor Ryzhenkin | 2f99730 | 2019-01-10 15:12:55 +0400 | [diff] [blame] | 1195 |          runSaltProcessStep(saltId, "I@salt:master ${extra_tgt}", 'reclass.cluster_meta_set', ["name=${key}", "value=${value}"], false) | 
| Matthew Mosesohn | 9e88085 | 2017-07-04 21:17:53 +0300 | [diff] [blame] | 1196 |     } | 
| Oleh Hryhorov | 5f96e09 | 2018-06-22 18:37:08 +0300 | [diff] [blame] | 1197 |     runSaltProcessStep(saltId, "I@salt:master ${extra_tgt}", 'cmd.run', ["git -C ${reclass_dir} update-index --skip-worktree classes/cluster/overrides.yml"]) | 
| Matthew Mosesohn | 9e88085 | 2017-07-04 21:17:53 +0300 | [diff] [blame] | 1198 | } | 
| Oleg Grigorov | bec4558 | 2017-09-12 20:29:24 +0300 | [diff] [blame] | 1199 |  | 
 | 1200 | /** | 
 | 1201 | * Execute salt commands via salt-api with | 
 | 1202 | * CLI client salt-pepper | 
 | 1203 | * | 
 | 1204 | * @param data   Salt command map | 
 | 1205 | * @param venv   Path to virtualenv with | 
 | 1206 | */ | 
 | 1207 |  | 
| azvyagintsev | 386e94e | 2019-06-13 13:39:04 +0300 | [diff] [blame] | 1208 | def runPepperCommand(data, venv) { | 
| Jakub Josef | 03d4d5a | 2017-12-20 16:35:09 +0100 | [diff] [blame] | 1209 |     def common = new com.mirantis.mk.Common() | 
| Oleg Grigorov | bec4558 | 2017-09-12 20:29:24 +0300 | [diff] [blame] | 1210 |     def python = new com.mirantis.mk.Python() | 
 | 1211 |     def dataStr = new groovy.json.JsonBuilder(data).toString() | 
| azvyagintsev | 386e94e | 2019-06-13 13:39:04 +0300 | [diff] [blame] | 1212 |     // TODO(alexz): parametrize? | 
 | 1213 |     int retry = 10 | 
| chnyda | 4901a04 | 2017-11-16 12:14:56 +0100 | [diff] [blame] | 1214 |  | 
| Jakub Josef | a2491ad | 2018-01-15 16:26:27 +0100 | [diff] [blame] | 1215 |     def pepperCmdFile = "${venv}/pepper-cmd.json" | 
 | 1216 |     writeFile file: pepperCmdFile, text: dataStr | 
 | 1217 |     def pepperCmd = "pepper -c ${venv}/pepperrc --make-token -x ${venv}/.peppercache --json-file ${pepperCmdFile}" | 
| Oleg Grigorov | bec4558 | 2017-09-12 20:29:24 +0300 | [diff] [blame] | 1218 |  | 
| azvyagintsev | 386e94e | 2019-06-13 13:39:04 +0300 | [diff] [blame] | 1219 |     int tries = 0 | 
 | 1220 |     def FullOutput = ['status': 1] | 
| Jakub Josef | 37cd497 | 2018-02-01 16:25:25 +0100 | [diff] [blame] | 1221 |     def outputObj | 
| azvyagintsev | 386e94e | 2019-06-13 13:39:04 +0300 | [diff] [blame] | 1222 |     while (tries++ < retry) { | 
 | 1223 |         try { | 
 | 1224 |             if (venv) { | 
 | 1225 |                 FullOutput = python.runVirtualenvCommand(venv, pepperCmd, true, true) | 
 | 1226 |             } else { | 
 | 1227 |                 FullOutput = common.shCmdStatus(pepperCmd) | 
 | 1228 |             } | 
 | 1229 |             if (FullOutput['status'] != 0) { | 
 | 1230 |                 error() | 
 | 1231 |             } | 
 | 1232 |             break | 
 | 1233 |         } catch (e) { | 
 | 1234 |             // Check , if we get failed pepper HTTP call, and retry | 
 | 1235 |             common.errorMsg("Command: ${pepperCmd} failed to execute with error:\n${FullOutput['stderr']}") | 
 | 1236 |             if (FullOutput['stderr'].contains('Error with request: HTTP Error 50') || FullOutput['stderr'].contains('Pepper error: Server error')) { | 
 | 1237 |                 common.errorMsg("Pepper HTTP Error detected. Most probably, " + | 
 | 1238 |                     "master SaltReqTimeoutError in master zmq thread issue...lets retry ${tries}/${retry}") | 
 | 1239 |                 sleep(5) | 
 | 1240 |                 continue | 
 | 1241 |             } | 
 | 1242 |         } | 
 | 1243 |     } | 
 | 1244 |     // Try to parse json output. No sense to check exit code, since we always expect json answer only. | 
| Jakub Josef | 37cd497 | 2018-02-01 16:25:25 +0100 | [diff] [blame] | 1245 |     try { | 
| azvyagintsev | 386e94e | 2019-06-13 13:39:04 +0300 | [diff] [blame] | 1246 |         outputObj = new groovy.json.JsonSlurperClassic().parseText(FullOutput['stdout']) | 
 | 1247 |     } catch (Exception jsonE) { | 
 | 1248 |         common.errorMsg('Parsing Salt API JSON response failed! Response: ' + FullOutput) | 
 | 1249 |         throw jsonE | 
| Jakub Josef | 37cd497 | 2018-02-01 16:25:25 +0100 | [diff] [blame] | 1250 |     } | 
 | 1251 |     return outputObj | 
| Oleg Grigorov | bec4558 | 2017-09-12 20:29:24 +0300 | [diff] [blame] | 1252 | } | 
| Martin Polreich | 232ad90 | 2019-01-21 14:31:00 +0100 | [diff] [blame] | 1253 |  | 
| azvyagintsev | 386e94e | 2019-06-13 13:39:04 +0300 | [diff] [blame] | 1254 |  | 
| Martin Polreich | 232ad90 | 2019-01-21 14:31:00 +0100 | [diff] [blame] | 1255 | /** | 
 | 1256 | * Check time settings on defined nodes, compares them | 
 | 1257 | * and evaluates the results | 
 | 1258 | * | 
 | 1259 | * @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 1260 | * @param target Targeted nodes to be checked | 
 | 1261 | * @param diff   Maximum time difference (in seconds) to be accepted during time sync check | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 1262 | * @param batch Batch param to salt (integer or string with percents) | 
| Martin Polreich | 232ad90 | 2019-01-21 14:31:00 +0100 | [diff] [blame] | 1263 | * @return bool  Return true if time difference is <= diff and returns false if time difference is > diff | 
 | 1264 | */ | 
 | 1265 |  | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 1266 | def checkClusterTimeSync(saltId, target, batch = null) { | 
| Martin Polreich | 232ad90 | 2019-01-21 14:31:00 +0100 | [diff] [blame] | 1267 |     def common = new com.mirantis.mk.Common() | 
 | 1268 |     def salt = new com.mirantis.mk.Salt() | 
 | 1269 |  | 
 | 1270 |     times = [] | 
 | 1271 |     try { | 
 | 1272 |         diff = salt.getReturnValues(salt.getPillar(saltId, 'I@salt:master', 'linux:system:time_diff')) | 
 | 1273 |         if (diff != null && diff != "" && diff.isInteger()) { | 
 | 1274 |             diff = diff.toInteger() | 
 | 1275 |         } else { | 
 | 1276 |             diff = 5 | 
 | 1277 |         } | 
| Martin Polreich | f797695 | 2019-03-04 10:06:11 +0100 | [diff] [blame] | 1278 |         out = salt.runSaltProcessStep(saltId, target, 'status.time', '%s', batch) | 
| Martin Polreich | 232ad90 | 2019-01-21 14:31:00 +0100 | [diff] [blame] | 1279 |         outParsed = out['return'][0] | 
 | 1280 |         def outKeySet = outParsed.keySet() | 
 | 1281 |         for (key in outKeySet) { | 
 | 1282 |             def time = outParsed[key].readLines().get(0) | 
 | 1283 |             common.infoMsg(time) | 
 | 1284 |             if (time.isInteger()) { | 
 | 1285 |                 times.add(time.toInteger()) | 
 | 1286 |             } | 
 | 1287 |         } | 
 | 1288 |         if ((times.max() - times.min()) <= diff) { | 
 | 1289 |             return true | 
 | 1290 |         } else { | 
 | 1291 |             return false | 
 | 1292 |         } | 
 | 1293 |     } catch(Exception e) { | 
 | 1294 |         common.errorMsg("Could not check cluster time sync.") | 
 | 1295 |         return false | 
 | 1296 |     } | 
 | 1297 | } | 
| Martin Polreich | b577f2d | 2019-02-27 09:28:35 +0100 | [diff] [blame] | 1298 |  | 
 | 1299 | /** | 
 | 1300 | * Finds out IP address of the given node or a list of nodes | 
 | 1301 | * | 
 | 1302 | * @param saltId     Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 1303 | * @param nodes      Targeted node hostnames to be checked (String or List of strings) | 
 | 1304 | * @param useGrains  If the, the value will be taken from grains. If false, it will be taken from 'hostname' command. | 
 | 1305 | * @return Map       Return result Map in format ['nodeName1': 'ipAdress1', 'nodeName2': 'ipAdress2', ...] | 
 | 1306 | */ | 
 | 1307 |  | 
 | 1308 | def getIPAddressesForNodenames(saltId, nodes = [], useGrains = true) { | 
 | 1309 |     result = [:] | 
 | 1310 |  | 
 | 1311 |     if (nodes instanceof String) { | 
 | 1312 |         nodes = [nodes] | 
 | 1313 |     } | 
 | 1314 |  | 
 | 1315 |     if (useGrains) { | 
 | 1316 |         for (String node in nodes) { | 
 | 1317 |             ip = getReturnValues(getGrain(saltId, node, "fqdn_ip4"))["fqdn_ip4"][0] | 
 | 1318 |             result[node] = ip | 
 | 1319 |         } | 
 | 1320 |     } else { | 
 | 1321 |         for (String node in nodes) { | 
 | 1322 |             ip = getReturnValues(cmdRun(saltId, node, "hostname -i")).readLines()[0] | 
 | 1323 |             result[node] = ip | 
 | 1324 |         } | 
 | 1325 |     } | 
 | 1326 |     return result | 
| Martin Polreich | 4512e2e | 2019-03-29 12:10:00 +0100 | [diff] [blame] | 1327 | } | 
 | 1328 |  | 
 | 1329 | /** | 
 | 1330 | * Checks if required package is installed and returns averaged IO stats for selected disks. | 
 | 1331 | * Allows getting averaged values of specific parameter for all disks or a specified disk. | 
 | 1332 | * Interval between checks and its number is parametrized and configurable. | 
 | 1333 | * | 
 | 1334 | * @param saltId         Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 1335 | * @param target         Node to be targeted (Should only match 1 node) | 
 | 1336 | * @param parameterName  Name of parameter from 'iostat' output (default = '' -- returns all variables) | 
 | 1337 | * @param interval       Interval between checks (default = 1) | 
 | 1338 | * @param count          Number of checks (default = 5) | 
 | 1339 | * @param disks          Disks to be checked (default = '' -- returns all disks) | 
 | 1340 | * @param output         Print Salt command return (default = true) | 
 | 1341 | * @return Map           Map containing desired values in format ['disk':'value'] | 
 | 1342 | */ | 
 | 1343 |  | 
 | 1344 | def getIostatValues(Map params) { | 
 | 1345 |     def common = new com.mirantis.mk.Common() | 
 | 1346 |     def ret = [:] | 
| Martin Polreich | a774420 | 2019-04-04 16:58:28 +0200 | [diff] [blame] | 1347 |     if (isPackageInstalled(['saltId': params.saltId, 'target': params.target, 'packageName': 'sysstat', 'output': false])) { | 
| Martin Polreich | 4512e2e | 2019-03-29 12:10:00 +0100 | [diff] [blame] | 1348 |         def arg = [params.get('interval', 1), params.get('count', 5), params.get('disks', '')] | 
 | 1349 |         def res = getReturnValues(runSaltProcessStep(params.saltId, params.target, 'disk.iostat', arg, null, params.output)) | 
 | 1350 |         if (res instanceof Map) { | 
 | 1351 |             for (int i = 0; i < res.size(); i++) { | 
 | 1352 |                 def key = res.keySet()[i] | 
 | 1353 |                 if (params.containsKey('parameterName')) { | 
 | 1354 |                     if (res[key].containsKey(params.parameterName)){ | 
 | 1355 |                         ret[key] = res[key][params.parameterName] | 
 | 1356 |                     } else { | 
 | 1357 |                         common.errorMsg("Parameter '${params.parameterName}' not found for disk '${key}'. Valid parameter for this disk are: '${res[key].keySet()}'") | 
 | 1358 |                     } | 
 | 1359 |                 } else { | 
 | 1360 |                     return res      // If no parameterName is defined, return all of them. | 
 | 1361 |                 } | 
 | 1362 |             } | 
 | 1363 |         } | 
 | 1364 |     } else { | 
 | 1365 |         common.errorMsg("Package 'sysstat' seems not to be installed on at least one of tageted nodes: ${params.target}. Please fix this to be able to check 'iostat' values. Find more in the docs TODO:<Add docs link>") | 
 | 1366 |     } | 
 | 1367 |     return ret | 
 | 1368 | } | 
 | 1369 |  | 
 | 1370 | /** | 
 | 1371 | * Checks if defined package is installed on all nodes defined by target parameter. | 
 | 1372 | * | 
 | 1373 | * @param saltId         Salt Connection object or pepperEnv (the command will be sent using the selected method) | 
 | 1374 | * @param target         Node or nodes to be targeted | 
 | 1375 | * @param packageName    Name of package to be checked | 
 | 1376 | * @param output         Print Salt command return (default = true) | 
 | 1377 | * @return boolean       True if package is installed on all defined nodes. False if not found on at least one of defined nodes. | 
 | 1378 | */ | 
 | 1379 |  | 
 | 1380 | def isPackageInstalled(Map params) { | 
 | 1381 |     def output = params.get('output', true) | 
| Ivan Berezovskiy | 61a493c | 2019-08-06 18:50:39 +0400 | [diff] [blame] | 1382 |     def res = runSaltProcessStep(params.saltId, params.target, "pkg.list_pkgs", [], null, output)['return'][0] | 
| Ivan Berezovskiy | 775a553 | 2019-07-26 16:44:22 +0400 | [diff] [blame] | 1383 |     if (res) { | 
 | 1384 |         for (int i = 0; i < res.size(); i++) { | 
 | 1385 |             def key = res.keySet()[i] | 
| Ivan Berezovskiy | 61a493c | 2019-08-06 18:50:39 +0400 | [diff] [blame] | 1386 |             if (!(res[key] instanceof Map && res[key].get(params.packageName, false))) { | 
| Ivan Berezovskiy | 775a553 | 2019-07-26 16:44:22 +0400 | [diff] [blame] | 1387 |                 return false | 
 | 1388 |             } | 
| Martin Polreich | 4512e2e | 2019-03-29 12:10:00 +0100 | [diff] [blame] | 1389 |         } | 
| Ivan Berezovskiy | 775a553 | 2019-07-26 16:44:22 +0400 | [diff] [blame] | 1390 |         return true | 
 | 1391 |     } else { | 
 | 1392 |         return false | 
| Martin Polreich | 4512e2e | 2019-03-29 12:10:00 +0100 | [diff] [blame] | 1393 |     } | 
| azvyagintsev | 386e94e | 2019-06-13 13:39:04 +0300 | [diff] [blame] | 1394 | } | 
| Denis Egorenko | 3621b96 | 2019-09-23 16:13:43 +0400 | [diff] [blame^] | 1395 |  | 
 | 1396 | /** | 
 | 1397 | * Returns nubmer of worker_threads set for Salt Master | 
 | 1398 | * | 
 | 1399 | * @param saltId  Salt Connection object or pepperEnv | 
 | 1400 | * | 
 | 1401 | */ | 
 | 1402 | def getWorkerThreads(saltId) { | 
 | 1403 |     if (env.getEnvironment().containsKey('SALT_MASTER_OPT_WORKER_THREADS')) { | 
 | 1404 |         return env['SALT_MASTER_OPT_WORKER_THREADS'].toString() | 
 | 1405 |     } | 
 | 1406 |     def threads = cmdRun(saltId, "I@salt:master", "cat /etc/salt/master.d/master.conf | grep worker_threads | cut -f 2 -d ':'", true, null, true) | 
 | 1407 |     return threads['return'][0].values()[0].replaceAll('Salt command execution success','').trim() | 
 | 1408 | } | 
 | 1409 |  |