myreplicator 0.0.16 → 0.0.17

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.
Files changed (33) hide show
  1. data/app/assets/stylesheets/myreplicator/application.css +53 -10
  2. data/app/controllers/myreplicator/exports_controller.rb +17 -1
  3. data/app/controllers/myreplicator/home_controller.rb +4 -1
  4. data/app/helpers/myreplicator/application_helper.rb +20 -0
  5. data/app/models/myreplicator/export.rb +108 -23
  6. data/app/models/myreplicator/log.rb +90 -0
  7. data/app/views/myreplicator/exports/_form.html.erb +33 -9
  8. data/app/views/myreplicator/home/_home_menu.erb +6 -2
  9. data/app/views/myreplicator/home/errors.html.erb +15 -5
  10. data/app/views/myreplicator/home/index.html.erb +20 -43
  11. data/db/migrate/20121025191622_create_myreplicator_exports.rb +0 -6
  12. data/db/migrate/20121212003652_create_myreplicator_logs.rb +21 -0
  13. data/lib/exporter/export_metadata.rb +8 -1
  14. data/lib/exporter/mysql_exporter.rb +32 -0
  15. data/lib/exporter/sql_commands.rb +85 -4
  16. data/lib/loader/loader.rb +48 -7
  17. data/lib/myreplicator.rb +16 -4
  18. data/lib/myreplicator/version.rb +1 -1
  19. data/lib/transporter/parallelizer.rb +8 -0
  20. data/lib/transporter/transporter.rb +75 -11
  21. data/test/dummy/config/myreplicator.yml +0 -2
  22. data/test/dummy/config/myreplicator.yml~ +2 -0
  23. data/test/dummy/db/migrate/{20121108204327_create_myreplicator_exports.myreplicator.rb~ → 20121213003552_create_myreplicator_exports.myreplicator.rb} +3 -1
  24. data/test/dummy/db/migrate/20121213003553_create_myreplicator_logs.myreplicator.rb +22 -0
  25. data/test/dummy/db/schema.rb +24 -7
  26. data/test/dummy/log/development.log +1083 -0
  27. data/test/dummy/tmp/cache/assets/CD5/B90/sprockets%2Fc999d13a6a21113981c0d820e8043bdf +0 -0
  28. data/test/dummy/tmp/cache/assets/D8B/B60/sprockets%2Faa32227c440a378ccd21218eefeb80bf +0 -0
  29. data/test/dummy/tmp/cache/assets/DF8/5D0/sprockets%2Fb815ed34d61cfed96222daa3bfd1d84d +0 -0
  30. data/test/dummy/tmp/myreplicator/okl_test_batchy_batches_1355432256.tsv.gz +0 -0
  31. data/test/unit/myreplicator/log_test.rb +9 -0
  32. metadata +14 -8
  33. data/test/dummy/db/migrate/20121115194022_create_myreplicator_exports.myreplicator.rb +0 -36
@@ -3,7 +3,6 @@
3
3
  export_to = ["destination_db","s3","both"]
4
4
  export_type = ["incremental","fulldump"]
5
5
  %>
6
-
7
6
  <%= form_for(@export) do |f| %>
8
7
  <% if @export.errors.any? %>
9
8
  <div id="error_explanation">
@@ -18,11 +17,11 @@ export_type = ["incremental","fulldump"]
18
17
  <% end %>
19
18
  <div class="form-section">
20
19
  <label>Source Schema</label>
21
- <%= f.text_field :source_schema %>
20
+ <%= f.select :source_schema, @dbs %>
22
21
  <label>Destination Schema</label>
23
- <%= f.text_field :destination_schema %>
22
+ <%= f.select :destination_schema, @dbs %>
24
23
  <label>Table Name</label>
25
- <%= f.text_field :table_name %>
24
+ <%= f.select :table_name, [] %>
26
25
  <label>Incremental Column</label>
27
26
  <%= f.text_field :incremental_column %>
28
27
  <label>Maximum Incremental Value</label>
@@ -93,12 +92,29 @@ export_type = ["incremental","fulldump"]
93
92
  <%= javascript_include_tag "myreplicator/chosen.jquery.min" %>
94
93
  <%= javascript_include_tag "myreplicator/cronwtf.min" %>
95
94
  <script>
95
+ var dbs = {
96
+ <% @tables.each do |key,values| %>
97
+ "<%= key %>":[
98
+ <% values.each do |table| %>
99
+ "<%= table %>",
100
+ <% end %>
101
+ ],
102
+ <% end %>
103
+ }
96
104
  $(function(){
97
105
  CronUI.translate();
98
106
  $(".chosen").chosen();
107
+ $("#export_destination_schema,#export_table_name").chosen();
99
108
  $(".cron").chosen().change(function(){CronUI.translate()});
109
+ $("#export_source_schema").chosen().change(function(){
110
+ exportSchemaSelect($(this).val())
111
+ });
112
+ exportSchemaSelect($("#export_source_schema").val());
113
+ <%- if @edit -%>
114
+ editInit();
115
+ <%- end -%>
116
+ })
100
117
 
101
- <%- if @edit -%>
102
118
  function editInit(){
103
119
  var cron = "<%= @export.cron %>";
104
120
  var displays = ["#min","#hour","#day","#month","#dow"];
@@ -108,10 +124,18 @@ function editInit(){
108
124
  $(displays[i]).val(cron_vals[i]).trigger("liszt:updated");
109
125
  }
110
126
  CronUI.translate();
127
+ $("#export_table_name").val("<%= @export.table_name %>").trigger("liszt:updated");
111
128
  }
112
- editInit();
113
- <%- end -%>
114
129
 
115
-
116
- })
130
+ function exportSchemaSelect(val){
131
+ var target = $("#export_table_name");
132
+ var tables = dbs[val];
133
+ var l = tables.length;
134
+ var options = "";
135
+ for(i=0;i<l;i++){
136
+ options += "<option value='"+tables[i]+"'>"+tables[i]+"</option>";
137
+ }
138
+ target.val('').find("option").remove().trigger("liszt:updated");
139
+ target.append(options).trigger("liszt:updated");
140
+ }
117
141
  </script>
@@ -1,5 +1,9 @@
1
+ <%
2
+ err = err_count
3
+ run = run_count
4
+ %>
1
5
  <div class="home-menu">
2
6
  <span>lists:</span>
3
- <a href="<%= root_url %>" class="<% if @option == 'overview' %>on<% end %>">overview</a>
4
- <a href="<%= errors_path %>" class="<% if @option == 'errors' %>on<% end %>">errors</a>
7
+ <a href="<%= root_url %>" class="<% if @option == 'overview' %>on<% end %> overview">overview <% if run > 0 %><span><%= run %></span><% end %></a>
8
+ <a href="<%= errors_path %>" class="<% if @option == 'errors' %>on<% end %>">errors <% if err > 0 %><span><%= err %></span><% end %></a>
5
9
  </div>
@@ -1,10 +1,20 @@
1
1
  <%= render :partial => 'home_menu' %>
2
2
  <% @exports.each do |ex| %>
3
3
  <div class="error-display">
4
- <span><em>table name:</em> <%= ex.table_name %></span>
5
- <span><em>source schema:</em><%= ex.source_schema %></span>
6
- <span><em>updated at:</em><%= ex.updated_at.strftime("%Y-%m-%d %H:%M") %></span>
7
- <span><em>error:</em></span>
8
- <pre><%= ex.error %></pre>
4
+ <span><em>table name:</em> <%= ex.table_name %></span>
5
+ <span><em>source schema:</em><%= ex.source_schema %></span>
6
+ <span><em>updated at:</em><%= ex.updated_at.strftime("%Y-%m-%d %H:%M") %></span>
7
+ <span><em>error:</em></span>
8
+ <pre><%= ex.error %></pre>
9
+ </div>
10
+ <% end %>
11
+ <% @logs.each do |log| %>
12
+ <div class="error-display">
13
+ <span><em>name:</em> <%= log.name %></span>
14
+ <span><em>file:</em> <%= log.file %></span>
15
+ <span><em>type:</em> <%= log.job_type %></span>
16
+ <span><em>started:</em> <%= log.started_at.strftime("%Y-%m-%d %H:%M:%S") %></span>
17
+ <span><em>error:</em></span>
18
+ <pre><%= log.error %></pre>
9
19
  </div>
10
20
  <% end %>
@@ -1,48 +1,25 @@
1
1
  <%= render :partial => 'home_menu' %>
2
- <% states = @exports.group_by(&:state) %>
3
2
  <div id="overview-wrapper">
4
- <div id="simple-wrapper">
5
- <table id="states-list">
6
- <thead>
7
- <tr>
8
- <th>State</th>
9
- <th class="centered">Count</th>
10
- </tr>
11
- </thead>
12
- <tbody>
13
- <% states.each do |state, exports| %>
14
- <tr>
15
- <td><a href="#<%= state.downcase %>_state" class="state-toggle"><%= state %></a></td>
16
- <td class="c"><%= exports.size %></td>
17
- </tr>
18
- <% end %>
19
- </tbody>
20
- </table>
21
- <p class="hint">***click a state to view export list</p>
22
- </div>
23
- <ul id="states-tables">
24
- <% states.each do |state, exports| %>
25
- <li id="<%= state.downcase %>_state">
26
- <h4><%= state %> Exports</h4>
27
- <table class="data-grid">
28
- <thead>
29
- <tr>
30
- <th>Table</th>
31
- <th>Schema</th>
32
- <th>Updated</th>
33
- </tr>
34
- </thead>
35
- <tbody>
36
- <% exports.sort{|a,b| b.updated_at<=>a.updated_at}.each do |ex| %>
37
- <td><%= ex.table_name %></td>
38
- <td><%= ex.source_schema %></td>
39
- <td><%= ex.updated_at.strftime("%Y-%m-%d %H:%M") %></td>
40
- <% end %>
41
- </tbody>
42
- </table>
43
- </li>
44
- <% end %>
45
- </ul>
3
+ <h6>Running tasks</h6>
4
+ <table class="overview">
5
+ <% @logs.each do |log| %>
6
+ <tr>
7
+ <td>
8
+ <span class="name"><%= log.name %></span>
9
+ <span class="file"><%= log.file %></span>
10
+ </td>
11
+ <td><%= log.state %></td>
12
+ <td><%= log.job_type %></td>
13
+ <td><%= log.started_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
14
+ <td><%= chronos(@now.to_i - log.started_at.to_i) %></td>
15
+ </tr>
16
+ <% end %>
17
+ </table>
18
+
19
+
20
+
21
+
22
+
46
23
  </div>
47
24
  <script>
48
25
  jQuery(function(){
@@ -15,14 +15,8 @@ class CreateMyreplicatorExports < ActiveRecord::Migration
15
15
  t.text :error
16
16
  t.boolean :active, :default => true
17
17
  t.integer :exporter_pid
18
- t.integer :transporter_pid
19
- t.integer :loader_pid
20
18
  t.datetime :export_started_at, :default => nil
21
19
  t.datetime :export_finished_at, :default => nil
22
- t.datetime :load_started_at, :default => nil
23
- t.datetime :load_finished_at, :default => nil
24
- t.datetime :transfer_started_at, :default => nil
25
- t.datetime :transfer_finished_at, :default => nil
26
20
  t.timestamps
27
21
  end
28
22
  add_index :myreplicator_exports, [:source_schema, :destination_schema, :table_name], :unique => true, :name => "unique_index"
@@ -0,0 +1,21 @@
1
+ class CreateMyreplicatorLogs < ActiveRecord::Migration
2
+ def change
3
+ create_table :myreplicator_logs do |t|
4
+ t.integer :pid
5
+ t.string :job_type
6
+ t.string :name
7
+ t.string :file
8
+ t.string :state
9
+ t.string :thread_state
10
+ t.string :hostname
11
+ t.string :export_id
12
+ t.text :error
13
+ t.text :backtrace
14
+ t.string :guid
15
+ t.datetime :started_at, :default => nil
16
+ t.datetime :finished_at, :default => nil
17
+
18
+ t.timestamps
19
+ end
20
+ end
21
+ end
@@ -32,6 +32,7 @@ module Myreplicator
32
32
  end
33
33
  end
34
34
 
35
+ # BOB : This only handles gzipped files, is that what you want?
35
36
  def filename
36
37
  name = filepath.split("/").last
37
38
  name = zipped ? "#{name}.gz" : name
@@ -44,7 +45,7 @@ module Myreplicator
44
45
 
45
46
  ##
46
47
  # Keeps track of the state of the export
47
- # Store itself in a JSON file on exit
48
+ # Stores itself in a JSON file on exit
48
49
  ##
49
50
  def self.record *args
50
51
  options = args.extract_options!
@@ -160,6 +161,11 @@ module Myreplicator
160
161
  return path
161
162
  end
162
163
 
164
+ ##
165
+ # Writes Json to file using echo
166
+ # file is written to remote server via SSH
167
+ # Echo is used for writing the file
168
+ ##
163
169
  def store!
164
170
  cmd = "echo \"#{self.to_json.gsub("\"","\\\\\"")}\" > #{@filepath}.json"
165
171
  puts cmd
@@ -175,6 +181,7 @@ module Myreplicator
175
181
 
176
182
  def set_attributes options
177
183
  options.symbolize_keys!
184
+
178
185
  @export_time = options[:export_time] if options[:export_time]
179
186
  @table = options[:table] if options[:table]
180
187
  @database = options[:database] if options[:database]
@@ -31,9 +31,12 @@ module Myreplicator
31
31
  metadata.state = "export_completed"
32
32
  wrapup metadata
33
33
  end
34
+
34
35
  elsif !is_running?
35
36
  # local max value for incremental export
37
+
36
38
  max_value = incremental_export(metadata)
39
+ #max_value = incremental_export_into_outfile(metadata)
37
40
 
38
41
  metadata.incremental_val = max_value # store max val in metadata
39
42
 
@@ -128,9 +131,38 @@ module Myreplicator
128
131
  puts "Exporting..."
129
132
  result = execute_export(cmd, metadata)
130
133
  check_result(result, 0)
134
+
135
+ return max_value
131
136
  end
132
137
 
133
138
 
139
+ ##
140
+ # Exports table incrementally, similar to incremental_export method
141
+ # Dumps file in tmp directory specified in myreplicator.yml
142
+ # Note that directory needs 777 permissions for mysql to be able to export the file
143
+ # Uses ;~; as the delimiter and new line for lines
144
+ ##
145
+
146
+ def incremental_export_into_outfile metadata
147
+ max_value = @export_obj.max_value
148
+ @export_obj.update_max_val if @export_obj.max_incremental_value.blank?
149
+
150
+ cmd = SqlCommands.mysql_export_outfile(:db => @export_obj.source_schema,
151
+ :table => @export_obj.table_name,
152
+ :filepath => filepath,
153
+ :incremental_col => @export_obj.incremental_column,
154
+ :incremental_col_type => @export_obj.incremental_column_type,
155
+ :incremental_val => @export_obj.max_incremental_value)
156
+
157
+ metadata.export_type = "incremental_outfile"
158
+ update_export(:state => "exporting", :export_started_at => Time.now, :exporter_pid => Process.pid)
159
+ puts "Exporting..."
160
+ result = execute_export(cmd, metadata)
161
+ check_result(result, 0)
162
+
163
+ return max_value
164
+ end
165
+
134
166
  ##
135
167
  # Completes an export process
136
168
  # Zips files, updates states etc
@@ -34,14 +34,25 @@ module Myreplicator
34
34
  return cmd
35
35
  end
36
36
 
37
+ ##
38
+ # Db configs for active record connection
39
+ ##
40
+
37
41
  def self.db_configs db
38
42
  ActiveRecord::Base.configurations[db]
39
43
  end
40
44
 
45
+ ##
46
+ # Configs needed for SSH connection to source server
47
+ ##
48
+
41
49
  def self.ssh_configs db
42
50
  Myreplicator.configs[db]
43
51
  end
44
52
 
53
+ ##
54
+ # Default dump flags
55
+ ##
45
56
  def self.dump_flags
46
57
  {"add-locks" => true,
47
58
  "compact" => false,
@@ -55,6 +66,10 @@ module Myreplicator
55
66
  }
56
67
  end
57
68
 
69
+ ##
70
+ # Mysql exports using -e flag
71
+ ##
72
+
58
73
  def self.mysql_export *args
59
74
  options = args.extract_options!
60
75
  options.reverse_merge! :flags => []
@@ -83,6 +98,69 @@ module Myreplicator
83
98
  return cmd
84
99
  end
85
100
 
101
+ ##
102
+ # Mysql export data into outfile option
103
+ # Provided for tables that need special delimiters
104
+ ##
105
+
106
+ def self.get_outfile_sql options
107
+ sql = "SELECT * INTO OUTFILE '#{options[:filepath]}' "
108
+
109
+ sql += " FIELDS TERMINATED BY ';~;' OPTIONALLY ENCLOSED BY '\\\"' LINES TERMINATED BY '\\n'"
110
+
111
+ sql += "FROM #{options[:db]}.#{options[:table]} "
112
+
113
+ if options[:incremental_col] && options[:incremental_val]
114
+ if options[:incremental_col_type] == "datetime"
115
+ sql += "WHERE #{options[:incremental_col]} >= '#{options[:incremental_val]}'"
116
+ else
117
+ sql += "WHERE #{options[:incremental_col]} >= #{options[:incremental_val]}"
118
+ end
119
+ end
120
+
121
+ return sql
122
+ end
123
+
124
+ ##
125
+ # Export using outfile
126
+ # ;~; delimited
127
+ # terminated by newline
128
+ # Location of the output file needs to have 777 perms
129
+ ##
130
+ def self.mysql_export_outfile *args
131
+ options = args.extract_options!
132
+ options.reverse_merge! :flags => []
133
+ db = options[:db]
134
+
135
+ # Database host when ssh'ed into the db server
136
+ db_host = ssh_configs(db)["ssh_db_host"].nil? ? "127.0.0.1" : ssh_configs(db)["ssh_db_host"]
137
+
138
+ flags = ""
139
+
140
+ self.mysql_flags.each_pair do |flag, value|
141
+ if options[:flags].include? flag
142
+ flags += " --#{flag} "
143
+ elsif value
144
+ flags += " --#{flag} "
145
+ end
146
+ end
147
+
148
+ # BOB : You always build the host,password,username part of the command
149
+ # Seems like this should be in a function somewhere
150
+ cmd = Myreplicator.mysql
151
+ cmd += "#{flags} -u#{db_configs(db)["username"]} -p#{db_configs(db)["password"]} "
152
+ cmd += "-h#{db_host} " if db_configs(db)["host"].blank?
153
+ cmd += db_configs(db)["port"].blank? ? "-P3306 " : "-P#{db_configs(db)["port"]} "
154
+ cmd += "--execute=\"#{get_outfile_sql(options)}\" "
155
+
156
+ puts cmd
157
+
158
+ return cmd
159
+ end
160
+
161
+ ##
162
+ # Default flags for mysql export
163
+ ##
86
164
  def self.mysql_flags
87
165
  {"column-names" => false,
88
166
  "quick" => true,
@@ -90,6 +168,9 @@ module Myreplicator
90
168
  }
91
169
  end
92
170
 
171
+ ##
172
+ # Builds SQL needed for incremental exports
173
+ ##
93
174
  def self.export_sql *args
94
175
  options = args.extract_options!
95
176
  sql = "SELECT * FROM #{options[:db]}.#{options[:table]} "
@@ -105,6 +186,10 @@ module Myreplicator
105
186
  return sql
106
187
  end
107
188
 
189
+ ##
190
+ # Gets the Maximum value for the incremental
191
+ # column of the export job
192
+ ##
108
193
  def self.max_value_sql *args
109
194
  options = args.extract_options!
110
195
  sql = ""
@@ -118,9 +203,5 @@ module Myreplicator
118
203
  return sql
119
204
  end
120
205
 
121
- def self.mysql_export_outfile
122
-
123
- end
124
-
125
206
  end
126
207
  end
@@ -3,6 +3,8 @@ require "exporter"
3
3
  module Myreplicator
4
4
  class Loader
5
5
 
6
+ @queue = :myreplicator_load # Provided for Resque
7
+
6
8
  def initialize *args
7
9
  options = args.extract_options!
8
10
  end
@@ -11,10 +13,22 @@ module Myreplicator
11
13
  @tmp_dir ||= File.join(Myreplicator.app_root,"tmp", "myreplicator")
12
14
  end
13
15
 
14
- def load
16
+ ##
17
+ # Main method provided for resque
18
+ ##
19
+ def self.perform
20
+ load # Kick off the load process
21
+ end
22
+
23
+ ##
24
+ # Kicks off all initial loads first and then all incrementals
25
+ # Looks at metadata files stored locally
26
+ ##
27
+ def self.load
15
28
  initials = []
16
29
  incrementals = []
17
30
 
31
+ # Read all metadata files
18
32
  metadata_files.each do |metadata|
19
33
  if metadata.export_type == "initial"
20
34
  initials << metadata
@@ -22,16 +36,32 @@ module Myreplicator
22
36
  incrementals << metadata
23
37
  end
24
38
  end
25
-
39
+
40
+ # Load all new tables
26
41
  initials.each do |metadata|
27
42
  puts metadata.table
28
- initial_load metadata
43
+
44
+ Log.run(:job_type => "loader",
45
+ :name => "initial_import",
46
+ :file => metadata.filename,
47
+ :export_id => metadata.export_id) do |log|
48
+
49
+ initial_load metadata
50
+ end
51
+
29
52
  cleanup metadata
30
53
  end
31
54
 
55
+ # Load all incremental files
32
56
  incrementals.each do |metadata|
33
57
  puts metadata.table
34
- incremental_load metadata
58
+ Log.run(:job_type => "loader",
59
+ :name => "incremental_import",
60
+ :file => metadata.filename,
61
+ :export_id => metadata.export_id) do |log|
62
+
63
+ incremental_load metadata
64
+ end
35
65
  cleanup metadata
36
66
  end
37
67
  end
@@ -47,12 +77,14 @@ module Myreplicator
47
77
  cmd = ImportSql.initial_load(:db => exp.destination_schema,
48
78
  :filepath => metadata.destination_filepath(tmp_dir))
49
79
  puts cmd
80
+
50
81
  result = `#{cmd}` # execute
51
82
  unless result.nil?
52
83
  if result.size > 0
53
84
  raise Exceptions::LoaderError.new("Initial Load #{metadata.filename} Failed!\n#{result}")
54
85
  end
55
86
  end
87
+
56
88
  end
57
89
 
58
90
  ##
@@ -63,12 +95,21 @@ module Myreplicator
63
95
  exp = Export.find(metadata.export_id)
64
96
  unzip(metadata.filename)
65
97
  metadata.zipped = false
98
+
99
+ options = {:table_name => exp.table_name, :db => exp.destination_schema,
100
+ :filepath => metadata.destination_filepath(tmp_dir)}
101
+
102
+ if metadata.export_type == "incremental_outfile"
103
+ options[:fields_terminated_by] = ";~;"
104
+ options[:lines_terminated_by] = "\\n"
105
+ end
106
+
107
+ cmd = ImportSql.load_data_infile(options)
66
108
 
67
- cmd = ImportSql.load_data_infile(:table_name => exp.table_name,
68
- :db => exp.destination_schema,
69
- :filepath => metadata.destination_filepath(tmp_dir))
70
109
  puts cmd
110
+
71
111
  result = `#{cmd}` # execute
112
+
72
113
  unless result.nil?
73
114
  if result.size > 0
74
115
  raise Exceptions::LoaderError.new("Incremental Load #{metadata.filename} Failed!\n#{result}")