rubyrep 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +137 -0
  4. data/README.txt +37 -0
  5. data/Rakefile +30 -0
  6. data/bin/rubyrep +8 -0
  7. data/config/hoe.rb +72 -0
  8. data/config/mysql_config.rb +25 -0
  9. data/config/postgres_config.rb +21 -0
  10. data/config/proxied_test_config.rb +14 -0
  11. data/config/redmine_config.rb +17 -0
  12. data/config/rep_config.rb +20 -0
  13. data/config/requirements.rb +32 -0
  14. data/config/test_config.rb +20 -0
  15. data/lib/rubyrep/base_runner.rb +195 -0
  16. data/lib/rubyrep/command_runner.rb +144 -0
  17. data/lib/rubyrep/committers/buffered_committer.rb +140 -0
  18. data/lib/rubyrep/committers/committers.rb +146 -0
  19. data/lib/rubyrep/configuration.rb +240 -0
  20. data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
  21. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
  22. data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
  23. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
  24. data/lib/rubyrep/database_proxy.rb +52 -0
  25. data/lib/rubyrep/direct_table_scan.rb +75 -0
  26. data/lib/rubyrep/generate_runner.rb +105 -0
  27. data/lib/rubyrep/initializer.rb +39 -0
  28. data/lib/rubyrep/logged_change.rb +326 -0
  29. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  30. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  31. data/lib/rubyrep/proxy_connection.rb +318 -0
  32. data/lib/rubyrep/proxy_cursor.rb +44 -0
  33. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  34. data/lib/rubyrep/proxy_runner.rb +89 -0
  35. data/lib/rubyrep/replication_difference.rb +91 -0
  36. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  37. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
  38. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  39. data/lib/rubyrep/replication_helper.rb +104 -0
  40. data/lib/rubyrep/replication_initializer.rb +307 -0
  41. data/lib/rubyrep/replication_run.rb +48 -0
  42. data/lib/rubyrep/replication_runner.rb +138 -0
  43. data/lib/rubyrep/replicators/replicators.rb +37 -0
  44. data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
  45. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  46. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  47. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  48. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  49. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  50. data/lib/rubyrep/scan_runner.rb +25 -0
  51. data/lib/rubyrep/session.rb +177 -0
  52. data/lib/rubyrep/sync_helper.rb +111 -0
  53. data/lib/rubyrep/sync_runner.rb +31 -0
  54. data/lib/rubyrep/syncers/syncers.rb +112 -0
  55. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  56. data/lib/rubyrep/table_scan.rb +54 -0
  57. data/lib/rubyrep/table_scan_helper.rb +38 -0
  58. data/lib/rubyrep/table_sorter.rb +70 -0
  59. data/lib/rubyrep/table_spec_resolver.rb +136 -0
  60. data/lib/rubyrep/table_sync.rb +68 -0
  61. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  62. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  63. data/lib/rubyrep/uninstall_runner.rb +92 -0
  64. data/lib/rubyrep/version.rb +9 -0
  65. data/lib/rubyrep.rb +68 -0
  66. data/script/destroy +14 -0
  67. data/script/generate +14 -0
  68. data/script/txt2html +74 -0
  69. data/setup.rb +1585 -0
  70. data/sims/performance/big_rep_spec.rb +100 -0
  71. data/sims/performance/big_scan_spec.rb +57 -0
  72. data/sims/performance/big_sync_spec.rb +141 -0
  73. data/sims/performance/performance.rake +228 -0
  74. data/sims/sim_helper.rb +24 -0
  75. data/spec/base_runner_spec.rb +218 -0
  76. data/spec/buffered_committer_spec.rb +271 -0
  77. data/spec/command_runner_spec.rb +145 -0
  78. data/spec/committers_spec.rb +174 -0
  79. data/spec/configuration_spec.rb +198 -0
  80. data/spec/connection_extender_interface_spec.rb +138 -0
  81. data/spec/connection_extenders_registration_spec.rb +129 -0
  82. data/spec/database_proxy_spec.rb +48 -0
  83. data/spec/database_rake_spec.rb +40 -0
  84. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  85. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  86. data/spec/direct_table_scan_spec.rb +61 -0
  87. data/spec/generate_runner_spec.rb +84 -0
  88. data/spec/initializer_spec.rb +46 -0
  89. data/spec/logged_change_spec.rb +480 -0
  90. data/spec/postgresql_replication_spec.rb +48 -0
  91. data/spec/postgresql_support_spec.rb +57 -0
  92. data/spec/progress_bar_spec.rb +77 -0
  93. data/spec/proxied_table_scan_spec.rb +151 -0
  94. data/spec/proxy_block_cursor_spec.rb +197 -0
  95. data/spec/proxy_connection_spec.rb +399 -0
  96. data/spec/proxy_cursor_spec.rb +56 -0
  97. data/spec/proxy_row_cursor_spec.rb +66 -0
  98. data/spec/proxy_runner_spec.rb +70 -0
  99. data/spec/replication_difference_spec.rb +160 -0
  100. data/spec/replication_extender_interface_spec.rb +365 -0
  101. data/spec/replication_extenders_spec.rb +32 -0
  102. data/spec/replication_helper_spec.rb +121 -0
  103. data/spec/replication_initializer_spec.rb +477 -0
  104. data/spec/replication_run_spec.rb +166 -0
  105. data/spec/replication_runner_spec.rb +213 -0
  106. data/spec/replicators_spec.rb +31 -0
  107. data/spec/rubyrep_spec.rb +8 -0
  108. data/spec/scan_detail_reporter_spec.rb +119 -0
  109. data/spec/scan_progress_printers_spec.rb +68 -0
  110. data/spec/scan_report_printers_spec.rb +67 -0
  111. data/spec/scan_runner_spec.rb +50 -0
  112. data/spec/scan_summary_reporter_spec.rb +61 -0
  113. data/spec/session_spec.rb +212 -0
  114. data/spec/spec.opts +1 -0
  115. data/spec/spec_helper.rb +295 -0
  116. data/spec/sync_helper_spec.rb +157 -0
  117. data/spec/sync_runner_spec.rb +78 -0
  118. data/spec/syncers_spec.rb +171 -0
  119. data/spec/table_scan_helper_spec.rb +29 -0
  120. data/spec/table_scan_spec.rb +49 -0
  121. data/spec/table_sorter_spec.rb +31 -0
  122. data/spec/table_spec_resolver_spec.rb +102 -0
  123. data/spec/table_sync_spec.rb +84 -0
  124. data/spec/trigger_mode_switcher_spec.rb +83 -0
  125. data/spec/two_way_replicator_spec.rb +551 -0
  126. data/spec/two_way_syncer_spec.rb +256 -0
  127. data/spec/type_casting_cursor_spec.rb +50 -0
  128. data/spec/uninstall_runner_spec.rb +86 -0
  129. data/tasks/database.rake +439 -0
  130. data/tasks/deployment.rake +29 -0
  131. data/tasks/environment.rake +9 -0
  132. data/tasks/java.rake +37 -0
  133. data/tasks/redmine_test.rake +47 -0
  134. data/tasks/rspec.rake +68 -0
  135. data/tasks/rubyrep.tailor +18 -0
  136. data/tasks/stats.rake +19 -0
  137. data/tasks/task_helper.rb +20 -0
  138. data.tar.gz.sig +0 -0
  139. metadata +243 -0
  140. metadata.gz.sig +0 -0
@@ -0,0 +1,307 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/..'
2
+
3
+ require 'rubyrep'
4
+
5
+ module RR
6
+
7
+ # Ensures all preconditions are met to start with replication
8
+ class ReplicationInitializer
9
+
10
+ # The active Session
11
+ attr_accessor :session
12
+
13
+ # Creates a new RepInititializer for the given Session
14
+ def initialize(session)
15
+ self.session = session
16
+ end
17
+
18
+ # Returns the options for the given table.
19
+ # If table is +nil+, returns general options.
20
+ def options(table = nil)
21
+ if table
22
+ session.configuration.options_for_table table
23
+ else
24
+ session.configuration.options
25
+ end
26
+ end
27
+
28
+ # Creates a trigger logging all table changes
29
+ # * database: either :+left+ or :+right+
30
+ # * table: name of the table
31
+ def create_trigger(database, table)
32
+ options = self.options(table)
33
+
34
+ params = {
35
+ :trigger_name => "#{options[:rep_prefix]}_#{table}",
36
+ :table => table,
37
+ :keys => session.send(database).primary_key_names(table),
38
+ :log_table => "#{options[:rep_prefix]}_pending_changes",
39
+ :activity_table => "#{options[:rep_prefix]}_running_flags",
40
+ :key_sep => options[:key_sep],
41
+ :exclude_rr_activity => false,
42
+ }
43
+
44
+ session.send(database).create_replication_trigger params
45
+ end
46
+
47
+ # Returns +true+ if the replication trigger for the given table exists.
48
+ # * database: either :+left+ or :+right+
49
+ # * table: name of the table
50
+ def trigger_exists?(database, table)
51
+ trigger_name = "#{options(table)[:rep_prefix]}_#{table}"
52
+ session.send(database).replication_trigger_exists? trigger_name, table
53
+ end
54
+
55
+ # Drops the replication trigger of the named table.
56
+ # * database: either :+left+ or :+right+
57
+ # * table: name of the table
58
+ def drop_trigger(database, table)
59
+ trigger_name = "#{options(table)[:rep_prefix]}_#{table}"
60
+ session.send(database).drop_replication_trigger trigger_name, table
61
+ end
62
+
63
+ # Ensures that the sequences of the named table (normally the primary key
64
+ # column) are generated with the correct increment and offset in both
65
+ # left and right database.
66
+ # The sequence is always updated in both databases.
67
+ # * +table_pair+: a hash of names of corresponding :left and :right tables
68
+ # * +increment+: increment of the sequence
69
+ # * +left_offset+: offset of table in left database
70
+ # * +right_offset+: offset of table in right database
71
+ # E. g. an increment of 2 and offset of 1 will lead to generation of odd
72
+ # numbers.
73
+ def ensure_sequence_setup(table_pair, increment, left_offset, right_offset)
74
+ table_options = options(table_pair[:left])
75
+ if table_options[:adjust_sequences]
76
+ rep_prefix = table_options[:rep_prefix]
77
+ left_sequence_values = session.left.sequence_values rep_prefix, table_pair[:left]
78
+ right_sequence_values = session.right.sequence_values rep_prefix, table_pair[:right]
79
+ [:left, :right].each do |database|
80
+ offset = database == :left ? left_offset : right_offset
81
+ session.send(database).update_sequences \
82
+ rep_prefix, table_pair[database], increment, offset,
83
+ left_sequence_values, right_sequence_values, table_options[:sequence_adjustment_buffer]
84
+ end
85
+ end
86
+ end
87
+
88
+ # Restores the original sequence settings for the named table.
89
+ # (Actually it sets the sequence increment to 1. If before, it had a
90
+ # different value, then the restoration will not be correct.)
91
+ # * database: either :+left+ or :+right+
92
+ # * +table_name+: name of the table
93
+ def clear_sequence_setup(database, table)
94
+ table_options = options(table)
95
+ if table_options[:adjust_sequences]
96
+ session.send(database).clear_sequence_setup(
97
+ table_options[:rep_prefix], table
98
+ )
99
+ end
100
+ end
101
+
102
+ # Returns +true+ if the change log exists in the specified database.
103
+ # * database: either :+left+ or :+right+
104
+ def change_log_exists?(database)
105
+ session.send(database).tables.include? "#{options[:rep_prefix]}_pending_changes"
106
+ end
107
+
108
+ # Returns +true+ if the replication log exists.
109
+ def event_log_exists?
110
+ session.left.tables.include? "#{options[:rep_prefix]}_logged_events"
111
+ end
112
+
113
+ # Drops the change log table in the specified database
114
+ # * database: either :+left+ or :+right+
115
+ def drop_change_log(database)
116
+ session.send(database).drop_table "#{options[:rep_prefix]}_pending_changes"
117
+ end
118
+
119
+ # Drops the replication log table.
120
+ def drop_event_log
121
+ session.left.drop_table "#{options[:rep_prefix]}_logged_events"
122
+ end
123
+
124
+ # Size of the replication log column diff_dump
125
+ DIFF_DUMP_SIZE = 2000
126
+
127
+ # Size of the repliation log column long_description
128
+ LONG_DESCRIPTION_SIZE = 1000
129
+
130
+ # Ensures that create_table and related statements don't print notices to
131
+ # stdout. Then restored original message setting.
132
+ # * +database+: either :+left+ or :+right+
133
+ def silence_ddl_notices(database)
134
+ if session.configuration.send(database)[:adapter] =~ /postgres/
135
+ old_message_level = session.send(database).
136
+ select_one("show client_min_messages")['client_min_messages']
137
+ session.send(database).execute "set client_min_messages = warning"
138
+ end
139
+ yield
140
+ ensure
141
+ if session.configuration.send(database)[:adapter] =~ /postgres/
142
+ session.send(database).execute "set client_min_messages = #{old_message_level}"
143
+ end
144
+ end
145
+
146
+ # Creates the replication log table.
147
+ def create_event_log
148
+ silence_ddl_notices(:left) do
149
+ session.left.create_table "#{options[:rep_prefix]}_logged_events", :id => false do |t|
150
+ t.column :activity, :string
151
+ t.column :change_table, :string
152
+ t.column :diff_type, :string
153
+ t.column :change_key, :string
154
+ t.column :left_change_type, :string
155
+ t.column :right_change_type, :string
156
+ t.column :description, :string
157
+ t.column :long_description, :string, :limit => LONG_DESCRIPTION_SIZE
158
+ t.column :event_time, :timestamp
159
+ t.column :diff_dump, :string, :limit => DIFF_DUMP_SIZE
160
+ end
161
+ session.left.add_big_primary_key "#{options[:rep_prefix]}_logged_events", 'id'
162
+ end
163
+ end
164
+
165
+ # Creates the change log table in the specified database
166
+ # * database: either :+left+ or :+right+
167
+ def create_change_log(database)
168
+ silence_ddl_notices(database) do
169
+ session.send(database).create_table "#{options[:rep_prefix]}_pending_changes", :id => false do |t|
170
+ t.column :change_table, :string
171
+ t.column :change_key, :string
172
+ t.column :change_new_key, :string
173
+ t.column :change_type, :string
174
+ t.column :change_time, :timestamp
175
+ end
176
+ session.send(database).add_big_primary_key "#{options[:rep_prefix]}_pending_changes", 'id'
177
+ end
178
+ end
179
+
180
+ # Adds to the current session's configuration an exclusion of rubyrep tables.
181
+ def exclude_rubyrep_tables
182
+ session.configuration.exclude_rubyrep_tables
183
+ end
184
+
185
+ # Checks in both databases, if the activity marker tables exist and if not,
186
+ # creates them.
187
+ def ensure_activity_markers
188
+ table_name = "#{options[:rep_prefix]}_running_flags"
189
+ [:left, :right].each do |database|
190
+ unless session.send(database).tables.include? table_name
191
+ session.send(database).create_table table_name, :id => false do |t|
192
+ t.column :active, :integer
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ # Checks if the event log table already exists and creates it if necessary
199
+ def ensure_event_log
200
+ create_event_log unless event_log_exists?
201
+ end
202
+
203
+ # Checks in both databases, if the change log tables exists and creates them
204
+ # if necessary
205
+ def ensure_change_logs
206
+ [:left, :right].each do |database|
207
+ create_change_log(database) unless change_log_exists?(database)
208
+ end
209
+ end
210
+
211
+ # Checks in both databases, if the infrastructure tables (change log, event
212
+ # log) exist and creates them if necessary.
213
+ def ensure_infrastructure
214
+ ensure_activity_markers
215
+ ensure_change_logs
216
+ ensure_event_log
217
+ end
218
+
219
+ # Checks in both databases, if the change_log tables exist. If yes, drops them.
220
+ def drop_change_logs
221
+ [:left, :right].each do |database|
222
+ drop_change_log(database) if change_log_exists?(database)
223
+ end
224
+ end
225
+
226
+ # Checks in both databases, if the activity_marker tables exist. If yes, drops them.
227
+ def drop_activity_markers
228
+ table_name = "#{options[:rep_prefix]}_running_flags"
229
+ [:left, :right].each do |database|
230
+ if session.send(database).tables.include? table_name
231
+ session.send(database).drop_table table_name
232
+ end
233
+ end
234
+ end
235
+
236
+ # Removes all rubyrep infrastructure tables from both databases.
237
+ def drop_infrastructure
238
+ drop_event_log if event_log_exists?
239
+ drop_change_logs
240
+ drop_activity_markers
241
+ end
242
+
243
+ # Checks for tables that have triggers but are not in the list of configured
244
+ # tables. Removes triggers and restores sequences of those tables.
245
+ # * +configured_table_pairs+:
246
+ # An array of table pairs (e. g. [{:left => 'xy', :right => 'xy2'}]).
247
+ def restore_unconfigured_tables(configured_table_pairs = session.configured_table_pairs)
248
+ [:left, :right].each do |database|
249
+ configured_tables = configured_table_pairs.map {|table_pair| table_pair[database]}
250
+ unconfigured_tables = session.send(database).tables - configured_tables
251
+ unconfigured_tables.each do |table|
252
+ if trigger_exists?(database, table)
253
+ drop_trigger(database, table)
254
+ session.send(database).execute(
255
+ "delete from #{options[:rep_prefix]}_pending_changes where change_table = '#{table}'")
256
+ end
257
+ clear_sequence_setup(database, table)
258
+ end
259
+ end
260
+ end
261
+
262
+ # Prepares the database / tables for replication.
263
+ def prepare_replication
264
+ exclude_rubyrep_tables
265
+
266
+ puts "Verifying RubyRep tables"
267
+ ensure_infrastructure
268
+
269
+ puts "Checking for and removing rubyrep triggers from unconfigured tables"
270
+ restore_unconfigured_tables
271
+
272
+ puts "Verifying rubyrep triggers of configured tables"
273
+ unsynced_table_pairs = []
274
+ table_pairs = session.sort_table_pairs(session.configured_table_pairs)
275
+ table_pairs.each do |table_pair|
276
+ table_options = options(table_pair[:left])
277
+ ensure_sequence_setup table_pair,
278
+ table_options[:sequence_increment],
279
+ table_options[:left_sequence_offset],
280
+ table_options[:right_sequence_offset]
281
+
282
+ unsynced = false
283
+ [:left, :right].each do |database|
284
+ unless trigger_exists? database, table_pair[database]
285
+ create_trigger database, table_pair[database]
286
+ unsynced = true
287
+ end
288
+ end
289
+ unsynced_table_pairs << table_pair if unsynced
290
+ end
291
+ unsynced_table_specs = unsynced_table_pairs.map do |table_pair|
292
+ "#{table_pair[:left]}, #{table_pair[:right]}"
293
+ end
294
+
295
+ unless unsynced_table_specs.empty?
296
+ puts "Executing initial table syncs"
297
+ runner = SyncRunner.new
298
+ runner.session = session
299
+ runner.options = {:table_specs => unsynced_table_specs}
300
+ runner.execute
301
+ end
302
+
303
+ puts "Starting replication"
304
+ end
305
+ end
306
+
307
+ end
@@ -0,0 +1,48 @@
1
+ module RR
2
+
3
+ # Executes a single replication run
4
+ class ReplicationRun
5
+
6
+ # The current Session object
7
+ attr_accessor :session
8
+
9
+ # Returns the current ReplicationHelper; creates it if necessary
10
+ def helper
11
+ @helper ||= ReplicationHelper.new(self)
12
+ end
13
+
14
+ # Returns the current replicator; creates it if necessary.
15
+ def replicator
16
+ @replicator ||=
17
+ Replicators.replicators[session.configuration.options[:replicator]].new(helper)
18
+ end
19
+
20
+ # Executes the replication run.
21
+ def run
22
+ success = false
23
+ replicator # ensure that replicator is created and has chance to validate settings
24
+
25
+ loop do
26
+ begin
27
+ session.reload_changes # ensure the cache of change log records is up-to-date
28
+ diff = ReplicationDifference.new session
29
+ diff.load
30
+ break unless diff.loaded?
31
+ replicator.replicate_difference diff if diff.type != :no_diff
32
+ rescue Exception => e
33
+ helper.log_replication_outcome diff, e.message,
34
+ e.class.to_s + "\n" + e.backtrace.join("\n")
35
+ end
36
+ end
37
+ success = true # considered to be successful if we get till here
38
+ ensure
39
+ helper.finalize success
40
+ end
41
+
42
+ # Creates a new ReplicationRun instance.
43
+ # * +session+: the current Session
44
+ def initialize(session)
45
+ self.session = session
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,138 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'optparse'
4
+ require 'thread'
5
+
6
+ module RR
7
+ # This class implements the functionality of the 'replicate' command.
8
+ class ReplicationRunner
9
+
10
+ CommandRunner.register 'replicate' => {
11
+ :command => self,
12
+ :description => 'Starts a replication process'
13
+ }
14
+
15
+ # Provided options. Possible values:
16
+ # * +:config_file+: path to config file
17
+ attr_accessor :options
18
+
19
+ # Should be set to +true+ if the replication runner should be terminated.
20
+ attr_accessor :termination_requested
21
+
22
+ # Parses the given command line parameter array.
23
+ # Returns the status (as per UNIX conventions: 1 if parameters were invalid,
24
+ # 0 otherwise)
25
+ def process_options(args)
26
+ status = 0
27
+ self.options = {}
28
+
29
+ parser = OptionParser.new do |opts|
30
+ opts.banner = <<EOS
31
+ Usage: #{$0} replicate [options]
32
+
33
+ Replicates two databases as per specified configuration file.
34
+ EOS
35
+ opts.separator ""
36
+ opts.separator " Specific options:"
37
+
38
+ opts.on("-c", "--config", "=CONFIG_FILE",
39
+ "Mandatory. Path to configuration file.") do |arg|
40
+ options[:config_file] = arg
41
+ end
42
+
43
+ opts.on_tail("--help", "Show this message") do
44
+ $stderr.puts opts
45
+ self.options = nil
46
+ end
47
+ end
48
+
49
+ begin
50
+ parser.parse!(args)
51
+ if options # this will be +nil+ if the --help option is specified
52
+ raise("Please specify configuration file") unless options.include?(:config_file)
53
+ end
54
+ rescue Exception => e
55
+ $stderr.puts "Command line parsing failed: #{e}"
56
+ $stderr.puts parser.help
57
+ self.options = nil
58
+ status = 1
59
+ end
60
+
61
+ return status
62
+ end
63
+
64
+ # Returns the active +Session+.
65
+ # Loads config file and creates session if necessary.
66
+ def session
67
+ unless @session
68
+ load options[:config_file]
69
+ @session = Session.new Initializer.configuration
70
+ end
71
+ @session
72
+ end
73
+
74
+ # Wait for the next replication time
75
+ def pause_replication
76
+ @last_run ||= 1.year.ago
77
+ now = Time.now
78
+ @next_run = @last_run + session.configuration.options[:replication_interval]
79
+ unless now >= @next_run
80
+ waiting_time = @next_run - now
81
+ @waiter_thread.join waiting_time
82
+ end
83
+ @last_run = Time.now
84
+ end
85
+
86
+ # Initializes the waiter thread used for replication pauses and processing
87
+ # the process TERM signal.
88
+ def init_waiter
89
+ @termination_mutex = Mutex.new
90
+ @termination_mutex.lock
91
+ @waiter_thread ||= Thread.new {@termination_mutex.lock; self.termination_requested = true}
92
+ Signal.trap('TERM') {@termination_mutex.unlock}
93
+ end
94
+
95
+ # Prepares the replication
96
+ def prepare_replication
97
+ initializer = ReplicationInitializer.new session
98
+ initializer.prepare_replication
99
+ end
100
+
101
+ # Executes an endless loop of replication runs
102
+ def execute
103
+ init_waiter
104
+ prepare_replication
105
+
106
+ until termination_requested do
107
+ begin
108
+ session.refresh
109
+ run = ReplicationRun.new session
110
+ run.run
111
+ rescue Exception => e
112
+ now = Time.now.iso8601
113
+ $stderr.puts "#{now} Exception caught: #{e}"
114
+ if @last_exception_message != e.to_s # only print backtrace if something changed
115
+ @last_exception_message = e.to_s
116
+ $stderr.puts e.backtrace.map {|line| line.gsub(/^/, "#{' ' * now.length} ")}
117
+ end
118
+ end
119
+ pause_replication
120
+ end
121
+ end
122
+
123
+ # Entry points for executing a processing run.
124
+ # args: the array of command line options that were provided by the user.
125
+ def self.run(args)
126
+ runner = new
127
+
128
+ status = runner.process_options(args)
129
+ if runner.options
130
+ runner.execute
131
+ end
132
+ status
133
+ end
134
+
135
+ end
136
+ end
137
+
138
+
@@ -0,0 +1,37 @@
1
+ module RR
2
+ # Replicators are classes that implement the replication policies.
3
+ # This module provides functionality to register replicators and access the
4
+ # list of registered replicators.
5
+ # Each Replicator must register itself with Replicators#register.
6
+ # Each Replicator must implement the following methods:
7
+ #
8
+ # # Creates a new replicator (A replicator is used for one replication run only)
9
+ # # * sync_helper: a SyncHelper object providing necessary information and functionalities
10
+ # def initialize(sync_helper)
11
+ #
12
+ # # Called to sync the provided difference.
13
+ # # +difference+ is an instance of +ReplicationDifference+
14
+ # def replicate_difference(difference)
15
+ #
16
+ # # Provides default option for the replicator. Optional.
17
+ # # Returns a hash with :key => value pairs.
18
+ # def self.default_options
19
+ module Replicators
20
+ # Returns a Hash of currently registered replicators.
21
+ # (Empty Hash if no replicators were defined.)
22
+ def self.replicators
23
+ @replicators ||= {}
24
+ @replicators
25
+ end
26
+
27
+ # Registers one or multiple replicators.
28
+ # syncer_hash is a Hash with
29
+ # key:: The adapter symbol as used to reference the replicator
30
+ # value:: The class implementing the replicator
31
+ def self.register(replicator_hash)
32
+ @replicators ||= {}
33
+ @replicators.merge! replicator_hash
34
+ end
35
+
36
+ end
37
+ end