continuent-tools-core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +202 -0
- data/README.md +44 -0
- data/lib/continuent-tools-core.rb +20 -0
- data/lib/tungsten.rb +46 -0
- data/lib/tungsten/README +297 -0
- data/lib/tungsten/api.rb +553 -0
- data/lib/tungsten/common.rb +190 -0
- data/lib/tungsten/exec.rb +570 -0
- data/lib/tungsten/install.rb +332 -0
- data/lib/tungsten/properties.rb +476 -0
- data/lib/tungsten/script.rb +952 -0
- data/lib/tungsten/status.rb +177 -0
- data/lib/tungsten/util.rb +523 -0
- metadata +154 -0
@@ -0,0 +1,952 @@
|
|
1
|
+
module TungstenScript
|
2
|
+
NAGIOS_OK=0
|
3
|
+
NAGIOS_WARNING=1
|
4
|
+
NAGIOS_CRITICAL=2
|
5
|
+
|
6
|
+
def run
|
7
|
+
begin
|
8
|
+
prepare()
|
9
|
+
main()
|
10
|
+
rescue CommandError => e
|
11
|
+
TU.debug(e)
|
12
|
+
rescue => e
|
13
|
+
TU.exception(e)
|
14
|
+
end
|
15
|
+
|
16
|
+
if TU.is_valid?()
|
17
|
+
cleanup(0)
|
18
|
+
else
|
19
|
+
cleanup(1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
# A tracking variable that will be set to true when the object is fully
|
25
|
+
# initizlied
|
26
|
+
@initialized = false
|
27
|
+
|
28
|
+
# Does this script required to run against an installed Tungsten directory
|
29
|
+
@require_installed_directory = true
|
30
|
+
|
31
|
+
# Definition of each command that this script will support
|
32
|
+
@command_definitions = {}
|
33
|
+
|
34
|
+
# The command, if any, the script should run
|
35
|
+
@command = nil
|
36
|
+
|
37
|
+
# Definition of each option that this script is expecting as input
|
38
|
+
@option_definitions = {}
|
39
|
+
|
40
|
+
# The command-line arguments of all options that have been defined
|
41
|
+
# This is used to identify duplicate arguments
|
42
|
+
@option_definition_arguments = {}
|
43
|
+
|
44
|
+
# The collected option values from the script input
|
45
|
+
@options = {}
|
46
|
+
|
47
|
+
TU.debug("Begin #{$0} #{ARGV.join(' ')}")
|
48
|
+
|
49
|
+
begin
|
50
|
+
configure()
|
51
|
+
@option_definitions.each{
|
52
|
+
|option_key,definition|
|
53
|
+
if definition.has_key?(:default)
|
54
|
+
opt(option_key, definition[:default])
|
55
|
+
end
|
56
|
+
}
|
57
|
+
|
58
|
+
if TU.display_help?()
|
59
|
+
display_help()
|
60
|
+
cleanup(0)
|
61
|
+
end
|
62
|
+
|
63
|
+
parse_options()
|
64
|
+
|
65
|
+
if @options[:autocomplete] == true
|
66
|
+
display_autocomplete()
|
67
|
+
cleanup(0)
|
68
|
+
end
|
69
|
+
|
70
|
+
unless TU.is_valid?()
|
71
|
+
cleanup(1)
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
if script_log_path() != nil
|
76
|
+
TU.set_log_path(script_log_path())
|
77
|
+
end
|
78
|
+
rescue => e
|
79
|
+
TU.debug("Unable to set script log path")
|
80
|
+
TU.debug(e)
|
81
|
+
end
|
82
|
+
|
83
|
+
TU.debug("Command: #{@command}")
|
84
|
+
TU.debug("Options:")
|
85
|
+
@options.each{
|
86
|
+
|k,v|
|
87
|
+
TU.debug(" #{k} => #{v}")
|
88
|
+
}
|
89
|
+
|
90
|
+
validate()
|
91
|
+
|
92
|
+
unless TU.is_valid?()
|
93
|
+
cleanup(1)
|
94
|
+
end
|
95
|
+
|
96
|
+
if @options[:validate] == true
|
97
|
+
cleanup(0)
|
98
|
+
end
|
99
|
+
rescue => e
|
100
|
+
TU.exception(e)
|
101
|
+
cleanup(1)
|
102
|
+
end
|
103
|
+
|
104
|
+
@initialized = true
|
105
|
+
end
|
106
|
+
|
107
|
+
def prepare
|
108
|
+
end
|
109
|
+
|
110
|
+
def command
|
111
|
+
@command
|
112
|
+
end
|
113
|
+
|
114
|
+
def configure
|
115
|
+
add_option(:validate, {
|
116
|
+
:on => "--validate",
|
117
|
+
:default => false,
|
118
|
+
:help => "Only run the script validation"
|
119
|
+
})
|
120
|
+
|
121
|
+
add_option(:force, {
|
122
|
+
:on => "--force",
|
123
|
+
:default => false,
|
124
|
+
:help => "Continue operation even if script validation fails"
|
125
|
+
})
|
126
|
+
|
127
|
+
add_option(:autocomplete, {
|
128
|
+
:on => "--autocomplete",
|
129
|
+
:default => false,
|
130
|
+
:hidden => true
|
131
|
+
})
|
132
|
+
end
|
133
|
+
|
134
|
+
def opt(option_key, value = nil)
|
135
|
+
if value != nil
|
136
|
+
@options[option_key] = value
|
137
|
+
end
|
138
|
+
|
139
|
+
return @options[option_key]
|
140
|
+
end
|
141
|
+
|
142
|
+
def add_command(command_key, definition)
|
143
|
+
begin
|
144
|
+
command_key = command_key.to_sym()
|
145
|
+
if @command_definitions.has_key?(command_key)
|
146
|
+
raise "The #{command_key} command has already been defined"
|
147
|
+
end
|
148
|
+
|
149
|
+
if definition[:default] == true
|
150
|
+
if @command != nil
|
151
|
+
raise "Multiple commands have been specified as the default"
|
152
|
+
end
|
153
|
+
@command = command_key.to_s()
|
154
|
+
end
|
155
|
+
|
156
|
+
@command_definitions[command_key] = definition
|
157
|
+
rescue => e
|
158
|
+
TU.exception(e)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def add_option(option_key, definition, &parse)
|
163
|
+
begin
|
164
|
+
option_key = option_key.to_sym()
|
165
|
+
if @option_definitions.has_key?(option_key)
|
166
|
+
raise "The #{option_key} option has already been defined"
|
167
|
+
end
|
168
|
+
|
169
|
+
unless definition[:on].is_a?(Array)
|
170
|
+
definition[:on] = [definition[:on]]
|
171
|
+
end
|
172
|
+
|
173
|
+
# Check if the arguments for this option overlap with any other options
|
174
|
+
definition[:on].each{
|
175
|
+
|arg|
|
176
|
+
|
177
|
+
arg = arg.split(" ").shift()
|
178
|
+
if @option_definition_arguments.has_key?(arg)
|
179
|
+
raise "The #{arg} argument is already defined for this script"
|
180
|
+
end
|
181
|
+
@option_definition_arguments[arg] = true
|
182
|
+
}
|
183
|
+
|
184
|
+
if parse != nil
|
185
|
+
definition[:parse] = parse
|
186
|
+
end
|
187
|
+
|
188
|
+
@option_definitions[option_key] = definition
|
189
|
+
rescue => e
|
190
|
+
TU.exception(e)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def set_option_default(option_key, default = nil)
|
195
|
+
unless @option_definitions.has_key?(option_key)
|
196
|
+
raise "Unable to set option default for #{:option_key.to_s()} because the option is not defined."
|
197
|
+
end
|
198
|
+
|
199
|
+
@option_definitions[option_key][:default] = default
|
200
|
+
end
|
201
|
+
|
202
|
+
def parse_options
|
203
|
+
opts = OptionParser.new()
|
204
|
+
|
205
|
+
@option_definitions.each{
|
206
|
+
|option_key,definition|
|
207
|
+
|
208
|
+
args = definition[:on]
|
209
|
+
if definition[:aliases] != nil && definition[:aliases].is_a?(Array)
|
210
|
+
definition[:aliases].each{
|
211
|
+
|arg_alias|
|
212
|
+
args << arg_alias
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
opts.on(*args) {
|
217
|
+
|val|
|
218
|
+
|
219
|
+
if definition[:parse] != nil
|
220
|
+
begin
|
221
|
+
val = definition[:parse].call(val)
|
222
|
+
|
223
|
+
unless val == nil
|
224
|
+
opt(option_key, val)
|
225
|
+
end
|
226
|
+
rescue MessageError => me
|
227
|
+
TU.error(me.message())
|
228
|
+
end
|
229
|
+
else
|
230
|
+
opt(option_key, val)
|
231
|
+
end
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
TU.run_option_parser(opts)
|
236
|
+
|
237
|
+
if @command_definitions.size() > 0 && TU.remaining_arguments.size() > 0
|
238
|
+
if TU.remaining_arguments[0] != nil
|
239
|
+
if @command_definitions.has_key?(TU.remaining_arguments[0].to_sym())
|
240
|
+
@command = TU.remaining_arguments.shift()
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def parse_integer_option(val)
|
247
|
+
v = val.to_i()
|
248
|
+
unless v.to_s() == val
|
249
|
+
raise MessageError.new("Unable to parse '#{val}' as an integer")
|
250
|
+
end
|
251
|
+
|
252
|
+
return v
|
253
|
+
end
|
254
|
+
|
255
|
+
def parse_float_option(val)
|
256
|
+
val.to_f()
|
257
|
+
end
|
258
|
+
|
259
|
+
def parse_boolean_option(val)
|
260
|
+
if val == "true"
|
261
|
+
true
|
262
|
+
elsif val == "false"
|
263
|
+
false
|
264
|
+
else
|
265
|
+
raise MessageError.new("Unable to parse value '#{val}' as a boolean")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def validate
|
270
|
+
if require_installed_directory?()
|
271
|
+
if TI == nil
|
272
|
+
raise "Unable to run #{$0} without the '--directory' argument pointing to an active Tungsten installation"
|
273
|
+
else
|
274
|
+
TI.inherit_path()
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
if require_command?() && @command_definitions.size() > 0 && @command == nil
|
279
|
+
TU.error("A command was not given for this script. Valid commands are #{@command_definitions.keys().join(', ')} and must be the first argument.")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def script_name
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
|
287
|
+
def display_help
|
288
|
+
if script_name().to_s() != ""
|
289
|
+
TU.output("Usage: #{script_name()} [global-options] [script-options]")
|
290
|
+
TU.output("")
|
291
|
+
end
|
292
|
+
|
293
|
+
unless description() == nil
|
294
|
+
description().split("<br>").each{
|
295
|
+
|section|
|
296
|
+
TU.output(TU.wrapped_lines(section))
|
297
|
+
}
|
298
|
+
TU.output("")
|
299
|
+
end
|
300
|
+
|
301
|
+
TU.display_help()
|
302
|
+
|
303
|
+
if @command_definitions.size() > 0
|
304
|
+
TU.write_header("Script Commands", nil)
|
305
|
+
|
306
|
+
commands = @command_definitions.keys().sort { |a, b| a.to_s <=> b.to_s }
|
307
|
+
commands.each{
|
308
|
+
|command_key|
|
309
|
+
definition = @command_definitions[command_key]
|
310
|
+
if definition[:default] == true
|
311
|
+
default = "default"
|
312
|
+
else
|
313
|
+
default = ""
|
314
|
+
end
|
315
|
+
|
316
|
+
TU.output_usage_line(command_key.to_s(), definition[:help], default)
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
TU.write_header("Script Options", nil)
|
321
|
+
|
322
|
+
@option_definitions.each{
|
323
|
+
|option_key,definition|
|
324
|
+
|
325
|
+
if definition[:hidden] == true
|
326
|
+
next
|
327
|
+
end
|
328
|
+
|
329
|
+
if definition[:help].is_a?(Array)
|
330
|
+
help = definition[:help].shift()
|
331
|
+
additional_help = definition[:help]
|
332
|
+
else
|
333
|
+
help = definition[:help]
|
334
|
+
additional_help = []
|
335
|
+
end
|
336
|
+
|
337
|
+
TU.output_usage_line(definition[:on].join(","),
|
338
|
+
help, definition[:default], nil, additional_help.join("\n"))
|
339
|
+
}
|
340
|
+
end
|
341
|
+
|
342
|
+
def display_autocomplete
|
343
|
+
values = TU.get_autocomplete_arguments()
|
344
|
+
if @command_definitions.size() > 0
|
345
|
+
@command_definitions.each{
|
346
|
+
|command_key,definition|
|
347
|
+
values << command_key.to_s()
|
348
|
+
}
|
349
|
+
end
|
350
|
+
|
351
|
+
@option_definitions.each{
|
352
|
+
|option_key,definition|
|
353
|
+
|
354
|
+
if definition[:hidden] == true
|
355
|
+
next
|
356
|
+
end
|
357
|
+
|
358
|
+
values = values + definition[:on]
|
359
|
+
}
|
360
|
+
|
361
|
+
values.map!{
|
362
|
+
|v|
|
363
|
+
parts = v.split(" ")
|
364
|
+
if parts.size() == 2
|
365
|
+
"#{parts[0]}="
|
366
|
+
else
|
367
|
+
v
|
368
|
+
end
|
369
|
+
}
|
370
|
+
|
371
|
+
puts values.join(" ")
|
372
|
+
end
|
373
|
+
|
374
|
+
def require_installed_directory?(v = nil)
|
375
|
+
if (v != nil)
|
376
|
+
@require_installed_directory = v
|
377
|
+
end
|
378
|
+
|
379
|
+
@require_installed_directory
|
380
|
+
end
|
381
|
+
|
382
|
+
def require_command?
|
383
|
+
true
|
384
|
+
end
|
385
|
+
|
386
|
+
def description(v = nil)
|
387
|
+
if v != nil
|
388
|
+
@description = v
|
389
|
+
end
|
390
|
+
|
391
|
+
@description || nil
|
392
|
+
end
|
393
|
+
|
394
|
+
def script_log_path
|
395
|
+
nil
|
396
|
+
end
|
397
|
+
|
398
|
+
def initialized?
|
399
|
+
@initialized
|
400
|
+
end
|
401
|
+
|
402
|
+
def cleanup(code = 0)
|
403
|
+
TU.debug("Finish #{$0} #{ARGV.join(' ')}")
|
404
|
+
TU.debug("RC: #{code}")
|
405
|
+
TU.exit(code)
|
406
|
+
end
|
407
|
+
|
408
|
+
def nagios_ok(msg)
|
409
|
+
puts "OK: #{msg}"
|
410
|
+
cleanup(NAGIOS_OK)
|
411
|
+
end
|
412
|
+
|
413
|
+
def nagios_warning(msg)
|
414
|
+
puts "WARNING: #{msg}"
|
415
|
+
cleanup(NAGIOS_WARNING)
|
416
|
+
end
|
417
|
+
|
418
|
+
def nagios_critical(msg)
|
419
|
+
puts "CRITICAL: #{msg}"
|
420
|
+
cleanup(NAGIOS_CRITICAL)
|
421
|
+
end
|
422
|
+
|
423
|
+
def sudo_prefix
|
424
|
+
if ENV['USER'] == "root" || TI.setting("root_command_prefix") != "true"
|
425
|
+
return ""
|
426
|
+
else
|
427
|
+
return "sudo -n "
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# Require the script to specify a default replication service. If there is a
|
433
|
+
# single replication service, that will be used as the default. If there are
|
434
|
+
# multiple, the user must specify --service.
|
435
|
+
module SingleServiceScript
|
436
|
+
def configure
|
437
|
+
super()
|
438
|
+
|
439
|
+
if TI
|
440
|
+
if TI.replication_services.size() > 1
|
441
|
+
default_service = nil
|
442
|
+
else
|
443
|
+
default_service = TI.default_dataservice()
|
444
|
+
end
|
445
|
+
|
446
|
+
add_option(:service, {
|
447
|
+
:on => "--service String",
|
448
|
+
:help => "Replication service to read information from",
|
449
|
+
:default => default_service
|
450
|
+
})
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def validate
|
455
|
+
super()
|
456
|
+
|
457
|
+
if @options[:service] == nil
|
458
|
+
TU.error("You must specify a dataservice for this command with the --service argument")
|
459
|
+
else
|
460
|
+
if TI
|
461
|
+
unless TI.replication_services().include?(@options[:service])
|
462
|
+
TU.error("The #{@options[:service]} service was not found in the replicator at #{TI.hostname()}:#{TI.root()}")
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# Require all replication services to be OFFLINE before proceeding with the
|
470
|
+
# main() method. The user can add --offline to have this done for them, and
|
471
|
+
# --online to bring them back ONLINE when the script finishes cleanly.
|
472
|
+
module OfflineServicesScript
|
473
|
+
def configure
|
474
|
+
super()
|
475
|
+
|
476
|
+
add_option(:clear_logs, {
|
477
|
+
:on => "--clear-logs",
|
478
|
+
:default => false,
|
479
|
+
:help => "Delete all THL and relay logs for the service"
|
480
|
+
})
|
481
|
+
|
482
|
+
add_option(:offline, {
|
483
|
+
:on => "--offline",
|
484
|
+
:help => "Put required replication services offline before processing",
|
485
|
+
:default => false
|
486
|
+
})
|
487
|
+
|
488
|
+
add_option(:offline_timeout, {
|
489
|
+
:on => "--offline-timeout Integer",
|
490
|
+
:help => "Put required replication services offline before processing",
|
491
|
+
:parse => method(:parse_integer_option),
|
492
|
+
:default => 60
|
493
|
+
})
|
494
|
+
|
495
|
+
add_option(:online, {
|
496
|
+
:on => "--online",
|
497
|
+
:help => "Put required replication services online after successful processing",
|
498
|
+
:default => false
|
499
|
+
})
|
500
|
+
end
|
501
|
+
|
502
|
+
def validate
|
503
|
+
super()
|
504
|
+
|
505
|
+
# Some scripts may disable the OFFLINE requirement depending on other
|
506
|
+
# arguments. These methods give them hooks to make that decision dynamic.
|
507
|
+
if allow_service_state_change?() && require_offline_services?()
|
508
|
+
# Check the state of each replication service
|
509
|
+
get_offline_services_list().each{
|
510
|
+
|ds|
|
511
|
+
if TI.trepctl_value(ds, "state") =~ /ONLINE/
|
512
|
+
TU.error("The replication service '#{ds}' must be OFFLINE to run this command. You can add the --offline argument to do this automatically.")
|
513
|
+
end
|
514
|
+
}
|
515
|
+
end
|
516
|
+
|
517
|
+
unless @options[:offline_timeout] > 0
|
518
|
+
TU.error("The --offline-timeout must be a number greater than zero")
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def prepare
|
523
|
+
super()
|
524
|
+
|
525
|
+
if TU.is_valid?()
|
526
|
+
begin
|
527
|
+
if allow_service_state_change?() == true && @options[:offline] == true
|
528
|
+
ds_list = get_offline_services_list()
|
529
|
+
|
530
|
+
# Put each replication service OFFLINE in parallel waiting for the
|
531
|
+
# command to complete
|
532
|
+
TU.notice("Put #{ds_list.join(",")} replication #{TU.pluralize(ds_list, "service", "services")} offline")
|
533
|
+
|
534
|
+
threads = []
|
535
|
+
begin
|
536
|
+
Timeout::timeout(@options[:offline_timeout]) {
|
537
|
+
ds_list.each{
|
538
|
+
|ds|
|
539
|
+
threads << Thread.new{
|
540
|
+
if TI.is_manager?()
|
541
|
+
begin
|
542
|
+
status = TI.status(ds)
|
543
|
+
unless status.is_replication?() == true
|
544
|
+
get_manager_api.call("#{ds}/#{TI.hostname()}", 'shun')
|
545
|
+
TU.cmd_result("#{TI.trepctl(ds)} offline")
|
546
|
+
else
|
547
|
+
TU.cmd_result("#{TI.trepctl(ds)} offline")
|
548
|
+
end
|
549
|
+
rescue => e
|
550
|
+
TU.exception(e)
|
551
|
+
raise("Unable to put replication services offline")
|
552
|
+
end
|
553
|
+
else
|
554
|
+
TU.cmd_result("#{TI.trepctl(ds)} offline")
|
555
|
+
end
|
556
|
+
}
|
557
|
+
}
|
558
|
+
threads.each{|t| t.join() }
|
559
|
+
}
|
560
|
+
rescue Timeout::Error
|
561
|
+
raise("The replication #{TU.pluralize(ds_list, "service", "services")} #{TU.pluralize(ds_list, "is", "are")} taking too long to go offline. Check the status for more information or use the --offline-timeout argument.")
|
562
|
+
end
|
563
|
+
end
|
564
|
+
rescue => e
|
565
|
+
TU.exception(e)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
def cleanup(code = 0)
|
571
|
+
if initialized?() == true && TI != nil && code == 0
|
572
|
+
begin
|
573
|
+
if allow_service_state_change?() == true && @options[:online] == true
|
574
|
+
cleanup_services(true, @options[:clear_logs])
|
575
|
+
elsif @options[:clear_logs] == true
|
576
|
+
cleanup_services(false, @options[:clear_logs])
|
577
|
+
end
|
578
|
+
rescue => e
|
579
|
+
TU.exception(e)
|
580
|
+
code = 1
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
super(code)
|
585
|
+
end
|
586
|
+
|
587
|
+
def cleanup_services(online = false, clear_logs = false)
|
588
|
+
ds_list = get_offline_services_list()
|
589
|
+
|
590
|
+
# Put each replication service ONLINE in parallel waiting for the
|
591
|
+
# command to complete
|
592
|
+
if online == true
|
593
|
+
TU.notice("Put the #{ds_list.join(",")} replication #{TU.pluralize(ds_list, "service", "services")} online")
|
594
|
+
end
|
595
|
+
|
596
|
+
# Emptying the THL and relay logs makes sure that we are starting with
|
597
|
+
# a fresh directory as if `datasource <hostname> restore` was run.
|
598
|
+
if clear_logs == true
|
599
|
+
TU.notice("Clear THL and relay logs for the #{ds_list.join(",")} replication #{TU.pluralize(ds_list, "service", "services")}")
|
600
|
+
end
|
601
|
+
|
602
|
+
threads = []
|
603
|
+
begin
|
604
|
+
Timeout::timeout(@options[:offline_timeout]) {
|
605
|
+
ds_list.each{
|
606
|
+
|ds|
|
607
|
+
threads << Thread.new{
|
608
|
+
if clear_logs == true
|
609
|
+
dir = TI.setting(TI.setting_key(REPL_SERVICES, ds, "repl_thl_directory"))
|
610
|
+
if File.exists?(dir)
|
611
|
+
TU.cmd_result("rm -rf #{dir}/*")
|
612
|
+
end
|
613
|
+
dir = TI.setting(TI.setting_key(REPL_SERVICES, ds, "repl_relay_directory"))
|
614
|
+
if File.exists?(dir)
|
615
|
+
TU.cmd_result("rm -rf #{dir}/*")
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
if online == true
|
620
|
+
if TI.is_manager?()
|
621
|
+
begin
|
622
|
+
status = TI.status(ds)
|
623
|
+
unless status.is_replication?() == true
|
624
|
+
TU.cmd_result("echo 'datasource #{TI.hostname()} recover' | #{TI.cctrl()}")
|
625
|
+
#get_manager_api.call("#{ds}/#{TI.hostname()}", 'recover')
|
626
|
+
else
|
627
|
+
TU.cmd_result("#{TI.trepctl(ds)} online")
|
628
|
+
end
|
629
|
+
rescue => e
|
630
|
+
TU.exception(e)
|
631
|
+
raise("The #{ds} replication service did not come online")
|
632
|
+
end
|
633
|
+
else
|
634
|
+
TU.cmd_result("#{TI.trepctl(ds)} online")
|
635
|
+
end
|
636
|
+
|
637
|
+
unless TI.trepctl_value(ds, "state") == "ONLINE"
|
638
|
+
raise("Unable to put the #{ds} replication service online")
|
639
|
+
end
|
640
|
+
end
|
641
|
+
}
|
642
|
+
}
|
643
|
+
|
644
|
+
threads.each{|t| t.join() }
|
645
|
+
}
|
646
|
+
rescue Timeout::Error
|
647
|
+
TU.error("The replication #{TU.pluralize(ds_list, "service", "services")} #{TU.pluralize(ds_list, "is", "are")} taking too long to cleanup. Check the replicator status for more information.")
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# All replication services must be OFFLINE
|
652
|
+
def get_offline_services_list
|
653
|
+
TI.replication_services()
|
654
|
+
end
|
655
|
+
|
656
|
+
def require_offline_services?
|
657
|
+
if @options[:offline] == true
|
658
|
+
false
|
659
|
+
else
|
660
|
+
true
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
def allow_service_state_change?
|
665
|
+
if TI == nil
|
666
|
+
return false
|
667
|
+
end
|
668
|
+
|
669
|
+
if TI.is_replicator?() && TI.is_running?("replicator")
|
670
|
+
true
|
671
|
+
else
|
672
|
+
false
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
def get_manager_api
|
677
|
+
if @api == nil && TI != nil
|
678
|
+
@api = TungstenAPI::TungstenDataserviceManager.new(TI.mgr_api_uri())
|
679
|
+
end
|
680
|
+
|
681
|
+
@api
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
# Only require a single replication service to be OFFLINE
|
686
|
+
module OfflineSingleServiceScript
|
687
|
+
include SingleServiceScript
|
688
|
+
include OfflineServicesScript
|
689
|
+
|
690
|
+
def get_offline_services_list
|
691
|
+
[@options[:service]]
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
# Group all MySQL validation and methods into a single module
|
696
|
+
module MySQLServiceScript
|
697
|
+
include SingleServiceScript
|
698
|
+
|
699
|
+
# Allow scripts to turn off MySQL validation of the local server
|
700
|
+
def require_local_mysql_service?
|
701
|
+
false
|
702
|
+
end
|
703
|
+
|
704
|
+
def validate
|
705
|
+
super()
|
706
|
+
|
707
|
+
if @options[:service].to_s() == ""
|
708
|
+
return
|
709
|
+
end
|
710
|
+
|
711
|
+
unless TI.replication_services().include?(@options[:service])
|
712
|
+
return
|
713
|
+
end
|
714
|
+
|
715
|
+
if @options[:mysqlhost] == nil
|
716
|
+
@options[:mysqlhost] = TI.setting(TI.setting_key(REPL_SERVICES, @options[:service], "repl_datasource_host"))
|
717
|
+
end
|
718
|
+
if @options[:mysqlport] == nil
|
719
|
+
@options[:mysqlport] = TI.setting(TI.setting_key(REPL_SERVICES, @options[:service], "repl_datasource_port"))
|
720
|
+
end
|
721
|
+
|
722
|
+
if @options[:my_cnf] == nil
|
723
|
+
@options[:my_cnf] = TI.setting(TI.setting_key(REPL_SERVICES, @options[:service], "repl_datasource_mysql_service_conf"))
|
724
|
+
end
|
725
|
+
if @options[:my_cnf] == nil
|
726
|
+
TU.error "Unable to determine location of MySQL my.cnf file"
|
727
|
+
else
|
728
|
+
unless File.exist?(@options[:my_cnf])
|
729
|
+
TU.error "The file #{@options[:my_cnf]} does not exist"
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
if require_local_mysql_service?()
|
734
|
+
if @options[:mysqluser] == nil
|
735
|
+
@options[:mysqluser] = get_mysql_option("user")
|
736
|
+
end
|
737
|
+
if @options[:mysqluser].to_s() == ""
|
738
|
+
@options[:mysqluser] = "mysql"
|
739
|
+
end
|
740
|
+
|
741
|
+
if @options[:mysql_service_command] == nil
|
742
|
+
@options[:mysql_service_command] = TI.setting(TI.setting_key(REPL_SERVICES, @options[:service], "repl_datasource_boot_script"))
|
743
|
+
end
|
744
|
+
if @options[:mysql_service_command] == nil
|
745
|
+
begin
|
746
|
+
service_command=TU.cmd_result("which service")
|
747
|
+
if TU.cmd("#{sudo_prefix()}test -x #{service_command}")
|
748
|
+
if TU.cmd("#{sudo_prefix()}test -x /etc/init.d/mysqld")
|
749
|
+
@options[:mysql_service_command] = "#{service_command} mysqld"
|
750
|
+
elsif TU.cmd("#{sudo_prefix()}test -x /etc/init.d/mysql")
|
751
|
+
@options[:mysql_service_command] = "#{service_command} mysql"
|
752
|
+
else
|
753
|
+
TU.error "Unable to determine the service command to start/stop mysql"
|
754
|
+
end
|
755
|
+
else
|
756
|
+
if TU.cmd("#{sudo_prefix()}test -x /etc/init.d/mysqld")
|
757
|
+
@options[:mysql_service_command] = "/etc/init.d/mysqld"
|
758
|
+
elsif TU.cmd("#{sudo_prefix()}test -x /etc/init.d/mysql")
|
759
|
+
@options[:mysql_service_command] = "/etc/init.d/mysql"
|
760
|
+
else
|
761
|
+
TU.error "Unable to determine the service command to start/stop mysql"
|
762
|
+
end
|
763
|
+
end
|
764
|
+
rescue CommandError
|
765
|
+
TU.error "Unable to determine the service command to start/stop mysql"
|
766
|
+
end
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
def get_mysql_command
|
772
|
+
"mysql --defaults-file=#{@options[:my_cnf]} -h#{@options[:mysqlhost]} --port=#{@options[:mysqlport]}"
|
773
|
+
end
|
774
|
+
|
775
|
+
def get_mysqldump_command
|
776
|
+
"mysqldump --defaults-file=#{@options[:my_cnf]} --host=#{@options[:mysqlhost]} --port=#{@options[:mysqlport]} --opt --single-transaction --all-databases --add-drop-database --master-data=2"
|
777
|
+
end
|
778
|
+
|
779
|
+
def get_xtrabackup_command
|
780
|
+
# Use the configured my.cnf file, or the additional config file
|
781
|
+
# if we created one
|
782
|
+
if @options[:extra_mysql_defaults_file] == nil
|
783
|
+
defaults_file = @options[:my_cnf]
|
784
|
+
else
|
785
|
+
defaults_file = @options[:extra_mysql_defaults_file].path()
|
786
|
+
end
|
787
|
+
|
788
|
+
"innobackupex-1.5.1 --defaults-file=#{defaults_file} --host=#{@options[:mysqlhost]} --port=#{@options[:mysqlport]}"
|
789
|
+
end
|
790
|
+
|
791
|
+
def xtrabackup_supports_argument(arg)
|
792
|
+
arg = arg.tr("-", "\\-")
|
793
|
+
supports_argument = TU.cmd_result("#{get_xtrabackup_command()} --help /tmp | grep -e\"#{arg}\" | wc -l")
|
794
|
+
if supports_argument == "1"
|
795
|
+
return true
|
796
|
+
else
|
797
|
+
return false
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
def get_mysql_result(command, timeout = 30)
|
802
|
+
begin
|
803
|
+
Timeout.timeout(timeout.to_i()) {
|
804
|
+
return TU.cmd_result("#{get_mysql_command()} -e \"#{command}\"")
|
805
|
+
}
|
806
|
+
rescue Timeout::Error
|
807
|
+
rescue => e
|
808
|
+
end
|
809
|
+
|
810
|
+
return nil
|
811
|
+
end
|
812
|
+
|
813
|
+
def get_mysql_value(command, column = nil)
|
814
|
+
response = get_mysql_result(command + "\\\\G")
|
815
|
+
if response == nil
|
816
|
+
return nil
|
817
|
+
end
|
818
|
+
|
819
|
+
response.split("\n").each{ | response_line |
|
820
|
+
parts = response_line.chomp.split(":")
|
821
|
+
if (parts.length != 2)
|
822
|
+
next
|
823
|
+
end
|
824
|
+
parts[0] = parts[0].strip;
|
825
|
+
parts[1] = parts[1].strip;
|
826
|
+
|
827
|
+
if parts[0] == column || column == nil
|
828
|
+
return parts[1]
|
829
|
+
end
|
830
|
+
}
|
831
|
+
|
832
|
+
return nil
|
833
|
+
end
|
834
|
+
|
835
|
+
# Read the configured value for a mysql variable
|
836
|
+
def get_mysql_option(opt)
|
837
|
+
begin
|
838
|
+
val = TU.cmd_result("my_print_defaults --config-file=#{@options[:my_cnf]} mysqld | grep -e'^--#{opt.gsub(/[\-\_]/, "[-_]")}'")
|
839
|
+
rescue CommandError => ce
|
840
|
+
return nil
|
841
|
+
end
|
842
|
+
|
843
|
+
return val.split("\n")[0].split("=")[1]
|
844
|
+
end
|
845
|
+
|
846
|
+
# Read the current value for a mysql variable
|
847
|
+
def get_mysql_variable(var)
|
848
|
+
response = TU.cmd_result("#{get_mysql_command()} -e \"SHOW VARIABLES LIKE '#{var}'\\\\G\"")
|
849
|
+
|
850
|
+
response.split("\n").each{ | response_line |
|
851
|
+
parts = response_line.chomp.split(":")
|
852
|
+
if (parts.length != 2)
|
853
|
+
next
|
854
|
+
end
|
855
|
+
parts[0] = parts[0].strip;
|
856
|
+
parts[1] = parts[1].strip;
|
857
|
+
|
858
|
+
if parts[0] == "Value"
|
859
|
+
return parts[1]
|
860
|
+
end
|
861
|
+
}
|
862
|
+
|
863
|
+
return nil
|
864
|
+
end
|
865
|
+
|
866
|
+
# Store additional MySQL configuration values in a temporary file
|
867
|
+
def set_mysql_defaults_value(value)
|
868
|
+
if @options[:extra_mysql_defaults_file] == nil
|
869
|
+
@options[:extra_mysql_defaults_file] = Tempfile.new("xtracfg")
|
870
|
+
@options[:extra_mysql_defaults_file].puts("!include #{@options[:my_cnf]}")
|
871
|
+
@options[:extra_mysql_defaults_file].puts("")
|
872
|
+
@options[:extra_mysql_defaults_file].puts("[mysqld]")
|
873
|
+
end
|
874
|
+
|
875
|
+
@options[:extra_mysql_defaults_file].puts(value)
|
876
|
+
@options[:extra_mysql_defaults_file].flush()
|
877
|
+
end
|
878
|
+
|
879
|
+
def start_mysql_server
|
880
|
+
TU.notice("Start the MySQL service")
|
881
|
+
begin
|
882
|
+
TU.cmd_result("#{sudo_prefix()}#{@options[:mysql_service_command]} start")
|
883
|
+
rescue CommandError
|
884
|
+
end
|
885
|
+
|
886
|
+
# Wait 30 seconds for the MySQL service to be responsive
|
887
|
+
begin
|
888
|
+
Timeout.timeout(30) {
|
889
|
+
while true
|
890
|
+
begin
|
891
|
+
if get_mysql_result("SELECT 1") != nil
|
892
|
+
break
|
893
|
+
end
|
894
|
+
|
895
|
+
# Pause for a second before running again
|
896
|
+
sleep 1
|
897
|
+
rescue
|
898
|
+
end
|
899
|
+
end
|
900
|
+
}
|
901
|
+
rescue Timeout::Error
|
902
|
+
raise "The MySQL server has taken too long to start"
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
# Make sure that the mysql server is stopped by stopping it and checking
|
907
|
+
# the process has disappeared
|
908
|
+
def stop_mysql_server
|
909
|
+
TU.notice("Stop the MySQL service")
|
910
|
+
begin
|
911
|
+
pid_file = get_mysql_variable("pid_file")
|
912
|
+
pid = TU.cmd_result("#{sudo_prefix()}cat #{pid_file}")
|
913
|
+
rescue CommandError
|
914
|
+
pid = ""
|
915
|
+
end
|
916
|
+
|
917
|
+
begin
|
918
|
+
TU.cmd_result("#{sudo_prefix()}#{@options[:mysql_service_command]} stop")
|
919
|
+
rescue CommandError
|
920
|
+
end
|
921
|
+
|
922
|
+
begin
|
923
|
+
# Verify that the stop command worked properly
|
924
|
+
# We are expecting an error so we have to catch the exception
|
925
|
+
TU.cmd_result("#{get_mysql_command()} -e \"select 1\" > /dev/null 2>&1")
|
926
|
+
raise "Unable to properly shutdown the MySQL service"
|
927
|
+
rescue CommandError
|
928
|
+
end
|
929
|
+
|
930
|
+
# We saw issues where MySQL would not close completely. This will
|
931
|
+
# watch the PID and make sure it does not appear
|
932
|
+
unless pid.to_s() == ""
|
933
|
+
begin
|
934
|
+
TU.debug("Verify that the MySQL pid has gone away")
|
935
|
+
Timeout.timeout(30) {
|
936
|
+
pid_missing = false
|
937
|
+
|
938
|
+
while pid_missing == false do
|
939
|
+
begin
|
940
|
+
TU.cmd_result("#{sudo_prefix()}ps -p #{pid}")
|
941
|
+
sleep 5
|
942
|
+
rescue CommandError
|
943
|
+
pid_missing = true
|
944
|
+
end
|
945
|
+
end
|
946
|
+
}
|
947
|
+
rescue Timeout::Error
|
948
|
+
raise "Unable to verify that MySQL has fully shutdown"
|
949
|
+
end
|
950
|
+
end
|
951
|
+
end
|
952
|
+
end
|