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.
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, member|
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
- cs_error = Corosync.cpg_model_initialize(handle_ptr, Corosync::CPG_MODEL_V1, @model.pointer, nil);
75
- if cs_error != :ok then
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
- cs_error = Corosync.cpg_fd_get(@handle, fd_ptr)
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
- cs_error = Corosync.cpg_finalize(@handle)
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
- cs_error = Corosync.cpg_join(@handle, cpg_name)
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
- cs_error = Corosync.cpg_leave(@handle, cpg_name)
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
- cs_error = Corosync.cpg_dispatch(@handle, Corosync::CS_DISPATCH_ONE_NONBLOCKING)
155
- return false if cs_error == :err_try_again
156
- if cs_error != :ok then
157
- raise StandardError, "Received #{cs_error.to_s.upcase} attempting perform dispatch"
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
- cs_error = Corosync.cpg_local_get(@handle, nodeid_p)
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
- cs_error = Corosync.cpg_iteration_initialize(@handle, Corosync::CPG_ITERATION_ONE_GROUP, cpg_name, iteration_handle_ptr)
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
- while (cs_error = Corosync.cpg_iteration_next(iteration_handle, iteration_description.pointer)) == :ok do
258
- members << Corosync::CPG::Member.new(iteration_description)
259
- end
260
- if cs_error != :err_no_sections then
261
- raise StandardError, "Received #{cs_error.to_s.upcase} attempting to iterate group members"
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
- cs_error = Corosync.cpg_iteration_finalize(iteration_handle)
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
- cs_error = Corosync.cpg_mcast_joined(@handle, Corosync::CPG_TYPE_AGREED, iovec_list_p, iovec_len)
296
- if cs_error != :ok then
297
- raise StandardError, "Received #{cs_error.to_s.upcase} attempting to send a message"
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