synchronised_migration 0.1.0.pre.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ab84276b5cbc3b6a059da59e59c414496f72a69
4
+ data.tar.gz: ba49512428c7d3554fbafa2d98b6746204f3d4ad
5
+ SHA512:
6
+ metadata.gz: f18218af70a990a21ccdb7a88944bcf5482d56fc84b9181b52b48ee60339d1b412f27dcf91922849d617e59f0ca4dcc8429b8c5cfea32eef707d4286633c489c
7
+ data.tar.gz: 3dc0653bc15b35736138bacbdb9752e76589f571657966bc0b5ef641e1f67350d1fa4b201690320c54aa00fcc38001e74ce44c3cd8d10c262d21bbfca67c0180
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /.bundle
2
+ /vendor
3
+ /coverage
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.7
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ language: ruby
3
+ script: bundle && bundle exec rspec
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Synchronised Migration
2
+
3
+ ## 0.1.0 Unreleased
4
+
5
+ * [DO-70] Use Redis to synchronise migrations across multiple deployments
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,58 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ synchronised_migration (0.1.0)
5
+ redlock (~> 0.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (9.1.0)
11
+ coderay (1.1.2)
12
+ diff-lcs (1.3)
13
+ docile (1.1.5)
14
+ json (2.1.0)
15
+ method_source (0.8.2)
16
+ pry (0.10.4)
17
+ coderay (~> 1.1.0)
18
+ method_source (~> 0.8.1)
19
+ slop (~> 3.4)
20
+ pry-byebug (3.5.0)
21
+ byebug (~> 9.1)
22
+ pry (~> 0.10)
23
+ redis (3.3.3)
24
+ redlock (0.2.0)
25
+ redis (~> 3, >= 3.0.0)
26
+ rspec (3.6.0)
27
+ rspec-core (~> 3.6.0)
28
+ rspec-expectations (~> 3.6.0)
29
+ rspec-mocks (~> 3.6.0)
30
+ rspec-core (3.6.0)
31
+ rspec-support (~> 3.6.0)
32
+ rspec-expectations (3.6.0)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.6.0)
35
+ rspec-mocks (3.6.0)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.6.0)
38
+ rspec-support (3.6.0)
39
+ simplecov (0.15.0)
40
+ docile (~> 1.1.0)
41
+ json (>= 1.8, < 3)
42
+ simplecov-html (~> 0.10.0)
43
+ simplecov-html (0.10.2)
44
+ simplecov-rcov (0.2.3)
45
+ simplecov (>= 0.4.1)
46
+ slop (3.6.0)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ pry-byebug (~> 3.5)
53
+ rspec (~> 3.6)
54
+ simplecov-rcov (~> 0.2)
55
+ synchronised_migration!
56
+
57
+ BUNDLED WITH
58
+ 1.15.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 SeaLink Travel Group Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Synchronised Migration
2
+
3
+ [![Build Status](https://travis-ci.org/sealink/synchronised-migration-rb.svg?branch=master)](https://travis-ci.org/sealink/synchronised-migration-rb)
4
+
5
+ This gem makes it possible to deploy multiple instances with data migration
6
+ simultaneously. It uses Redis to ensure that there will be only one migration
7
+ running.
8
+
9
+ This gem works out of the box with a Rails project. It should work with other
10
+ Ruby projects so long as you load the rake task in Rakefile instead of relying
11
+ on Railtie.
12
+
13
+ This is a Ruby port of the same logic written in PHP in our [Craft
14
+ Docker](https://github.com/sealink/craft-docker) project.
15
+
16
+ ## Usage
17
+
18
+ Class `RedisConfig` needs to be provided as follow.
19
+
20
+ ```
21
+ RedisConfig.get[:host] # example.com
22
+ RedisConfig.get[:port] # 6379
23
+ RedisConfig.get[:db] # 0
24
+ ```
25
+
26
+ You may override these settings through environment variables.
27
+
28
+ ```
29
+ SYNC_RAKE_TASK=launch:migrate
30
+ REDLOCK_TIMEOUT_MS=3600000
31
+ REDLOCK_RETRY_DELAY_MS=200
32
+ REDLOCK_LOCK_KEY=migration-in-progress
33
+ REDLOCK_FAIL_KEY=migration-failed
34
+ ```
35
+
36
+ Run this before you launch the application during deployment.
37
+
38
+ ```
39
+ $ rake synchronised_migrate:execute
40
+ ```
41
+
42
+ ## Testing
43
+
44
+ Please refer to `.travis.yml` for testing.
@@ -0,0 +1,99 @@
1
+ require 'synchronised_migration'
2
+ require 'synchronised_migration/result'
3
+ require 'redis'
4
+ require 'redlock'
5
+ require 'singleton'
6
+
7
+ class SynchronisedMigration::Main
8
+ include Singleton
9
+
10
+ Result = SynchronisedMigration::Result
11
+
12
+ class << self
13
+ extend Forwardable
14
+ def_delegators :instance, :call
15
+ end
16
+
17
+ def call
18
+ lock_and_execute
19
+ end
20
+
21
+ private
22
+
23
+ def lock_and_execute
24
+ redlock.lock! lock_key, timeout do
25
+ execute
26
+ end
27
+ end
28
+
29
+ def execute
30
+ return Result.new 'Halting the script because the previous migration failed.' if previous_failed?
31
+ mark_failed
32
+ migrate
33
+ remove_fail_marker
34
+ Result.new
35
+ end
36
+
37
+ def previous_failed?
38
+ value = redis.get(fail_key)
39
+ not value.nil? and not value.empty?
40
+ end
41
+
42
+ def mark_failed
43
+ redis.set fail_key, 1
44
+ end
45
+
46
+ def remove_fail_marker
47
+ redis.del fail_key
48
+ end
49
+
50
+ def migrate
51
+ Rake::Task[target_rake_task].invoke
52
+ end
53
+
54
+ def target_rake_task
55
+ ENV.fetch 'SYNC_RAKE_TASK', 'launch:migrate'
56
+ end
57
+
58
+ def redis
59
+ @redis ||= Redis.new(url: redis_url)
60
+ end
61
+
62
+ def redlock
63
+ @redlock ||= Redlock::Client.new(
64
+ [ redis_url ], {
65
+ retry_count: retry_count,
66
+ retry_delay: retry_delay
67
+ }
68
+ )
69
+ end
70
+
71
+ def redis_url
72
+ sprintf(
73
+ 'redis:://%s:%s/%s',
74
+ RedisConfig.get[:host],
75
+ RedisConfig.get[:port],
76
+ RedisConfig.get[:db]
77
+ )
78
+ end
79
+
80
+ def timeout
81
+ ENV.fetch('REDLOCK_TIMEOUT_MS', 3_600_000).to_i
82
+ end
83
+
84
+ def retry_delay
85
+ ENV.fetch('REDLOCK_RETRY_DELAY_MS', 200).to_i
86
+ end
87
+
88
+ def retry_count
89
+ timeout / retry_delay
90
+ end
91
+
92
+ def lock_key
93
+ ENV.fetch 'REDLOCK_LOCK_KEY', 'migration-in-progress'
94
+ end
95
+
96
+ def fail_key
97
+ ENV.fetch 'REDLOCK_FAIL_KEY', 'migration-failed'
98
+ end
99
+ end
@@ -0,0 +1,5 @@
1
+ class SynchronisedMigration::Railtie < Rails::Railtie
2
+ rake_tasks do
3
+ load 'tasks/synchronised_migration.rake'
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ require 'synchronised_migration'
2
+
3
+ class SynchronisedMigration::Result
4
+ attr_accessor :error
5
+
6
+ def initialize(error = nil)
7
+ @error = error
8
+ end
9
+
10
+ def success?
11
+ error.nil?
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module SynchronisedMigration
2
+ VERSION = '0.1.0-alpha.1'
3
+ end
@@ -0,0 +1,4 @@
1
+ module SynchronisedMigration
2
+ end
3
+
4
+ require 'synchronised_migration/railtie' if defined?(Rails)
@@ -0,0 +1,9 @@
1
+ require 'synchronised_migration/main'
2
+
3
+ namespace :synchronised_migration do
4
+ task :execute do
5
+ result = SynchronisedMigration::Main.call
6
+ next if result.success?
7
+ fail result.error
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'pry'
2
+ require 'simplecov'
3
+ require 'simplecov-rcov'
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
6
+ SimpleCov.minimum_coverage 100
7
+ SimpleCov.start do
8
+ add_filter %r{^/vendor}
9
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'synchronised_migration/main'
3
+
4
+ describe SynchronisedMigration::Main do
5
+ subject { described_class }
6
+ let(:result) { subject.call }
7
+
8
+ context 'when the prerequisites are meet' do
9
+ let(:redis) { double }
10
+ let(:redlock) { double }
11
+ let(:fail_marker_value) { nil }
12
+ let(:rake_task) { double }
13
+
14
+ before do
15
+ subject.instance.instance_variable_set :@redis, nil
16
+ subject.instance.instance_variable_set :@redlock, nil
17
+
18
+ allow(Redis).to receive(:new).and_return(redis)
19
+ allow(redis).to receive(:get).and_return(fail_marker_value)
20
+ allow(redis).to receive(:set)
21
+ allow(redis).to receive(:del)
22
+
23
+ allow(Redlock::Client).to receive(:new).and_return(redlock)
24
+ allow(redlock).to receive(:lock!) { |lock_key, timeout, &block| block.call }
25
+
26
+ stub_const 'Rake', Module.new
27
+ stub_const 'Rake::Task', { 'launch:migrate' => rake_task }
28
+
29
+ allow(rake_task).to receive(:invoke)
30
+
31
+ stub_const(
32
+ 'RedisConfig', double(
33
+ get: {
34
+ host: 'example.com',
35
+ port: 6379,
36
+ db: 0
37
+ }
38
+ )
39
+ )
40
+ end
41
+
42
+ context 'in the happy path' do
43
+ it 'executes the migration successfully' do
44
+ expect(result).to be_success
45
+ expect(redlock).to have_received(:lock!)
46
+ expect(redis).to have_received(:get).with('migration-failed')
47
+ expect(redis).to have_received(:set).with('migration-failed', 1)
48
+ expect(rake_task).to have_received(:invoke)
49
+ expect(redis).to have_received(:del).with('migration-failed')
50
+ end
51
+ end
52
+
53
+ context 'after a deployment failed previously' do
54
+ let(:fail_marker_value) { '1' }
55
+
56
+ it "doesn't execute the migration" do
57
+ expect(result).not_to be_success
58
+ expect(rake_task).not_to have_received(:invoke)
59
+ end
60
+ end
61
+
62
+ context 'when the task crashed' do
63
+ before do
64
+ allow(rake_task).to receive(:invoke) do
65
+ fail 'An error message'
66
+ end
67
+ end
68
+
69
+ it 'marks the failure in Redis' do
70
+ expect { result }.to raise_error('An error message')
71
+ expect(redis).to have_received(:set).with('migration-failed', 1)
72
+ expect(redis).not_to have_received(:del)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'synchronised_migration/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'synchronised_migration'
7
+ spec.version = SynchronisedMigration::VERSION
8
+ spec.authors = ['Alvin Yim']
9
+ spec.email = 'support@travellink.com.au'
10
+ spec.description = 'Use Redis to record the data migration status'
11
+ spec.summary = 'For deploying to multiple instances simultaneously'
12
+ spec.homepage = 'https://github.com/sealink/synchronised-migration-rb'
13
+
14
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_dependency 'redlock', '~> 0.2'
20
+ spec.add_development_dependency 'simplecov-rcov', '~> 0.2'
21
+ spec.add_development_dependency 'rspec', '~> 3.6'
22
+ spec.add_development_dependency 'pry-byebug', '~> 3.5'
23
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: synchronised_migration
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.alpha.1
5
+ platform: ruby
6
+ authors:
7
+ - Alvin Yim
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redlock
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: simplecov-rcov
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.5'
69
+ description: Use Redis to record the data migration status
70
+ email: support@travellink.com.au
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - ".gitignore"
76
+ - ".ruby-version"
77
+ - ".travis.yml"
78
+ - CHANGELOG.md
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - LICENSE
82
+ - README.md
83
+ - lib/synchronised_migration.rb
84
+ - lib/synchronised_migration/main.rb
85
+ - lib/synchronised_migration/railtie.rb
86
+ - lib/synchronised_migration/result.rb
87
+ - lib/synchronised_migration/version.rb
88
+ - lib/tasks/synchronised_migration.rake
89
+ - spec/spec_helper.rb
90
+ - spec/synchronised_migration/main_spec.rb
91
+ - synchronised_migration.gemspec
92
+ homepage: https://github.com/sealink/synchronised-migration-rb
93
+ licenses: []
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">"
107
+ - !ruby/object:Gem::Version
108
+ version: 1.3.1
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.4.5.2
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: For deploying to multiple instances simultaneously
115
+ test_files:
116
+ - spec/spec_helper.rb
117
+ - spec/synchronised_migration/main_spec.rb