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.
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +137 -0
- data/README.txt +37 -0
- data/Rakefile +30 -0
- data/bin/rubyrep +8 -0
- data/config/hoe.rb +72 -0
- data/config/mysql_config.rb +25 -0
- data/config/postgres_config.rb +21 -0
- data/config/proxied_test_config.rb +14 -0
- data/config/redmine_config.rb +17 -0
- data/config/rep_config.rb +20 -0
- data/config/requirements.rb +32 -0
- data/config/test_config.rb +20 -0
- data/lib/rubyrep/base_runner.rb +195 -0
- data/lib/rubyrep/command_runner.rb +144 -0
- data/lib/rubyrep/committers/buffered_committer.rb +140 -0
- data/lib/rubyrep/committers/committers.rb +146 -0
- data/lib/rubyrep/configuration.rb +240 -0
- data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
- data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
- data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
- data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
- data/lib/rubyrep/database_proxy.rb +52 -0
- data/lib/rubyrep/direct_table_scan.rb +75 -0
- data/lib/rubyrep/generate_runner.rb +105 -0
- data/lib/rubyrep/initializer.rb +39 -0
- data/lib/rubyrep/logged_change.rb +326 -0
- data/lib/rubyrep/proxied_table_scan.rb +171 -0
- data/lib/rubyrep/proxy_block_cursor.rb +145 -0
- data/lib/rubyrep/proxy_connection.rb +318 -0
- data/lib/rubyrep/proxy_cursor.rb +44 -0
- data/lib/rubyrep/proxy_row_cursor.rb +43 -0
- data/lib/rubyrep/proxy_runner.rb +89 -0
- data/lib/rubyrep/replication_difference.rb +91 -0
- data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
- data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
- data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
- data/lib/rubyrep/replication_helper.rb +104 -0
- data/lib/rubyrep/replication_initializer.rb +307 -0
- data/lib/rubyrep/replication_run.rb +48 -0
- data/lib/rubyrep/replication_runner.rb +138 -0
- data/lib/rubyrep/replicators/replicators.rb +37 -0
- data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
- data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
- data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
- data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
- data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
- data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
- data/lib/rubyrep/scan_runner.rb +25 -0
- data/lib/rubyrep/session.rb +177 -0
- data/lib/rubyrep/sync_helper.rb +111 -0
- data/lib/rubyrep/sync_runner.rb +31 -0
- data/lib/rubyrep/syncers/syncers.rb +112 -0
- data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
- data/lib/rubyrep/table_scan.rb +54 -0
- data/lib/rubyrep/table_scan_helper.rb +38 -0
- data/lib/rubyrep/table_sorter.rb +70 -0
- data/lib/rubyrep/table_spec_resolver.rb +136 -0
- data/lib/rubyrep/table_sync.rb +68 -0
- data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
- data/lib/rubyrep/type_casting_cursor.rb +31 -0
- data/lib/rubyrep/uninstall_runner.rb +92 -0
- data/lib/rubyrep/version.rb +9 -0
- data/lib/rubyrep.rb +68 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/sims/performance/big_rep_spec.rb +100 -0
- data/sims/performance/big_scan_spec.rb +57 -0
- data/sims/performance/big_sync_spec.rb +141 -0
- data/sims/performance/performance.rake +228 -0
- data/sims/sim_helper.rb +24 -0
- data/spec/base_runner_spec.rb +218 -0
- data/spec/buffered_committer_spec.rb +271 -0
- data/spec/command_runner_spec.rb +145 -0
- data/spec/committers_spec.rb +174 -0
- data/spec/configuration_spec.rb +198 -0
- data/spec/connection_extender_interface_spec.rb +138 -0
- data/spec/connection_extenders_registration_spec.rb +129 -0
- data/spec/database_proxy_spec.rb +48 -0
- data/spec/database_rake_spec.rb +40 -0
- data/spec/db_specific_connection_extenders_spec.rb +34 -0
- data/spec/db_specific_replication_extenders_spec.rb +38 -0
- data/spec/direct_table_scan_spec.rb +61 -0
- data/spec/generate_runner_spec.rb +84 -0
- data/spec/initializer_spec.rb +46 -0
- data/spec/logged_change_spec.rb +480 -0
- data/spec/postgresql_replication_spec.rb +48 -0
- data/spec/postgresql_support_spec.rb +57 -0
- data/spec/progress_bar_spec.rb +77 -0
- data/spec/proxied_table_scan_spec.rb +151 -0
- data/spec/proxy_block_cursor_spec.rb +197 -0
- data/spec/proxy_connection_spec.rb +399 -0
- data/spec/proxy_cursor_spec.rb +56 -0
- data/spec/proxy_row_cursor_spec.rb +66 -0
- data/spec/proxy_runner_spec.rb +70 -0
- data/spec/replication_difference_spec.rb +160 -0
- data/spec/replication_extender_interface_spec.rb +365 -0
- data/spec/replication_extenders_spec.rb +32 -0
- data/spec/replication_helper_spec.rb +121 -0
- data/spec/replication_initializer_spec.rb +477 -0
- data/spec/replication_run_spec.rb +166 -0
- data/spec/replication_runner_spec.rb +213 -0
- data/spec/replicators_spec.rb +31 -0
- data/spec/rubyrep_spec.rb +8 -0
- data/spec/scan_detail_reporter_spec.rb +119 -0
- data/spec/scan_progress_printers_spec.rb +68 -0
- data/spec/scan_report_printers_spec.rb +67 -0
- data/spec/scan_runner_spec.rb +50 -0
- data/spec/scan_summary_reporter_spec.rb +61 -0
- data/spec/session_spec.rb +212 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +295 -0
- data/spec/sync_helper_spec.rb +157 -0
- data/spec/sync_runner_spec.rb +78 -0
- data/spec/syncers_spec.rb +171 -0
- data/spec/table_scan_helper_spec.rb +29 -0
- data/spec/table_scan_spec.rb +49 -0
- data/spec/table_sorter_spec.rb +31 -0
- data/spec/table_spec_resolver_spec.rb +102 -0
- data/spec/table_sync_spec.rb +84 -0
- data/spec/trigger_mode_switcher_spec.rb +83 -0
- data/spec/two_way_replicator_spec.rb +551 -0
- data/spec/two_way_syncer_spec.rb +256 -0
- data/spec/type_casting_cursor_spec.rb +50 -0
- data/spec/uninstall_runner_spec.rb +86 -0
- data/tasks/database.rake +439 -0
- data/tasks/deployment.rake +29 -0
- data/tasks/environment.rake +9 -0
- data/tasks/java.rake +37 -0
- data/tasks/redmine_test.rake +47 -0
- data/tasks/rspec.rake +68 -0
- data/tasks/rubyrep.tailor +18 -0
- data/tasks/stats.rake +19 -0
- data/tasks/task_helper.rb +20 -0
- data.tar.gz.sig +0 -0
- metadata +243 -0
- 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
|