corosync 0.0.3 → 0.1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/README.md +24 -5
- data/Rakefile +62 -1
- data/VERSION +1 -0
- data/corosync.gemspec +1 -3
- data/examples/cmap-cli.rb +72 -0
- data/examples/cpg-chat.rb +75 -0
- data/examples/{example.rb → cpg-monitor.rb} +1 -1
- data/ffi/cmap.i +14 -0
- data/ffi/cmap.rb +91 -0
- data/ffi/common.rb +1 -1
- data/ffi/cpg.rb +57 -15
- data/ffi/quorum.i +14 -0
- data/ffi/quorum.rb +36 -0
- data/ffi/votequorum.i +14 -0
- data/ffi/votequorum.rb +87 -0
- data/lib/corosync.rb +69 -1
- data/lib/corosync/cmap.rb +381 -0
- data/lib/corosync/cpg.rb +27 -46
- data/lib/corosync/exceptions.rb +34 -0
- data/lib/corosync/quorum.rb +143 -0
- data/lib/corosync/votequorum.rb +218 -0
- data/lib/ffi_pointer.rb +28 -0
- data/spec/corosync/cmap.rb +99 -0
- data/spec/corosync/cpg.rb +1 -0
- data/spec/corosync/quorum.rb +66 -0
- data/spec/corosync/votequorum.rb +81 -0
- data/spec/spec_helper.rb +1 -1
- metadata +20 -4
- data/lib/version.rb +0 -3
data/lib/corosync/cpg.rb
CHANGED
@@ -24,7 +24,7 @@ require 'corosync/cpg/member'
|
|
24
24
|
# @example
|
25
25
|
# require 'corosync/cpg'
|
26
26
|
# cpg = Corosync::CPG.new('mygroup')
|
27
|
-
# cpg.on_message do |message,
|
27
|
+
# cpg.on_message do |message, sender|
|
28
28
|
# puts "Received #{message}"
|
29
29
|
# end
|
30
30
|
# puts "Member node IDs: #{cpg.members.map {|m| m.nodeid}.join(" ")}"
|
@@ -71,17 +71,12 @@ class Corosync::CPG
|
|
71
71
|
# @return [void]
|
72
72
|
def connect
|
73
73
|
handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cpg_handle_t))
|
74
|
-
|
75
|
-
|
76
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to connect to corosync"
|
77
|
-
end
|
74
|
+
model_cast = Corosync::CpgModelDataT.new(@model.to_ptr)
|
75
|
+
Corosync.cs_send(:cpg_model_initialize, handle_ptr, Corosync::CPG_MODEL_V1, model_cast, nil)
|
78
76
|
@handle = handle_ptr.read_uint64
|
79
77
|
|
80
78
|
fd_ptr = FFI::MemoryPointer.new(:int)
|
81
|
-
|
82
|
-
if cs_error != :ok then
|
83
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to get handle descriptor"
|
84
|
-
end
|
79
|
+
Corosync.cs_send(:cpg_fd_get, @handle, fd_ptr)
|
85
80
|
@fd = IO.new(fd_ptr.read_int)
|
86
81
|
end
|
87
82
|
|
@@ -90,10 +85,7 @@ class Corosync::CPG
|
|
90
85
|
def finalize
|
91
86
|
return if @handle.nil?
|
92
87
|
|
93
|
-
|
94
|
-
if cs_error != :ok then
|
95
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to perform finalize"
|
96
|
-
end
|
88
|
+
Corosync.cs_send(:cpg_finalize, @handle)
|
97
89
|
|
98
90
|
@group = nil
|
99
91
|
@fd = nil
|
@@ -113,10 +105,7 @@ class Corosync::CPG
|
|
113
105
|
cpg_name = Corosync::CpgName.new
|
114
106
|
cpg_name[:value] = name
|
115
107
|
cpg_name[:length] = name.size
|
116
|
-
|
117
|
-
if cs_error != :ok then
|
118
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to join group"
|
119
|
-
end
|
108
|
+
Corosync.cs_send(:cpg_join, @handle, cpg_name)
|
120
109
|
|
121
110
|
@group = name
|
122
111
|
|
@@ -132,10 +121,7 @@ class Corosync::CPG
|
|
132
121
|
cpg_name[:length] = @group.size
|
133
122
|
|
134
123
|
# we can't join multiple groups, so I dont know why corosync requires you to specify the group name
|
135
|
-
|
136
|
-
if cs_error != :ok then
|
137
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to leave group"
|
138
|
-
end
|
124
|
+
Corosync.cs_send(:cpg_leave, @handle, cpg_name)
|
139
125
|
|
140
126
|
@group = nil
|
141
127
|
end
|
@@ -151,11 +137,14 @@ class Corosync::CPG
|
|
151
137
|
timeout = nil if timeout == -1
|
152
138
|
select([@fd], [], [], timeout)
|
153
139
|
end
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
140
|
+
|
141
|
+
begin
|
142
|
+
Corosync.cs_send!(:cpg_dispatch, @handle, Corosync::CS_DISPATCH_ONE_NONBLOCKING)
|
143
|
+
rescue Corosync::TryAgainError => e
|
144
|
+
raise e if e.depth > 1 # this exception is from a nested corosync function, not our cpg_dispatch we just called
|
145
|
+
return false
|
158
146
|
end
|
147
|
+
|
159
148
|
return true
|
160
149
|
end
|
161
150
|
|
@@ -229,10 +218,7 @@ class Corosync::CPG
|
|
229
218
|
# @return [Integer]
|
230
219
|
def nodeid
|
231
220
|
nodeid_p = FFI::MemoryPointer.new(:uint)
|
232
|
-
|
233
|
-
if cs_error != :ok then
|
234
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to get nodeid"
|
235
|
-
end
|
221
|
+
Corosync.cs_send(:cpg_local_get, @handle, nodeid_p)
|
236
222
|
nodeid_p.read_uint
|
237
223
|
end
|
238
224
|
|
@@ -246,25 +232,21 @@ class Corosync::CPG
|
|
246
232
|
cpg_name[:length] = @group.size
|
247
233
|
|
248
234
|
iteration_handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cpg_iteration_handle_t))
|
249
|
-
|
250
|
-
if cs_error != :ok then
|
251
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to initialize member iteration"
|
252
|
-
end
|
235
|
+
Corosync.cs_send(:cpg_iteration_initialize, @handle, Corosync::CPG_ITERATION_ONE_GROUP, cpg_name, iteration_handle_ptr)
|
253
236
|
iteration_handle = iteration_handle_ptr.read_uint64
|
254
237
|
|
255
238
|
begin
|
256
239
|
iteration_description = Corosync::CpgIterationDescriptionT.new
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
240
|
+
begin
|
241
|
+
loop do
|
242
|
+
Corosync.cs_send(:cpg_iteration_next, iteration_handle, iteration_description)
|
243
|
+
members << Corosync::CPG::Member.new(iteration_description)
|
244
|
+
end
|
245
|
+
rescue Corosync::NoSectionsError
|
246
|
+
# signals end of iteration
|
262
247
|
end
|
263
248
|
ensure
|
264
|
-
|
265
|
-
if cs_error != :ok then
|
266
|
-
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to finalize member iteration"
|
267
|
-
end
|
249
|
+
Corosync.cs_send(:cpg_iteration_finalize, iteration_handle)
|
268
250
|
end
|
269
251
|
|
270
252
|
members
|
@@ -292,9 +274,8 @@ class Corosync::CPG
|
|
292
274
|
end
|
293
275
|
iovec_len = messages.size
|
294
276
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
end
|
277
|
+
Corosync.cs_send(:cpg_mcast_joined, @handle, Corosync::CPG_TYPE_AGREED, iovec_list_p, iovec_len)
|
278
|
+
|
279
|
+
true
|
299
280
|
end
|
300
281
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Corosync
|
2
|
+
|
3
|
+
# Base class that all Corosync exceptions descend from.
|
4
|
+
# Corosync exception classes are programitcally generated from the `:cs_error_t` enum in `ffi/common.rb`. See that file for a comprehensive list of exception classes. The `:err_try_again` value will map to `Corosync::TryAgainError`. All exception classes follow the same pattern.
|
5
|
+
class Error < StandardError
|
6
|
+
Map = {}
|
7
|
+
|
8
|
+
# @!attribute [rw] depth
|
9
|
+
# The number of {Corosync.cs_send} methods the exception has passed through. This is important so that we don't rescue nested exceptions. For example, we call cs_send(:cpg_dispatch) which calls a callback which calls cs_send(:cpg_mcast_joined). If the cpg_mcast_joined were to raise an exception, and we had a rescue around the cpg_dispatch, we wouldn't know whether the exception came from cpg_mcast_joined or cpg_dispatch. However in this case the depth would be 2, and so we would know not to rescue it.
|
10
|
+
# @return [Fixnum] Number of cs_send methods the exception has passed through
|
11
|
+
def depth
|
12
|
+
@depth ||= 0
|
13
|
+
end
|
14
|
+
def depth=(value)
|
15
|
+
@depth = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Corosync.enum_type(:cs_error_t).to_h.each do |name, value|
|
20
|
+
next if name == :ok
|
21
|
+
|
22
|
+
name_s = name.to_s.sub(/^err_/, '').capitalize.gsub(/_(.)/){|m| m[1].upcase} + 'Error'
|
23
|
+
|
24
|
+
c = Class.new(Error) do
|
25
|
+
const_set :VALUE, value
|
26
|
+
def value
|
27
|
+
self.class.const_get :VALUE
|
28
|
+
end
|
29
|
+
end
|
30
|
+
const_set name_s, c
|
31
|
+
|
32
|
+
Error::Map[name] = c
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require File.expand_path('../../corosync.rb', __FILE__)
|
2
|
+
require File.expand_path('../../../ffi/quorum.rb', __FILE__)
|
3
|
+
|
4
|
+
# Quorum is used for tracking the health of the cluster.
|
5
|
+
# This simply reads the quorum state as defined by corosync. Whenever the node gains or loses quorum, a notification callback is called. You can also poll the quorum state instead of using a callback.
|
6
|
+
#
|
7
|
+
# ----
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require 'corosync/quorum'
|
11
|
+
# quorum = Corosync::Quorum.new
|
12
|
+
# quorum.on_notify do |quorate,member_list|
|
13
|
+
# puts "Cluster is#{quorate ? '' ' not'} quorate"
|
14
|
+
# puts " Members: #{member_list.join(' ')}"
|
15
|
+
# end
|
16
|
+
# quorum.connect
|
17
|
+
# loop do
|
18
|
+
# quorum.dispatch
|
19
|
+
# end
|
20
|
+
|
21
|
+
class Corosync::Quorum
|
22
|
+
# The IO object containing the file descriptor notifications come across.
|
23
|
+
# You can use this to check for activity, but do not read anything from it.
|
24
|
+
# @return [IO]
|
25
|
+
attr_reader :fd
|
26
|
+
|
27
|
+
# Creates a new Quorum instance
|
28
|
+
#
|
29
|
+
# @param connect [Boolean] Whether to join the cluster immediately. If not provided, you must call {#connect} and/or {#connect} later.
|
30
|
+
#
|
31
|
+
# @return [void]
|
32
|
+
def initialize(connect = false)
|
33
|
+
@handle = nil
|
34
|
+
@fd = nil
|
35
|
+
|
36
|
+
@callbacks = Corosync::QuorumCallbacksT.new
|
37
|
+
@callbacks[:quorum_notify_fn] = self.method(:callback_notify)
|
38
|
+
|
39
|
+
self.connect if connect
|
40
|
+
end
|
41
|
+
|
42
|
+
# Connect to the Quorum service
|
43
|
+
# @param start [Boolean] Whether to start listening for notifications (will not make initial call to callback).
|
44
|
+
# @return [void]
|
45
|
+
def connect(start = false)
|
46
|
+
handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:quorum_handle_t))
|
47
|
+
quorum_type_ptr = FFI::MemoryPointer.new(:uint32)
|
48
|
+
|
49
|
+
Corosync.cs_send(:quorum_initialize, handle_ptr, @callbacks, quorum_type_ptr)
|
50
|
+
|
51
|
+
@handle = handle_ptr.read_uint64
|
52
|
+
|
53
|
+
fd_ptr = FFI::MemoryPointer.new(:int)
|
54
|
+
Corosync.cs_send(:quorum_fd_get, @handle, fd_ptr)
|
55
|
+
@fd = IO.new(fd_ptr.read_int)
|
56
|
+
|
57
|
+
self.start if start
|
58
|
+
end
|
59
|
+
|
60
|
+
# Shuts down the connection to the Quorum service
|
61
|
+
# @return [void]
|
62
|
+
def finalize
|
63
|
+
return if @handle.nil?
|
64
|
+
|
65
|
+
Corosync.cs_send(:quorum_finalize, @handle)
|
66
|
+
|
67
|
+
@handle = nil
|
68
|
+
@fd = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Start monitoring for changes to quorum status.
|
72
|
+
# This basically just enables triggering the callback. If not called you can still call {#quorate?} to get quorum state.
|
73
|
+
# @param initial_callback [Boolean] Whether to call the callback after start.
|
74
|
+
# @return [Boolean]
|
75
|
+
def start(initial_callback = false)
|
76
|
+
connect if @handle.nil?
|
77
|
+
|
78
|
+
Corosync.cs_send(:quorum_trackstart, @handle, Corosync::CS_TRACK_CHANGES)
|
79
|
+
|
80
|
+
if initial_callback and @callback_notify then
|
81
|
+
@callback_notify.call(quorate?, [])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Stop monitoring for changes to quorum status.
|
86
|
+
# @return [void]
|
87
|
+
def stop
|
88
|
+
Corosync.cs_send(:quorum_trackstop, @handle)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Checks for a single pending event and triggers the appropriate callback if found.
|
92
|
+
# @param timeout [Integer] How long to wait for an event.
|
93
|
+
# * +-1+: Indefinite. Wait forever
|
94
|
+
# * +0+: Non-blocking. If there isn't a pending event, return immediately
|
95
|
+
# * +>0+: Wait the specified number of seconds.
|
96
|
+
# @return [Boolean] Returns +True+ if an event was triggered. Otherwise +False+.
|
97
|
+
def dispatch(timeout = -1)
|
98
|
+
if !timeout != 0 then
|
99
|
+
timeout = nil if timeout == -1
|
100
|
+
select([@fd], [], [], timeout)
|
101
|
+
end
|
102
|
+
|
103
|
+
begin
|
104
|
+
Corosync.cs_send!(:quorum_dispatch, @handle, Corosync::CS_DISPATCH_ONE_NONBLOCKING)
|
105
|
+
rescue Corosync::TryAgainError => e
|
106
|
+
raise e if e.depth > 1 # this exception is from a nested corosync function, not our quorum_dispatch we just called
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
return true
|
111
|
+
end
|
112
|
+
|
113
|
+
# Proc to call when quorum state changes.
|
114
|
+
# @param block [Proc] Proc to call when quorm state changes. Pass +Nil+ to disable the callback.
|
115
|
+
# @yieldparam quorate [Boolean] Whether cluster is quorate.
|
116
|
+
# @yieldparam members [Array<Fixnum>] Node ID of cluster members.
|
117
|
+
# @return [void]
|
118
|
+
def on_notify(&block)
|
119
|
+
@callback_notify = block
|
120
|
+
end
|
121
|
+
def callback_notify(handle, quorate, ring_id, view_list_entries, view_list_ptr)
|
122
|
+
return if !@callback_notify
|
123
|
+
|
124
|
+
view_list = view_list_ptr.read_array_of_type(FFI.find_type(:uint32), :read_uint32, view_list_entries)
|
125
|
+
#view_list = []
|
126
|
+
#view_list_entries.times do |i|
|
127
|
+
#view_list << (view_list_ptr + i * FFI.type_size(:uint32)).read_uint32
|
128
|
+
#end
|
129
|
+
|
130
|
+
@callback_notify.call(quorate > 0, view_list)
|
131
|
+
end
|
132
|
+
private :callback_notify
|
133
|
+
|
134
|
+
# Get node quorum status
|
135
|
+
# @return [Boolean] Whether node is quorate.
|
136
|
+
def getquorate
|
137
|
+
quorate_ptr = FFI::MemoryPointer.new(:int)
|
138
|
+
Corosync.cs_send(:quorum_getquorate, @handle, quorate_ptr)
|
139
|
+
|
140
|
+
quorate_ptr.read_int > 0
|
141
|
+
end
|
142
|
+
alias_method :quorate?, :getquorate
|
143
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require File.expand_path('../../corosync.rb', __FILE__)
|
2
|
+
require File.expand_path('../../../ffi/votequorum.rb', __FILE__)
|
3
|
+
|
4
|
+
# Votequorum is used for tracking the health of the cluster.
|
5
|
+
# This monitors the quorum state as configured. Whenever the node gains or loses quorum, a notification callback is called. You can also poll the quorum state instead of using a callback.
|
6
|
+
#
|
7
|
+
# ----
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require 'corosync/votequorum'
|
11
|
+
# vq = Corosync::Votequorum.new
|
12
|
+
# vq.on_notify do |quorate,node_list|
|
13
|
+
# puts "Cluster is#{quorate ? '' ' not'} quorate"
|
14
|
+
# puts " Nodes:"
|
15
|
+
# node_list.each do |name,state|
|
16
|
+
# puts " #{name}=#{state}"
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
# vq.connect
|
20
|
+
# loop do
|
21
|
+
# vq.dispatch
|
22
|
+
# end
|
23
|
+
class Corosync::Votequorum
|
24
|
+
require 'ostruct'
|
25
|
+
|
26
|
+
# The IO object containing the file descriptor notifications come across.
|
27
|
+
# You can use this to check for activity, but do not read anything from it.
|
28
|
+
# @return [IO]
|
29
|
+
attr_reader :fd
|
30
|
+
|
31
|
+
# Creates a new Votequorum instance
|
32
|
+
#
|
33
|
+
# @param connect [Boolean] Whether to join the cluster immediately. If not provided, you must call {#connect} and/or {#connect} later.
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
def initialize(connect = false)
|
37
|
+
@handle = nil
|
38
|
+
@fd = nil
|
39
|
+
|
40
|
+
@callbacks = Corosync::VotequorumCallbacksT.new
|
41
|
+
@callbacks[:votequorum_notify_fn] = self.method(:callback_notify)
|
42
|
+
@callbacks[:votequorum_expectedvotes_notify_fn] = self.method(:callback_expectedvotes_notify)
|
43
|
+
|
44
|
+
self.connect if connect
|
45
|
+
end
|
46
|
+
|
47
|
+
# Connect to the Votequorum service
|
48
|
+
# @param start [Boolean] Whether to start listening for notifications (will not make initial call to callback).
|
49
|
+
# @return [void]
|
50
|
+
def connect(start = false)
|
51
|
+
handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:votequorum_handle_t))
|
52
|
+
|
53
|
+
Corosync.cs_send(:votequorum_initialize, handle_ptr, @callbacks)
|
54
|
+
|
55
|
+
@handle = handle_ptr.read_uint64
|
56
|
+
|
57
|
+
fd_ptr = FFI::MemoryPointer.new(:int)
|
58
|
+
Corosync.cs_send(:votequorum_fd_get, @handle, fd_ptr)
|
59
|
+
@fd = IO.new(fd_ptr.read_int)
|
60
|
+
|
61
|
+
self.start if start
|
62
|
+
end
|
63
|
+
|
64
|
+
# Shuts down the connection to the Quorum service
|
65
|
+
# @return [void]
|
66
|
+
def finalize
|
67
|
+
return if @handle.nil?
|
68
|
+
|
69
|
+
Corosync.cs_send(:votequorum_finalize, @handle)
|
70
|
+
|
71
|
+
@handle = nil
|
72
|
+
@fd = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# Start monitoring for changes to quorum status/config.
|
76
|
+
# This basically just enables triggering the callback. If not called you can still call {#quorate?} to get quorum state.
|
77
|
+
# @param initial_callback [Boolean] Whether to call the callback after start.
|
78
|
+
# @return [Boolean]
|
79
|
+
def start(initial_callback = false)
|
80
|
+
connect if @handle.nil?
|
81
|
+
|
82
|
+
Corosync.cs_send(:votequorum_trackstart, @handle, 0, Corosync::CS_TRACK_CHANGES)
|
83
|
+
|
84
|
+
if initial_callback and @callback_notify then
|
85
|
+
@callback_notify.call(quorate?)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Stop monitoring for changes.
|
90
|
+
# @return [void]
|
91
|
+
def stop
|
92
|
+
Corosync.cs_send(:votequorum_trackstop, @handle)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Checks for a single pending event and triggers the appropriate callback if found.
|
96
|
+
# @param timeout [Integer] How long to wait for an event.
|
97
|
+
# * +-1+: Indefinite. Wait forever
|
98
|
+
# * +0+: Non-blocking. If there isn't a pending event, return immediately
|
99
|
+
# * +>0+: Wait the specified number of seconds.
|
100
|
+
# @return [Boolean] Returns +True+ if an event was triggered. Otherwise +False+.
|
101
|
+
def dispatch(timeout = -1)
|
102
|
+
if !timeout != 0 then
|
103
|
+
timeout = nil if timeout == -1
|
104
|
+
select([@fd], [], [], timeout)
|
105
|
+
end
|
106
|
+
|
107
|
+
begin
|
108
|
+
Corosync.cs_send(:votequorum_dispatch, @handle, Corosync::CS_DISPATCH_ONE_NONBLOCKING)
|
109
|
+
rescue Corosync::TryAgainError
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
return true
|
114
|
+
end
|
115
|
+
|
116
|
+
# Proc to call when quorum state changes.
|
117
|
+
# @param block [Proc] Proc to call when quorm state changes. Pass +Nil+ to disable the callback.
|
118
|
+
# @yieldparam quorate [Boolean] Whether cluster is quorate.
|
119
|
+
# @yieldparam nodes [Hash] Hash of node IDs and their state.
|
120
|
+
# The state is one of :member, :dead, or :leaving
|
121
|
+
# @return [void]
|
122
|
+
def on_notify(&block)
|
123
|
+
@callback_notify = block
|
124
|
+
end
|
125
|
+
def callback_notify(handle, context, quorate, node_list_entries, node_list_ptr)
|
126
|
+
return if !@callback_notify
|
127
|
+
|
128
|
+
node_list = {}
|
129
|
+
node_list_entries.times do |i|
|
130
|
+
node = Corosync::VotequorumNodeT.new(node_list_ptr + i * Corosync::VotequorumNodeT.size)
|
131
|
+
node_list[node[:nodeid]] = {
|
132
|
+
Corosync::VOTEQUORUM_NODESTATE_MEMBER => :member,
|
133
|
+
Corosync::VOTEQUORUM_NODESTATE_DEAD => :dead,
|
134
|
+
Corosync::VOTEQUORUM_NODESTATE_LEAVING => :leaving,
|
135
|
+
}[node[:state]]
|
136
|
+
node_list[node[:nodeid]] = :UNKNOWN if node_list[node[:nodeid]].nil?
|
137
|
+
end
|
138
|
+
|
139
|
+
@callback_notify.call(quorate > 0, node_list)
|
140
|
+
end
|
141
|
+
private :callback_notify
|
142
|
+
|
143
|
+
# Proc to call when the number of expected votes changes.
|
144
|
+
# @param block [Proc] Proc to call when the expected votes changes. Pass +Nil+ to disable the callback.
|
145
|
+
# @yieldparam expected_votes [Integer] New number of expected votes.
|
146
|
+
# @return [void]
|
147
|
+
def on_expectedvotes_notify(&block)
|
148
|
+
@callback_expectedvotes_notify = block
|
149
|
+
end
|
150
|
+
def callback_expectedvotes_notify(handle, context, expected_votes)
|
151
|
+
return if !@callback_expectedvotes_notify
|
152
|
+
|
153
|
+
@callback_expectedvotes_notify.call(expected_votes)
|
154
|
+
end
|
155
|
+
private :callback_expectedvotes_notify
|
156
|
+
|
157
|
+
# Get the votequorum info about a node.
|
158
|
+
# The return openstruct will contain the following keys
|
159
|
+
# * node_id - Integer
|
160
|
+
# * node_state - Symbol: :member or :dead or :leaving
|
161
|
+
# * node_votes - Integer
|
162
|
+
# * node_expected_votes - Integer
|
163
|
+
# * highest_expected - Integer
|
164
|
+
# * total_votes - Integer
|
165
|
+
# * quorum - Integer
|
166
|
+
# * flags - Array<Symbol> where each symbol is one of: :twonode, :quorate, :wait_for_all, :last_man_standing, :auto_tie_breaker, :allow_downscale, :qdevice_registered, :qdevice_alive, :qdevice_cast_vote, or :qdevice_master_wins
|
167
|
+
# * qdevice_votes - Integer
|
168
|
+
# * qdevice_name - String
|
169
|
+
# @param node_id [Integer] The node id to look up. 0 for the current node.
|
170
|
+
# @return [OpenStruct]
|
171
|
+
def info(node_id = 0)
|
172
|
+
info = Corosync::VotequorumInfo.new
|
173
|
+
|
174
|
+
Corosync.cs_send(:votequorum_getinfo, @handle, node_id, info)
|
175
|
+
|
176
|
+
info = OpenStruct.new(Hash[info.members.zip(info.values)])
|
177
|
+
|
178
|
+
info.qdevice_name = info.qdevice_name.to_s
|
179
|
+
|
180
|
+
flags = info.flags
|
181
|
+
info.flags = []
|
182
|
+
[:twonode,:quorate,:wait_for_all,:last_man_standing,:auto_tie_breaker,:allow_downscale,:qdevice_registered,:qdevice_alive,:qdevice_cast_vote,:qdevice_master_wins].each do |flag_name|
|
183
|
+
flag_value = Corosync.const_get("VOTEQUORUM_INFO_#{flag_name.to_s.upcase}")
|
184
|
+
info.flags << flag_name if flags & flag_value >= 1
|
185
|
+
end
|
186
|
+
|
187
|
+
info
|
188
|
+
#Corosync::Votequorum::Info.new info
|
189
|
+
end
|
190
|
+
|
191
|
+
# Set the number of expected votes for this node
|
192
|
+
# @param count [Integer]
|
193
|
+
# @return [void]
|
194
|
+
def set_expected(count)
|
195
|
+
Corosync.cs_send(:votequorum_setexpected, @handle, count)
|
196
|
+
end
|
197
|
+
alias_method :expected=, :set_expected
|
198
|
+
|
199
|
+
# Set the number of votes contributed by the specified node.
|
200
|
+
# @param count [Integer]
|
201
|
+
# @param node_id [Integer] The node to modify
|
202
|
+
# @return [void]
|
203
|
+
def set_votes(count, node_id = 0)
|
204
|
+
Corosync.cs_send(:votequorum_setvotes, @handle, node_id, count)
|
205
|
+
end
|
206
|
+
# Set the number of votes contributed by this node.
|
207
|
+
# Shorthand for {#set_votes}(count)
|
208
|
+
def votes=(count)
|
209
|
+
set_votes(count)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Get whether this node is quorate or not
|
213
|
+
# Shorthand for {#info}.flags.include?(:quorate)
|
214
|
+
# @return [Boolean]
|
215
|
+
def quorate?
|
216
|
+
self.info.flags.include?(:quorate)
|
217
|
+
end
|
218
|
+
end
|