corosync 0.0.3 → 0.1.0.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/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
|