gru 0.0.10 → 0.1.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.
- checksums.yaml +4 -4
- data/lib/gru/adapters/redis_adapter.rb +43 -33
- data/lib/gru/configuration.rb +5 -10
- data/lib/gru/version.rb +1 -1
- data/lib/gru/worker_manager.rb +1 -1
- data/spec/gru/adapters/redis_adapter_spec.rb +2 -4
- data/spec/gru/gru_integration_spec.rb +171 -0
- data/spec/gru/worker_manager_spec.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3568199a19c30ad45c3e4888884901d19ebf8c2c
|
4
|
+
data.tar.gz: 987f4860c98601be7dca13be6dcd4ec590f84881
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 386d66bc3221a042fc314ff088dfec031a68def5245873dfb3ba99af9a8567a49096ac9e6f8d97ef038ea077ee8ac429c5e62b09803afb2a9d270b5436940238
|
7
|
+
data.tar.gz: a2ffe6817185cd9c44fd3aaa0968b17bf49a6e27ba004dcaca2007d5998f4f8a6aeffe9e3fa03b73f601ba7d60f6be2e8f7a773a9783a236d36778342e2fe038
|
@@ -5,9 +5,9 @@ module Gru
|
|
5
5
|
class RedisAdapter
|
6
6
|
attr_reader :client
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@client = client
|
8
|
+
def initialize(settings)
|
10
9
|
@settings = settings
|
10
|
+
@client = initialize_client(settings.client_settings)
|
11
11
|
end
|
12
12
|
|
13
13
|
def set_worker_counts
|
@@ -18,6 +18,7 @@ module Gru
|
|
18
18
|
set_max_worker_counts(@settings.host_maximums)
|
19
19
|
register_global_workers(@settings.cluster_maximums)
|
20
20
|
set_max_global_worker_counts(@settings.cluster_maximums)
|
21
|
+
update_heartbeat if manage_heartbeat?
|
21
22
|
end
|
22
23
|
|
23
24
|
def provision_workers
|
@@ -51,32 +52,31 @@ module Gru
|
|
51
52
|
def release_workers
|
52
53
|
workers = max_host_workers
|
53
54
|
workers.keys.each do |worker|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
if
|
59
|
-
global_running_count = send_message(:hincrby, global_workers_running_key,worker,-1)
|
60
|
-
end
|
61
|
-
if running_count > 0
|
62
|
-
running_count = send_message(:hincrby, host_workers_running_key,worker,-1)
|
63
|
-
end
|
55
|
+
host_count = local_running_count(worker)
|
56
|
+
global_count = host_count
|
57
|
+
host_count.times do
|
58
|
+
global_count = send_message(:hincrby, global_workers_running_key, worker, -1) if global_count > 0
|
59
|
+
host_count = send_message(:hincrby, host_workers_running_key,worker,-1) if host_count > 0
|
64
60
|
end
|
65
61
|
end
|
66
62
|
send_message(:del, host_workers_running_key)
|
67
63
|
send_message(:del, host_max_worker_key)
|
64
|
+
send_message(:hdel, heartbeat_key, hostname)
|
68
65
|
end
|
69
66
|
|
70
|
-
def
|
71
|
-
|
67
|
+
def release_presumed_dead_worker_hosts
|
68
|
+
return false unless manage_heartbeat?
|
69
|
+
update_heartbeat
|
70
|
+
presumed_dead_worker_hosts.each_pair do |hostname,timestamp|
|
72
71
|
lock_key = "#{gru_key}:removing_dead_host:#{hostname}"
|
73
72
|
if send_message(:setnx,lock_key,Time.now.to_i)
|
74
|
-
|
73
|
+
remove_worker_host(hostname)
|
74
|
+
send_message(:hdel,heartbeat_key,hostname)
|
75
75
|
send_message(:del,lock_key)
|
76
76
|
return true
|
77
77
|
end
|
78
|
-
false
|
79
78
|
end
|
79
|
+
false
|
80
80
|
end
|
81
81
|
|
82
82
|
private
|
@@ -122,17 +122,24 @@ module Gru
|
|
122
122
|
send_message(:hset,global_max_worker_key,worker,count)
|
123
123
|
end
|
124
124
|
|
125
|
-
def
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
125
|
+
def manage_heartbeat?
|
126
|
+
@settings.manage_worker_heartbeats
|
127
|
+
end
|
128
|
+
|
129
|
+
def update_heartbeat
|
130
|
+
send_message(:hset,heartbeat_key,hostname,Time.now.to_i)
|
131
|
+
end
|
132
|
+
|
133
|
+
def remove_worker_host(hostname)
|
134
|
+
workers = send_message(:hgetall, "#{gru_key}:#{hostname}:workers_running")
|
135
|
+
workers.each_pair do |worker_name, count|
|
136
|
+
local_count, global_count = Integer(count), Integer(count)
|
137
|
+
Integer(count).times do
|
138
|
+
local_count = send_message(:hincrby,"#{gru_key}:#{hostname}:workers_running",worker_name,-1) if local_count > 0
|
139
|
+
global_count = send_message(:hincrby,global_workers_running_key,worker_name,-1) if global_count > 0
|
132
140
|
end
|
133
|
-
send_message(:del,lock_key)
|
134
|
-
send_message(:hdel,resque_cluster_pings_key,hostname)
|
135
141
|
end
|
142
|
+
send_message(:del,"#{gru_key}:#{hostname}:workers_running")
|
136
143
|
end
|
137
144
|
|
138
145
|
def reset_removed_global_worker_counts(workers)
|
@@ -175,8 +182,8 @@ module Gru
|
|
175
182
|
send_message(:hgetall,global_max_worker_key)
|
176
183
|
end
|
177
184
|
|
178
|
-
def
|
179
|
-
send_message(:hgetall,
|
185
|
+
def workers_with_heartbeats
|
186
|
+
send_message(:hgetall, heartbeat_key)
|
180
187
|
end
|
181
188
|
|
182
189
|
def reserve_worker?(worker)
|
@@ -215,8 +222,8 @@ module Gru
|
|
215
222
|
counts
|
216
223
|
end
|
217
224
|
|
218
|
-
def
|
219
|
-
|
225
|
+
def presumed_dead_worker_hosts
|
226
|
+
workers_with_heartbeats.select{ |hostname, timestamp| timestamp.to_i + presume_host_dead_after < Time.now.to_i}
|
220
227
|
end
|
221
228
|
|
222
229
|
def local_running_count(worker)
|
@@ -270,12 +277,12 @@ module Gru
|
|
270
277
|
"#{gru_key}:#{hostname}"
|
271
278
|
end
|
272
279
|
|
273
|
-
def
|
274
|
-
"
|
280
|
+
def heartbeat_key
|
281
|
+
"#{gru_key}:heartbeats"
|
275
282
|
end
|
276
283
|
|
277
|
-
def
|
278
|
-
"
|
284
|
+
def gru_key
|
285
|
+
"GRU:#{@settings.environment_name}:#{@settings.cluster_name}"
|
279
286
|
end
|
280
287
|
|
281
288
|
def hostname
|
@@ -286,6 +293,9 @@ module Gru
|
|
286
293
|
@client.send(action,*args)
|
287
294
|
end
|
288
295
|
|
296
|
+
def initialize_client(config=nil)
|
297
|
+
Redis.new(config || {})
|
298
|
+
end
|
289
299
|
end
|
290
300
|
end
|
291
301
|
end
|
data/lib/gru/configuration.rb
CHANGED
@@ -3,25 +3,20 @@ require 'gru/adapters/redis_adapter'
|
|
3
3
|
|
4
4
|
module Gru
|
5
5
|
class Configuration
|
6
|
-
attr_reader :cluster_maximums, :host_maximums, :rebalance_flag, :adapter, :cluster_name, :environment_name, :presume_host_dead_after
|
6
|
+
attr_reader :cluster_maximums, :host_maximums, :rebalance_flag, :adapter, :cluster_name, :environment_name, :presume_host_dead_after, :client_settings, :manage_worker_heartbeats
|
7
7
|
def initialize(settings)
|
8
8
|
@host_maximums = settings.delete(:host_maximums) || settings.delete(:cluster_maximums)
|
9
9
|
@cluster_maximums = settings.delete(:cluster_maximums) || @host_maximums
|
10
10
|
@rebalance_flag = settings.delete(:rebalance_flag) || false
|
11
11
|
@cluster_name = settings.delete(:cluster_name) || 'default'
|
12
12
|
@environment_name = settings.delete(:environment_name) || 'default'
|
13
|
-
client = initialize_client(settings.delete(:client_settings))
|
14
13
|
@presume_host_dead_after = settings.delete(:presume_host_dead_after)
|
15
|
-
@
|
14
|
+
@client_settings = settings.delete(:client_settings)
|
15
|
+
@manage_worker_heartbeats = settings.delete(:manage_worker_heartbeats) || false
|
16
|
+
@adapter = Gru::Adapters::RedisAdapter.new(self)
|
16
17
|
if @cluster_maximums.nil?
|
17
|
-
raise ArgumentError "Need at least a cluster configuration"
|
18
|
+
raise ArgumentError, "Need at least a cluster configuration"
|
18
19
|
end
|
19
20
|
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def initialize_client(config=nil)
|
24
|
-
Redis.new(config || {})
|
25
|
-
end
|
26
21
|
end
|
27
22
|
end
|
data/lib/gru/version.rb
CHANGED
data/lib/gru/worker_manager.rb
CHANGED
@@ -5,7 +5,7 @@ require 'pry'
|
|
5
5
|
describe Gru::Adapters::RedisAdapter do
|
6
6
|
before(:each) do
|
7
7
|
allow(Socket).to receive(:gethostname).and_return(hostname)
|
8
|
-
allow_any_instance_of(Gru::
|
8
|
+
allow_any_instance_of(Gru::Adapters::RedisAdapter).to receive(:initialize_client).and_return(client)
|
9
9
|
end
|
10
10
|
|
11
11
|
let(:hostname) { 'foo' }
|
@@ -19,14 +19,13 @@ describe Gru::Adapters::RedisAdapter do
|
|
19
19
|
}
|
20
20
|
|
21
21
|
let(:adapter) {
|
22
|
-
Gru::Adapters::RedisAdapter.new(
|
22
|
+
Gru::Adapters::RedisAdapter.new(config)
|
23
23
|
}
|
24
24
|
|
25
25
|
let(:gru_key) {
|
26
26
|
"GRU:#{config.environment_name}:#{config.cluster_name}"
|
27
27
|
}
|
28
28
|
|
29
|
-
|
30
29
|
context "initialization" do
|
31
30
|
it "has a client" do
|
32
31
|
expect(adapter.client).to eq(client)
|
@@ -227,7 +226,6 @@ describe Gru::Adapters::RedisAdapter do
|
|
227
226
|
expect(client).to receive(:del).with("#{gru_key}:test_worker").exactly(3).times
|
228
227
|
expect(client).to receive(:get).with("#{gru_key}:rebalance").and_return("true").exactly(3).times
|
229
228
|
adapter.provision_workers
|
230
|
-
|
231
229
|
end
|
232
230
|
|
233
231
|
it "increases load when workers are removed" do
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require './lib/gru.rb'
|
2
|
+
require 'socket'
|
3
|
+
require 'digest'
|
4
|
+
require 'redis'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
|
8
|
+
# This requires a redis server instance running on localhost
|
9
|
+
# un-pend to run with actual redis-server
|
10
|
+
xdescribe Gru do
|
11
|
+
|
12
|
+
after {
|
13
|
+
client.flushdb
|
14
|
+
}
|
15
|
+
|
16
|
+
let(:settings) {
|
17
|
+
{
|
18
|
+
cluster_maximums: {
|
19
|
+
'test_worker' => '3'
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
let(:client) {
|
25
|
+
Redis.new
|
26
|
+
}
|
27
|
+
|
28
|
+
let(:hostname) {
|
29
|
+
Socket.gethostname
|
30
|
+
}
|
31
|
+
|
32
|
+
let(:host_key) {
|
33
|
+
"GRU:default:default:#{hostname}"
|
34
|
+
}
|
35
|
+
|
36
|
+
let(:global_key) {
|
37
|
+
"GRU:default:default:global"
|
38
|
+
}
|
39
|
+
|
40
|
+
context "configuration" do
|
41
|
+
it "sets configuration in redis" do
|
42
|
+
manager = Gru.create(settings.clone)
|
43
|
+
expect(client.hgetall("#{host_key}:max_workers")).to eq(settings[:cluster_maximums])
|
44
|
+
expect(manager.provision_workers).to eq({ 'test_worker' => 3 })
|
45
|
+
expect(client.hgetall("#{host_key}:workers_running")).to eq(settings[:cluster_maximums])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "adjusting worker counts" do
|
50
|
+
it "adjusts worker counts based on changing global counts" do
|
51
|
+
manager = Gru.create(settings.clone)
|
52
|
+
expect(manager.provision_workers).to eq({ 'test_worker' => 3 })
|
53
|
+
client.hset("#{global_key}:max_workers", 'test_worker', 1)
|
54
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => -2})
|
55
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 0})
|
56
|
+
expect(client.hget("#{global_key}:workers_running", 'test_worker')).to eq("1")
|
57
|
+
client.hset("#{global_key}:max_workers", 'test_worker', 3)
|
58
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 2})
|
59
|
+
expect(client.hget("#{global_key}:workers_running", 'test_worker')).to eq("3")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "adjusts worker counts based on changing local counts" do
|
63
|
+
manager = Gru.create(settings.clone)
|
64
|
+
expect(manager.provision_workers).to eq({ 'test_worker' => 3 })
|
65
|
+
client.hset("#{host_key}:max_workers", 'test_worker', 1)
|
66
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => -1})
|
67
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => -1})
|
68
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 0})
|
69
|
+
expect(client.hget("#{global_key}:workers_running", 'test_worker')).to eq("1")
|
70
|
+
expect(client.hget("#{host_key}:workers_running", 'test_worker')).to eq("1")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "does not exceed local maximum counts" do
|
74
|
+
manager = Gru.create(settings.clone)
|
75
|
+
expect(manager.provision_workers).to eq({ 'test_worker' => 3 })
|
76
|
+
client.hset("#{host_key}:max_workers", 'test_worker', 2)
|
77
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => -1})
|
78
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 0})
|
79
|
+
client.hset("#{global_key}:max_workers", 'test_worker', 4)
|
80
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 0})
|
81
|
+
expect(client.hget("#{global_key}:workers_running", 'test_worker')).to eq("2")
|
82
|
+
expect(client.hget("#{host_key}:workers_running", 'test_worker')).to eq("2")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not exceed global maximum counts" do
|
86
|
+
manager = Gru.create(settings.clone)
|
87
|
+
expect(manager.provision_workers).to eq({ 'test_worker' => 3 })
|
88
|
+
client.hset("#{host_key}:max_workers", 'test_worker', 0)
|
89
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 0})
|
90
|
+
client.hset("#{global_key}:max_workers", 'test_worker', 3)
|
91
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 0})
|
92
|
+
client.hset("#{host_key}:max_workers", 'test_worker', 4)
|
93
|
+
expect(manager.adjust_workers).to eq({ 'test_worker' => 0})
|
94
|
+
expect(client.hget("#{global_key}:workers_running", 'test_worker')).to eq("3")
|
95
|
+
expect(client.hget("#{host_key}:workers_running", 'test_worker')).to eq("3")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "multiple workers" do
|
100
|
+
let(:settings) {
|
101
|
+
{
|
102
|
+
rebalance_flag: true,
|
103
|
+
manage_worker_heartbeats: true,
|
104
|
+
cluster_maximums: {
|
105
|
+
'test_worker' => '6'
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
let(:test_client) {
|
111
|
+
Redis.new
|
112
|
+
}
|
113
|
+
|
114
|
+
let(:adapter1) {
|
115
|
+
adapter1 = Gru::Adapters::RedisAdapter.new(Gru::Configuration.new(settings.clone))
|
116
|
+
allow(adapter1).to receive(:hostname).and_return('test1')
|
117
|
+
adapter1
|
118
|
+
}
|
119
|
+
|
120
|
+
let(:adapter2) {
|
121
|
+
adapter2 = Gru::Adapters::RedisAdapter.new(Gru::Configuration.new(settings.clone))
|
122
|
+
allow(adapter2).to receive(:hostname).and_return('test2')
|
123
|
+
adapter2
|
124
|
+
}
|
125
|
+
|
126
|
+
let(:adapter3) {
|
127
|
+
adapter3 = Gru::Adapters::RedisAdapter.new(Gru::Configuration.new(settings.clone))
|
128
|
+
allow(adapter3).to receive(:hostname).and_return('test3')
|
129
|
+
adapter3
|
130
|
+
}
|
131
|
+
|
132
|
+
it "adjusts workers when new hosts are added" do
|
133
|
+
test1 = Gru::WorkerManager.new(adapter1)
|
134
|
+
test2 = Gru::WorkerManager.new(adapter2)
|
135
|
+
test3 = Gru::WorkerManager.new(adapter3)
|
136
|
+
test1.register_workers
|
137
|
+
test2.register_workers
|
138
|
+
expect(test_client.hget('GRU:default:default:test1:workers_running', 'test_worker')).to eq('0')
|
139
|
+
expect(test1.adjust_workers).to eq( { 'test_worker' => 3 })
|
140
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => 3 })
|
141
|
+
test3.register_workers
|
142
|
+
expect(test1.adjust_workers).to eq( { 'test_worker' => -1 })
|
143
|
+
expect(test1.adjust_workers).to eq( { 'test_worker' => 0 })
|
144
|
+
expect(test3.adjust_workers).to eq( { 'test_worker' => 1 })
|
145
|
+
expect(test3.adjust_workers).to eq( { 'test_worker' => 0 })
|
146
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => -1 })
|
147
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => 0 })
|
148
|
+
expect(test3.adjust_workers).to eq( { 'test_worker' => 1 })
|
149
|
+
expect(test3.adjust_workers).to eq( { 'test_worker' => 0 })
|
150
|
+
expect(test1.adjust_workers).to eq( { 'test_worker' => 0 })
|
151
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => 0 })
|
152
|
+
end
|
153
|
+
|
154
|
+
it "recovers lost worker counts" do
|
155
|
+
test1 = Gru::WorkerManager.new(adapter1)
|
156
|
+
test2 = Gru::WorkerManager.new(adapter2)
|
157
|
+
test3 = Gru::WorkerManager.new(adapter3)
|
158
|
+
test1.register_workers
|
159
|
+
test2.register_workers
|
160
|
+
expect(test1.adjust_workers).to eq( { 'test_worker' => 3 })
|
161
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => 3 })
|
162
|
+
test_client.hset("GRU:default:default:heartbeats", 'test1', Time.now - 300)
|
163
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => 3 })
|
164
|
+
test3.register_workers
|
165
|
+
expect(test3.adjust_workers).to eq( { 'test_worker' => 0 })
|
166
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => -3 })
|
167
|
+
test_client.hset("GRU:default:default:heartbeats", 'test3', Time.now - 300)
|
168
|
+
expect(test2.adjust_workers).to eq( { 'test_worker' => 3 })
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -31,7 +31,7 @@ describe Gru::WorkerManager do
|
|
31
31
|
it "determines new workers to create" do
|
32
32
|
expect(adapter).to receive(:provision_workers).and_return({})
|
33
33
|
expect(adapter).to receive(:expire_workers).and_return({})
|
34
|
-
expect(adapter).to receive(:
|
34
|
+
expect(adapter).to receive(:release_presumed_dead_worker_hosts)
|
35
35
|
manager.adjust_workers
|
36
36
|
end
|
37
37
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gru
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeffrey Gillis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- lib/gru/version.rb
|
115
115
|
- lib/gru/worker_manager.rb
|
116
116
|
- spec/gru/adapters/redis_adapter_spec.rb
|
117
|
+
- spec/gru/gru_integration_spec.rb
|
117
118
|
- spec/gru/worker_manager_spec.rb
|
118
119
|
homepage: ''
|
119
120
|
licenses:
|
@@ -141,4 +142,5 @@ specification_version: 4
|
|
141
142
|
summary: An atomic worker/minion manager.
|
142
143
|
test_files:
|
143
144
|
- spec/gru/adapters/redis_adapter_spec.rb
|
145
|
+
- spec/gru/gru_integration_spec.rb
|
144
146
|
- spec/gru/worker_manager_spec.rb
|