jetpants 0.8.0 → 0.8.2

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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +4 -9
  3. data/bin/jetpants +7 -6
  4. data/doc/capacity_plan.rdoc +77 -0
  5. data/doc/commands.rdoc +1 -1
  6. data/doc/jetpants_collins.rdoc +2 -1
  7. data/doc/online_schema_change.rdoc +45 -0
  8. data/doc/plugins.rdoc +7 -1
  9. data/doc/requirements.rdoc +1 -1
  10. data/doc/upgrade_helper.rdoc +68 -0
  11. data/lib/jetpants/db/client.rb +2 -1
  12. data/lib/jetpants/db/import_export.rb +12 -3
  13. data/lib/jetpants/db/replication.rb +6 -2
  14. data/lib/jetpants/db/schema.rb +40 -0
  15. data/lib/jetpants/db/server.rb +2 -2
  16. data/lib/jetpants/host.rb +12 -1
  17. data/lib/jetpants/pool.rb +41 -0
  18. data/lib/jetpants/shard.rb +201 -124
  19. data/lib/jetpants/table.rb +80 -10
  20. data/plugins/capacity_plan/capacity_plan.rb +353 -0
  21. data/plugins/capacity_plan/commandsuite.rb +19 -0
  22. data/plugins/capacity_plan/monkeypatch.rb +20 -0
  23. data/plugins/jetpants_collins/db.rb +45 -6
  24. data/plugins/jetpants_collins/jetpants_collins.rb +32 -21
  25. data/plugins/jetpants_collins/pool.rb +22 -1
  26. data/plugins/jetpants_collins/shard.rb +9 -2
  27. data/plugins/jetpants_collins/topology.rb +8 -9
  28. data/plugins/online_schema_change/commandsuite.rb +56 -0
  29. data/plugins/online_schema_change/db.rb +33 -0
  30. data/plugins/online_schema_change/online_schema_change.rb +5 -0
  31. data/plugins/online_schema_change/pool.rb +105 -0
  32. data/plugins/online_schema_change/topology.rb +56 -0
  33. data/plugins/simple_tracker/shard.rb +1 -1
  34. data/plugins/upgrade_helper/commandsuite.rb +212 -0
  35. data/plugins/upgrade_helper/db.rb +78 -0
  36. data/plugins/upgrade_helper/host.rb +22 -0
  37. data/plugins/upgrade_helper/pool.rb +259 -0
  38. data/plugins/upgrade_helper/shard.rb +61 -0
  39. data/plugins/upgrade_helper/upgrade_helper.rb +21 -0
  40. data/scripts/global_rowcount.rb +75 -0
  41. metadata +28 -15
@@ -0,0 +1,19 @@
1
+ require 'thor'
2
+
3
+ module Jetpants
4
+ class CommandSuite < Thor
5
+
6
+ desc 'capacity_snapshot', 'create a snapshot of the current useage'
7
+ def capacity_snapshot
8
+ Plugin::Capacity.new().snapshot
9
+ end
10
+
11
+ desc 'capacity_plan', 'capacity plan'
12
+ method_option :email, :email => 'email address to send capacity plan report to'
13
+ def capacity_plan
14
+ email = options[:email] || false
15
+ Plugin::Capacity.new().plan(email)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Enumerable
2
+ def sum
3
+ self.inject(0){|accum, i| accum + i }
4
+ end
5
+
6
+ def mean
7
+ self.sum/self.length.to_f
8
+ end
9
+
10
+ def sample_variance
11
+ m = self.mean
12
+ sum = self.inject(0){|accum, i| accum +(i-m)**2 }
13
+ sum/(self.length - 1).to_f
14
+ end
15
+
16
+ def standard_deviation
17
+ return Math.sqrt(self.sample_variance)
18
+ end
19
+
20
+ end
@@ -66,6 +66,11 @@ module Jetpants
66
66
  end
67
67
  end
68
68
 
69
+ # After changing the status of a node, clear its list of spare-node-related
70
+ # validation errors, so that we will re-probe when necessary
71
+ def after_collins_status=(value)
72
+ @spare_validation_errors = nil
73
+ end
69
74
 
70
75
  ##### NEW METHODS ##########################################################
71
76
 
@@ -76,14 +81,48 @@ module Jetpants
76
81
  end
77
82
 
78
83
  # Returns true if this database is a spare node and looks ready for use, false otherwise.
79
- # The default implementation just ensures a collins status of Provisioned.
80
- # Downstream plugins may override this to do additional checks to ensure the node is
81
- # in a sane state. (The caller of this method already checks that the node is SSHable,
82
- # and that MySQL is running, and the node isn't already in a pool -- so no need to
83
- # check any of those here.)
84
+ # Normally no need for plugins to override this (as of Jetpants 0.8.1), they should
85
+ # override DB#validate_spare instead.
84
86
  def usable_spare?
85
- collins_status.downcase == 'provisioned'
87
+ if @spare_validation_errors.nil?
88
+ @spare_validation_errors = []
89
+
90
+ # The order of checks is important -- if the node isn't even reachable by SSH,
91
+ # don't run any of the other checks, for example.
92
+ # Note that we probe concurrently in Topology#query_spare_assets, ahead of time
93
+ if !probed?
94
+ @spare_validation_errors << 'Attempt to probe node failed'
95
+ elsif !available?
96
+ @spare_validation_errors << 'Node is not reachable via SSH'
97
+ elsif !running?
98
+ @spare_validation_errors << 'MySQL is not running'
99
+ elsif pool
100
+ @spare_validation_errors << 'Node already has a pool'
101
+ else
102
+ validate_spare
103
+ end
104
+
105
+ unless @spare_validation_errors.empty?
106
+ error_text = @spare_validation_errors.join '; '
107
+ output "Removed from spare pool for failing checks: #{error_text}"
108
+ end
109
+ end
110
+ @spare_validation_errors.empty?
86
111
  end
87
112
 
113
+ # Performs validation checks on this node to see whether it is a usable spare.
114
+ # The default implementation just ensures a collins status of Allocated and state
115
+ # of SPARE.
116
+ # Downstream plugins may override this to do additional checks to ensure the node is
117
+ # in a sane condition.
118
+ # No need to check whether the node is SSHable, MySQL is running, or not already in
119
+ # a pool -- DB#usable_spare? already does that automatically.
120
+ def validate_spare
121
+ # Confirm node is in Allocated:SPARE status:state. (Because Collins find API hits a
122
+ # search index which isn't synchronously updated with all writes, there's potential
123
+ # for a find call to return assets that just transitioned to a different status or state.)
124
+ status_state = collins_status_state
125
+ @spare_validation_errors << "Unexpected status:state value: #{status_state}" unless status_state == 'allocated:spare'
126
+ end
88
127
  end
89
128
  end
@@ -156,7 +156,7 @@ module Jetpants
156
156
 
157
157
  # Pass in a hash mapping field name symbols to values to set
158
158
  # Symbol => String -- optionally set any Collins attribute
159
- # :status => String -- optionally set the status value for the asset
159
+ # :status => String -- optionally set the status value for the asset. Can optionally be a "status:state" string too.
160
160
  # :asset => Collins::Asset -- optionally pass this in to avoid an extra Collins API lookup, if asset already obtained
161
161
  #
162
162
  # Alternatively, pass in 2 strings (field_name, value) to set just a single Collins attribute (or status)
@@ -180,26 +180,30 @@ module Jetpants
180
180
  output "WARNING: unable to set Collins status to #{val}"
181
181
  next
182
182
  end
183
- if attrs[:state]
184
- previous_state = asset.state.name
185
- previous_status = asset.status
186
- if previous_state != attrs[:state].to_s || previous_status != attrs[:status].to_s
187
- success = Jetpants::Plugin::JetCollins.set_status!(asset, attrs[:status], 'changed through jetpants', attrs[:state])
188
- unless success
189
- Jetpants::Plugin::JetCollins.state_create!(attrs[:state], attrs[:state], attrs[:state], attrs[:status])
190
- success = Jetpants::Plugin::JetCollins.set_status!(asset, attrs[:status], 'changed through jetpants', attrs[:state])
191
- end
192
- raise "#{self}: Unable to set Collins state to #{attrs[:state]} and Unable to set Collins status to #{attrs[:status]}" unless success
193
- output "Collins state changed from #{previous_state} to #{attrs[:state]}"
194
- output "Collins status changed from #{previous_status} to #{attrs[:status]}"
195
- end
196
- else
197
- previous_value = asset.status
198
- if previous_value != val.to_s
199
- success = Jetpants::Plugin::JetCollins.set_status!(asset, val)
200
- raise "#{self}: Unable to set Collins status to #{val}" unless success
201
- output "Collins status changed from #{previous_value} to #{val}"
183
+ state_val = attrs[:state]
184
+ previous_status = asset.status.capitalize
185
+ # Allow setting status:state at once via foo.collins_status = 'allocated:running'
186
+ if val.include? ':'
187
+ raise "Attempting to set state in two places" if state_val
188
+ vals = val.split(':', 2)
189
+ val = vals.first.capitalize
190
+ state_val = vals.last.upcase
191
+ end
192
+ if state_val
193
+ previous_state = asset.state.name.upcase
194
+ next unless previous_state != state_val.to_s.upcase || previous_status != val.to_s.capitalize
195
+ success = Jetpants::Plugin::JetCollins.set_status!(asset, val, 'changed through jetpants', state_val)
196
+ unless success
197
+ # If we failed to set to this state, try creating the state as new
198
+ Jetpants::Plugin::JetCollins.state_create!(state_val, state_val, state_val, val)
199
+ success = Jetpants::Plugin::JetCollins.set_status!(asset, val, 'changed through jetpants', state_val)
202
200
  end
201
+ raise "#{self}: Unable to set Collins state to #{state_val} and Unable to set Collins status to #{val}" unless success
202
+ output "Collins status:state changed from #{previous_status}:#{previous_state} to #{val.capitalize}:#{state_val.upcase}"
203
+ elsif previous_status != val.to_s.capitalize
204
+ success = Jetpants::Plugin::JetCollins.set_status!(asset, val)
205
+ raise "#{self}: Unable to set Collins status to #{val}" unless success
206
+ output "Collins status changed from #{previous_status} to #{val}"
203
207
  end
204
208
  when :state
205
209
  unless asset && asset.status && attrs[:status]
@@ -231,9 +235,16 @@ module Jetpants
231
235
 
232
236
  end
233
237
 
238
+ # Returns a single downcased "status:state" string, useful when trying to compare both fields
239
+ # at once.
240
+ def collins_status_state
241
+ values = collins_get :status, :state
242
+ "#{values[:status]}:#{values[:state]}".downcase
243
+ end
244
+
234
245
  end # module JetCollins
235
246
  end # module Plugin
236
- end
247
+ end # module Jetpants
237
248
 
238
249
 
239
250
  # load all the monkeypatches for other Jetpants classes
@@ -70,7 +70,7 @@ module Jetpants
70
70
  master_read_weight: master_read_weight
71
71
  [@master, slaves].flatten.each do |db|
72
72
  current_status = (db.collins_status || '').downcase
73
- db.collins_status = 'Allocated' unless current_status == 'maintenance'
73
+ db.collins_status = 'Allocated:RUNNING' unless current_status == 'maintenance'
74
74
  db.collins_pool = @name
75
75
  end
76
76
  @master.collins_secondary_role = 'MASTER'
@@ -128,11 +128,32 @@ module Jetpants
128
128
  end
129
129
  end
130
130
  end
131
+
132
+ # Clean up any slaves that are no longer slaving (again only looking at current datacenter)
133
+ assets = Jetpants.topology.server_node_assets(@name, :slave)
134
+ assets.reject! {|a| a.location && a.location.upcase != Plugin::JetCollins.datacenter}
135
+ assets.map(&:to_db).each do |db|
136
+ if !db.running? || db.pool != self
137
+ db.output "Not replicating from new master, removing from pool #{self}"
138
+ db.collins_pool = ''
139
+ db.collins_secondary_role = ''
140
+ db.collins_status = 'Unallocated'
141
+ end
142
+ end
131
143
  end
132
144
 
133
145
 
134
146
  ##### NEW METHODS ##########################################################
135
147
 
148
+ # Returns the pool's creation time (as a unix timestamp) according to Collins.
149
+ # (note: may be off by a few hours until https://github.com/tumblr/collins/issues/80
150
+ # is resolved)
151
+ # Not called from anything in jetpants_collins, but available to your own
152
+ # custom automation if useful
153
+ def collins_creation_timestamp
154
+ collins_asset.created.to_time.to_i
155
+ end
156
+
136
157
  # Called from DB#after_probe_master and DB#after_probe_slave for machines
137
158
  # that are unreachable via SSH, or reachable but MySQL isn't running.
138
159
  def slaves_according_to_collins
@@ -77,13 +77,13 @@ module Jetpants
77
77
  db.collins_secondary_role = ''
78
78
  db.collins_status = 'Unallocated'
79
79
  end
80
- else
80
+ elsif @state != :initializing
81
81
  # Note that we don't call Pool#slaves here to get all 3 types in one shot,
82
82
  # because that potentially includes child shards, and we don't want to overwrite
83
83
  # their pool setting...
84
84
  [@master, active_slaves, standby_slaves, backup_slaves].flatten.each do |db|
85
85
  current_status = (db.collins_status || '').downcase
86
- db.collins_status = 'Allocated' unless current_status == 'maintenance'
86
+ db.collins_status = 'Allocated:RUNNING' unless current_status == 'maintenance'
87
87
  db.collins_pool = @name
88
88
  end
89
89
  @master.collins_secondary_role = 'MASTER'
@@ -91,6 +91,13 @@ module Jetpants
91
91
  standby_slaves.each {|db| db.collins_secondary_role = 'STANDBY_SLAVE'}
92
92
  backup_slaves.each {|db| db.collins_secondary_role = 'BACKUP_SLAVE'}
93
93
  end
94
+
95
+ # handle lockless master migration situations
96
+ if @state == :child && master.master && !@parent
97
+ to_be_ejected_master = master.master
98
+ to_be_ejected_master.collins_secondary_role = :standby_slave # not accurate, but no better option for now
99
+ end
100
+
94
101
  true
95
102
  end
96
103
 
@@ -73,7 +73,7 @@ module Jetpants
73
73
  end
74
74
 
75
75
 
76
- # Returns (count) DB objects. Pulls from machines in the Provisioned status
76
+ # Returns (count) DB objects. Pulls from machines in the spare state
77
77
  # and converts them to the Allocated status.
78
78
  # You can pass in :role to request spares with a particular secondary_role
79
79
  def claim_spares(count, options={})
@@ -84,7 +84,7 @@ module Jetpants
84
84
  db.collins_pool = ''
85
85
  db.collins_secondary_role = ''
86
86
  db.collins_slave_weight = ''
87
- db.collins_status = 'Allocated'
87
+ db.collins_status = 'Allocated:CLAIMED'
88
88
  db
89
89
  end
90
90
  end
@@ -181,6 +181,7 @@ module Jetpants
181
181
 
182
182
  if primary_roles.count == 1
183
183
  selector[:type] = '^CONFIGURATION$'
184
+ selector[:primary_role] = primary_roles.first
184
185
  else
185
186
  values = primary_roles.map {|r| "primary_role = ^#{r}$"}
186
187
  selector[:query] = 'type = ^CONFIGURATION$ AND (' + values.join(' OR ') + ')'
@@ -239,7 +240,7 @@ module Jetpants
239
240
 
240
241
  private
241
242
 
242
- # Helper method to query Collins for spare provisioned DBs.
243
+ # Helper method to query Collins for spare DBs.
243
244
  def query_spare_assets(count, options={})
244
245
  # Intentionally no remoteLookup=true here. We only want to grab spare nodes
245
246
  # from the datacenter that Jetpants is running in.
@@ -247,7 +248,8 @@ module Jetpants
247
248
  operation: 'and',
248
249
  details: true,
249
250
  type: 'SERVER_NODE',
250
- status: 'Provisioned',
251
+ status: 'Allocated',
252
+ state: 'SPARE',
251
253
  primary_role: 'DATABASE',
252
254
  size: 100,
253
255
  }
@@ -257,15 +259,12 @@ module Jetpants
257
259
  keep_nodes = []
258
260
 
259
261
  # Probe concurrently for speed reasons
260
- nodes.map(&:to_db).concurrent_each(&:probe)
262
+ nodes.map(&:to_db).concurrent_each {|db| db.probe rescue nil}
261
263
 
262
264
  # Now iterate in a single-threaded way for simplicity
263
265
  nodes.each do |node|
264
266
  db = node.to_db
265
- db.probe
266
- if node.pool || !db.available? || !db.running? || !db.usable_spare?
267
- db.output "Removed from potential spare pool for failing checks"
268
- else
267
+ if db.usable_spare?
269
268
  keep_nodes << node
270
269
  break if keep_nodes.size >= count
271
270
  end
@@ -0,0 +1,56 @@
1
+ require 'thor'
2
+
3
+ module Jetpants
4
+ class CommandSuite < Thor
5
+
6
+ desc 'alter_table', 'perform an alter table'
7
+ method_option :pool, :desc => 'Name of pool to run the alter table on'
8
+ method_option :dry_run, :desc => 'Dry run of the alter table', :type => :boolean
9
+ method_option :alter, :desc => 'The alter statment (eg ADD COLUMN c1 INT)'
10
+ method_option :database, :desc => 'Database to run the alter table on'
11
+ method_option :table, :desc => 'Table to run the alter table on'
12
+ method_option :all_shards, :desc => 'To run on all the shards', :type => :boolean
13
+ def alter_table
14
+ unless options[:all_shards]
15
+ pool_name = options[:pool] || ask('Please enter a name of a pool: ')
16
+ pool = Jetpants.topology.pool(pool_name)
17
+ raise "#{pool_name} is not a pool name" unless pool
18
+ end
19
+
20
+ database = options[:database] || false
21
+ table = options[:table] || ask('Please enter a name of a table: ')
22
+ alter = options[:alter] || ask('Please enter a alter table statment (eg ADD COLUMN c1 INT): ')
23
+
24
+ if options[:all_shards]
25
+ Jetpants.topology.alter_table_shards(database, table, alter, options[:dry_run])
26
+ else
27
+ unless pool.alter_table(database, table, alter, options[:dry_run])
28
+ print "check for errors during online schema change\n"
29
+ end
30
+ end
31
+ end
32
+
33
+ desc 'alter_table_drop', 'drop the old table after the alter table is complete'
34
+ method_option :pool, :desc => 'Name of pool that your ran the alter table on'
35
+ method_option :table, :desc => 'Table you ran the alter table on'
36
+ method_option :database, :desc => 'Database you ran the alter table on'
37
+ method_option :all_shards, :desc => 'To run on all the shards', :type => :boolean
38
+ def alter_table_drop
39
+ unless options[:all_shards]
40
+ pool_name = options[:pool] || ask('Please enter a name of a pool: ')
41
+ pool = Jetpants.topology.pool(pool_name)
42
+ raise "#{pool_name} is not a pool name" unless pool
43
+ end
44
+
45
+ database = options[:database] || false
46
+ table = options[:table] || ask('Please enter a name of a table: ')
47
+
48
+ if options[:all_shards]
49
+ Jetpants.topology.drop_old_alter_table_shards(database, table)
50
+ else
51
+ pool.drop_old_alter_table(database, table)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,33 @@
1
+ module Jetpants
2
+ class DB
3
+
4
+ # Creates a temporary user for use of pt-table-checksum and pt-upgrade,
5
+ # yields to the supplied block, and then drops the user.
6
+ # The user will have a randomly-generated 50-character password, and will
7
+ # have elevated permissions (ALL PRIVILEGES on the application schema, and
8
+ # a few global privs as well) since these are necessary to run the tools.
9
+ # The block will be passed the randomly-generated password.
10
+ def with_online_schema_change_user(username, database)
11
+ password = DB.random_password
12
+ create_user username, password
13
+ grant_privileges username, '*', 'PROCESS', 'REPLICATION CLIENT', 'REPLICATION SLAVE', 'SUPER'
14
+ grant_privileges username, database, 'ALL PRIVILEGES'
15
+ begin
16
+ yield password
17
+ rescue StandardError, Interrupt, IOError
18
+ drop_user username
19
+ raise
20
+ end
21
+ drop_user username
22
+ end
23
+
24
+ # make sure there is enough space to do an online schema change
25
+ def has_space_for_alter?(table_name, database_name=nil)
26
+ database_name ||= app_schema
27
+ table_size = dir_size("#{mysql_directory}/#{database_name}/#{table_name}.ibd")
28
+
29
+ table_size < mount_stats['available']
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ require 'json'
2
+
3
+
4
+ # load all the monkeypatches for other Jetpants classes
5
+ %w(pool db topology commandsuite).each {|mod| require "online_schema_change/#{mod}"}
@@ -0,0 +1,105 @@
1
+ # JetCollins monkeypatches to add Collins integration
2
+
3
+ module Jetpants
4
+ class Pool
5
+ collins_attr_accessor :online_schema_change
6
+
7
+ def alter_table(database, table, alter, dry_run=true, force=false)
8
+ database ||= app_schema
9
+ error = false
10
+
11
+ raise "not enough space to run alter table on #{table}" unless master.has_space_for_alter?(table, database)
12
+
13
+ unless(check_collins_for_alter)
14
+ raise "alter table already running on #{@name}"
15
+ end
16
+
17
+ max_threads = max_threads_running(30,1)
18
+ max_threads = 50 unless max_threads > 50
19
+
20
+ critical_threads_running = 2 * max_threads > 500 ? 2 * max_threads : 500
21
+
22
+ update_collins_for_alter(database, table, alter)
23
+
24
+ master.with_online_schema_change_user('pt-osc', database) do |password|
25
+
26
+ command = "pt-online-schema-change --nocheck-replication-filters --max-load='Threads_running:#{max_threads}' --critical-load='Threads_running:#{critical_threads_running}' --nodrop-old-table --retries=10 --set-vars='wait_timeout=100000' --dry-run --print --alter '#{alter}' D=#{database},t=#{table},h=#{master.ip},u=#{'pt-osc'},p=#{password}"
27
+
28
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]---------------------------------------------------------------------------------------\n"
29
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{command}\n"
30
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]---------------------------------------------------------------------------------------\n"
31
+
32
+ IO.popen command do |io|
33
+ io.each do |line|
34
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{line.gsub("\n","")}\n"
35
+ end
36
+ error = true if $?.to_i > 0
37
+ end
38
+
39
+ if !(dry_run || error)
40
+ continue = 'no'
41
+ unless force
42
+ continue = ask('Dry run complete would you like to continue?: (YES/no)')
43
+ end
44
+
45
+ if force || continue == 'YES'
46
+ command = "pt-online-schema-change --nocheck-replication-filters --max-load='Threads_running:#{max_threads}' --critical-load='Threads_running:#{critical_threads_running}' --nodrop-old-table --retries=10 --set-vars='wait_timeout=100000' --execute --print --alter '#{alter}' D=#{database},t=#{table},h=#{master.ip},u=#{'pt-osc'},p=#{password}"
47
+
48
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]---------------------------------------------------------------------------------------\n\n\n"
49
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{command}\n"
50
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}]\n\n---------------------------------------------------------------------------------------\n\n\n"
51
+
52
+ IO.popen command do |io|
53
+ io.each do |line|
54
+ print "[#{@name.to_s.red}][#{Time.now.to_s.blue}] #{line.gsub("\n","")}\n"
55
+ end
56
+ error = true if $?.to_i > 0
57
+ end #end execute
58
+
59
+ end #end continue
60
+
61
+ end #end if ! dry run
62
+
63
+ end #end user grant block
64
+
65
+ clean_up_collins_for_alter
66
+
67
+ ! error
68
+ end
69
+
70
+ # update collins for tracking alters, so there is only one running at a time
71
+ def update_collins_for_alter(database, table, alter)
72
+ self.collins_online_schema_change = JSON.pretty_generate({
73
+ 'running' => true,
74
+ 'started' => Time.now.to_i,
75
+ 'database' => database,
76
+ 'table' => table,
77
+ 'alter' => alter
78
+ })
79
+
80
+ end
81
+
82
+ # check if a alter is already running
83
+ def check_collins_for_alter()
84
+ return true if self.collins_online_schema_change.empty?
85
+ meta = JSON.parse(self.collins_online_schema_change)
86
+ if(meta['running'])
87
+ return false
88
+ end
89
+ return true
90
+ end
91
+
92
+ # clean up collins after alter
93
+ def clean_up_collins_for_alter()
94
+ self.collins_online_schema_change = ''
95
+ end
96
+
97
+ # drop old table after an alter, this is because
98
+ # we do not drop the table after an alter
99
+ def drop_old_alter_table(database, table)
100
+ database ||= app_schema
101
+ master.mysql_root_cmd("USE #{database}; DROP TABLE _#{table}_old")
102
+ end
103
+
104
+ end
105
+ end