pt-osc 0.0.4 → 0.0.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.0.5
2
+
3
+ - remove dependence on ActiveSupport/Rails
4
+ - can specify log file in `percona` config
5
+
1
6
  ## 0.0.4
2
7
 
3
8
  - Make sure `active_record/migration` is `require`d
data/README.md CHANGED
@@ -39,6 +39,7 @@ environment:
39
39
  Additional/modified options for the `percona` hash include:
40
40
  - `defaults-file`: Can be specified as an absolute path (with leading `/`) or relative (without). Relative paths will be treated as relative to your project's working directory.
41
41
  - `run_mode`: Specify `'execute'` to actually run `pt-online-schema-change` when the migration runs. Specify `'print'` to output the commands to run to STDOUT instead. Default is `'print'`.
42
+ - `log`: Specify the file used for logging activity. Can be a relative or absolute path.
42
43
 
43
44
  ## Caveats
44
45
 
@@ -109,25 +109,23 @@ module ActiveRecord
109
109
  end
110
110
 
111
111
  def get_commands_string(table_name)
112
- @osc_commands ||= {}
113
- @osc_commands[table_name] ||= []
114
- @osc_commands[table_name].join(',')
112
+ get_commands[table_name] ||= []
113
+ get_commands[table_name].join(',')
115
114
  end
116
115
 
117
116
  def get_commanded_tables
118
- @osc_commands.keys
117
+ get_commands.keys
119
118
  end
120
119
 
121
120
  protected
122
121
  def add_command(table_name, command)
123
- @osc_commands ||= {}
124
- @osc_commands[table_name] ||= []
125
- @osc_commands[table_name] << command
122
+ get_commands[table_name] ||= []
123
+ get_commands[table_name] << command
126
124
  end
127
125
 
128
- def get_commands(table_name)
126
+ def get_commands(table_name = nil)
129
127
  @osc_commands ||= {}
130
- @osc_commands[table_name]
128
+ table_name.nil? ? @osc_commands : @osc_commands[table_name]
131
129
  end
132
130
  end
133
131
  end
@@ -71,33 +71,36 @@ module ActiveRecord
71
71
  protected
72
72
  def execute_pt_osc
73
73
  return unless @connection.is_a? ActiveRecord::ConnectionAdapters::PtOscAdapter
74
+ return if @connection.get_commanded_tables.empty?
74
75
 
75
- @connection.get_commanded_tables.each do |table_name|
76
- execute_sql = @connection.get_commands_string(table_name)
76
+ database_name = database_config[:database]
77
+ announce 'running pt-online-schema-change'
77
78
 
78
- Rails.logger.tagged('pt-osc') do |logger|
79
+ @connection.get_commanded_tables.each { |table| migrate_table(database_name, table) }
80
+ @connection.clear_commands
81
+ end
79
82
 
80
- database_name = database_config[:database]
83
+ def migrate_table(database_name, table_name)
84
+ execute_sql = @connection.get_commands_string(table_name)
81
85
 
82
- logger.info "Running on #{database_name}|#{table_name}: #{execute_sql}"
83
- announce 'running pt-online-schema-change'
86
+ logger.info "Running on #{database_name}|#{table_name}: #{execute_sql}"
84
87
 
85
- [true, false].each do |dry_run|
86
- command = percona_command(execute_sql, database_name, table_name, execute: !dry_run)
87
- logger.info "Command is #{command}"
88
- success = Kernel.system command
89
- if success
90
- logger.info "Successfully #{dry_run ? 'dry ran' : 'executed'} on #{database_name}|#{table_name}: #{execute_sql}"
91
- else
92
- failure_message = "Unable to #{dry_run ? 'dry run' : 'execute'} query on #{database_name}|#{table_name}: #{execute_sql}"
93
- logger.error failure_message
94
- raise RuntimeError.new(failure_message)
95
- end
96
- end
97
- end
98
- end
88
+ [true, false].each { |dry_run| execute_sql_for_table(execute_sql, database_name, table_name, dry_run) }
89
+ end
99
90
 
100
- @connection.clear_commands
91
+ def execute_sql_for_table(execute_sql, database_name, table_name, dry_run = true)
92
+ command = percona_command(execute_sql, database_name, table_name, execute: !dry_run)
93
+ logger.info "Command is #{command}"
94
+
95
+ success = Kernel.system command
96
+
97
+ if success
98
+ logger.info "Successfully #{dry_run ? 'dry ran' : 'executed'} on #{database_name}|#{table_name}: #{execute_sql}"
99
+ else
100
+ failure_message = "Unable to #{dry_run ? 'dry run' : 'execute'} query on #{database_name}|#{table_name}: #{execute_sql}"
101
+ logger.error failure_message
102
+ raise RuntimeError.new(failure_message)
103
+ end
101
104
  end
102
105
 
103
106
  def print_pt_osc
@@ -159,6 +162,18 @@ module ActiveRecord
159
162
  database_config[:percona] || {}
160
163
  end
161
164
 
165
+ def logfile
166
+ File.open(make_path_absolute(percona_config[:log] || 'log/pt_osc.log'), 'a')
167
+ end
168
+
169
+ def logger
170
+ return @logger if @logger
171
+ @logger = Logger.new(logfile)
172
+ @logger.formatter = Logger::Formatter.new # Don't let ActiveSupport override with SimpleFormatter
173
+ @logger.progname = 'pt-osc'
174
+ @logger
175
+ end
176
+
162
177
  private
163
178
  # Flag mutators
164
179
  def make_path_absolute(path)
@@ -1,5 +1,5 @@
1
1
  module Pt
2
2
  module Osc
3
- VERSION = '0.0.4'
3
+ VERSION = '0.0.5'
4
4
  end
5
5
  end
@@ -191,6 +191,25 @@ class PtOscAdapterTest < Test::Unit::TestCase
191
191
  assert_nil @adapter.send(:get_commands, table_name)
192
192
  end
193
193
  end
194
+
195
+ context 'with existing commands' do
196
+ setup do
197
+ @commands_hash = 3.times.inject({}) do |hash|
198
+ hash[Faker::Lorem.word] = [Faker::Lorem.sentence]
199
+ hash
200
+ end
201
+ @adapter.instance_variable_set(:@osc_commands, @commands_hash)
202
+ end
203
+
204
+ should 'return the entire commands hash when no table is given' do
205
+ assert_equal @commands_hash, @adapter.send(:get_commands)
206
+ end
207
+
208
+ should "return only the given table's command array" do
209
+ table = @commands_hash.keys.first
210
+ assert_equal @commands_hash[table], @adapter.send(:get_commands, table)
211
+ end
212
+ end
194
213
  end
195
214
 
196
215
  context '#get_commands_string' do
@@ -99,5 +99,237 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
99
99
  end
100
100
  end
101
101
  end
102
+
103
+ context '#execute_pt_osc' do
104
+ context 'with a pt-osc connection' do
105
+ setup do
106
+ @mock_connection = mock
107
+ @mock_connection.stubs(:is_a?).with(ActiveRecord::ConnectionAdapters::PtOscAdapter).returns(true)
108
+
109
+ @old_connection = @migration.instance_variable_get(:@connection)
110
+ @migration.instance_variable_set(:@connection, @mock_connection)
111
+ end
112
+
113
+ teardown do
114
+ @migration.instance_variable_set(:@connection, @old_connection)
115
+ end
116
+
117
+ context 'connected to a database' do
118
+ setup do
119
+ @database_name = Faker::Lorem.word
120
+ @migration.stubs(:database_config).returns(database: @database_name)
121
+ end
122
+
123
+ teardown do
124
+ @migration.unstub(:database_config)
125
+ end
126
+
127
+ context 'with no tables or commands' do
128
+ setup do
129
+ fake_not_empty_array = []
130
+ fake_not_empty_array.stubs(:empty?).returns(false)
131
+ @mock_connection.stubs(:get_commanded_tables).returns(fake_not_empty_array)
132
+ @mock_connection.stubs(:clear_commands)
133
+ end
134
+
135
+ teardown do
136
+ @mock_connection.unstub(:get_commanded_tables)
137
+ @mock_connection.unstub(:clear_commands)
138
+ end
139
+
140
+ should 'announce its intention' do
141
+ @migration.expects(:announce).with('running pt-online-schema-change')
142
+ @migration.send(:execute_pt_osc)
143
+ end
144
+
145
+ should 'call migrate_table for each table' do
146
+ num_tables = (0..5).to_a.sample
147
+ @mock_connection.unstub(:get_commanded_tables)
148
+ @mock_connection.expects(:get_commanded_tables).twice.returns(num_tables.times.map { Faker::Lorem.word })
149
+
150
+ @migration.expects(:migrate_table).times(num_tables)
151
+ quietly { @migration.send(:execute_pt_osc) }
152
+ end
153
+
154
+ should 'clear commands when finished' do
155
+ @mock_connection.expects(:clear_commands)
156
+ quietly { @migration.send(:execute_pt_osc) }
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ context '#migrate_table' do
164
+ context 'with a pt-osc connection' do
165
+ setup do
166
+ @mock_connection = mock
167
+
168
+ @old_connection = @migration.instance_variable_get(:@connection)
169
+ @migration.instance_variable_set(:@connection, @mock_connection)
170
+ end
171
+
172
+ teardown do
173
+ @migration.instance_variable_set(:@connection, @old_connection)
174
+ end
175
+
176
+ context 'with a command string' do
177
+ setup do
178
+ @mock_connection.stubs(:get_commands_string).returns('<<command string>>')
179
+ end
180
+
181
+ teardown do
182
+ @mock_connection.unstub(:get_commands_string)
183
+ end
184
+
185
+ context 'with stubbed log' do
186
+ setup do
187
+ @dummy_log = StringIO.new
188
+ @migration.stubs(:logfile).returns(@dummy_log)
189
+ end
190
+
191
+ teardown do
192
+ @migration.unstub(:logfile)
193
+ end
194
+
195
+ should 'log the database, table, and SQL command' do
196
+ database_name = Faker::Lorem.word
197
+ table_name = Faker::Lorem.word
198
+
199
+ @migration.stubs(:execute_sql_for_table)
200
+ @migration.send(:migrate_table, database_name, table_name)
201
+
202
+ assert database_name.in?(@dummy_log.string), 'Log entry did not contain database name'
203
+ assert table_name.in?(@dummy_log.string), 'Log entry did not contain table name'
204
+ assert '<<command string>>'.in?(@dummy_log.string), 'Log entry did not contain command string'
205
+ end
206
+
207
+ should 'call execute twice (dry run and execute)' do
208
+ @migration.expects(:execute_sql_for_table).with(anything, anything, anything, true).once
209
+ @migration.expects(:execute_sql_for_table).with(anything, anything, anything, false).once
210
+ @migration.send(:migrate_table, nil, nil)
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ context '#execute_sql_for_table' do
218
+ context 'with stubbed command' do
219
+ setup do
220
+ Kernel.stubs(:system).returns(true)
221
+ end
222
+
223
+ teardown do
224
+ Kernel.unstub(:system)
225
+ end
226
+
227
+ context 'with stubbed log' do
228
+ setup do
229
+ @dummy_log = StringIO.new
230
+ @migration.stubs(:logfile).returns(@dummy_log)
231
+ end
232
+
233
+ teardown do
234
+ @migration.unstub(:logfile)
235
+ end
236
+
237
+ should 'log the command' do
238
+ @migration.expects(:percona_command).returns('<<percona command>>')
239
+ @migration.send(:execute_sql_for_table, nil, nil, nil)
240
+ assert '<<percona command>>'.in?(@dummy_log.string), 'Log entry did not contain percona command'
241
+ end
242
+
243
+ context 'with successful execution' do
244
+ setup do
245
+ Kernel.expects(:system).returns(true)
246
+ end
247
+
248
+ teardown do
249
+ Kernel.unstub(:system)
250
+ end
251
+
252
+ should 'log success' do
253
+ @migration.send(:execute_sql_for_table, nil, nil, nil)
254
+ assert 'Success'.in?(@dummy_log.string), 'Success not mentioned in log'
255
+ end
256
+ end
257
+
258
+ context 'with failed execution' do
259
+ setup do
260
+ Kernel.expects(:system).returns(false)
261
+ end
262
+
263
+ teardown do
264
+ Kernel.unstub(:system)
265
+ end
266
+
267
+ should 'log failure' do
268
+ @migration.send(:execute_sql_for_table, nil, nil, nil) rescue nil
269
+ assert 'Unable to'.in?(@dummy_log.string), 'Failure not mentioned in log'
270
+ end
271
+
272
+ should 'raise a RuntimeError' do
273
+ assert_raises(RuntimeError) { @migration.send(:execute_sql_for_table, nil, nil, nil) }
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ context '#logger' do
281
+ context 'with stubbed log' do
282
+ setup do
283
+ @dummy_log = StringIO.new
284
+ @migration.stubs(:logfile).returns(@dummy_log)
285
+ end
286
+
287
+ teardown do
288
+ @migration.unstub(:logfile)
289
+ end
290
+
291
+ should 'log entries with "pt-osc"' do
292
+ logger = @migration.send(:logger)
293
+ logger.info 'test'
294
+ assert 'pt-osc'.in?(@dummy_log.string), "Log entry did not contain 'pt-osc': #{@dummy_log.string}"
295
+ end
296
+ end
297
+ end
298
+
299
+ context '#logfile' do
300
+ context 'with nothing in config' do
301
+ setup do
302
+ @migration.stubs(:percona_config).returns({})
303
+ end
304
+
305
+ teardown do
306
+ @migration.unstub(:percona_config)
307
+ end
308
+
309
+ should 'use log/pt_osc.log' do
310
+ @migration.stubs(:make_path_absolute).with('log/pt_osc.log')
311
+ .returns(File.expand_path('../dummy/log/pt_osc.log', File.dirname(__FILE__)))
312
+ logfile = @migration.send(:logfile)
313
+ assert 'log/pt_osc.log'.in?(logfile.path), 'Default log file not found in path'
314
+ end
315
+ end
316
+
317
+ context 'with a logfile specified in config' do
318
+ setup do
319
+ @migration.stubs(:percona_config).returns(log: 'log/fakelog.file')
320
+ end
321
+
322
+ teardown do
323
+ @migration.unstub(:percona_config)
324
+ end
325
+
326
+ should 'use log/pt_osc.log' do
327
+ @migration.stubs(:make_path_absolute).with('log/fakelog.file')
328
+ .returns(File.expand_path('../dummy/log/fakelog.file', File.dirname(__FILE__)))
329
+ logfile = @migration.send(:logfile)
330
+ assert 'log/fakelog.file'.in?(logfile.path), 'Configured log file not found in path'
331
+ end
332
+ end
333
+ end
102
334
  end
103
335
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pt-osc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -229,7 +229,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
229
229
  version: '0'
230
230
  segments:
231
231
  - 0
232
- hash: -2272605143598001323
232
+ hash: -1631463669037670360
233
233
  required_rubygems_version: !ruby/object:Gem::Requirement
234
234
  none: false
235
235
  requirements:
@@ -238,7 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
238
  version: '0'
239
239
  segments:
240
240
  - 0
241
- hash: -2272605143598001323
241
+ hash: -1631463669037670360
242
242
  requirements: []
243
243
  rubyforge_project:
244
244
  rubygems_version: 1.8.23