repctl 0.0.4 → 0.0.5

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.
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
2
  .bundle
3
+ webserver/sass/.sass-cache/*
3
4
  Gemfile.lock
4
5
  pkg/*
data/bin/repctl CHANGED
@@ -5,34 +5,6 @@ require 'fileutils'
5
5
  require 'repctl'
6
6
  require 'thor'
7
7
 
8
- module Repctl
9
- module Helpers
10
- def do_stop(instance)
11
- do_admin(instance, "shutdown")
12
- end
13
-
14
- def do_reset(instance)
15
- do_stop(instance)
16
- do_config(instance)
17
- do_start(instance)
18
- do_secure_accounts(instance)
19
- end
20
-
21
- def do_start_slave(instance)
22
- run_mysql_query(instance, "START SLAVE")
23
- end
24
-
25
- def do_stop_slave(instance)
26
- run_mysql_query(instance, "STOP SLAVE")
27
- end
28
-
29
- def do_restart(instance)
30
- do_admin(instance, "shutown")
31
- do_start(instance)
32
- end
33
- end
34
- end
35
-
36
8
  class RepctlCmds < Thor
37
9
 
38
10
  include Thor::Actions
@@ -40,6 +12,7 @@ class RepctlCmds < Thor
40
12
  include Repctl::Commands
41
13
  include Repctl::Servers
42
14
  include Repctl::Helpers
15
+ include Repctl::Color
43
16
 
44
17
  desc "start [INSTANCES]", "Start one or more defined server instances."
45
18
  method_option :all, :type => :boolean, :aliases => "-a",
@@ -200,49 +173,12 @@ class RepctlCmds < Thor
200
173
  method_option :servers, :aliases => "-s", :type => :array,
201
174
  :desc => "Only check the status of given servers."
202
175
  def status
203
- todos = options[:servers] || all_live_instances
204
- unless todos.any?
205
- say "No Running Servers. Are you running as the mysql user?", :blue
206
- return
207
- end
208
176
  header = sprintf("%-5s%-27s%-27s%-27s%-8s\n",
209
177
  "inst", "master", "received", "applied", "lag")
210
-
211
178
  loop do
212
- say header, :blue
213
-
214
- todos.each do |i|
215
- coordinates = get_coordinates(i)
216
- master_file = coordinates[:file]
217
- master_pos = coordinates[:position]
218
- slave = is_slave?(i)
219
- if slave
220
- slave_status = get_slave_status(i)
221
- recv_file = slave_status["Master_Log_File"]
222
- recv_pos = slave_status["Read_Master_Log_Pos"]
223
- apply_file = slave_status["Relay_Master_Log_File"]
224
- apply_pos = slave_status["Exec_Master_Log_Pos"]
225
- lag = slave_status["Seconds_Behind_Master"]
226
- master_host = slave_status["Master_Host"]
227
- master_port = slave_status["Master_Port"]
228
- master_instance = instance_for(master_host, master_port)
229
- end
230
-
231
- if slave
232
- if lag
233
- lag = lag.to_s
234
- else
235
- lag = "-"
236
- end
237
- format = "%1d%-4s%16s:%-10d%16s:%-10d%16s:%-10d%-8s"
238
- str = sprintf(format, i, "(#{master_instance.to_s})", master_file,
239
- master_pos, recv_file, recv_pos, apply_file, apply_pos, lag)
240
- else
241
- format = "%-5d%16s:%-10d"
242
- str = sprintf(format, i, master_file, master_pos)
243
- end
244
-
245
- say str + "\n", :yellow
179
+ output = formatted_status(options)
180
+ output.each do |line|
181
+ puts line
246
182
  end
247
183
  break unless options[:continuous]
248
184
  sleep options[:continuous]
@@ -255,8 +191,7 @@ class RepctlCmds < Thor
255
191
  coordinates = do_dump(instance, dumpfile)
256
192
  file = coordinates[:file]
257
193
  position = coordinates[:position]
258
- puts "(#{file}, #{position})"
259
- [file, String(position)]
194
+ say "Dumped at coordinates (#{file}, #{position})", :green
260
195
  end
261
196
 
262
197
  desc "restore INSTANCE [DUMPFILE]", "Restore INSTANCE from a \'mysqldump\' file DUMPFILE."
@@ -269,25 +204,14 @@ class RepctlCmds < Thor
269
204
  # master has existing data.
270
205
  #
271
206
  desc "add_slave MASTER SLAVE", "Master has some data that is used to initialize the slave."
272
- method_option :populate, :type => :boolean, :default => false
273
207
  def add_slave(master, slave)
274
- reset(slave)
275
-
276
- if options[:populate]
277
- invoke "utils:bench", [master, "/opt/MySQL/b2.properties"]
278
- end
279
- file, position = invoke "dump", [master]
280
-
281
- # Slave is running, but it is not yet configured as a slave.
282
- # Load slave from the dump file...
283
- invoke "restore", [slave]
284
-
285
- do_change_master(master, slave, :file => file, :position => position)
286
- do_start_slave(slave)
287
-
288
- do_cluster_user(slave)
289
- do_repl_user(slave)
208
+ dumpfile = DEFAULT_DUMPFILE
209
+ do_add_slave(master, slave, dumpfile)
210
+ end
290
211
 
212
+ desc "remove_slave SLAVE", "Remove a slave from the replica set."
213
+ def remove_slave(slave)
214
+ do_remove_slave(slave)
291
215
  end
292
216
 
293
217
  DEFAULT_MASTER = 1
@@ -385,16 +309,7 @@ class RepctlCmds < Thor
385
309
  "Set up a single master/slave replication pair from the very beginning."
386
310
  def repl_pair(master, slave)
387
311
  say "master is #{master}, slave is #{slave}", :green
388
- do_reset(master)
389
- do_reset(slave)
390
- do_cluster_user(master)
391
- do_repl_user(master)
392
- coordinates = get_coordinates(master)
393
- file = coordinates[:file]
394
- position = coordinates[:position]
395
- say "File is #{file}, Position is #{position}", :green
396
- do_change_master(master, slave, :file => file, :position => position)
397
- do_start_slave(slave)
312
+ do_repl_pair(master, slave)
398
313
  end
399
314
 
400
315
  desc "repl_trio MASTER SLAVE1 SLAVE2",
@@ -402,30 +317,7 @@ class RepctlCmds < Thor
402
317
  method_option :reset, :type => :boolean, :default => true
403
318
  def repl_trio(master, slave1, slave2)
404
319
  say "master is #{master}, slaves are #{slave1} and #{slave2}", :green
405
- if options[:reset]
406
- do_reset(master)
407
- do_reset(slave1)
408
- do_reset(slave2)
409
- else
410
- do_restart(master)
411
- do_restart(slave1)
412
- do_restart(slave2)
413
- end
414
- # Set up the replication accounts for all servers, in case we
415
- # decide to switch masters later.
416
- [master, slave1, slave2].each do |s|
417
- do_cluster_user(s)
418
- do_repl_user(s)
419
- end
420
-
421
- coordinates = get_coordinates(master)
422
- file = coordinates[:file]
423
- position = coordinates[:position]
424
- say "File is #{file}, Position is #{position}", :green
425
- do_change_master(master, slave1, :file => file, :position => position)
426
- do_start_slave(slave1)
427
- do_change_master(master, slave2, :file => file, :position => position)
428
- do_start_slave(slave2)
320
+ do_repl_trio(master, slave1, slave2, options)
429
321
  end
430
322
 
431
323
  desc "install_sample_configs DIRECTORY", "After initial install, use these "
data/config/servers.yml CHANGED
@@ -9,6 +9,7 @@
9
9
  innodb_data_home_dir: /opt/MySQL/instances/data1
10
10
  innodb_log_group_home_dir: /opt/MySQL/instances/data1
11
11
  pid-file: /opt/MySQL/instances/data1/deimos.pid
12
+ user: _mysql
12
13
 
13
14
  - instance: 2
14
15
  defaults-file: "/opt/projects/repctl/config/mysql.cnf"
@@ -20,6 +21,7 @@
20
21
  innodb_data_home_dir: /opt/MySQL/instances/data2
21
22
  innodb_log_group_home_dir: /opt/MySQL/instances/data2
22
23
  pid-file: /opt/MySQL/instances/data2/deimos.pid
24
+ user: _mysql
23
25
 
24
26
  - instance: 3
25
27
  defaults-file: "/opt/projects/repctl/config/mysql.cnf"
@@ -31,6 +33,7 @@
31
33
  innodb_data_home_dir: /opt/MySQL/instances/data3
32
34
  innodb_log_group_home_dir: /opt/MySQL/instances/data3
33
35
  pid-file: /opt/MySQL/instances/data3/deimos.pid
36
+ user: _mysql
34
37
 
35
38
  - instance: 4
36
39
  defaults-file: "/opt/projects/repctl/config/mysql.cnf"
@@ -42,4 +45,5 @@
42
45
  innodb_data_home_dir: /opt/MySQL/instances/data4
43
46
  innodb_log_group_home_dir: /opt/MySQL/instances/data4
44
47
  pid-file: /opt/MySQL/instances/data4/deimos.pid
48
+ user: _mysql
45
49
 
@@ -0,0 +1,61 @@
1
+ module Repctl
2
+ module Color
3
+ module Constants
4
+
5
+ # Embed in a String to clear all previous ANSI sequences.
6
+ CLEAR = "\e[0m"
7
+ # The start of an ANSI bold sequence.
8
+ BOLD = "\e[1m"
9
+
10
+ # Set the terminal's foreground ANSI color to black.
11
+ BLACK = "\e[30m"
12
+ # Set the terminal's foreground ANSI color to red.
13
+ RED = "\e[31m"
14
+ # Set the terminal's foreground ANSI color to green.
15
+ GREEN = "\e[32m"
16
+ # Set the terminal's foreground ANSI color to yellow.
17
+ YELLOW = "\e[33m"
18
+ # Set the terminal's foreground ANSI color to blue.
19
+ BLUE = "\e[34m"
20
+ # Set the terminal's foreground ANSI color to magenta.
21
+ MAGENTA = "\e[35m"
22
+ # Set the terminal's foreground ANSI color to cyan.
23
+ CYAN = "\e[36m"
24
+ # Set the terminal's foreground ANSI color to white.
25
+ WHITE = "\e[37m"
26
+
27
+ # Set the terminal's background ANSI color to black.
28
+ ON_BLACK = "\e[40m"
29
+ # Set the terminal's background ANSI color to red.
30
+ ON_RED = "\e[41m"
31
+ # Set the terminal's background ANSI color to green.
32
+ ON_GREEN = "\e[42m"
33
+ # Set the terminal's background ANSI color to yellow.
34
+ ON_YELLOW = "\e[43m"
35
+ # Set the terminal's background ANSI color to blue.
36
+ ON_BLUE = "\e[44m"
37
+ # Set the terminal's background ANSI color to magenta.
38
+ ON_MAGENTA = "\e[45m"
39
+ # Set the terminal's background ANSI color to cyan.
40
+ ON_CYAN = "\e[46m"
41
+ # Set the terminal's background ANSI color to white.
42
+ ON_WHITE = "\e[47m"
43
+ end
44
+
45
+ # Set color by using a string or one of the defined constants. If a third
46
+ # option is set to true, it also adds bold to the string. This is based
47
+ # on Highline implementation and it automatically appends CLEAR to the end
48
+ # of the returned String.
49
+ #
50
+ def colorize(color, bold = false)
51
+ color = Constants.const_get(color.to_s.upcase) if color.is_a?(Symbol)
52
+ bold = bold ? Constants::BOLD : ""
53
+ "#{bold}#{color}#{self.to_s}#{Constants::CLEAR}"
54
+ end
55
+ end
56
+ end
57
+
58
+ String.class_eval do
59
+ include Repctl::Color
60
+ end
61
+
@@ -0,0 +1,146 @@
1
+ module Repctl
2
+ module Helpers
3
+
4
+ include Commands
5
+ include Servers
6
+
7
+ def do_stop(instance)
8
+ do_admin(instance, "shutdown")
9
+ end
10
+
11
+ def do_reset(instance)
12
+ do_stop(instance)
13
+ do_config(instance)
14
+ do_start(instance)
15
+ do_secure_accounts(instance)
16
+ end
17
+
18
+ def do_start_slave(instance)
19
+ run_mysql_query(instance, "START SLAVE")
20
+ end
21
+
22
+ def do_stop_slave(instance)
23
+ run_mysql_query(instance, "STOP SLAVE")
24
+ end
25
+
26
+ def do_restart(instance)
27
+ do_admin(instance, "shutown")
28
+ do_start(instance)
29
+ end
30
+
31
+ # Generate an array of hashes, one hash per fabric-wide instance.
32
+ def repl_status(options = {})
33
+ todos = options[:servers] || all_live_instances
34
+ return [] unless todos.any?
35
+ status_array = []
36
+ todos.each do |i|
37
+ coordinates = get_coordinates(i)
38
+ next unless coordinates
39
+ master_file = coordinates[:file]
40
+ master_pos = coordinates[:position]
41
+
42
+ fields = {}
43
+ fields[:instance] = i.to_s
44
+ fields[:server] = "#{server_for_instance(i)['hostname']}:#{i}"
45
+ fields[:generated_binlog] = "#{master_file}:#{master_pos}"
46
+ if is_slave?(i)
47
+ slave_status = get_slave_status(i)
48
+ recv_file = slave_status["Master_Log_File"]
49
+ recv_pos = slave_status["Read_Master_Log_Pos"]
50
+ apply_file = slave_status["Relay_Master_Log_File"]
51
+ apply_pos = slave_status["Exec_Master_Log_Pos"]
52
+ lag = slave_status["Seconds_Behind_Master"]
53
+ master_host = slave_status["Master_Host"]
54
+ master_port = slave_status["Master_Port"]
55
+ master_instance = instance_for(master_host, master_port)
56
+
57
+ fields[:applied_binlog] = "#{apply_file}:#{apply_pos}"
58
+ fields[:received_binlog] = "#{recv_file}:#{recv_pos}"
59
+ fields[:master] = "#{master_host}:#{master_instance}"
60
+ fields[:lag] = lag
61
+ end
62
+ status_array << fields
63
+ end
64
+ status_array
65
+ end
66
+
67
+ def formatted_status(options = {})
68
+ output = []
69
+ header = sprintf("%-5s%-27s%-27s%-27s%-8s",
70
+ "inst", "master", "received", "applied", "lag")
71
+ output << header.colorize(:green)
72
+ todos = repl_status(options)
73
+ todos.each do |server|
74
+ instance = server[:instance]
75
+ gen_binlog = server[:generated_binlog]
76
+ if server[:master]
77
+ server[:master].match(/.*:(\d*)$/)
78
+ master_instance = $1
79
+ recv_binlog = server[:received_binlog]
80
+ app_binlog = server[:applied_binlog]
81
+ lag = server[:lag]
82
+ if lag == nil
83
+ lag = "-"
84
+ else
85
+ lag = lag.to_s
86
+ end
87
+ format = "%1d%-4s%-27s%-27s%-27s%-8s"
88
+ str = sprintf(format, instance, "(#{master_instance})",
89
+ gen_binlog, recv_binlog, app_binlog, lag)
90
+ else
91
+ format = "%-5d%-26s"
92
+ str = sprintf(format, instance, gen_binlog)
93
+ end
94
+ output << str.colorize(:yellow)
95
+ end
96
+ output
97
+ end
98
+
99
+ def do_add_slave(master, slave, dumpfile)
100
+ do_reset(slave)
101
+ coordinates = do_dump(master, dumpfile)
102
+ do_restore(slave, dumpfile)
103
+ do_change_master(master, slave, coordinates)
104
+ do_start_slave(slave)
105
+ do_cluster_user(slave)
106
+ do_repl_user(slave)
107
+ end
108
+
109
+ def do_repl_pair(master, slave)
110
+ do_reset(master)
111
+ do_reset(slave)
112
+ do_cluster_user(master)
113
+ do_repl_user(master)
114
+ coordinates = get_coordinates(master)
115
+ file = coordinates[:file]
116
+ position = coordinates[:position]
117
+ do_change_master(master, slave, :file => file, :position => position)
118
+ do_start_slave(slave)
119
+ end
120
+
121
+ def do_repl_trio(master, slave1, slave2, options = {})
122
+ if options[:reset]
123
+ do_reset(master)
124
+ do_reset(slave1)
125
+ do_reset(slave2)
126
+ else
127
+ do_restart(master)
128
+ do_restart(slave1)
129
+ do_restart(slave2)
130
+ end
131
+ # Set up the replication accounts for all servers, in case we
132
+ # decide to switch masters later.
133
+ [master, slave1, slave2].each do |s|
134
+ do_cluster_user(s)
135
+ do_repl_user(s)
136
+ end
137
+ coordinates = get_coordinates(master)
138
+ file = coordinates[:file]
139
+ position = coordinates[:position]
140
+ do_change_master(master, slave1, :file => file, :position => position)
141
+ do_start_slave(slave1)
142
+ do_change_master(master, slave2, :file => file, :position => position)
143
+ do_start_slave(slave2)
144
+ end
145
+ end
146
+ end
@@ -7,7 +7,6 @@ module Repctl
7
7
 
8
8
  class Client < DelegateClass(Mysql2::Client)
9
9
  include Servers
10
-
11
10
  @@clients = {}
12
11
 
13
12
  def initialize(instance, opts)
@@ -36,7 +35,6 @@ module Repctl
36
35
  while timeout >= 0
37
36
  begin
38
37
  @@clients[instance] ||= Client.new(instance, opts)
39
- # puts "Connected to instance #{instance}."
40
38
  break
41
39
  rescue Mysql2::Error => e
42
40
  puts "#{e.message}, retrying connection to instance #{instance}..."
@@ -59,7 +57,6 @@ module Repctl
59
57
  @@clients[@instance] = nil
60
58
  Client.open(@instance)
61
59
  end
62
-
63
60
  end
64
61
 
65
62
  module Commands
@@ -131,7 +128,7 @@ module Repctl
131
128
  "--innodb_log_group_home_dir=#{server['innodb_log_group_home_dir']}",
132
129
  "--relay-log=#{Socket.gethostname}-relay-bin",
133
130
  "--socket=#{server['socket']}",
134
- "--user=mysql")
131
+ "--user=#{server['user']}")
135
132
  end
136
133
  end
137
134
  end
@@ -415,14 +412,6 @@ EOT
415
412
  client.close if client
416
413
  end
417
414
 
418
- # Return the process ID (pid) for an instance.
419
- def get_mysqld_pid(instance)
420
- server = server_for_instance(instance)
421
- pidfile = server['pid-file']
422
- return nil unless File.exist?(pidfile)
423
- Integer(File.open(pidfile, &:readline).strip)
424
- end
425
-
426
415
  # 'master' is currently a slave that is to be the new master.
427
416
  # 'slaves' contains the list of slaves, one of these may be the
428
417
  # current master.
@@ -448,6 +437,13 @@ EOT
448
437
  end
449
438
  end
450
439
 
440
+ def do_remove_slave(instance)
441
+ stop_slave_io_thread(instance)
442
+ server = server_for_instance(instance)
443
+ datadir = server['datadir']
444
+ %x{ rm -f #{File.join(datadir, 'master.info')} }
445
+ end
446
+
451
447
  private
452
448
 
453
449
  # This is an example template to create commands to issue queries.
@@ -596,30 +592,9 @@ EOT
596
592
  client.close
597
593
  end
598
594
  end
599
-
600
-
601
-
602
595
  end
603
596
  end
604
597
 
605
- class RunExamples
606
- include Repctl::Commands
607
-
608
- def runtest
609
-
610
- switch_master_to(3)
611
- =begin
612
- (1..4).each {|i| ensure_running(i)}
613
- puts get_master_coordinates(1)
614
- (2..4).each {|i| puts get_slave_coordinates(i)}
615
- (1..4).each {|i| puts is_master?(i) }
616
- puts "Master is #{find_master}"
617
- drain_relay_log(2)
618
- # sleep(10)
619
- # (1..4).each {|i| crash(i)}
620
- =end
621
- end
622
- end
623
598
 
624
599
 
625
600
 
@@ -23,7 +23,13 @@ module Repctl
23
23
  end
24
24
 
25
25
  def all_live_servers
26
- all_servers.select {|s| get_mysqld_pid(s["instance"]) }
26
+ s = all_servers.select do |s|
27
+ if pid = get_mysqld_pid(s["instance"])
28
+ mysqld_running?(pid)
29
+ else
30
+ false
31
+ end
32
+ end
27
33
  end
28
34
 
29
35
  def all_live_instances
@@ -43,9 +49,13 @@ module Repctl
43
49
  return nil
44
50
  end
45
51
 
46
- private
52
+ # See if a MySQL server with the given pid is running.
53
+ def mysqld_running?(pid)
54
+ pids = %x{ ps -e | grep mysqld}.split("\n").map { |row| row =~ /\s*(\d+)\s+/; $1.to_i}
55
+ pids.include?(pid)
56
+ end
47
57
 
48
- # Return the process ID (pid) for an instance.
58
+ # Return the process ID (pid) for an instance. This only consults the PID file.
49
59
  def get_mysqld_pid(instance)
50
60
  server = server_for_instance(instance)
51
61
  pidfile = server['pid-file']
@@ -1,3 +1,3 @@
1
1
  module Repctl
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/repctl.rb CHANGED
@@ -4,9 +4,11 @@ config_dir = ENV["REPCTL_CONFIG_DIR"] ||
4
4
 
5
5
  require File.join(config_dir, 'config')
6
6
 
7
- require "repctl/version"
8
- require "repctl/servers"
9
- require "repctl/mysql_admin"
7
+ require 'repctl/version'
8
+ require 'repctl/servers'
9
+ require 'repctl/mysql_admin'
10
+ require 'repctl/helpers'
11
+ require 'repctl/color'
10
12
 
11
13
  module Repctl
12
14
  # Your code goes here...
data/repctl.gemspec CHANGED
@@ -20,6 +20,11 @@ Gem::Specification.new do |s|
20
20
 
21
21
  # specify any dependencies here; for example:
22
22
  s.add_development_dependency "rspec"
23
+ s.add_development_dependency "shotgun"
24
+ s.add_development_dependency "compass"
23
25
  s.add_runtime_dependency "thor"
24
26
  s.add_runtime_dependency "mysql2"
27
+ s.add_runtime_dependency "sinatra"
28
+ s.add_runtime_dependency "thin"
29
+
25
30
  end
Binary file
@@ -0,0 +1,24 @@
1
+ # Require any additional compass plugins here.
2
+
3
+ # Set this to the root of your project when deployed:
4
+ http_path = "/"
5
+ css_dir = "stylesheets"
6
+ sass_dir = "sass"
7
+ images_dir = "images"
8
+ javascripts_dir = "javascripts"
9
+
10
+ # You can select your preferred output style here (can be overridden via the command line):
11
+ # output_style = :expanded or :nested or :compact or :compressed
12
+
13
+ # To enable relative paths to assets via compass helper functions. Uncomment:
14
+ # relative_assets = true
15
+
16
+ # To disable debugging comments that display the original location of your selectors. Uncomment:
17
+ # line_comments = false
18
+
19
+
20
+ # If you prefer the indented syntax, you might want to regenerate this
21
+ # project again passing --syntax sass, or you can uncomment this:
22
+ # preferred_syntax = :sass
23
+ # and then run:
24
+ # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
@@ -0,0 +1,5 @@
1
+ /* Welcome to Compass. Use this file to write IE specific override styles.
2
+ * Import this file using the following HTML or equivalent:
3
+ * <!--[if IE]>
4
+ * <link href="/stylesheets/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
5
+ * <![endif]--> */
@@ -0,0 +1,3 @@
1
+ /* Welcome to Compass. Use this file to define print styles.
2
+ * Import this file using the following HTML or equivalent:
3
+ * <link href="/stylesheets/print.css" media="print" rel="stylesheet" type="text/css" /> */
@@ -0,0 +1,6 @@
1
+ /* Welcome to Compass.
2
+ * In this file you should write your main styles. (or centralize your imports)
3
+ * Import this file using the following HTML or equivalent:
4
+ * <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
5
+
6
+ @import "compass/reset";
@@ -0,0 +1,5 @@
1
+ /* Welcome to Compass. Use this file to write IE specific override styles.
2
+ * Import this file using the following HTML or equivalent:
3
+ * <!--[if IE]>
4
+ * <link href="/stylesheets/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
5
+ * <![endif]--> */
@@ -0,0 +1,3 @@
1
+ /* Welcome to Compass. Use this file to define print styles.
2
+ * Import this file using the following HTML or equivalent:
3
+ * <link href="/stylesheets/print.css" media="print" rel="stylesheet" type="text/css" /> */
@@ -0,0 +1,68 @@
1
+ /* Welcome to Compass.
2
+ * In this file you should write your main styles. (or centralize your imports)
3
+ * Import this file using the following HTML or equivalent:
4
+ * <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
5
+ /* line 17, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
6
+ html, body, div, span, applet, object, iframe,
7
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8
+ a, abbr, acronym, address, big, cite, code,
9
+ del, dfn, em, img, ins, kbd, q, s, samp,
10
+ small, strike, strong, sub, sup, tt, var,
11
+ b, u, i, center,
12
+ dl, dt, dd, ol, ul, li,
13
+ fieldset, form, label, legend,
14
+ table, caption, tbody, tfoot, thead, tr, th, td,
15
+ article, aside, canvas, details, embed,
16
+ figure, figcaption, footer, header, hgroup,
17
+ menu, nav, output, ruby, section, summary,
18
+ time, mark, audio, video {
19
+ margin: 0;
20
+ padding: 0;
21
+ border: 0;
22
+ font-size: 100%;
23
+ font: inherit;
24
+ vertical-align: baseline;
25
+ }
26
+
27
+ /* line 20, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
28
+ body {
29
+ line-height: 1;
30
+ }
31
+
32
+ /* line 22, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
33
+ ol, ul {
34
+ list-style: none;
35
+ }
36
+
37
+ /* line 24, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
38
+ table {
39
+ border-collapse: collapse;
40
+ border-spacing: 0;
41
+ }
42
+
43
+ /* line 26, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
44
+ caption, th, td {
45
+ text-align: left;
46
+ font-weight: normal;
47
+ vertical-align: middle;
48
+ }
49
+
50
+ /* line 28, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
51
+ q, blockquote {
52
+ quotes: none;
53
+ }
54
+ /* line 101, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
55
+ q:before, q:after, blockquote:before, blockquote:after {
56
+ content: "";
57
+ content: none;
58
+ }
59
+
60
+ /* line 30, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
61
+ a img {
62
+ border: none;
63
+ }
64
+
65
+ /* line 114, ../../../../../../usr/local/rvm/gems/ruby-1.9.2-p290@rails-3.1/gems/compass-0.11.7/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
66
+ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
67
+ display: block;
68
+ }
@@ -0,0 +1,80 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>MySQL Replication Manager</title>
6
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
7
+ </script>
8
+
9
+ <script type="text/javascript">
10
+ /* Fix this. It should not be in the global namespace. */
11
+ function getUpdate() {
12
+ $('#status-table').load('/status');
13
+ setTimeout(getUpdate, 3000);
14
+ }
15
+ $(document).ready(function() {
16
+ getUpdate();
17
+
18
+ $('#switch-master').submit(function() {
19
+ $('.spinner').show();
20
+ $.post('/switch_master', $(this).serialize(), function(data) {
21
+ $('.spinner').hide();
22
+ $("#switch-master-result").html(data)
23
+ });
24
+ return false;
25
+ });
26
+
27
+ $('#repl-trio').submit(function() {
28
+ $('#repl-trio-spinner').show();
29
+ $.post('/repl_trio', $(this).serialize(), function(data) {
30
+ $('#repl-trio-spinner').hide();
31
+ $("#repl-trio-result").html(data)
32
+ });
33
+ return false;
34
+ });
35
+
36
+ });
37
+ </script>
38
+
39
+ <style type="text/css">
40
+ #banner {
41
+ text-align: center;
42
+ }
43
+ .spinner {
44
+ display:none;
45
+ }
46
+ .success {
47
+ color: green;
48
+ }
49
+ .failure {
50
+ color: red;
51
+ }
52
+ table.gridtable {
53
+ font-family: verdana,arial,sans-serif;
54
+ font-size:11px;
55
+ color:#333333;
56
+ border-width: 1px;
57
+ border-color: #666666;
58
+ border-collapse: collapse;
59
+ }
60
+ table.gridtable th {
61
+ border-width: 1px;
62
+ padding: 8px;
63
+ border-style: solid;
64
+ border-color: #666666;
65
+ background-color: #dedede;
66
+ }
67
+ table.gridtable td {
68
+ border-width: 1px;
69
+ padding: 8px;
70
+ border-style: solid;
71
+ border-color: #666666;
72
+ background-color: #ffffff;
73
+ }
74
+ </style>
75
+ </head>
76
+
77
+ <body>
78
+ <%= yield %>
79
+ </body>
80
+ </html>
@@ -0,0 +1,38 @@
1
+ <header id="banner">
2
+ <h1>MySQL Replication Manager</h1>
3
+ </header>
4
+
5
+ <section>
6
+ <header>
7
+ <h2>Status Summary</h2>
8
+ </header>
9
+ <div id="status-table">
10
+ </div>
11
+ </section>
12
+
13
+ <section>
14
+ <header><h2>Switch Master</h2></header>
15
+ <form id="switch-master" action="/switch_master" method="post">
16
+ <p>Master: <input type="text" name="switch[master]" size="20"/></p>
17
+ <p>Slaves: <input type="text" name="switch[slaves]" size="20"/></p>
18
+ <input type="submit" value="Switch Master">
19
+ </form>
20
+ <div>
21
+ <div style="float:left;" id="switch-master-result"></div>
22
+ <img style="float:left;" class='spinner' id="switch-master-spinner" src='images/wait30.gif' alt='spinner'></img>
23
+ </div>
24
+ </section>
25
+
26
+ <section>
27
+ <header><h2>Set up Replication Trio from Scratch</h2></header>
28
+ <form id="repl-trio" action="/repl_trio" method="post">
29
+ <p>Master: <input type="text" name="repl_trio[master]" size="20"/></p>
30
+ <p>Slaves: <input type="text" name="repl_trio[slaves]" size="20"/></p>
31
+ <input type="submit" value="Create Repliction Trio">
32
+ </form>
33
+ <div>
34
+ <div style="float:left;" id="repl-trio-result"></div>
35
+ <img style="float:left;" class="spinner" id='repl-trio-spinner' src='images/wait30.gif' alt='spinner'></img>
36
+ </div>
37
+ </section>
38
+
@@ -0,0 +1,5 @@
1
+ <% if @success %>
2
+ <span class="success"><%= @message %></span>
3
+ <% else %>
4
+ <span class="failure"><%= @message %></span>
5
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <div>
2
+ Status as of <%= @timestamp %>
3
+ </div>
4
+ <table class="gridtable">
5
+ <tr>
6
+ <th>instance</th>
7
+ <th>master</th>
8
+ <th>generated binlog</th>
9
+ <th>received binlog</th>
10
+ <th>applied binlog</th>
11
+ <th>lag (secs)</th>
12
+ </tr>
13
+ <% @status_array.each do |s| %>
14
+ <tr>
15
+ <td><%= s[:server] %></td>
16
+ <td><%= s[:master] %></td>
17
+ <td><%= s[:generated_binlog] %></td>
18
+ <td><%= s[:received_binlog] %></td>
19
+ <td><%= s[:applied_binlog] %></td>
20
+ <td><%= s[:lag] %></td>
21
+ </tr>
22
+ <% end %>
23
+ </table>
24
+
@@ -0,0 +1,108 @@
1
+ require 'sinatra'
2
+ require 'repctl'
3
+
4
+ include Repctl::Config
5
+ include Repctl::Commands
6
+ include Repctl::Servers
7
+ include Repctl::Utils
8
+ include Repctl::Color
9
+
10
+ def time
11
+ start = Time.now
12
+ yield
13
+ Time.now - start
14
+ end
15
+
16
+ helpers do
17
+ def img(name)
18
+ "<img src='images/#{ name}' alt='#{ name}' />"
19
+ end
20
+
21
+ def master_slave_params(params)
22
+ param_error = false
23
+ @error_message = nil
24
+ if params.nil? || params["master"] == "" || params["master"].nil? ||
25
+ params["slaves"] == nil || params["slaves"] == ""
26
+ @error_message = "parameters missing"
27
+ else
28
+ begin
29
+ master = params["master"].to_i
30
+ slaves = params["slaves"].split("\s").map(&:to_i)
31
+ rescue Exception => e
32
+ @error_message = "invalid format for parameters"
33
+ else
34
+ if master == 0 || slaves.include?(0)
35
+ @error_message = "0 can not be an instance"
36
+ elsif slaves.include?(master)
37
+ @error_message = "master can not also be a slave"
38
+ elsif slaves.empty?
39
+ @error_message = "no slaves are specified"
40
+ end
41
+ end
42
+ end
43
+ [@error_message, master, slaves]
44
+ end
45
+ end
46
+
47
+ get '/' do
48
+ erb :main
49
+ end
50
+
51
+ # curl deimos:9393/switch_master --header "Accept: text/plain" \
52
+ # -d switch[slaves]="2 1 4" -d switch[master]=3
53
+ post '/switch_master' do
54
+ @message, master, slaves = master_slave_params(params["switch"])
55
+ if @message
56
+ @success = false
57
+ else
58
+ secs = time do
59
+ do_switch_master(master, slaves)
60
+ end
61
+ @message = "Switch master processed in #{secs} secs."
62
+ @success = true
63
+ end
64
+ if request.accept == ['text/plain']
65
+ if @success
66
+ "#{@message}\n".colorize(:green)
67
+ else
68
+ "#{@message}\n".colorize(:red)
69
+ end
70
+ else
71
+ erb :operation_complete, :layout => !request.xhr?
72
+ end
73
+ end
74
+
75
+ post '/repl_trio' do
76
+ @message, master, slaves = master_slave_params(params["repl_trio"])
77
+ if @message
78
+ @success = false
79
+ else
80
+ secs = time do
81
+ sleep 1
82
+ end
83
+ @message = "Create replication trio with master #{master} and slaves #{slaves} in #{secs} secs."
84
+ @success = true
85
+ end
86
+ if request.accept == ['text/plain']
87
+ if @success
88
+ "#{@message}\n".colorize(:green)
89
+ else
90
+ "#{@message}\n".colorize(:red)
91
+ end
92
+ else
93
+ erb :operation_complete, :layout => !request.xhr?
94
+ end
95
+ end
96
+
97
+ # curl deimos:9393/status --header "Accept: text/plain"
98
+ get '/status' do
99
+ if request.accept == ['text/plain']
100
+ formatted_status.join("\n") + "\n"
101
+ else
102
+ @timestamp = Time.now.strftime("%I:%M:%S %p")
103
+ @status_array = repl_status
104
+ erb :status , :layout => !request.xhr?
105
+ end
106
+ end
107
+
108
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: repctl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-17 00:00:00.000000000Z
12
+ date: 2012-03-08 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &76697300 !ruby/object:Gem::Requirement
16
+ requirement: &121456320 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,32 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *76697300
24
+ version_requirements: *121456320
25
+ - !ruby/object:Gem::Dependency
26
+ name: shotgun
27
+ requirement: &121455900 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *121455900
36
+ - !ruby/object:Gem::Dependency
37
+ name: compass
38
+ requirement: &121455480 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *121455480
25
47
  - !ruby/object:Gem::Dependency
26
48
  name: thor
27
- requirement: &76696880 !ruby/object:Gem::Requirement
49
+ requirement: &121455060 !ruby/object:Gem::Requirement
28
50
  none: false
29
51
  requirements:
30
52
  - - ! '>='
@@ -32,10 +54,32 @@ dependencies:
32
54
  version: '0'
33
55
  type: :runtime
34
56
  prerelease: false
35
- version_requirements: *76696880
57
+ version_requirements: *121455060
36
58
  - !ruby/object:Gem::Dependency
37
59
  name: mysql2
38
- requirement: &76696460 !ruby/object:Gem::Requirement
60
+ requirement: &121454640 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *121454640
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra
71
+ requirement: &121454220 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *121454220
80
+ - !ruby/object:Gem::Dependency
81
+ name: thin
82
+ requirement: &121453800 !ruby/object:Gem::Requirement
39
83
  none: false
40
84
  requirements:
41
85
  - - ! '>='
@@ -43,7 +87,7 @@ dependencies:
43
87
  version: '0'
44
88
  type: :runtime
45
89
  prerelease: false
46
- version_requirements: *76696460
90
+ version_requirements: *121453800
47
91
  description: Ruby gem with Thor script to manage MySQL and PostgreSQL replication
48
92
  email:
49
93
  - lydianblues@gmail.com
@@ -63,10 +107,25 @@ files:
63
107
  - config/mysql.cnf
64
108
  - config/servers.yml
65
109
  - lib/repctl.rb
110
+ - lib/repctl/color.rb
111
+ - lib/repctl/helpers.rb
66
112
  - lib/repctl/mysql_admin.rb
67
113
  - lib/repctl/servers.rb
68
114
  - lib/repctl/version.rb
69
115
  - repctl.gemspec
116
+ - webserver/public/images/wait30.gif
117
+ - webserver/sass/config.rb
118
+ - webserver/sass/sass/ie.scss
119
+ - webserver/sass/sass/print.scss
120
+ - webserver/sass/sass/screen.scss
121
+ - webserver/sass/stylesheets/ie.css
122
+ - webserver/sass/stylesheets/print.css
123
+ - webserver/sass/stylesheets/screen.css
124
+ - webserver/views/layout.erb
125
+ - webserver/views/main.erb
126
+ - webserver/views/operation_complete.erb
127
+ - webserver/views/status.erb
128
+ - webserver/webserver.rb
70
129
  homepage: ''
71
130
  licenses: []
72
131
  post_install_message: