myreplicator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +40 -0
- data/app/assets/javascripts/myreplicator/application.js +21 -0
- data/app/assets/javascripts/myreplicator/chosen.jquery.min.js +10 -0
- data/app/assets/javascripts/myreplicator/cronwtf.js +156 -0
- data/app/assets/javascripts/myreplicator/exports.js +2 -0
- data/app/assets/javascripts/myreplicator/jquery.tipTip.minified.js +21 -0
- data/app/assets/stylesheets/myreplicator/FrancoisOne.ttf +0 -0
- data/app/assets/stylesheets/myreplicator/application.css +544 -0
- data/app/assets/stylesheets/myreplicator/asc-white.gif +0 -0
- data/app/assets/stylesheets/myreplicator/bg.gif +0 -0
- data/app/assets/stylesheets/myreplicator/bg.png +0 -0
- data/app/assets/stylesheets/myreplicator/chosen-sprite.png +0 -0
- data/app/assets/stylesheets/myreplicator/chosen.css +398 -0
- data/app/assets/stylesheets/myreplicator/clipboard-list.png +0 -0
- data/app/assets/stylesheets/myreplicator/cross.png +0 -0
- data/app/assets/stylesheets/myreplicator/desc-white.gif +0 -0
- data/app/assets/stylesheets/myreplicator/exports.css +4 -0
- data/app/assets/stylesheets/myreplicator/gear.png +0 -0
- data/app/assets/stylesheets/myreplicator/plus.png +0 -0
- data/app/assets/stylesheets/myreplicator/status-busy.png +0 -0
- data/app/assets/stylesheets/myreplicator/status.png +0 -0
- data/app/assets/stylesheets/myreplicator/tipTip.css +113 -0
- data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.eot +0 -0
- data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.svg +108 -0
- data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.ttf +0 -0
- data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.woff +0 -0
- data/app/assets/stylesheets/scaffold.css +56 -0
- data/app/controllers/myreplicator/application_controller.rb +4 -0
- data/app/controllers/myreplicator/exports_controller.rb +106 -0
- data/app/controllers/myreplicator/home_controller.rb +23 -0
- data/app/helpers/myreplicator/application_helper.rb +12 -0
- data/app/helpers/myreplicator/exports_helper.rb +4 -0
- data/app/models/myreplicator/export.rb +121 -0
- data/app/views/layouts/myreplicator/application.html.erb +24 -0
- data/app/views/myreplicator/exports/_form.html.erb +117 -0
- data/app/views/myreplicator/exports/edit.html.erb +2 -0
- data/app/views/myreplicator/exports/index.html.erb +56 -0
- data/app/views/myreplicator/exports/new.html.erb +2 -0
- data/app/views/myreplicator/exports/show.html.erb +28 -0
- data/app/views/myreplicator/home/_home_menu.erb +5 -0
- data/app/views/myreplicator/home/errors.html.erb +10 -0
- data/app/views/myreplicator/home/index.html.erb +58 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20121025191622_create_myreplicator_exports.rb +35 -0
- data/lib/configuration.rb +25 -0
- data/lib/exporter/export_metadata.rb +198 -0
- data/lib/exporter/export_metadata.rb~ +9 -0
- data/lib/exporter/mysql_exporter.rb +187 -0
- data/lib/exporter/sql_commands.rb +126 -0
- data/lib/exporter/sql_commands.rb~ +5 -0
- data/lib/exporter.rb +3 -0
- data/lib/loader/import_sql.rb +91 -0
- data/lib/loader/import_sql.rb~ +27 -0
- data/lib/loader/loader.rb +122 -0
- data/lib/loader/loader.rb~ +4 -0
- data/lib/myreplicator/engine.rb +5 -0
- data/lib/myreplicator/version.rb +3 -0
- data/lib/myreplicator.rb +34 -0
- data/lib/tasks/myreplicator_tasks.rake +4 -0
- data/lib/transporter/parallelizer.rb +78 -0
- data/lib/transporter/transporter.rb +80 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +59 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +42 -0
- data/test/dummy/config/database.yml.sample +47 -0
- data/test/dummy/config/database.yml.sample~ +26 -0
- data/test/dummy/config/database.yml~ +35 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/myreplicator.yml +22 -0
- data/test/dummy/config/myreplicator.yml.sample +23 -0
- data/test/dummy/config/myreplicator.yml~ +21 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20121101005152_sample_data.rb +24 -0
- data/test/dummy/db/migrate/20121115194022_create_myreplicator_exports.myreplicator.rb +36 -0
- data/test/dummy/db/schema.rb +45 -0
- data/test/dummy/db/seeds.rb +13 -0
- data/test/dummy/db/seeds.rb~ +1 -0
- data/test/dummy/log/development.log +1364 -0
- data/test/dummy/log/test.log +49 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/test/fixtures/myreplicator_exports.yml +0 -0
- data/test/dummy/tmp/cache/assets/C2E/D00/sprockets%2F667019818351638709494c01bddb5f68 +0 -0
- data/test/dummy/tmp/cache/assets/C83/BA0/sprockets%2F701a6339a558e5af28f150c161f43878 +0 -0
- data/test/dummy/tmp/cache/assets/C8B/150/sprockets%2F37163d05e55ad0b31b602ac5330412e3 +0 -0
- data/test/dummy/tmp/cache/assets/CBF/800/sprockets%2F00142a873933017aaa760316d0e2dcea +0 -0
- data/test/dummy/tmp/cache/assets/CC5/870/sprockets%2F4e91734f6f02a779d39f8272f627dd24 +0 -0
- data/test/dummy/tmp/cache/assets/CC9/1C0/sprockets%2Fa7a2b5a56180e1b21f783a9b30c03167 +0 -0
- data/test/dummy/tmp/cache/assets/CD5/B90/sprockets%2Fc999d13a6a21113981c0d820e8043bdf +0 -0
- data/test/dummy/tmp/cache/assets/CD7/030/sprockets%2F9ba4859590582b8b72a650b2b00b6cd2 +0 -0
- data/test/dummy/tmp/cache/assets/CDE/780/sprockets%2F6982ce9303b4e69c26f2a1a246a180e7 +0 -0
- data/test/dummy/tmp/cache/assets/CE5/670/sprockets%2Fe9e4122f1706626a21da6f8457f088ce +0 -0
- data/test/dummy/tmp/cache/assets/CF7/820/sprockets%2F6284656df87a7eb2545ed9b957560a7b +0 -0
- data/test/dummy/tmp/cache/assets/D00/9B0/sprockets%2F2bc203eb4802b393a589093debb65c04 +0 -0
- data/test/dummy/tmp/cache/assets/D00/A90/sprockets%2F3fa6fcf2205c530208681446cbc36bd3 +0 -0
- data/test/dummy/tmp/cache/assets/D0B/B30/sprockets%2F55059589d999952597c5c86e5becaf4e +0 -0
- data/test/dummy/tmp/cache/assets/D0D/DA0/sprockets%2F34d6b075a16a5a58ff4050988d8bd4b0 +0 -0
- data/test/dummy/tmp/cache/assets/D12/B40/sprockets%2F9c825562fe2a2a38bd6f41a635265bd9 +0 -0
- data/test/dummy/tmp/cache/assets/D1B/D40/sprockets%2F7cc2142509e95f7168a82a652d8d9dea +0 -0
- data/test/dummy/tmp/cache/assets/D1D/700/sprockets%2Ffe9cb975216709e2881c74b3d1d3e35f +0 -0
- data/test/dummy/tmp/cache/assets/D20/A20/sprockets%2Fb503e93ff1966dd94d03e79f291d75c1 +0 -0
- data/test/dummy/tmp/cache/assets/D26/A70/sprockets%2F63d95ae156df465783cd78e95069b2cc +0 -0
- data/test/dummy/tmp/cache/assets/D2B/E60/sprockets%2F8615ecf645b553c959544af9ff8438da +0 -0
- data/test/dummy/tmp/cache/assets/D34/F70/sprockets%2Fb93de9992473bee94e369fe3198c529c +0 -0
- data/test/dummy/tmp/cache/assets/D36/4D0/sprockets%2Fc050a64b5a803a638e155d05dcfe577d +0 -0
- data/test/dummy/tmp/cache/assets/D3E/310/sprockets%2F333a2a7535eac766267ebb7d5c2ab489 +0 -0
- data/test/dummy/tmp/cache/assets/D3F/A00/sprockets%2F7a803404e1f60b8d672d763cb9ba8af5 +0 -0
- data/test/dummy/tmp/cache/assets/D50/570/sprockets%2F6c1d20a178f66e798958d1e437fdb5da +0 -0
- data/test/dummy/tmp/cache/assets/D57/420/sprockets%2F937ea7429c536578ec7b688dee0cdf41 +0 -0
- data/test/dummy/tmp/cache/assets/D69/6F0/sprockets%2F94fff7f55bc4c300b25f3f9361ac1a52 +0 -0
- data/test/dummy/tmp/cache/assets/D86/D00/sprockets%2Fa4f32b4234d0d1bba272cd75e0d48e1d +0 -0
- data/test/dummy/tmp/cache/assets/D8B/B60/sprockets%2Faa32227c440a378ccd21218eefeb80bf +0 -0
- data/test/dummy/tmp/cache/assets/D9F/0B0/sprockets%2Faf0d2e69be3a6b56a76c20bf14d9e468 +0 -0
- data/test/dummy/tmp/cache/assets/DA8/910/sprockets%2Fab5775c4a837bd4d97ac394d473cda9b +0 -0
- data/test/dummy/tmp/cache/assets/DC0/100/sprockets%2F7a5d4f0d352bceed0dce0449c82251bd +0 -0
- data/test/dummy/tmp/cache/assets/DD2/490/sprockets%2Fa452ee92a092bc2feabc572cce49896d +0 -0
- data/test/dummy/tmp/cache/assets/DE1/320/sprockets%2F9f44ecdec8ceeef70871e15d88a448b1 +0 -0
- data/test/dummy/tmp/cache/assets/DF8/5D0/sprockets%2Fb815ed34d61cfed96222daa3bfd1d84d +0 -0
- data/test/dummy/tmp/cache/assets/E16/C70/sprockets%2F21ad93418ff75ceac86c7a85dfffe8f6 +0 -0
- data/test/dummy/tmp/cache/assets/E35/4F0/sprockets%2F96b1cdf8db6a1c8eb8abcce05958ae74 +0 -0
- data/test/dummy/tmp/cache/assets/E4E/300/sprockets%2Fefaae6e1a19a7c8f3acebdd5a36a6103 +0 -0
- data/test/fixtures/myreplicator/exports.yml +11 -0
- data/test/functional/myreplicator/exports_controller_test.rb +51 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/myreplicator_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/helpers/myreplicator/exports_helper_test.rb +6 -0
- data/test/unit/myreplicator/export_test.rb +10 -0
- data/test/unit/myreplicator/exporter_test.rb +11 -0
- data/test/unit/myreplicator/exporter_test.rb~ +10 -0
- metadata +410 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
<%= link_to content_tag(:span, 'add export'), new_export_path, :class => "btn right main add" %>
|
2
|
+
<h2>Exports</h2>
|
3
|
+
<%= will_paginate @exports , :previous_label => '<', :next_label => '>', :inner_window => 2, :outer_window => 0%>
|
4
|
+
<% if @exports.total_entries > @exports.per_page %>
|
5
|
+
<div id="pagination-display-message">Displaying <%= @exports.offset + 1 %> - <%= @exports.offset + @exports.length %> of <%= pluralize(@exports.total_entries, 'exports') %></div>
|
6
|
+
<% end %>
|
7
|
+
<div class="table-wrapper">
|
8
|
+
<table class="data-grid">
|
9
|
+
<thead>
|
10
|
+
<tr>
|
11
|
+
<th colspan="2"><%= sortable "source_schema" %></th>
|
12
|
+
<th><%= sortable "destination_schema" %></th>
|
13
|
+
<th><%= sortable "table_name" %></th>
|
14
|
+
<th><%= sortable "incremental_column" %></th>
|
15
|
+
<th><%= sortable "max_incremental_value" %></th>
|
16
|
+
<th><%= sortable "export_to", "Export Desitination" %></th>
|
17
|
+
<th><%= sortable "export_type" %></th>
|
18
|
+
<th><%= sortable "s3_path" %></th>
|
19
|
+
<th><%= sortable "cron" %></th>
|
20
|
+
<th class="center">Actions</th>
|
21
|
+
</tr>
|
22
|
+
</thead>
|
23
|
+
<tbody>
|
24
|
+
<% @exports.each do |export| %>
|
25
|
+
<tr>
|
26
|
+
<td class="state"><span class="status <% if export.active %>active<% else %>inactive<% end %>" title="<% if export.active %>Active<% else %>Inactive<% end %>"></span></td>
|
27
|
+
<td class="source"><%= export.source_schema %></td>
|
28
|
+
<td><%= export.destination_schema %></td>
|
29
|
+
<td><%= export.table_name %></td>
|
30
|
+
<td><%= export.incremental_column %></td>
|
31
|
+
<td><%= export.max_incremental_value %></td>
|
32
|
+
<td><%= export.export_to %></td>
|
33
|
+
<td><%= export.export_type %></td>
|
34
|
+
<td><%= export.s3_path %></td>
|
35
|
+
<td><span class="cron" data-cron="<%= export.cron %>" title=""><%= export.cron %></span></td>
|
36
|
+
<td>
|
37
|
+
<%= link_to 'review', export, :class=> 'action view' %>
|
38
|
+
<%= link_to 'edit', edit_export_path(export), :class => 'action edit' %>
|
39
|
+
<%= link_to 'destroy', export, method: :delete, data: { confirm: 'Are you sure?' }, :class=> 'action delete' %>
|
40
|
+
</td>
|
41
|
+
</tr>
|
42
|
+
<% end %>
|
43
|
+
</tbody>
|
44
|
+
</table>
|
45
|
+
</div>
|
46
|
+
<script>
|
47
|
+
$(function(){
|
48
|
+
$.each($("span.cron"),function(i){
|
49
|
+
var span = $(this)
|
50
|
+
var trans = String(CronWTF.parse(span.data("cron")));
|
51
|
+
span.attr("title", trans)
|
52
|
+
})
|
53
|
+
$("span.status").tipTip();
|
54
|
+
$("span.cron").tipTip();
|
55
|
+
})
|
56
|
+
</script>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<%= link_to content_tag(:span, 'edit export'), edit_export_path(@export), :class =>'btn right main edit' %>
|
2
|
+
<h2>Review Export</h2>
|
3
|
+
<ul id="export-review">
|
4
|
+
<% @export.attributes.sort{|a,b| a[0] <=> b[0]}.each do |key,val| %>
|
5
|
+
<% type = @export.column_for_attribute(key).type %>
|
6
|
+
<li><span><%= key.gsub("_"," ") %> : </span>
|
7
|
+
<%- if type == :datetime -%>
|
8
|
+
<%- unless val.blank? -%>
|
9
|
+
<%= val.strftime("%B #{val.day.ordinalize}, %Y %I:%M %p") -%>
|
10
|
+
<%- end -%>
|
11
|
+
<%- else -%>
|
12
|
+
<% if key == 'cron' %>
|
13
|
+
<em class="cron" data-cron="<%= val %>" title=""><%= val %></em>
|
14
|
+
<%- else -%>
|
15
|
+
<%= val -%>
|
16
|
+
<%- end -%>
|
17
|
+
<%- end -%>
|
18
|
+
</li>
|
19
|
+
<% end %>
|
20
|
+
</ul>
|
21
|
+
<script>
|
22
|
+
$(function(){
|
23
|
+
var span = $("em.cron")
|
24
|
+
var trans = String(CronWTF.parse(span.data("cron")));
|
25
|
+
span.attr("title", trans)
|
26
|
+
$("em.cron").tipTip();
|
27
|
+
})
|
28
|
+
</script>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= render :partial => 'home_menu' %>
|
2
|
+
<% @exports.each do |ex| %>
|
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>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<%= render :partial => 'home_menu' %>
|
2
|
+
<% states = @exports.group_by(&:state) %>
|
3
|
+
<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>
|
46
|
+
</div>
|
47
|
+
<script>
|
48
|
+
jQuery(function(){
|
49
|
+
var toggles = jQuery("a.state-toggle");
|
50
|
+
toggles.click(function(e){
|
51
|
+
e.preventDefault();
|
52
|
+
var link = $(this);
|
53
|
+
toggles.removeClass("on");
|
54
|
+
link.addClass("on");
|
55
|
+
jQuery(link.attr('href')).fadeIn().siblings("li").hide();
|
56
|
+
})
|
57
|
+
})
|
58
|
+
</script>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class CreateMyreplicatorExports < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :myreplicator_exports do |t|
|
4
|
+
t.string :source_schema
|
5
|
+
t.string :destination_schema
|
6
|
+
t.string :table_name
|
7
|
+
t.string :incremental_column
|
8
|
+
t.string :max_incremental_value
|
9
|
+
t.string :incremental_column_type
|
10
|
+
t.string :export_to, :default => "destination_db"
|
11
|
+
t.string :export_type, :default => "incremental"
|
12
|
+
t.string :s3_path
|
13
|
+
t.string :cron
|
14
|
+
t.string :state, :default => "new"
|
15
|
+
t.text :error
|
16
|
+
t.boolean :active, :default => true
|
17
|
+
t.integer :exporter_pid
|
18
|
+
t.integer :transporter_pid
|
19
|
+
t.integer :loader_pid
|
20
|
+
t.datetime :export_started_at, :default => nil
|
21
|
+
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
|
+
t.timestamps
|
27
|
+
end
|
28
|
+
add_index :myreplicator_exports, [:source_schema, :destination_schema, :table_name], :unique => true, :name => "unique_index"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.down
|
32
|
+
drop_table :myreplicator_exports
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "myreplicator"
|
2
|
+
|
3
|
+
module Myreplicator
|
4
|
+
##
|
5
|
+
# Configuration class for avoiding load the yml file everytime and cleaner config settings
|
6
|
+
##
|
7
|
+
class Configuration
|
8
|
+
attr_accessor :tmp_path, :mysqldump, :mysql
|
9
|
+
|
10
|
+
yml = YAML.load(File.read("#{Myreplicator.app_root}/config/myreplicator.yml"))
|
11
|
+
Kernel.p yml
|
12
|
+
Kernel.p yml["myreplicator"]["tmp_path"]
|
13
|
+
@@tmp_path = yml["myreplicator"]["tmp_path"]
|
14
|
+
@@mysql = yml["myreplicator"]["mysql"]
|
15
|
+
@@mysqldump = yml["myreplicator"]["mysqldump"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.config(&block)
|
19
|
+
@@config ||= Myreplicator::Configuration.new
|
20
|
+
|
21
|
+
yield @@config if block
|
22
|
+
|
23
|
+
return @@config
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Myreplicator
|
4
|
+
class ExportMetadata
|
5
|
+
|
6
|
+
attr_accessor(:export_time,
|
7
|
+
:export_finished_at,
|
8
|
+
:table,
|
9
|
+
:database,
|
10
|
+
:state,
|
11
|
+
:incremental_col,
|
12
|
+
:export_id,
|
13
|
+
:incremental_val,
|
14
|
+
:ssh,
|
15
|
+
:export_type,
|
16
|
+
:on_duplicate,
|
17
|
+
:filepath,
|
18
|
+
:zipped,
|
19
|
+
:error)
|
20
|
+
|
21
|
+
attr_reader :failure_callbacks
|
22
|
+
attr_reader :success_callbacks
|
23
|
+
attr_reader :ensure_callbacks
|
24
|
+
attr_reader :ignore_callbacks
|
25
|
+
|
26
|
+
def initialize *args
|
27
|
+
options = args.extract_options!
|
28
|
+
if options[:metadata_path]
|
29
|
+
load options[:metadata_path]
|
30
|
+
else
|
31
|
+
set_attributes options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def filename
|
36
|
+
name = filepath.split("/").last
|
37
|
+
name = zipped ? "#{name}.gz" : name
|
38
|
+
return name
|
39
|
+
end
|
40
|
+
|
41
|
+
def destination_filepath tmp_dir
|
42
|
+
File.join(tmp_dir, filename)
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Keeps track of the state of the export
|
47
|
+
# Store itself in a JSON file on exit
|
48
|
+
##
|
49
|
+
def self.record *args
|
50
|
+
options = args.extract_options!
|
51
|
+
options.reverse_merge!(:export_time => Time.now,
|
52
|
+
:state => "exporting")
|
53
|
+
begin
|
54
|
+
metadata = ExportMetadata.new
|
55
|
+
metadata.set_attributes options
|
56
|
+
|
57
|
+
yield metadata
|
58
|
+
|
59
|
+
metadata.run_success_callbacks
|
60
|
+
|
61
|
+
rescue Exceptions::ExportError => e
|
62
|
+
metadata.state = "failed"
|
63
|
+
metadata.error = "#{e.message}\n#{e.backtrace}"
|
64
|
+
metadata.run_failure_callbacks
|
65
|
+
|
66
|
+
rescue Exceptions::ExportIgnored => e
|
67
|
+
metadata.state = "ignored"
|
68
|
+
metadata.run_ignore_callbacks
|
69
|
+
metadata.filepath = metadata.filepath + ".ignored"
|
70
|
+
|
71
|
+
ensure
|
72
|
+
metadata.export_finished_at = Time.now
|
73
|
+
metadata.state = "failed" if metadata.state == "exporting"
|
74
|
+
metadata.store!
|
75
|
+
metadata.ssh.close
|
76
|
+
|
77
|
+
metadata.run_ensure_callbacks
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Add a callback to run on failure of the
|
82
|
+
# export
|
83
|
+
def on_failure *args, &block
|
84
|
+
if block_given?
|
85
|
+
@failure_callbacks << block
|
86
|
+
else
|
87
|
+
@failure_callbacks << args.shift
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Adds a callback that runs if the
|
92
|
+
# export is already running
|
93
|
+
def on_ignore *args, &block
|
94
|
+
if block_given?
|
95
|
+
@ignore_callbacks << block
|
96
|
+
else
|
97
|
+
@ignore_callbacks << args.shift
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds a callback that runs if the
|
102
|
+
# export is completed successfully
|
103
|
+
def on_success *args, &block
|
104
|
+
if block_given?
|
105
|
+
@success_callbacks << block
|
106
|
+
else
|
107
|
+
@success_callbacks << args.shift
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# :nodoc:
|
112
|
+
def run_ensure_callbacks
|
113
|
+
@ensure_callbacks.each do | ec |
|
114
|
+
ec.call(self)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# :nodoc:
|
119
|
+
def run_ignore_callbacks
|
120
|
+
@ignore_callbacks.each do | ic |
|
121
|
+
ic.call(self)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# :nodoc:
|
126
|
+
def run_success_callbacks
|
127
|
+
@success_callbacks.each do | sc |
|
128
|
+
sc.call(self)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# :nodoc:
|
133
|
+
def run_failure_callbacks
|
134
|
+
@failure_callbacks.each do | fc |
|
135
|
+
fc.call(self)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_json
|
140
|
+
obj = {
|
141
|
+
:export_time => @export_time,
|
142
|
+
:table => @table,
|
143
|
+
:database => @database,
|
144
|
+
:state => @state,
|
145
|
+
:incremental_col => @incremental_col,
|
146
|
+
:incremental_val => @incremental_val,
|
147
|
+
:export_id => @export_id,
|
148
|
+
:filepath => @filepath,
|
149
|
+
:zipped => @zipped,
|
150
|
+
:export_type => @export_type
|
151
|
+
}
|
152
|
+
return obj.to_json
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Final path of the dump file after zip
|
157
|
+
##
|
158
|
+
def export_path
|
159
|
+
path = @zipped ? @filepath + ".gz" : @filepath
|
160
|
+
return path
|
161
|
+
end
|
162
|
+
|
163
|
+
def store!
|
164
|
+
cmd = "echo \"#{self.to_json.gsub("\"","\\\\\"")}\" > #{@filepath}.json"
|
165
|
+
puts cmd
|
166
|
+
result = @ssh.exec!(cmd)
|
167
|
+
puts result
|
168
|
+
end
|
169
|
+
|
170
|
+
def load metadata_path
|
171
|
+
json = File.open(metadata_path, "rb").read
|
172
|
+
hash = JSON.parse(json)
|
173
|
+
set_attributes hash
|
174
|
+
end
|
175
|
+
|
176
|
+
def set_attributes options
|
177
|
+
options.symbolize_keys!
|
178
|
+
@export_time = options[:export_time] if options[:export_time]
|
179
|
+
@table = options[:table] if options[:table]
|
180
|
+
@database = options[:database] if options[:database]
|
181
|
+
@state = options[:state] if options[:state]
|
182
|
+
@incremental_col = options[:incremental_col] if options[:incremental_col]
|
183
|
+
@incremental_val = options[:incremental_val] if options[:incremental_val]
|
184
|
+
@export_id = options[:export_id] if options[:export_id]
|
185
|
+
@filepath = options[:filepath].nil? ? nil : options[:filepath]
|
186
|
+
@on_duplicate = options[:on_duplicate] if options[:on_duplicate]
|
187
|
+
@export_type = options[:export_type] if options[:export_type]
|
188
|
+
@zipped = options[:zipped].nil? ? false : options[:zipped]
|
189
|
+
@ssh = nil
|
190
|
+
|
191
|
+
@success_callbacks = []
|
192
|
+
@failure_callbacks = []
|
193
|
+
@ensure_callbacks = []
|
194
|
+
@ignore_callbacks = []
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module Myreplicator
|
2
|
+
class MysqlExporter
|
3
|
+
|
4
|
+
def initialize *args
|
5
|
+
options = args.extract_options!
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# Gets an Export object and dumps the data
|
10
|
+
# Initially using mysqldump
|
11
|
+
# Incrementally using mysql -e afterwards
|
12
|
+
##
|
13
|
+
def export_table export_obj
|
14
|
+
@export_obj = export_obj
|
15
|
+
|
16
|
+
ExportMetadata.record(:table => @export_obj.table_name,
|
17
|
+
:database => @export_obj.source_schema,
|
18
|
+
:export_id => @export_obj.id,
|
19
|
+
:filepath => filepath,
|
20
|
+
:incremental_col => @export_obj.incremental_column) do |metadata|
|
21
|
+
|
22
|
+
metadata.on_failure do |m|
|
23
|
+
update_export(:state => "failed", :export_finished_at => Time.now, :error => metadata.error)
|
24
|
+
end
|
25
|
+
|
26
|
+
prepare metadata
|
27
|
+
|
28
|
+
if @export_obj.state == "new"
|
29
|
+
initial_export metadata
|
30
|
+
metadata.on_success do |m|
|
31
|
+
metadata.state = "export_completed"
|
32
|
+
wrapup metadata
|
33
|
+
end
|
34
|
+
elsif !is_running?
|
35
|
+
# local max value for incremental export
|
36
|
+
max_value = incremental_export(metadata)
|
37
|
+
|
38
|
+
metadata.incremental_val = max_value # store max val in metadata
|
39
|
+
|
40
|
+
# Call back that updates the maximum value of incremental col
|
41
|
+
metadata.on_success do |m|
|
42
|
+
metadata.state = "export_completed"
|
43
|
+
wrapup metadata
|
44
|
+
@export_obj.update_max_val(max_value) # update max value if export was successful
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Setups SSH connection to remote host
|
53
|
+
##
|
54
|
+
def prepare metadata
|
55
|
+
ssh = @export_obj.ssh_to_source
|
56
|
+
metadata.ssh = ssh
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Throws ExportIgnored if the job is still running
|
61
|
+
# Checks the state of the job using PID and state
|
62
|
+
##
|
63
|
+
def is_running?
|
64
|
+
return false if @export_obj.state != "exporting"
|
65
|
+
begin
|
66
|
+
Process.getpgid(@export_obj.exporter_pid)
|
67
|
+
raise Exceptions::ExportIgnored.new("Ignored")
|
68
|
+
rescue Errno::ESRCH
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def update_export *args
|
74
|
+
options = args.extract_options!
|
75
|
+
@export_obj.update_attributes! options
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# File path on remote server
|
80
|
+
##
|
81
|
+
def filepath
|
82
|
+
File.join(Myreplicator.configs[@export_obj.source_schema]["ssh_tmp_dir"], @export_obj.filename)
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Exports Table using mysqldump. This method is invoked only once.
|
87
|
+
# Dumps with create options, no need to create table manaully
|
88
|
+
##
|
89
|
+
|
90
|
+
def initial_export metadata
|
91
|
+
flags = ["create-options", "single-transaction"]
|
92
|
+
cmd = SqlCommands.mysqldump(:db => @export_obj.source_schema,
|
93
|
+
:flags => flags,
|
94
|
+
:filepath => filepath,
|
95
|
+
:table_name => @export_obj.table_name)
|
96
|
+
|
97
|
+
metadata.export_type = "initial"
|
98
|
+
|
99
|
+
update_export(:state => "exporting", :export_started_at => Time.now, :exporter_pid => Process.pid)
|
100
|
+
|
101
|
+
puts "Exporting..."
|
102
|
+
result = execute_export(cmd, metadata)
|
103
|
+
check_result(result, 0)
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Exports table incrementally, using the incremental column specified
|
108
|
+
# If column is not specified, it will export the entire table
|
109
|
+
# Maximum value of the incremental column is recorded BEFORE export starts
|
110
|
+
##
|
111
|
+
|
112
|
+
def incremental_export metadata
|
113
|
+
max_value = @export_obj.max_value
|
114
|
+
@export_obj.update_max_val if @export_obj.max_incremental_value.blank?
|
115
|
+
|
116
|
+
sql = SqlCommands.export_sql(:db => @export_obj.source_schema,
|
117
|
+
:table => @export_obj.table_name,
|
118
|
+
:incremental_col => @export_obj.incremental_column,
|
119
|
+
:incremental_col_type => @export_obj.incremental_column_type,
|
120
|
+
:incremental_val => @export_obj.max_incremental_value)
|
121
|
+
|
122
|
+
cmd = SqlCommands.mysql_export(:db => @export_obj.source_schema,
|
123
|
+
:filepath => filepath,
|
124
|
+
:sql => sql)
|
125
|
+
|
126
|
+
metadata.export_type = "incremental"
|
127
|
+
update_export(:state => "exporting", :export_started_at => Time.now, :exporter_pid => Process.pid)
|
128
|
+
puts "Exporting..."
|
129
|
+
result = execute_export(cmd, metadata)
|
130
|
+
check_result(result, 0)
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
##
|
135
|
+
# Completes an export process
|
136
|
+
# Zips files, updates states etc
|
137
|
+
##
|
138
|
+
def wrapup metadata
|
139
|
+
puts "Zipping..."
|
140
|
+
zipfile(metadata)
|
141
|
+
update_export(:state => "export_completed", :export_finished_at => Time.now)
|
142
|
+
puts "Done.."
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Checks the returned resut from SSH CMD
|
147
|
+
# Size specifies if there should be any returned results or not
|
148
|
+
##
|
149
|
+
def check_result result, size
|
150
|
+
unless result.nil?
|
151
|
+
raise Exceptions::ExportError.new("Export Error\n#{result}") if result.length > 0
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Executes export command via ssh on the source DB
|
157
|
+
# Updates/interacts with the metadata object
|
158
|
+
##
|
159
|
+
def execute_export cmd, metadata
|
160
|
+
metadata.store!
|
161
|
+
result = ""
|
162
|
+
|
163
|
+
# Execute Export command on the source DB server
|
164
|
+
result = metadata.ssh.exec!(cmd)
|
165
|
+
|
166
|
+
return result
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# zips the file on the source DB server
|
171
|
+
##
|
172
|
+
def zipfile metadata
|
173
|
+
cmd = "cd #{Myreplicator.configs[@export_obj.source_schema]["ssh_tmp_dir"]}; gzip #{@export_obj.filename}"
|
174
|
+
|
175
|
+
zip_result = metadata.ssh.exec!(cmd)
|
176
|
+
|
177
|
+
unless zip_result.nil?
|
178
|
+
raise Exceptions::ExportError.new("Export Error\n#{zip_result}") if zip_result.length > 0
|
179
|
+
end
|
180
|
+
|
181
|
+
metadata.zipped = true
|
182
|
+
|
183
|
+
return zip_result
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|