rubyrep 1.0.0

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