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