resque-cluster 0.1.1.1 → 0.2.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 (37) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +47 -44
  4. data/lib/resque/cluster.rb +1 -0
  5. data/lib/resque/cluster/config.rb +169 -0
  6. data/lib/resque/cluster/config/file.rb +40 -0
  7. data/lib/resque/cluster/config/verifier.rb +27 -0
  8. data/lib/resque/cluster/member.rb +17 -27
  9. data/lib/resque/cluster/version.rb +1 -1
  10. data/lib/resque/pool/patches.rb +5 -0
  11. data/resque-cluster.gemspec +1 -1
  12. data/spec/integration/cluster_spec.rb +41 -11
  13. data/spec/integration/config/bad_global_config.yml +3 -0
  14. data/spec/integration/config/global_rebalance_config3.yml +5 -0
  15. data/spec/integration/config/rebalance_config.yml +4 -0
  16. data/spec/integration/spec_helper.rb +3 -0
  17. data/spec/integration/test_member_manager.rb +50 -19
  18. data/spec/unit/config/verifier_spec.rb +86 -0
  19. data/spec/unit/config_spec.rb +194 -0
  20. data/spec/unit/member_spec.rb +5 -32
  21. data/spec/unit/resque_pool_patches_spec.rb +3 -3
  22. data/spec/unit/spec_helper.rb +5 -1
  23. data/spec/unit/support/broken_yaml_config.yml +1 -0
  24. data/spec/unit/support/empty_config.yml +1 -0
  25. data/spec/unit/support/missing_global_maximum.yml +3 -0
  26. data/spec/unit/support/missing_local_maximum.yml +3 -0
  27. data/spec/unit/support/non_hash_config.yml +0 -0
  28. data/spec/unit/support/separate_missing_global.yml +2 -0
  29. data/spec/unit/support/separate_missing_local.yml +2 -0
  30. data/spec/unit/support/single_old_missing_global.yml +9 -0
  31. data/spec/unit/support/single_old_missing_local.yml +9 -0
  32. data/spec/unit/support/valid_config.yml +13 -0
  33. data/spec/unit/support/valid_global_config.yml +1 -0
  34. data/spec/unit/support/valid_local_config.yml +1 -0
  35. data/spec/unit/support/valid_single_new_config.yml +13 -0
  36. data/spec/unit/support/valid_single_old_config.yml +10 -0
  37. metadata +72 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: a6accecb72d6d1219eaa363bc58efd32cb89778d45d3e99354537c6f181600c3
4
- data.tar.gz: 0298d96640d2232e0d0a73640a9e89670be80fab64189b50ad6afc4d84f52ef3
2
+ SHA1:
3
+ metadata.gz: ff333888f274415257e79dbb38df4cea39f85aec
4
+ data.tar.gz: 2e4633a132a0b8ca17e96ac13e37446cb88c06e0
5
5
  SHA512:
6
- metadata.gz: acef7b4118b0a66bfb41c373ee68302550dc28b57299c8587cd29ee4a897d0618e2b6e3364213e53eeb1c419e3a098dada0a760ac99aecec7b93bc56c69f29e6
7
- data.tar.gz: 6085ee6845fba320b7b622913482af1062533bce58cc02fa87d0dc47edee47b331fe25d6cfafb1d273f9eebe1162524b2453f52e034e85a60afe6b13267c2594
6
+ metadata.gz: 60b4c0e0c44a770c927ae9395b63b28774ea5bc728f527ba9d612ae14f498671335cd7e288918c2898f0a01d33d83a266e72706bbc6145860f6b4c86c474b2c1
7
+ data.tar.gz: 6ba4a2699620059ffd384fffba71eabf3e44a1c2778636934787ea9f422b6b85d79f74d225938fa422ca67ef0ad666c63bff40764ef2933005a86082a0508ca2
data/Gemfile CHANGED
@@ -1,9 +1,11 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
3
  gem 'resque-pool', '~> 0.5.0'
4
- gem 'gru', '= 0.1.2'
4
+ gem 'gru'
5
5
 
6
6
  group :development do
7
+ gem 'sys-proctable'
8
+ gem 'ffi'
7
9
  gem 'pry'
8
10
  gem 'awesome_print'
9
11
  gem 'rspec', '~> 3.1.0'
data/Gemfile.lock CHANGED
@@ -1,32 +1,29 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- addressable (2.3.8)
5
- ast (2.0.0)
6
- astrolabe (1.3.1)
7
- parser (~> 2.2)
8
- awesome_print (1.6.1)
4
+ addressable (2.4.0)
5
+ ast (2.3.0)
6
+ awesome_print (1.7.0)
9
7
  builder (3.2.2)
10
- coderay (1.1.0)
8
+ coderay (1.1.1)
11
9
  descendants_tracker (0.0.4)
12
10
  thread_safe (~> 0.3, >= 0.3.1)
13
11
  diff-lcs (1.2.5)
14
12
  docile (1.1.5)
15
- faraday (0.9.1)
13
+ faraday (0.9.2)
16
14
  multipart-post (>= 1.2, < 3)
17
- git (1.2.9.1)
18
- github_api (0.12.3)
19
- addressable (~> 2.3)
15
+ ffi (1.9.14)
16
+ git (1.3.0)
17
+ github_api (0.14.5)
18
+ addressable (~> 2.4.0)
20
19
  descendants_tracker (~> 0.0.4)
21
20
  faraday (~> 0.8, < 0.10)
22
- hashie (>= 3.3)
23
- multi_json (>= 1.7.5, < 2.0)
24
- nokogiri (~> 1.6.3)
25
- oauth2
26
- gru (0.1.2)
21
+ hashie (>= 3.4)
22
+ oauth2 (~> 1.0)
23
+ gru (0.1.3)
27
24
  redis (> 0.0)
28
- hashie (3.4.2)
29
- highline (1.7.2)
25
+ hashie (3.4.4)
26
+ highline (1.7.8)
30
27
  jeweler (2.0.1)
31
28
  builder
32
29
  bundler (>= 1.0)
@@ -37,40 +34,42 @@ GEM
37
34
  rake
38
35
  rdoc
39
36
  json (1.8.3)
40
- jwt (1.5.1)
37
+ jwt (1.5.4)
41
38
  method_source (0.8.2)
42
- mini_portile (0.6.2)
43
- mock_redis (0.15.0)
39
+ mini_portile2 (2.1.0)
40
+ mock_redis (0.15.4)
44
41
  mono_logger (1.1.0)
45
- multi_json (1.11.2)
42
+ multi_json (1.12.1)
46
43
  multi_xml (0.5.5)
47
44
  multipart-post (2.0.0)
48
- nokogiri (1.6.6.2)
49
- mini_portile (~> 0.6.0)
50
- oauth2 (1.0.0)
45
+ nokogiri (1.6.8)
46
+ mini_portile2 (~> 2.1.0)
47
+ pkg-config (~> 1.1.7)
48
+ oauth2 (1.2.0)
51
49
  faraday (>= 0.8, < 0.10)
52
50
  jwt (~> 1.0)
53
51
  multi_json (~> 1.3)
54
52
  multi_xml (~> 0.5)
55
- rack (~> 1.2)
56
- parser (2.2.2.6)
57
- ast (>= 1.1, < 3.0)
53
+ rack (>= 1.2, < 3)
54
+ parser (2.3.1.2)
55
+ ast (~> 2.2)
56
+ pkg-config (1.1.7)
58
57
  powerpack (0.1.1)
59
- pry (0.10.1)
58
+ pry (0.10.4)
60
59
  coderay (~> 1.1.0)
61
60
  method_source (~> 0.8.1)
62
61
  slop (~> 3.4)
63
62
  rack (1.6.4)
64
63
  rack-protection (1.5.3)
65
64
  rack
66
- rainbow (2.0.0)
67
- rake (10.4.2)
65
+ rainbow (2.1.0)
66
+ rake (11.2.2)
68
67
  rdoc (3.12.2)
69
68
  json (~> 1.4)
70
- redis (3.2.2)
69
+ redis (3.3.1)
71
70
  redis-namespace (1.5.2)
72
71
  redis (~> 3.0, >= 3.0.4)
73
- resque (1.25.2)
72
+ resque (1.26.0)
74
73
  mono_logger (~> 1.0)
75
74
  multi_json (~> 1.0)
76
75
  redis-namespace (~> 1.3)
@@ -92,26 +91,28 @@ GEM
92
91
  rspec-mocks (3.1.3)
93
92
  rspec-support (~> 3.1.0)
94
93
  rspec-support (3.1.2)
95
- rubocop (0.32.1)
96
- astrolabe (~> 1.3)
97
- parser (>= 2.2.2.5, < 3.0)
94
+ rubocop (0.42.0)
95
+ parser (>= 2.3.1.1, < 3.0)
98
96
  powerpack (~> 0.1)
99
97
  rainbow (>= 1.99.1, < 3.0)
100
- ruby-progressbar (~> 1.4)
101
- ruby-progressbar (1.7.5)
102
- simplecov (0.10.0)
98
+ ruby-progressbar (~> 1.7)
99
+ unicode-display_width (~> 1.0, >= 1.0.1)
100
+ ruby-progressbar (1.8.1)
101
+ simplecov (0.12.0)
103
102
  docile (~> 1.1.0)
104
- json (~> 1.8)
103
+ json (>= 1.8, < 3)
105
104
  simplecov-html (~> 0.10.0)
106
105
  simplecov-html (0.10.0)
107
- sinatra (1.4.6)
108
- rack (~> 1.4)
106
+ sinatra (1.4.7)
107
+ rack (~> 1.5)
109
108
  rack-protection (~> 1.4)
110
109
  tilt (>= 1.3, < 3)
111
110
  slop (3.6.0)
111
+ sys-proctable (1.1.1)
112
112
  thread_safe (0.3.5)
113
- tilt (2.0.1)
113
+ tilt (2.0.5)
114
114
  trollop (2.1.2)
115
+ unicode-display_width (1.1.0)
115
116
  vegas (0.1.11)
116
117
  rack (>= 1.0.0)
117
118
 
@@ -121,7 +122,8 @@ PLATFORMS
121
122
  DEPENDENCIES
122
123
  awesome_print
123
124
  bundler (~> 1.0)
124
- gru (= 0.1.2)
125
+ ffi
126
+ gru
125
127
  jeweler (~> 2.0.1)
126
128
  mock_redis (~> 0.15.0)
127
129
  pry
@@ -130,6 +132,7 @@ DEPENDENCIES
130
132
  rspec (~> 3.1.0)
131
133
  rubocop (~> 0.31)
132
134
  simplecov
135
+ sys-proctable
133
136
 
134
137
  BUNDLED WITH
135
- 1.11.2
138
+ 1.12.5
@@ -1,4 +1,5 @@
1
1
  require 'resque/cluster/member'
2
+ require 'resque/cluster/config'
2
3
 
3
4
  module Resque
4
5
  # Distributed Pool is a clustered resque-pool
@@ -0,0 +1,169 @@
1
+ require 'resque/cluster/config/file'
2
+ require 'resque/cluster/config/verifier'
3
+
4
+ module Resque
5
+ class Cluster
6
+ # Config is a global and local configuration of a member of a resque pool cluster
7
+ class Config
8
+ extend Forwardable
9
+
10
+ attr_reader :configs, :config, :global_config, :verifier, :version_git_hash
11
+
12
+ def initialize(config_path, global_config_path = nil)
13
+ @config = Config::File.new(config_path)
14
+
15
+ @configs = [config]
16
+
17
+ if global_config_path
18
+ global_config = Config::File.new(global_config_path)
19
+
20
+ if global_config.expand_path != config.expand_path
21
+ @global_config = global_config
22
+
23
+ @configs << global_config
24
+ end
25
+ end
26
+
27
+ @errors = Set.new
28
+ @verifier = Verifier.new(configs)
29
+ @version_git_hash = config_version
30
+ end
31
+
32
+ def verified?
33
+ verifier.verified? && complete_worker_config?
34
+ end
35
+
36
+ def gru_format
37
+ return {} unless verified?
38
+
39
+ {
40
+ manage_worker_heartbeats: true,
41
+ host_maximums: host_maximums,
42
+ client_settings: Resque.redis.client.options,
43
+ cluster_name: Cluster.config[:cluster_name],
44
+ environment_name: Cluster.config[:environment],
45
+ cluster_maximums: cluster_maximums,
46
+ rebalance_flag: rebalance_flag || false,
47
+ max_workers_per_host: max_workers_per_host || nil,
48
+ presume_host_dead_after: presume_dead_after || 120,
49
+ version_hash: version_git_hash
50
+ }
51
+ end
52
+
53
+ def errors
54
+ @errors + configs.map { |config| config.errors.map { |error| "#{config}: #{error}" } }.flatten
55
+ end
56
+
57
+ def warnings
58
+ @warnings ||= []
59
+ end
60
+
61
+ def log_warnings
62
+ warnings.each do |warning|
63
+ puts warning
64
+ end
65
+ end
66
+
67
+ def log_errors
68
+ errors.each do |error|
69
+ puts error
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def complete_worker_config?
76
+ host = host_maximums.delete_if { |_, v| v.nil? }
77
+ cluster = cluster_maximums.delete_if { |_, v| v.nil? }
78
+
79
+ (host.keys == cluster.keys).tap do |complete|
80
+ @errors << "Every worker configuration must contain a local and a global maximum." unless complete
81
+ end
82
+ end
83
+
84
+ def host_maximums
85
+ case config_type
86
+ when :separate
87
+ config.contents
88
+ when :old
89
+ config.contents.reject do |k, _|
90
+ ['global_maximums', 'presume_dead_after', 'max_workers_per_host', 'rebalance_cluster'].include?(k)
91
+ end
92
+ when :new
93
+ config['workers'].each.with_object({}) do |(pool, maximums), local_maximums|
94
+ local_maximums[pool] = maximums['local']
95
+ end
96
+ end
97
+ end
98
+
99
+ def cluster_maximums
100
+ case config_type
101
+ when :separate
102
+ global_config['global_maximums'] || global_config.contents.reject do |k, _|
103
+ ['global_maximums', 'presume_dead_after', 'max_workers_per_host', 'rebalance_cluster'].include?(k)
104
+ end
105
+ when :old
106
+ config['global_maximums']
107
+ when :new
108
+ config['workers'].each.with_object({}) do |(pool, maximums), global_maximums|
109
+ global_maximums[pool] = maximums['global']
110
+ end
111
+ end
112
+ end
113
+
114
+ def rebalance_flag
115
+ case config_type
116
+ when :separate
117
+ global_config['rebalance_cluster']
118
+ when :old, :new
119
+ config['rebalance_cluster']
120
+ end
121
+ end
122
+
123
+ def max_workers_per_host
124
+ case config_type
125
+ when :separate
126
+ global_config['max_workers_per_host']
127
+ when :old, :new
128
+ config['max_workers_per_host']
129
+ end
130
+ end
131
+
132
+ def presume_dead_after
133
+ case config_type
134
+ when :separate
135
+ global_config['presume_dead_after']
136
+ when :old, :new
137
+ config['presume_dead_after']
138
+ end
139
+ end
140
+
141
+ def config_type
142
+ @config_type ||=
143
+ if global_config
144
+ :separate
145
+ elsif config['workers']
146
+ :new
147
+ else
148
+ :old
149
+ end
150
+ end
151
+
152
+ def config_version
153
+ return unless verified?
154
+
155
+ directory_name = config.dirname
156
+
157
+ if directory_name.exist?
158
+ output = Dir.chdir(directory_name) { `git rev-parse --verify HEAD`.chomp }
159
+
160
+ if $?.success?
161
+ @version_git_hash = output
162
+ else
163
+ @warnings << "Your config directory: #{directory_name} is not a git repo. Your configuration will not be versioned"
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,40 @@
1
+ module Resque
2
+ class Cluster
3
+ class Config
4
+ class File < Pathname
5
+ extend Forwardable
6
+
7
+ delegate :[] => :contents
8
+
9
+ def exist?
10
+ super.tap { |exists| errors << "Configuration file doesn't exist" unless exists }
11
+ end
12
+
13
+ def valid?
14
+ if contents.is_a?(Hash)
15
+ errors << "Config file is empty" unless contents.any?
16
+ else
17
+ errors << "Parsed config as invalid type: expected Hash, got #{contents.class}"
18
+ end
19
+
20
+ contents.is_a?(Hash) && contents.any?
21
+ end
22
+
23
+ def errors
24
+ @errors ||= Set.new
25
+ end
26
+
27
+ def contents
28
+ @contents ||=
29
+ begin
30
+ YAML.load(ERB.new(self.read).result)
31
+ rescue => e
32
+ errors << e.message
33
+
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ module Resque
2
+ class Cluster
3
+ class Config
4
+ class Verifier
5
+ attr_reader :configs
6
+
7
+ def initialize(configs)
8
+ @configs = configs
9
+ end
10
+
11
+ def verified?
12
+ configs_exist? && configs_are_valid?
13
+ end
14
+
15
+ private
16
+
17
+ def configs_exist?
18
+ configs.all?(&:exist?)
19
+ end
20
+
21
+ def configs_are_valid?
22
+ configs.all?(&:valid?)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -5,14 +5,18 @@ module Resque
5
5
  class Cluster
6
6
  # Member is a single member of a resque pool cluster
7
7
  class Member
8
- attr_reader :hostname, :pool, :local_config, :global_config
8
+ attr_reader :hostname, :pool, :config
9
9
 
10
10
  def initialize(started_pool)
11
11
  @pool = started_pool
12
- @local_config = parse_config(Cluster.config[:local_config_path])
13
- @global_config = parse_config(Cluster.config[:global_config_path])
14
- @global_config = @local_config if global_config.empty?
15
- @worker_count_manager = initialize_gru
12
+ @config = Config.new(Cluster.config[:local_config_path], Cluster.config[:global_config_path])
13
+ if @config.verified?
14
+ @config.log_warnings
15
+ @worker_count_manager = initialize_gru
16
+ else
17
+ @config.log_errors
18
+ @pool.quit
19
+ end
16
20
  end
17
21
 
18
22
  def perform
@@ -25,6 +29,7 @@ module Resque
25
29
  end
26
30
 
27
31
  def check_for_worker_count_adjustment
32
+ return unless gru_is_inititalized?
28
33
  host_count_adjustment = @worker_count_manager.adjust_workers
29
34
  adjust_worker_counts(host_count_adjustment) if host_count_adjustment
30
35
  end
@@ -32,7 +37,7 @@ module Resque
32
37
  private
33
38
 
34
39
  def global_prefix
35
- "cluster:#{Cluster.config[:cluster_name]}:#{Cluster.config[:environment]}"
40
+ "cluster:#{Cluster.config[:cluster_name]}:#{Cluster.config[:environment]}:#{@config.version_git_hash}"
36
41
  end
37
42
 
38
43
  def member_prefix
@@ -44,7 +49,7 @@ module Resque
44
49
  end
45
50
 
46
51
  def initialize_gru
47
- Gru.create(cluster_member_settings)
52
+ Gru.create(@config.gru_format)
48
53
  end
49
54
 
50
55
  def hostname
@@ -53,29 +58,23 @@ module Resque
53
58
 
54
59
  def adjust_worker_counts(count_adjustments)
55
60
  count_adjustments.each do |worker, count|
56
- next if count == 0
57
61
  @pool.adjust_worker_counts(worker, count)
58
62
  update_counts
59
63
  end
60
64
  end
61
65
 
62
- def parse_config(config_path)
63
- return {} unless config_path && File.exist?(config_path)
64
- YAML.load(ERB.new(IO.read(config_path)).result)
65
- end
66
-
67
66
  def remove_counts
68
67
  Resque.redis.del(running_workers_key_name)
69
68
  end
70
69
 
71
70
  def unqueue_all_workers
72
- @worker_count_manager.release_workers
71
+ @worker_count_manager.release_workers if gru_is_inititalized?
73
72
  end
74
73
 
75
74
  def unqueue_workers(workers)
76
75
  workers = Array(workers)
77
76
  workers.each do |worker|
78
- @worker_count_manager.release_workers(worker)
77
+ @worker_count_manager.release_workers(worker) if gru_is_inititalized?
79
78
  end
80
79
  end
81
80
 
@@ -86,19 +85,10 @@ module Resque
86
85
  end
87
86
  end
88
87
 
89
- def cluster_member_settings
90
- {
91
- cluster_maximums: @global_config["global_maximums"] || @global_config,
92
- host_maximums: @local_config,
93
- client_settings: Resque.redis.client.options,
94
- rebalance_flag: @global_config["rebalance_cluster"] || false,
95
- max_workers_per_host: @global_config["max_workers_per_host"] || nil,
96
- cluster_name: Cluster.config[:cluster_name],
97
- environment_name: Cluster.config[:environment],
98
- presume_host_dead_after: @global_config["presume_dead_after"] || 120,
99
- manage_worker_heartbeats: true
100
- }
88
+ def gru_is_inititalized?
89
+ ! @worker_count_manager.nil?
101
90
  end
91
+
102
92
  end
103
93
  end
104
94
  end