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