myreplicator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/myreplicator/application.js +21 -0
  5. data/app/assets/javascripts/myreplicator/chosen.jquery.min.js +10 -0
  6. data/app/assets/javascripts/myreplicator/cronwtf.js +156 -0
  7. data/app/assets/javascripts/myreplicator/exports.js +2 -0
  8. data/app/assets/javascripts/myreplicator/jquery.tipTip.minified.js +21 -0
  9. data/app/assets/stylesheets/myreplicator/FrancoisOne.ttf +0 -0
  10. data/app/assets/stylesheets/myreplicator/application.css +544 -0
  11. data/app/assets/stylesheets/myreplicator/asc-white.gif +0 -0
  12. data/app/assets/stylesheets/myreplicator/bg.gif +0 -0
  13. data/app/assets/stylesheets/myreplicator/bg.png +0 -0
  14. data/app/assets/stylesheets/myreplicator/chosen-sprite.png +0 -0
  15. data/app/assets/stylesheets/myreplicator/chosen.css +398 -0
  16. data/app/assets/stylesheets/myreplicator/clipboard-list.png +0 -0
  17. data/app/assets/stylesheets/myreplicator/cross.png +0 -0
  18. data/app/assets/stylesheets/myreplicator/desc-white.gif +0 -0
  19. data/app/assets/stylesheets/myreplicator/exports.css +4 -0
  20. data/app/assets/stylesheets/myreplicator/gear.png +0 -0
  21. data/app/assets/stylesheets/myreplicator/plus.png +0 -0
  22. data/app/assets/stylesheets/myreplicator/status-busy.png +0 -0
  23. data/app/assets/stylesheets/myreplicator/status.png +0 -0
  24. data/app/assets/stylesheets/myreplicator/tipTip.css +113 -0
  25. data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.eot +0 -0
  26. data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.svg +108 -0
  27. data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.ttf +0 -0
  28. data/app/assets/stylesheets/myreplicator/websymbols-regular-webfont.woff +0 -0
  29. data/app/assets/stylesheets/scaffold.css +56 -0
  30. data/app/controllers/myreplicator/application_controller.rb +4 -0
  31. data/app/controllers/myreplicator/exports_controller.rb +106 -0
  32. data/app/controllers/myreplicator/home_controller.rb +23 -0
  33. data/app/helpers/myreplicator/application_helper.rb +12 -0
  34. data/app/helpers/myreplicator/exports_helper.rb +4 -0
  35. data/app/models/myreplicator/export.rb +121 -0
  36. data/app/views/layouts/myreplicator/application.html.erb +24 -0
  37. data/app/views/myreplicator/exports/_form.html.erb +117 -0
  38. data/app/views/myreplicator/exports/edit.html.erb +2 -0
  39. data/app/views/myreplicator/exports/index.html.erb +56 -0
  40. data/app/views/myreplicator/exports/new.html.erb +2 -0
  41. data/app/views/myreplicator/exports/show.html.erb +28 -0
  42. data/app/views/myreplicator/home/_home_menu.erb +5 -0
  43. data/app/views/myreplicator/home/errors.html.erb +10 -0
  44. data/app/views/myreplicator/home/index.html.erb +58 -0
  45. data/config/routes.rb +6 -0
  46. data/db/migrate/20121025191622_create_myreplicator_exports.rb +35 -0
  47. data/lib/configuration.rb +25 -0
  48. data/lib/exporter/export_metadata.rb +198 -0
  49. data/lib/exporter/export_metadata.rb~ +9 -0
  50. data/lib/exporter/mysql_exporter.rb +187 -0
  51. data/lib/exporter/sql_commands.rb +126 -0
  52. data/lib/exporter/sql_commands.rb~ +5 -0
  53. data/lib/exporter.rb +3 -0
  54. data/lib/loader/import_sql.rb +91 -0
  55. data/lib/loader/import_sql.rb~ +27 -0
  56. data/lib/loader/loader.rb +122 -0
  57. data/lib/loader/loader.rb~ +4 -0
  58. data/lib/myreplicator/engine.rb +5 -0
  59. data/lib/myreplicator/version.rb +3 -0
  60. data/lib/myreplicator.rb +34 -0
  61. data/lib/tasks/myreplicator_tasks.rake +4 -0
  62. data/lib/transporter/parallelizer.rb +78 -0
  63. data/lib/transporter/transporter.rb +80 -0
  64. data/test/dummy/README.rdoc +261 -0
  65. data/test/dummy/Rakefile +7 -0
  66. data/test/dummy/app/assets/javascripts/application.js +15 -0
  67. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  68. data/test/dummy/app/controllers/application_controller.rb +3 -0
  69. data/test/dummy/app/helpers/application_helper.rb +2 -0
  70. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  71. data/test/dummy/config/application.rb +59 -0
  72. data/test/dummy/config/boot.rb +10 -0
  73. data/test/dummy/config/database.yml +42 -0
  74. data/test/dummy/config/database.yml.sample +47 -0
  75. data/test/dummy/config/database.yml.sample~ +26 -0
  76. data/test/dummy/config/database.yml~ +35 -0
  77. data/test/dummy/config/environment.rb +5 -0
  78. data/test/dummy/config/environments/development.rb +37 -0
  79. data/test/dummy/config/environments/production.rb +67 -0
  80. data/test/dummy/config/environments/test.rb +37 -0
  81. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  82. data/test/dummy/config/initializers/inflections.rb +15 -0
  83. data/test/dummy/config/initializers/mime_types.rb +5 -0
  84. data/test/dummy/config/initializers/secret_token.rb +7 -0
  85. data/test/dummy/config/initializers/session_store.rb +8 -0
  86. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  87. data/test/dummy/config/locales/en.yml +5 -0
  88. data/test/dummy/config/myreplicator.yml +22 -0
  89. data/test/dummy/config/myreplicator.yml.sample +23 -0
  90. data/test/dummy/config/myreplicator.yml~ +21 -0
  91. data/test/dummy/config/routes.rb +4 -0
  92. data/test/dummy/config.ru +4 -0
  93. data/test/dummy/db/migrate/20121101005152_sample_data.rb +24 -0
  94. data/test/dummy/db/migrate/20121115194022_create_myreplicator_exports.myreplicator.rb +36 -0
  95. data/test/dummy/db/schema.rb +45 -0
  96. data/test/dummy/db/seeds.rb +13 -0
  97. data/test/dummy/db/seeds.rb~ +1 -0
  98. data/test/dummy/log/development.log +1364 -0
  99. data/test/dummy/log/test.log +49 -0
  100. data/test/dummy/public/404.html +26 -0
  101. data/test/dummy/public/422.html +26 -0
  102. data/test/dummy/public/500.html +25 -0
  103. data/test/dummy/public/favicon.ico +0 -0
  104. data/test/dummy/script/rails +6 -0
  105. data/test/dummy/test/fixtures/myreplicator_exports.yml +0 -0
  106. data/test/dummy/tmp/cache/assets/C2E/D00/sprockets%2F667019818351638709494c01bddb5f68 +0 -0
  107. data/test/dummy/tmp/cache/assets/C83/BA0/sprockets%2F701a6339a558e5af28f150c161f43878 +0 -0
  108. data/test/dummy/tmp/cache/assets/C8B/150/sprockets%2F37163d05e55ad0b31b602ac5330412e3 +0 -0
  109. data/test/dummy/tmp/cache/assets/CBF/800/sprockets%2F00142a873933017aaa760316d0e2dcea +0 -0
  110. data/test/dummy/tmp/cache/assets/CC5/870/sprockets%2F4e91734f6f02a779d39f8272f627dd24 +0 -0
  111. data/test/dummy/tmp/cache/assets/CC9/1C0/sprockets%2Fa7a2b5a56180e1b21f783a9b30c03167 +0 -0
  112. data/test/dummy/tmp/cache/assets/CD5/B90/sprockets%2Fc999d13a6a21113981c0d820e8043bdf +0 -0
  113. data/test/dummy/tmp/cache/assets/CD7/030/sprockets%2F9ba4859590582b8b72a650b2b00b6cd2 +0 -0
  114. data/test/dummy/tmp/cache/assets/CDE/780/sprockets%2F6982ce9303b4e69c26f2a1a246a180e7 +0 -0
  115. data/test/dummy/tmp/cache/assets/CE5/670/sprockets%2Fe9e4122f1706626a21da6f8457f088ce +0 -0
  116. data/test/dummy/tmp/cache/assets/CF7/820/sprockets%2F6284656df87a7eb2545ed9b957560a7b +0 -0
  117. data/test/dummy/tmp/cache/assets/D00/9B0/sprockets%2F2bc203eb4802b393a589093debb65c04 +0 -0
  118. data/test/dummy/tmp/cache/assets/D00/A90/sprockets%2F3fa6fcf2205c530208681446cbc36bd3 +0 -0
  119. data/test/dummy/tmp/cache/assets/D0B/B30/sprockets%2F55059589d999952597c5c86e5becaf4e +0 -0
  120. data/test/dummy/tmp/cache/assets/D0D/DA0/sprockets%2F34d6b075a16a5a58ff4050988d8bd4b0 +0 -0
  121. data/test/dummy/tmp/cache/assets/D12/B40/sprockets%2F9c825562fe2a2a38bd6f41a635265bd9 +0 -0
  122. data/test/dummy/tmp/cache/assets/D1B/D40/sprockets%2F7cc2142509e95f7168a82a652d8d9dea +0 -0
  123. data/test/dummy/tmp/cache/assets/D1D/700/sprockets%2Ffe9cb975216709e2881c74b3d1d3e35f +0 -0
  124. data/test/dummy/tmp/cache/assets/D20/A20/sprockets%2Fb503e93ff1966dd94d03e79f291d75c1 +0 -0
  125. data/test/dummy/tmp/cache/assets/D26/A70/sprockets%2F63d95ae156df465783cd78e95069b2cc +0 -0
  126. data/test/dummy/tmp/cache/assets/D2B/E60/sprockets%2F8615ecf645b553c959544af9ff8438da +0 -0
  127. data/test/dummy/tmp/cache/assets/D34/F70/sprockets%2Fb93de9992473bee94e369fe3198c529c +0 -0
  128. data/test/dummy/tmp/cache/assets/D36/4D0/sprockets%2Fc050a64b5a803a638e155d05dcfe577d +0 -0
  129. data/test/dummy/tmp/cache/assets/D3E/310/sprockets%2F333a2a7535eac766267ebb7d5c2ab489 +0 -0
  130. data/test/dummy/tmp/cache/assets/D3F/A00/sprockets%2F7a803404e1f60b8d672d763cb9ba8af5 +0 -0
  131. data/test/dummy/tmp/cache/assets/D50/570/sprockets%2F6c1d20a178f66e798958d1e437fdb5da +0 -0
  132. data/test/dummy/tmp/cache/assets/D57/420/sprockets%2F937ea7429c536578ec7b688dee0cdf41 +0 -0
  133. data/test/dummy/tmp/cache/assets/D69/6F0/sprockets%2F94fff7f55bc4c300b25f3f9361ac1a52 +0 -0
  134. data/test/dummy/tmp/cache/assets/D86/D00/sprockets%2Fa4f32b4234d0d1bba272cd75e0d48e1d +0 -0
  135. data/test/dummy/tmp/cache/assets/D8B/B60/sprockets%2Faa32227c440a378ccd21218eefeb80bf +0 -0
  136. data/test/dummy/tmp/cache/assets/D9F/0B0/sprockets%2Faf0d2e69be3a6b56a76c20bf14d9e468 +0 -0
  137. data/test/dummy/tmp/cache/assets/DA8/910/sprockets%2Fab5775c4a837bd4d97ac394d473cda9b +0 -0
  138. data/test/dummy/tmp/cache/assets/DC0/100/sprockets%2F7a5d4f0d352bceed0dce0449c82251bd +0 -0
  139. data/test/dummy/tmp/cache/assets/DD2/490/sprockets%2Fa452ee92a092bc2feabc572cce49896d +0 -0
  140. data/test/dummy/tmp/cache/assets/DE1/320/sprockets%2F9f44ecdec8ceeef70871e15d88a448b1 +0 -0
  141. data/test/dummy/tmp/cache/assets/DF8/5D0/sprockets%2Fb815ed34d61cfed96222daa3bfd1d84d +0 -0
  142. data/test/dummy/tmp/cache/assets/E16/C70/sprockets%2F21ad93418ff75ceac86c7a85dfffe8f6 +0 -0
  143. data/test/dummy/tmp/cache/assets/E35/4F0/sprockets%2F96b1cdf8db6a1c8eb8abcce05958ae74 +0 -0
  144. data/test/dummy/tmp/cache/assets/E4E/300/sprockets%2Fefaae6e1a19a7c8f3acebdd5a36a6103 +0 -0
  145. data/test/fixtures/myreplicator/exports.yml +11 -0
  146. data/test/functional/myreplicator/exports_controller_test.rb +51 -0
  147. data/test/integration/navigation_test.rb +10 -0
  148. data/test/myreplicator_test.rb +7 -0
  149. data/test/test_helper.rb +15 -0
  150. data/test/unit/helpers/myreplicator/exports_helper_test.rb +6 -0
  151. data/test/unit/myreplicator/export_test.rb +10 -0
  152. data/test/unit/myreplicator/exporter_test.rb +11 -0
  153. data/test/unit/myreplicator/exporter_test.rb~ +10 -0
  154. 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,2 @@
1
+ <h2>New Export</h2>
2
+ <%= render 'form' %>
@@ -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,5 @@
1
+ <div class="home-menu">
2
+ <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>
5
+ </div>
@@ -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,6 @@
1
+ Myreplicator::Engine.routes.draw do
2
+ resources :exports
3
+
4
+ root :to => "home#index"
5
+ match '/errors', :to => "home#errors", :as => 'errors'
6
+ end
@@ -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,9 @@
1
+ module Myreplicator
2
+ class ExportMetadata
3
+
4
+ def initialize *args
5
+ options = args.extract_options!
6
+ end
7
+
8
+ end
9
+ 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