blob: 3b6f62698db0e8afc1af0ac2c7c6d18d0aa82d94 [file] [log] [blame]
Martin Polreich8f0f3ac2019-02-15 10:03:33 +01001package com.mirantis.mk
2
3/**
4 *
5 * Galera functions
6 *
7 */
8
9
10/**
11 * Returns parameters from mysql.status output on given target node
12 *
13 * @param env Salt Connection object or pepperEnv
14 * @param target Targeted node
15 * @param parameters Parameters to be retruned (String or list of Strings). If no parameters are provided or is set to '[]', it returns all of them.
16 * @return result List of parameters with its values
17 */
18
19def getWsrepParameters(env, target, parameters=[], print=false) {
Martin Polreich1281cde2019-02-28 11:39:49 +010020 def salt = new com.mirantis.mk.Salt()
21 def common = new com.mirantis.mk.Common()
22 result = [:]
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010023 out = salt.runSaltProcessStep(env, "${target}", "mysql.status", [], null, false)
24 outlist = out['return'][0]
25 resultYaml = outlist.get(outlist.keySet()[0]).sort()
26 if (print) {
27 common.prettyPrint(resultYaml)
28 }
29 if (parameters instanceof String) {
Martin Polreich1fbda522019-02-26 14:46:33 +010030 parameters = [parameters]
31 }
32 if (parameters == [] || parameters == ['']) {
33 result = resultYaml
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010034 } else {
Martin Polreich1281cde2019-02-28 11:39:49 +010035 for (String param in parameters) {
36 value = resultYaml[param]
Martin Polreich1fbda522019-02-26 14:46:33 +010037 if (value instanceof String && value.isBigDecimal()) {
38 value = value.toBigDecimal()
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010039 }
Martin Polreich1281cde2019-02-28 11:39:49 +010040 result[param] = value
Martin Polreich1fbda522019-02-26 14:46:33 +010041 }
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010042 }
43 return result
44}
45
46/**
47 * Verifies Galera database
48 *
49 * This function checks for Galera master, tests connection and if reachable, it obtains the result
50 * of Salt mysql.status function. The result is then parsed, validated and outputed to the user.
51 *
52 * @param env Salt Connection object or pepperEnv
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010053 * @param checkTimeSync Boolean value to enable time sync check
54 * @return resultCode int values used to determine exit status in the calling function
55 */
Denis Egorenko527bf4b2019-08-12 18:17:18 +040056def verifyGaleraStatus(env, checkTimeSync=false) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010057 def salt = new com.mirantis.mk.Salt()
58 def common = new com.mirantis.mk.Common()
Denis Egorenko527bf4b2019-08-12 18:17:18 +040059 def mysqlStatusReport = [
60 'clusterMembersOnPower': [],
61 'clusterMembersNotAvailable': [],
62 'clusterMembersInClusterAlive': [],
63 'clusterMembersNotAlive': [],
64 'error': 0
65 ]
66
67 try {
68 def clusterMembers = salt.getMinions(env, "I@galera:master or I@galera:slave")
69 for (minion in clusterMembers) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010070 try {
Martin Polreich5df75782019-02-19 14:29:24 +010071 salt.minionsReachable(env, "I@salt:master", minion)
Denis Egorenko527bf4b2019-08-12 18:17:18 +040072 mysqlStatusReport['clusterMembersOnPower'] << minion
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010073 } catch (Exception e) {
Martin Polreich5df75782019-02-19 14:29:24 +010074 common.warningMsg("Slave '${minion}' is not reachable.")
Denis Egorenko527bf4b2019-08-12 18:17:18 +040075 mysqlStatusReport['clusterMembersNotAvailable'] << minion
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010076 }
77 }
Denis Egorenko527bf4b2019-08-12 18:17:18 +040078 } catch (Exception e) {
79 common.errorMsg('Cannot obtain Galera minions list.')
80 common.errorMsg(e.getMessage())
81 mysqlStatusReport['error'] = 128
82 return mysqlStatusReport
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010083 }
Denis Egorenko527bf4b2019-08-12 18:17:18 +040084
85 if (!mysqlStatusReport['clusterMembersOnPower']) {
86 common.errorMsg("No Galera member was reachable.")
87 mysqlStatusReport['error'] = 130
88 return mysqlStatusReport
Martin Polreich8f0f3ac2019-02-15 10:03:33 +010089 }
Denis Egorenko527bf4b2019-08-12 18:17:18 +040090
Martin Polreich047257f2019-05-02 11:36:57 +020091 def checkTargets = salt.getMinions(env, "I@xtrabackup:client or I@xtrabackup:server")
Martin Polreichda7ff402019-04-02 14:12:58 +020092 for (checkTarget in checkTargets) {
Martin Polreich047257f2019-05-02 11:36:57 +020093 def nodeStatus = salt.minionsReachable(env, 'I@salt:master', checkTarget, null, 10, 5)
94 if (nodeStatus != null) {
95 def iostatRes = salt.getIostatValues(['saltId': env, 'target': checkTarget, 'parameterName': "%util", 'output': true])
96 if (iostatRes == [:]) {
97 common.errorMsg("Recevived empty response from iostat call on ${checkTarget}. Maybe 'sysstat' package is not installed?")
Denis Egorenko527bf4b2019-08-12 18:17:18 +040098 mysqlStatusReport['error'] = 140
99 return mysqlStatusReport
Martin Polreich047257f2019-05-02 11:36:57 +0200100 }
101 for (int i = 0; i < iostatRes.size(); i++) {
102 def diskKey = iostatRes.keySet()[i]
Martin Polreich74f51752019-07-08 12:06:04 +0200103 if (!(iostatRes[diskKey].toString().isBigDecimal() && (iostatRes[diskKey].toBigDecimal() < 50 ))) {
104 common.errorMsg("Disk ${diskKey} has to high i/o utilization. Maximum value is 50 and current value is ${iostatRes[diskKey]}.")
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400105 mysqlStatusReport['error'] = 141
106 return mysqlStatusReport
Martin Polreich047257f2019-05-02 11:36:57 +0200107 }
Martin Polreichda7ff402019-04-02 14:12:58 +0200108 }
109 }
110 }
111 common.infoMsg("Disk i/o utilization was checked and everything seems to be in order.")
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100112 if (checkTimeSync && !salt.checkClusterTimeSync(env, "I@galera:master or I@galera:slave")) {
113 common.errorMsg("Time in cluster is desynchronized or it couldn't be detemined. You should fix this issue manually before proceeding.")
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400114 mysqlStatusReport['error'] = 131
115 return mysqlStatusReport
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100116 }
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400117
118 for(member in mysqlStatusReport['clusterMembersOnPower']) {
119 def clusterStatus = getWsrepParameters(env, member, 'wsrep_cluster_status')
120 if (clusterStatus['wsrep_cluster_status']) {
121 mysqlStatusReport['clusterMembersInClusterAlive'] << member
122 } else {
123 mysqlStatusReport['clusterMembersNotAlive'] << member
124 }
125 }
126 if (!mysqlStatusReport['clusterMembersInClusterAlive']) {
127 common.errorMsg("Could not determine mysql status, because all nodes are not connected to cluster.")
128 mysqlStatusReport['error'] = 256
129 return mysqlStatusReport
130 }
131 def testNode = mysqlStatusReport['clusterMembersInClusterAlive'].sort().first()
132
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100133 try {
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400134 mysqlStatusReport['statusRaw'] = salt.runSaltProcessStep(env, testNode, "mysql.status", [], null, false)
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100135 } catch (Exception e) {
136 common.errorMsg('Could not determine mysql status.')
Martin Polreich71307db2019-04-03 09:14:51 +0200137 common.errorMsg(e.getMessage())
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400138 mysqlStatusReport['error'] = 256
139 return mysqlStatusReport
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100140 }
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400141
142 def status = "unknown"
143 def galeraMasterNode = salt.getReturnValues(salt.getPillar(env, testNode, "galera:master:enabled")) ? true : false
144
145 if (mysqlStatusReport['statusRaw']) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100146 try {
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400147 status = validateAndPrintGaleraStatusReport(env, mysqlStatusReport['statusRaw'], testNode, galeraMasterNode)
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100148 } catch (Exception e) {
149 common.errorMsg('Could not parse the mysql status output. Check it manually.')
Martin Polreich71307db2019-04-03 09:14:51 +0200150 common.errorMsg(e.getMessage())
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100151 }
152 } else {
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400153 common.errorMsg("Mysql status response unrecognized or is empty. Response: ${mysqlStatusReport['statusRaw']}")
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100154 }
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400155 if (mysqlStatusReport['clusterMembersNotAvailable']) {
156 common.errorMsg("Next nodes are unavailable: ${mysqlStatusReport['clusterMembersNotAvailable'].join(',')}")
157 }
158 if (mysqlStatusReport['clusterMembersNotAlive']) {
159 common.errorMsg("Next nodes are not connected to cluster: ${mysqlStatusReport['clusterMembersNotAlive'].join(',')}")
160 }
161
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100162 if (status == "OK") {
163 common.infoMsg("No errors found - MySQL status is ${status}.")
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400164 return mysqlStatusReport
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100165 } else if (status == "unknown") {
166 common.warningMsg('MySQL status cannot be detemined')
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400167 mysqlStatusReport['error'] = 1
168 return mysqlStatusReport
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100169 } else {
170 common.errorMsg("Errors found.")
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400171 mysqlStatusReport['error'] = 2
172 return mysqlStatusReport
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100173 }
174}
175
176/** Validates and prints result of verifyGaleraStatus function
177@param env Salt Connection object or pepperEnv
178@param out Output of the mysql.status Salt function
179@return status "OK", "ERROR" or "uknown" depending on result of validation
180*/
181
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400182def validateAndPrintGaleraStatusReport(env, out, minion, nodeRoleMaster=false) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100183 def salt = new com.mirantis.mk.Salt()
184 def common = new com.mirantis.mk.Common()
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400185 def role = 'slave'
186 if (nodeRoleMaster) {
187 role = 'master'
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100188 }
189 sizeOut = salt.getReturnValues(salt.getPillar(env, minion, "galera:${role}:members"))
190 expected_cluster_size = sizeOut.size()
191 outlist = out['return'][0]
192 resultYaml = outlist.get(outlist.keySet()[0]).sort()
193 common.prettyPrint(resultYaml)
194 parameters = [
195 wsrep_cluster_status: [title: 'Cluster status', expectedValues: ['Primary'], description: ''],
196 wsrep_cluster_size: [title: 'Current cluster size', expectedValues: [expected_cluster_size], description: ''],
197 wsrep_ready: [title: 'Node status', expectedValues: ['ON', true], description: ''],
198 wsrep_local_state_comment: [title: 'Node status comment', expectedValues: ['Joining', 'Waiting on SST', 'Joined', 'Synced', 'Donor'], description: ''],
199 wsrep_connected: [title: 'Node connectivity', expectedValues: ['ON', true], description: ''],
200 wsrep_local_recv_queue_avg: [title: 'Average size of local reveived queue', expectedThreshold: [warn: 0.5, error: 1.0], description: '(Value above 0 means that the node cannot apply write-sets as fast as it receives them, which can lead to replication throttling)'],
201 wsrep_local_send_queue_avg: [title: 'Average size of local send queue', expectedThreshold: [warn: 0.5, error: 1.0], description: '(Value above 0 indicate replication throttling or network throughput issues, such as a bottleneck on the network link.)']
202 ]
203 for (key in parameters.keySet()) {
204 value = resultYaml[key]
205 if (value instanceof String && value.isBigDecimal()) {
206 value = value.toBigDecimal()
207 }
208 parameters.get(key) << [actualValue: value]
209 }
210 for (key in parameters.keySet()) {
211 param = parameters.get(key)
212 if (key == 'wsrep_local_recv_queue_avg' || key == 'wsrep_local_send_queue_avg') {
Martin Polreich79810262019-02-25 12:51:11 +0100213 if (param.get('actualValue') == null || (param.get('actualValue') > param.get('expectedThreshold').get('error'))) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100214 param << [match: 'error']
215 } else if (param.get('actualValue') > param.get('expectedThreshold').get('warn')) {
216 param << [match: 'warn']
217 } else {
218 param << [match: 'ok']
219 }
220 } else {
221 for (expValue in param.get('expectedValues')) {
222 if (expValue == param.get('actualValue')) {
223 param << [match: 'ok']
224 break
225 } else {
226 param << [match: 'error']
227 }
228 }
229 }
230 }
231 cluster_info_report = []
232 cluster_warning_report = []
233 cluster_error_report = []
234 for (key in parameters.keySet()) {
235 param = parameters.get(key)
236 if (param.containsKey('expectedThreshold')) {
237 expValues = "below ${param.get('expectedThreshold').get('warn')}"
238 } else {
239 if (param.get('expectedValues').size() > 1) {
240 expValues = param.get('expectedValues').join(' or ')
241 } else {
242 expValues = param.get('expectedValues')[0]
243 }
244 }
245 reportString = "${param.title}: ${param.actualValue} (Expected: ${expValues}) ${param.description}"
246 if (param.get('match').equals('ok')) {
247 cluster_info_report.add("[OK ] ${reportString}")
248 } else if (param.get('match').equals('warn')) {
249 cluster_warning_report.add("[WARNING] ${reportString}")
250 } else {
251 cluster_error_report.add("[ ERROR] ${reportString})")
252 }
253 }
254 common.infoMsg("CLUSTER STATUS REPORT: ${cluster_info_report.size()} expected values, ${cluster_warning_report.size()} warnings and ${cluster_error_report.size()} error found:")
255 if (cluster_info_report.size() > 0) {
256 common.infoMsg(cluster_info_report.join('\n'))
257 }
258 if (cluster_warning_report.size() > 0) {
259 common.warningMsg(cluster_warning_report.join('\n'))
260 }
261 if (cluster_error_report.size() > 0) {
262 common.errorMsg(cluster_error_report.join('\n'))
263 return "ERROR"
264 } else {
265 return "OK"
266 }
267}
268
Martin Polreich1281cde2019-02-28 11:39:49 +0100269/** Returns last shutdown node of Galera cluster
270@param env Salt Connection object or pepperEnv
271@param nodes List of nodes to check only (defaults to []). If not provided, it will check all nodes.
272 Use this parameter if the cluster splits to several components and you only want to check one fo them.
273@return status ip address or hostname of last shutdown node
274*/
275
276def getGaleraLastShutdownNode(env, nodes = []) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100277 def salt = new com.mirantis.mk.Salt()
278 def common = new com.mirantis.mk.Common()
Martin Polreich1281cde2019-02-28 11:39:49 +0100279 members = []
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100280 lastNode = [ip: '', seqno: -2]
281 try {
Martin Polreich1281cde2019-02-28 11:39:49 +0100282 if (nodes) {
283 nodes = salt.getIPAddressesForNodenames(env, nodes)
284 for (node in nodes) {
285 members = [host: "${node.get(node.keySet()[0])}"] + members
286 }
287 } else {
288 members = salt.getReturnValues(salt.getPillar(env, "I@galera:master", "galera:master:members"))
289 }
Martin Polreich71307db2019-04-03 09:14:51 +0200290 } catch (Exception e) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100291 common.errorMsg('Could not retrieve members list')
Martin Polreich71307db2019-04-03 09:14:51 +0200292 common.errorMsg(e.getMessage())
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100293 return 'I@galera:master'
294 }
295 if (members) {
296 for (member in members) {
297 try {
298 salt.minionsReachable(env, 'I@salt:master', "S@${member.host}")
299 out = salt.getReturnValues(salt.cmdRun(env, "S@${member.host}", 'cat /var/lib/mysql/grastate.dat | grep "seqno" | cut -d ":" -f2', true, null, false))
300 seqno = out.tokenize('\n')[0].trim()
301 if (seqno.isNumber()) {
302 seqno = seqno.toInteger()
303 } else {
Denis Egorenko8a339982019-08-09 18:22:30 +0400304 // in case if /var/lib/mysql/grastate.dat has no any seqno - set it to 0
305 // thus node will be recovered if no other failed found
306 seqno = 0
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100307 }
Martin Polreich71307db2019-04-03 09:14:51 +0200308 } catch (Exception e) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100309 common.warningMsg("Could not determine 'seqno' value for node ${member.host} ")
Martin Polreich71307db2019-04-03 09:14:51 +0200310 common.warningMsg(e.getMessage())
Denis Egorenko8a339982019-08-09 18:22:30 +0400311 seqno = 0
312 }
313 highestSeqno = lastNode.get('seqno')
314 if (seqno > highestSeqno) {
315 lastNode << [ip: "${member.host}", seqno: seqno]
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100316 }
317 }
318 }
319 if (lastNode.get('ip') != '') {
320 return "S@${lastNode.ip}"
321 } else {
322 return "I@galera:master"
323 }
324}
325
326/**
Denis Egorenko8a339982019-08-09 18:22:30 +0400327 * Wrapper around Mysql systemd service
328 * @param env Salt Connection object or pepperEnv
329 * @param targetNode Node to apply changes
330 * @param checkStatus Whether to check status of Mysql
331 * @param checkState State of service to check
332*/
333def manageServiceMysql(env, targetNode, action, checkStatus=true, checkState='running') {
334 def salt = new com.mirantis.mk.Salt()
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400335 salt.runSaltProcessStep(env, targetNode, "service.${action}", ['mysql'])
Denis Egorenko8a339982019-08-09 18:22:30 +0400336 if (checkStatus) {
337 try {
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400338 salt.commandStatus(env, targetNode, 'service mysql status', checkState)
Denis Egorenko8a339982019-08-09 18:22:30 +0400339 } catch (Exception er) {
340 input message: "Database is not running please fix it first and only then click on PROCEED."
341 }
342 }
343}
344
345/**
Martin Polreich9044fe42019-03-21 16:00:23 +0100346 * Restores Galera cluster
347 * @param env Salt Connection object or pepperEnv
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400348 * @param galeraStatus Map, Status of Galera cluster output from verifyGaleraStatus func
349 * @param restoreDb Run restore DB procedure
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100350 * @return output of salt commands
351 */
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400352def restoreGaleraCluster(env, galeraStatus, restoreDb=true) {
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100353 def salt = new com.mirantis.mk.Salt()
354 def common = new com.mirantis.mk.Common()
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400355 def nodesToRecover = []
356 def total = false // whole cluster
357 if (galeraStatus['clusterMembersNotAlive']) {
358 nodesToRecover = galeraStatus['clusterMembersNotAlive']
359 if (galeraStatus['clusterMembersInClusterAlive'].size() == 0) {
360 total = true
361 }
362 } else {
363 nodesToRecover = galeraStatus['clusterMembersInClusterAlive']
364 total = true
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100365 }
Martin Polreich9044fe42019-03-21 16:00:23 +0100366
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400367 def lastNodeTarget = ''
368 if (total) {
369 manageServiceMysql(env, 'I@galera:slave', 'stop', true, 'inactive')
370 manageServiceMysql(env, 'I@galera:master', 'stop', true, 'inactive')
371 lastNodeTarget = getGaleraLastShutdownNode(env) // in case if master was already down before
372 salt.cmdRun(env, "( I@galera:master or I@galera:slave ) and not ${lastNodeTarget}", "rm -f /var/lib/mysql/ib_logfile*")
373 salt.cmdRun(env, "( I@galera:master or I@galera:slave ) and not ${lastNodeTarget}", "rm -f /var/lib/mysql/grastate.dat")
374 } else {
375 lastNodeTarget = nodesToRecover.join(' or ')
376 manageServiceMysql(env, lastNodeTarget, 'stop', true, 'inactive')
377 }
Martin Polreich9044fe42019-03-21 16:00:23 +0100378
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400379 if (restoreDb) {
380 def timestamp = common.getDatetime()
Denis Egorenko410e7722019-12-16 14:57:10 +0400381 def bakDir = "/root/mysql/mysql.bak.${timestamp}".toString()
382 salt.cmdRun(env, lastNodeTarget, "mkdir -p ${bakDir}")
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400383 salt.cmdRun(env, lastNodeTarget, "mv /var/lib/mysql/* ${bakDir} || echo 'Nothing to backup from directory /var/lib/mysql/'")
384 }
385 if (total) {
386 // make sure that gcom parameter is empty
387 salt.cmdRun(env, lastNodeTarget, "sed -i '/gcomm/c\\wsrep_cluster_address=\"gcomm://\"' /etc/mysql/my.cnf")
388 } else if (!restoreDb) {
389 // node rejoin
390 salt.cmdRun(env, lastNodeTarget, "rm -f /var/lib/mysql/ib_logfile*")
391 salt.cmdRun(env, lastNodeTarget, "rm -f /var/lib/mysql/grastate.dat")
392 }
393
394 if (restoreDb) {
Martin Polreich9044fe42019-03-21 16:00:23 +0100395 restoreGaleraDb(env, lastNodeTarget)
396 }
397
Denis Egorenko8a339982019-08-09 18:22:30 +0400398 manageServiceMysql(env, lastNodeTarget, 'start')
Martin Polreich9044fe42019-03-21 16:00:23 +0100399
Denis Egorenko527bf4b2019-08-12 18:17:18 +0400400 if (total) {
401 manageServiceMysql(env, "( I@galera:master or I@galera:slave ) and not ${lastNodeTarget}", 'start')
402 salt.runSaltProcessStep(env, lastNodeTarget, 'state.sls_id', ['galera_config', 'galera'])
403 }
Martin Polreich8f0f3ac2019-02-15 10:03:33 +0100404}
Martin Polreich9044fe42019-03-21 16:00:23 +0100405
406/**
407 * Restores Galera database
408 * @param env Salt Connection object or pepperEnv
409 * @param targetNode Node to be targeted
410 */
411def restoreGaleraDb(env, targetNode) {
Martin Polreichef778c32019-08-08 13:47:45 +0200412 def salt = new com.mirantis.mk.Salt()
Martin Polreich9044fe42019-03-21 16:00:23 +0100413 def backup_dir = salt.getReturnValues(salt.getPillar(env, targetNode, 'xtrabackup:client:backup_dir'))
414 if(backup_dir == null || backup_dir.isEmpty()) { backup_dir='/var/backups/mysql/xtrabackup' }
415 salt.runSaltProcessStep(env, targetNode, 'file.remove', ["${backup_dir}/dbrestored"])
Martin Polreichef778c32019-08-08 13:47:45 +0200416 salt.enforceState(['saltId': env, 'target': targetNode, 'state': 'xtrabackup.client'])
417 salt.enforceState(['saltId': env, 'target': targetNode, 'state': 'xtrabackup.client.restore'])
Martin Polreich9044fe42019-03-21 16:00:23 +0100418}
419
420def restoreGaleraDb(env) {
Martin Polreichef778c32019-08-08 13:47:45 +0200421 def common = new com.mirantis.mk.Common()
Martin Polreich9044fe42019-03-21 16:00:23 +0100422 common.warningMsg("This method was renamed to 'restoreGaleraCluster'. Please change your pipeline to use this call instead! If you think that you really wanted to call 'restoreGaleraDb' you may be missing 'targetNode' parameter in you call.")
423 return restoreGaleraCluster(env)
sgarbuz5e10ba22019-07-17 12:01:57 +0300424}
425
426/**
427 * Start first node in mysql cluster. Cluster members stay removed in mysql config, additional service restart will be needed once all nodes are up.
428 * https://docs.mirantis.com/mcp/q4-18/mcp-operations-guide/tshooting/
429 * tshoot-mcp-openstack/tshoot-galera/restore-galera-cluster/
430 * restore-galera-manually.html#restore-galera-manually
431 *
432 * @param env Salt Connection object or pepperEnv
433 * @param target last stopped Galera node
434 * @return output of salt commands
435 */
436def startFirstNode(env, target) {
437 def salt = new com.mirantis.mk.Salt()
438 def common = new com.mirantis.mk.Common()
439
440 // make sure that gcom parameter is empty
441 salt.cmdRun(env, target, "sed -i '/wsrep_cluster_address/ s/^#*/#/' /etc/mysql/my.cnf")
442 salt.cmdRun(env, target, "sed -i '/wsrep_cluster_address/a wsrep_cluster_address=\"gcomm://\"' /etc/mysql/my.cnf")
443
444 // start mysql service on the last node
445 salt.runSaltProcessStep(env, target, 'service.start', ['mysql'])
446
447 // wait until mysql service on the last node is up
448
449 common.retry(30, 10) {
450 value = getWsrepParameters(env, target, 'wsrep_evs_state')
451 if (value['wsrep_evs_state'] == 'OPERATIONAL') {
452 common.infoMsg('WSREP state: OPERATIONAL')
453 } else {
454 throw new Exception("Mysql service is not running please fix it.")
455 }
456 }
Martin Polreich9044fe42019-03-21 16:00:23 +0100457}