corosync-commander 0.0.1 → 0.0.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +73 -0
- data/lib/corosync_commander/execution.rb +3 -0
- data/lib/corosync_commander.rb +75 -18
- data/lib/version.rb +1 -1
- data/spec/corosync_commander.rb +20 -19
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f9f90af1b3fac9c2164e54e664cae15be76ef65
|
4
|
+
data.tar.gz: ccddc4d241a87b80def6ce1ad6dae3073a520e5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47de186e7ba2e4bfc304761b6e704f776ded20ab55d3428fdb5dbd5d5e886511fc80ee438003d357a269c5f66703f5c68142502507c38a6b0d55004ccccdf3c1
|
7
|
+
data.tar.gz: 47136a7dbb32771007e34f76a4ae86341b14d360b6cbfa7988e34c05533451961991c2564cce2bcf6da891190d3cbba17129c9f1017649066c472902223764ad
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -2,6 +2,79 @@
|
|
2
2
|
desc 'Run tests'
|
3
3
|
require 'rspec/core/rake_task'
|
4
4
|
RSpec::Core::RakeTask.new(:test) do |t|
|
5
|
+
ENV['RUBY_THREAD_MACHINE_STACK_SIZE'] = '1572864'
|
5
6
|
t.pattern = 'spec/**/*.rb'
|
6
7
|
t.rspec_opts = '-c -f d --fail-fast'
|
7
8
|
end
|
9
|
+
|
10
|
+
|
11
|
+
########################################
|
12
|
+
@gemspec_file = Dir.glob('*.gemspec').first
|
13
|
+
def spec
|
14
|
+
require 'rubygems' unless defined? Gem::Specification
|
15
|
+
@spec ||= eval(File.read(@gemspec_file))
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Bump version'
|
19
|
+
task 'version' do
|
20
|
+
file = 'lib/version.rb'
|
21
|
+
const = 'CorosyncCommander::GEM_VERSION'
|
22
|
+
|
23
|
+
current_version = %x{ruby -e "require './#{file}'; puts #{const}"}.chomp
|
24
|
+
current_version_commit = %x{git rev-parse --verify #{current_version} 2>/dev/null}.chomp
|
25
|
+
current_head_commit = %x{git rev-parse HEAD}.chomp
|
26
|
+
if current_version_commit != '' and current_version_commit != current_head_commit then
|
27
|
+
# there have been commits since the current version
|
28
|
+
|
29
|
+
next_version = current_version.split('.')
|
30
|
+
next_version[-1] = next_version.last.to_i + 1
|
31
|
+
next_version = next_version.join('.')
|
32
|
+
print "Next version? (#{next_version}): "
|
33
|
+
response = STDIN.gets.chomp
|
34
|
+
if response != '' then
|
35
|
+
raise StandardError, "Not a valid version" unless response.match(/^[0-9\.]$/)
|
36
|
+
next_version = response
|
37
|
+
end
|
38
|
+
|
39
|
+
const_name = const.sub(/^.+:/, '')
|
40
|
+
new_file_content = ''
|
41
|
+
File.open(file, 'r') do |file|
|
42
|
+
file.each_line do |line|
|
43
|
+
new_file_content += line.sub(/(#{const_name}\s*=\s*['"])#{current_version}(['"])/, "\\1#{next_version}\\2")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
File.open(file, 'w') do |file|
|
47
|
+
file.write new_file_content
|
48
|
+
end
|
49
|
+
message = %x{git log #{current_version_commit}..HEAD --pretty=format:'* %s%n %an (%ai) - @%h%n'}.gsub(/'/, "'\\\\''")
|
50
|
+
|
51
|
+
sh "git commit -m 'Version: #{next_version}\n\n#{message}' #{file}"
|
52
|
+
|
53
|
+
@spec = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Build gem file'
|
58
|
+
task 'build' do
|
59
|
+
sh "gem build #{@gemspec_file}"
|
60
|
+
end
|
61
|
+
|
62
|
+
desc 'Publish gem file'
|
63
|
+
task 'publish' do
|
64
|
+
gem_file = "#{spec.name}-#{spec.version}.gem"
|
65
|
+
sh "git tag #{spec.version}"
|
66
|
+
sh "git push"
|
67
|
+
sh "gem push #{gem_file}"
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'Release a new version'
|
71
|
+
task 'release' do
|
72
|
+
raise StandardError, "Not on master branch" if %x{git rev-parse --abbrev-ref HEAD}.chomp != "master"
|
73
|
+
raise StandardError, "Uncommitted files" if %x{git status --porcelain}.chomp.size != 0
|
74
|
+
|
75
|
+
[:test, :version, :build, :publish].each do |task|
|
76
|
+
puts "# #{task}\n"
|
77
|
+
Rake::Task[task].execute
|
78
|
+
puts "\n"
|
79
|
+
end
|
80
|
+
end
|
@@ -95,6 +95,9 @@ class CorosyncCommander::Execution
|
|
95
95
|
return if !@queue.is_a?(Queue) # we've called `clear`
|
96
96
|
|
97
97
|
while @pending_members.nil?
|
98
|
+
if Thread.current.object_id == @cc.dispatch_thread.object_id then
|
99
|
+
raise StandardError, "Deadlock! Can not call wait for responses on callback thread."
|
100
|
+
end
|
98
101
|
message = @queue.shift
|
99
102
|
|
100
103
|
next if message.type == 'leave' # we havent received the echo, so we dont care yet
|
data/lib/corosync_commander.rb
CHANGED
@@ -27,7 +27,7 @@ require File.expand_path('../corosync_commander/callback_list', __FILE__)
|
|
27
27
|
# hostnames = []
|
28
28
|
# begin
|
29
29
|
# enum.each do |response, node|
|
30
|
-
#
|
30
|
+
# hostnames << response
|
31
31
|
# end
|
32
32
|
# rescue CorosyncCommander::RemoteException => e
|
33
33
|
# puts "Caught remote exception: #{e}"
|
@@ -51,6 +51,9 @@ class CorosyncCommander
|
|
51
51
|
|
52
52
|
attr_reader :execution_queues
|
53
53
|
|
54
|
+
# @!visibility private
|
55
|
+
attr_reader :dispatch_thread
|
56
|
+
|
54
57
|
# Creates a new instance and connects to CPG.
|
55
58
|
# If a group name is provided, it will join that group. Otherwise it will only connect. This is so that you can establish the command callbacks and avoid NotImplementedError exceptions
|
56
59
|
# @param group_name [String] Name of the group to join
|
@@ -63,6 +66,9 @@ class CorosyncCommander
|
|
63
66
|
|
64
67
|
@cpg_members = nil
|
65
68
|
|
69
|
+
@leader_pool = []
|
70
|
+
@leader_pool.extend(Sync_m)
|
71
|
+
|
66
72
|
# we can either share the msgid counter across all threads, or have a msgid counter on each thread and send the thread ID with each message. I prefer the former
|
67
73
|
@next_execution_id = 0
|
68
74
|
@next_execution_id_mutex = Mutex.new
|
@@ -127,31 +133,41 @@ class CorosyncCommander
|
|
127
133
|
def cpg_message(message, sender)
|
128
134
|
message = CorosyncCommander::Execution::Message.from_cpg_message(message, sender)
|
129
135
|
|
130
|
-
|
136
|
+
# This is the possible message classifications
|
137
|
+
# Command echo (potentially also a command to us)
|
138
|
+
# Response echo
|
139
|
+
# Command to us
|
140
|
+
# Response to us
|
141
|
+
# Command to someone else
|
142
|
+
# Response to someone else
|
143
|
+
|
144
|
+
if message.type == 'command' and sender == @cpg.member then
|
145
|
+
# It's a command echo
|
131
146
|
execution_queue = nil
|
132
147
|
@execution_queues.sync_synchronize(:SH) do
|
133
148
|
execution_queue = @execution_queues[message.execution_id]
|
134
149
|
end
|
135
150
|
if !execution_queue.nil? then
|
136
151
|
# someone is listening
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
152
|
+
message_echo = message.dup
|
153
|
+
message_echo.type = 'echo'
|
154
|
+
message_echo.content = @cpg_members
|
155
|
+
execution_queue << message_echo
|
156
|
+
end
|
157
|
+
elsif message.type != 'command' and message.recipients.include?(@cpg.member) then
|
158
|
+
# It's a response to us
|
159
|
+
execution_queue = nil
|
160
|
+
@execution_queues.sync_synchronize(:SH) do
|
161
|
+
execution_queue = @execution_queues[message.execution_id]
|
162
|
+
end
|
163
|
+
if !execution_queue.nil? then
|
164
|
+
# someone is listening
|
165
|
+
execution_queue << message
|
146
166
|
end
|
147
167
|
end
|
148
168
|
|
149
|
-
if message.recipients.size
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
if message.type == 'command' then
|
154
|
-
# we received a command from another node
|
169
|
+
if message.type == 'command' and (message.recipients.size == 0 or message.recipients.include?(@cpg.member)) then
|
170
|
+
# It's a command to us
|
155
171
|
begin
|
156
172
|
# see if we've got a registered callback
|
157
173
|
command_callback = nil
|
@@ -163,7 +179,7 @@ class CorosyncCommander
|
|
163
179
|
end
|
164
180
|
|
165
181
|
command_args = message.content[1]
|
166
|
-
reply_value = command_callback.call(*command_args)
|
182
|
+
reply_value = command_callback.call(message.sender, *command_args)
|
167
183
|
message_reply = message.reply(reply_value)
|
168
184
|
@cpg.send(message_reply)
|
169
185
|
rescue => e
|
@@ -178,6 +194,18 @@ class CorosyncCommander
|
|
178
194
|
def cpg_confchg(member_list, left_list, join_list)
|
179
195
|
@cpg_members = member_list
|
180
196
|
|
197
|
+
if leader_position == -1 then # this will only happen on join
|
198
|
+
@leader_pool.sync_synchronize(:EX) do
|
199
|
+
@leader_pool.replace(member_list.to_a)
|
200
|
+
end
|
201
|
+
elsif left_list.size > 0 then
|
202
|
+
@leader_pool.sync_synchronize(:EX) do
|
203
|
+
@leader_pool.delete_if {|m| left_list.include?(m)}
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
@confchg_callback.call(member_list, left_list, join_list) if @confchg_callback
|
208
|
+
|
181
209
|
# we look for any members leaving the cluster, and if so we notify all threads that are waiting for a response that they may have just lost a node
|
182
210
|
return if left_list.size == 0
|
183
211
|
|
@@ -194,6 +222,10 @@ class CorosyncCommander
|
|
194
222
|
end
|
195
223
|
end
|
196
224
|
|
225
|
+
def on_confchg(&block)
|
226
|
+
@confchg_callback = block
|
227
|
+
end
|
228
|
+
|
197
229
|
# @!attribute [r] commands
|
198
230
|
# @return [CorosyncCommander::CallbackList] List of command callbacks
|
199
231
|
def commands
|
@@ -230,4 +262,29 @@ class CorosyncCommander
|
|
230
262
|
end
|
231
263
|
end
|
232
264
|
private :execution_queue_finalizer
|
265
|
+
|
266
|
+
# Gets the member's position in the leadership queue.
|
267
|
+
# The leadership position is simply how many nodes currently in the group were in the group before we joined.
|
268
|
+
# @return [Integer]
|
269
|
+
def leader_position
|
270
|
+
@leader_pool.synchronize(:SH) do
|
271
|
+
@leader_pool.size - 1
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Indicates whether we are the group leader.
|
276
|
+
# If we are the leader, it means that we are the oldest member of the group.
|
277
|
+
# This is slightly different than just calling `leader_position == 0` in that if it is -1 (meaning we havent received the CPG confchg callback yet), we wait for the CPG join to complete.
|
278
|
+
# @return [Boolean]
|
279
|
+
def leader?
|
280
|
+
position = nil
|
281
|
+
loop do
|
282
|
+
position = leader_position
|
283
|
+
break if position != -1
|
284
|
+
Thread.pass # let the dispatch thread run so we can get our join message
|
285
|
+
# This isn't ideal as if the dispatch thread doesn't immediatly complete the join, we start spinning.
|
286
|
+
# But the only other way is to use condition variables, which combined with Sync_m, would just be really messy and stupidly complex (and I don't want to go to a plain mutex and lose the ability to use shared locks).
|
287
|
+
end
|
288
|
+
position == 0
|
289
|
+
end
|
233
290
|
end
|
data/lib/version.rb
CHANGED
data/spec/corosync_commander.rb
CHANGED
@@ -2,22 +2,6 @@ require 'spec_helper'
|
|
2
2
|
require 'timeout'
|
3
3
|
|
4
4
|
describe CorosyncCommander do
|
5
|
-
def fork_execute(wait, cc_recip, command, *args)
|
6
|
-
recip = cc_recip.cpg.member
|
7
|
-
pid = fork do
|
8
|
-
cc = CorosyncCommander.new(cc_recip.cpg.group)
|
9
|
-
exe = cc.execute([recip], command, *args)
|
10
|
-
exe.wait
|
11
|
-
exit!(0)
|
12
|
-
end
|
13
|
-
if wait then
|
14
|
-
status = Process.wait2(pid)
|
15
|
-
status.exitstatus
|
16
|
-
else
|
17
|
-
pid
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
5
|
before(:all) do
|
22
6
|
Timeout.timeout(1) do
|
23
7
|
@cc = CorosyncCommander.new("CorosyncCommander RSPEC #{Random.rand(2 ** 32)}")
|
@@ -44,7 +28,7 @@ describe CorosyncCommander do
|
|
44
28
|
end
|
45
29
|
|
46
30
|
it 'registers a callback (block style)' do
|
47
|
-
@cc.commands.register 'summation' do |arg1,arg2|
|
31
|
+
@cc.commands.register 'summation' do |sender,arg1,arg2|
|
48
32
|
arg1 + arg2
|
49
33
|
end
|
50
34
|
|
@@ -52,7 +36,7 @@ describe CorosyncCommander do
|
|
52
36
|
end
|
53
37
|
|
54
38
|
it 'registers a callback (assignment style)' do
|
55
|
-
@cc.commands['summation'] = Proc.new do |arg1,arg2|
|
39
|
+
@cc.commands['summation'] = Proc.new do |sender,arg1,arg2|
|
56
40
|
arg1 + arg2
|
57
41
|
end
|
58
42
|
expect(@cc.commands['summation']).to be_a(Proc)
|
@@ -95,7 +79,7 @@ describe CorosyncCommander do
|
|
95
79
|
num1 = Random.rand(2 ** 32)
|
96
80
|
num2 = Random.rand(2 ** 32)
|
97
81
|
|
98
|
-
@cc.commands.register('summation2') do |number|
|
82
|
+
@cc.commands.register('summation2') do |sender,number|
|
99
83
|
sum += number
|
100
84
|
end
|
101
85
|
|
@@ -178,4 +162,21 @@ describe CorosyncCommander do
|
|
178
162
|
expect(responses.find_all{|r| r == 'OK'}.size).to eq(2)
|
179
163
|
end
|
180
164
|
end
|
165
|
+
|
166
|
+
it 'checks for leadership (true)' do
|
167
|
+
expect(@cc.leader?).to eq(true)
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'checks for leadership (false)' do
|
171
|
+
forkpid = fork do
|
172
|
+
cc = CorosyncCommander.new(@cc.cpg.group)
|
173
|
+
if !cc.leader? then
|
174
|
+
exit 0 # we aren't a leader, as expected
|
175
|
+
end
|
176
|
+
exit 1 # something went wrong
|
177
|
+
end
|
178
|
+
|
179
|
+
status = Process.wait2(forkpid)
|
180
|
+
expect(status[1]).to eq(0)
|
181
|
+
end
|
181
182
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: corosync-commander
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Hemmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-11-
|
11
|
+
date: 2013-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: corosync
|