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.
@@ -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