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/ffi/cpg.rb CHANGED
@@ -5,6 +5,11 @@ module Corosync
5
5
  extend FFI::Library
6
6
  ffi_lib 'libcpg'
7
7
 
8
+ class CpgCallbacksT < FFI::Struct; end
9
+ class CpgModelDataT < FFI::Struct; end
10
+ class CpgName < FFI::Struct; end
11
+ class CpgAddress < FFI::Struct; end
12
+ class CpgIterationDescriptionT < FFI::Struct; end
8
13
  typedef :uint64, :cpg_handle_t
9
14
  typedef :uint64, :cpg_iteration_handle_t
10
15
  CPG_TYPE_UNORDERED = 0
@@ -69,7 +74,7 @@ module Corosync
69
74
  CPG_MEMBERS_MAX = 128
70
75
  class CpgIterationDescriptionT < FFI::Struct
71
76
  layout(
72
- :group, CpgName,
77
+ :group, CpgName.by_value,
73
78
  :nodeid, :uint32,
74
79
  :pid, :uint32
75
80
  )
@@ -80,14 +85,29 @@ module Corosync
80
85
  :seq, :uint64
81
86
  )
82
87
  end
83
- Callback_cpg_deliver_fn_t = callback(:cpg_deliver_fn_t, [ :cpg_handle_t, :pointer, :uint32, :uint32, :pointer, :uint ], :void)
84
- Callback_cpg_confchg_fn_t = callback(:cpg_confchg_fn_t, [ :cpg_handle_t, :pointer, :pointer, :uint, :pointer, :uint, :pointer, :uint ], :void)
85
- Callback_cpg_totem_confchg_fn_t = callback(:cpg_totem_confchg_fn_t, [ :cpg_handle_t, CpgRingId, :uint32, :pointer ], :void)
88
+ Callback_cpg_deliver_fn_t = callback(:cpg_deliver_fn_t, [ :cpg_handle_t, :pointer, :uint32, :uint32, :pointer, :size_t ], :void)
89
+ Callback_cpg_confchg_fn_t = callback(:cpg_confchg_fn_t, [ :cpg_handle_t, :pointer, :pointer, :size_t, :pointer, :size_t, :pointer, :size_t ], :void)
90
+ Callback_cpg_totem_confchg_fn_t = callback(:cpg_totem_confchg_fn_t, [ :cpg_handle_t, CpgRingId.by_value, :uint32, :pointer ], :void)
86
91
  class CpgCallbacksT < FFI::Struct
87
92
  layout(
88
- :cpg_deliver_fn, :cpg_deliver_fn_t,
89
- :cpg_confchg_fn, :cpg_confchg_fn_t
93
+ :cpg_deliver_fn, Callback_cpg_deliver_fn_t,
94
+ :cpg_confchg_fn, Callback_cpg_confchg_fn_t
90
95
  )
96
+ def cpg_deliver_fn=(cb)
97
+ @cpg_deliver_fn = cb
98
+ self[:cpg_deliver_fn] = @cpg_deliver_fn
99
+ end
100
+ def cpg_deliver_fn
101
+ @cpg_deliver_fn
102
+ end
103
+ def cpg_confchg_fn=(cb)
104
+ @cpg_confchg_fn = cb
105
+ self[:cpg_confchg_fn] = @cpg_confchg_fn
106
+ end
107
+ def cpg_confchg_fn
108
+ @cpg_confchg_fn
109
+ end
110
+
91
111
  end
92
112
  class CpgModelDataT < FFI::Struct
93
113
  layout(
@@ -98,14 +118,36 @@ module Corosync
98
118
  class CpgModelV1DataT < FFI::Struct
99
119
  layout(
100
120
  :model, :cpg_model_t,
101
- :cpg_deliver_fn, :cpg_deliver_fn_t,
102
- :cpg_confchg_fn, :cpg_confchg_fn_t,
103
- :cpg_totem_confchg_fn, :cpg_totem_confchg_fn_t,
121
+ :cpg_deliver_fn, Callback_cpg_deliver_fn_t,
122
+ :cpg_confchg_fn, Callback_cpg_confchg_fn_t,
123
+ :cpg_totem_confchg_fn, Callback_cpg_totem_confchg_fn_t,
104
124
  :flags, :uint
105
125
  )
126
+ def cpg_deliver_fn=(cb)
127
+ @cpg_deliver_fn = cb
128
+ self[:cpg_deliver_fn] = @cpg_deliver_fn
129
+ end
130
+ def cpg_deliver_fn
131
+ @cpg_deliver_fn
132
+ end
133
+ def cpg_confchg_fn=(cb)
134
+ @cpg_confchg_fn = cb
135
+ self[:cpg_confchg_fn] = @cpg_confchg_fn
136
+ end
137
+ def cpg_confchg_fn
138
+ @cpg_confchg_fn
139
+ end
140
+ def cpg_totem_confchg_fn=(cb)
141
+ @cpg_totem_confchg_fn = cb
142
+ self[:cpg_totem_confchg_fn] = @cpg_totem_confchg_fn
143
+ end
144
+ def cpg_totem_confchg_fn
145
+ @cpg_totem_confchg_fn
146
+ end
147
+
106
148
  end
107
- attach_function :cpg_initialize, :cpg_initialize, [ :pointer, :pointer ], :cs_error_t
108
- attach_function :cpg_model_initialize, :cpg_model_initialize, [ :pointer, :cpg_model_t, :pointer, :pointer ], :cs_error_t
149
+ attach_function :cpg_initialize, :cpg_initialize, [ :pointer, CpgCallbacksT.ptr ], :cs_error_t
150
+ attach_function :cpg_model_initialize, :cpg_model_initialize, [ :pointer, :cpg_model_t, CpgModelDataT.ptr, :pointer ], :cs_error_t
109
151
  attach_function :cpg_finalize, :cpg_finalize, [ :cpg_handle_t ], :cs_error_t
110
152
  attach_function :cpg_fd_get, :cpg_fd_get, [ :cpg_handle_t, :pointer ], :cs_error_t
111
153
  attach_function :cpg_context_get, :cpg_context_get, [ :cpg_handle_t, :pointer ], :cs_error_t
@@ -114,14 +156,14 @@ module Corosync
114
156
  attach_function :cpg_join, :cpg_join, [ :cpg_handle_t, :pointer ], :cs_error_t
115
157
  attach_function :cpg_leave, :cpg_leave, [ :cpg_handle_t, :pointer ], :cs_error_t
116
158
  attach_function :cpg_mcast_joined, :cpg_mcast_joined, [ :cpg_handle_t, :cpg_guarantee_t, :pointer, :uint ], :cs_error_t
117
- attach_function :cpg_membership_get, :cpg_membership_get, [ :cpg_handle_t, :pointer, :pointer, :pointer ], :cs_error_t
159
+ attach_function :cpg_membership_get, :cpg_membership_get, [ :cpg_handle_t, CpgName.ptr, CpgAddress.ptr, :pointer ], :cs_error_t
118
160
  attach_function :cpg_local_get, :cpg_local_get, [ :cpg_handle_t, :pointer ], :cs_error_t
119
161
  attach_function :cpg_flow_control_state_get, :cpg_flow_control_state_get, [ :cpg_handle_t, :pointer ], :cs_error_t
120
- attach_function :cpg_zcb_alloc, :cpg_zcb_alloc, [ :cpg_handle_t, :uint, :pointer ], :cs_error_t
162
+ attach_function :cpg_zcb_alloc, :cpg_zcb_alloc, [ :cpg_handle_t, :size_t, :pointer ], :cs_error_t
121
163
  attach_function :cpg_zcb_free, :cpg_zcb_free, [ :cpg_handle_t, :pointer ], :cs_error_t
122
- attach_function :cpg_zcb_mcast_joined, :cpg_zcb_mcast_joined, [ :cpg_handle_t, :cpg_guarantee_t, :pointer, :uint ], :cs_error_t
164
+ attach_function :cpg_zcb_mcast_joined, :cpg_zcb_mcast_joined, [ :cpg_handle_t, :cpg_guarantee_t, :pointer, :size_t ], :cs_error_t
123
165
  attach_function :cpg_iteration_initialize, :cpg_iteration_initialize, [ :cpg_handle_t, :cpg_iteration_type_t, :pointer, :pointer ], :cs_error_t
124
- attach_function :cpg_iteration_next, :cpg_iteration_next, [ :cpg_iteration_handle_t, :pointer ], :cs_error_t
166
+ attach_function :cpg_iteration_next, :cpg_iteration_next, [ :cpg_iteration_handle_t, CpgIterationDescriptionT.ptr ], :cs_error_t
125
167
  attach_function :cpg_iteration_finalize, :cpg_iteration_finalize, [ :cpg_iteration_handle_t ], :cs_error_t
126
168
 
127
169
  end
data/ffi/quorum.i ADDED
@@ -0,0 +1,14 @@
1
+ %module Corosync
2
+ %{
3
+ require File.expand_path('../common.rb', __FILE__)
4
+
5
+ module Corosync
6
+ extend FFI::Library
7
+ ffi_lib 'libquorum'
8
+
9
+ %}
10
+ %import "common.i"
11
+ %include <corosync/quorum.h>
12
+ %{
13
+ end
14
+ %}
data/ffi/quorum.rb ADDED
@@ -0,0 +1,36 @@
1
+
2
+ require File.expand_path('../common.rb', __FILE__)
3
+
4
+ module Corosync
5
+ extend FFI::Library
6
+ ffi_lib 'libquorum'
7
+
8
+ class QuorumCallbacksT < FFI::Struct; end
9
+ typedef :uint64, :quorum_handle_t
10
+ Callback_quorum_notification_fn_t = callback(:quorum_notification_fn_t, [ :quorum_handle_t, :uint32, :uint64, :uint32, :pointer ], :void)
11
+ class QuorumCallbacksT < FFI::Struct
12
+ layout(
13
+ :quorum_notify_fn, Callback_quorum_notification_fn_t
14
+ )
15
+ def quorum_notify_fn=(cb)
16
+ @quorum_notify_fn = cb
17
+ self[:quorum_notify_fn] = @quorum_notify_fn
18
+ end
19
+ def quorum_notify_fn
20
+ @quorum_notify_fn
21
+ end
22
+
23
+ end
24
+ QUORUM_FREE = 0
25
+ QUORUM_SET = 1
26
+ attach_function :quorum_initialize, :quorum_initialize, [ :pointer, QuorumCallbacksT.ptr, :pointer ], :cs_error_t
27
+ attach_function :quorum_finalize, :quorum_finalize, [ :quorum_handle_t ], :cs_error_t
28
+ attach_function :quorum_fd_get, :quorum_fd_get, [ :quorum_handle_t, :pointer ], :cs_error_t
29
+ attach_function :quorum_dispatch, :quorum_dispatch, [ :quorum_handle_t, :cs_dispatch_flags_t ], :cs_error_t
30
+ attach_function :quorum_getquorate, :quorum_getquorate, [ :quorum_handle_t, :pointer ], :cs_error_t
31
+ attach_function :quorum_trackstart, :quorum_trackstart, [ :quorum_handle_t, :uint ], :cs_error_t
32
+ attach_function :quorum_trackstop, :quorum_trackstop, [ :quorum_handle_t ], :cs_error_t
33
+ attach_function :quorum_context_set, :quorum_context_set, [ :quorum_handle_t, :pointer ], :cs_error_t
34
+ attach_function :quorum_context_get, :quorum_context_get, [ :quorum_handle_t, :pointer ], :cs_error_t
35
+
36
+ end
data/ffi/votequorum.i ADDED
@@ -0,0 +1,14 @@
1
+ %module Corosync
2
+ %{
3
+ require File.expand_path('../common.rb', __FILE__)
4
+
5
+ module Corosync
6
+ extend FFI::Library
7
+ ffi_lib 'libvotequorum'
8
+
9
+ %}
10
+ %import "common.i"
11
+ %include <corosync/votequorum.h>
12
+ %{
13
+ end
14
+ %}
data/ffi/votequorum.rb ADDED
@@ -0,0 +1,87 @@
1
+
2
+ require File.expand_path('../common.rb', __FILE__)
3
+
4
+ module Corosync
5
+ extend FFI::Library
6
+ ffi_lib 'libvotequorum'
7
+
8
+ class VotequorumCallbacksT < FFI::Struct; end
9
+ class VotequorumInfo < FFI::Struct; end
10
+ typedef :uint64, :votequorum_handle_t
11
+ VOTEQUORUM_INFO_TWONODE = 1
12
+ VOTEQUORUM_INFO_QUORATE = 2
13
+ VOTEQUORUM_INFO_WAIT_FOR_ALL = 4
14
+ VOTEQUORUM_INFO_LAST_MAN_STANDING = 8
15
+ VOTEQUORUM_INFO_AUTO_TIE_BREAKER = 16
16
+ VOTEQUORUM_INFO_ALLOW_DOWNSCALE = 32
17
+ VOTEQUORUM_INFO_QDEVICE_REGISTERED = 64
18
+ VOTEQUORUM_INFO_QDEVICE_ALIVE = 128
19
+ VOTEQUORUM_INFO_QDEVICE_CAST_VOTE = 256
20
+ VOTEQUORUM_INFO_QDEVICE_MASTER_WINS = 512
21
+ VOTEQUORUM_QDEVICE_NODEID = 0
22
+ VOTEQUORUM_QDEVICE_MAX_NAME_LEN = 255
23
+ VOTEQUORUM_QDEVICE_DEFAULT_TIMEOUT = 10000
24
+ VOTEQUORUM_NODESTATE_MEMBER = 1
25
+ VOTEQUORUM_NODESTATE_DEAD = 2
26
+ VOTEQUORUM_NODESTATE_LEAVING = 3
27
+ class VotequorumInfo < FFI::Struct
28
+ layout(
29
+ :node_id, :uint,
30
+ :node_state, :uint,
31
+ :node_votes, :uint,
32
+ :node_expected_votes, :uint,
33
+ :highest_expected, :uint,
34
+ :total_votes, :uint,
35
+ :quorum, :uint,
36
+ :flags, :uint,
37
+ :qdevice_votes, :uint,
38
+ :qdevice_name, [:char, 255]
39
+ )
40
+ end
41
+ class VotequorumNodeT < FFI::Struct
42
+ layout(
43
+ :nodeid, :uint32,
44
+ :state, :uint32
45
+ )
46
+ end
47
+ Callback_votequorum_notification_fn_t = callback(:votequorum_notification_fn_t, [ :votequorum_handle_t, :uint64, :uint32, :uint32, :pointer ], :void)
48
+ Callback_votequorum_expectedvotes_notification_fn_t = callback(:votequorum_expectedvotes_notification_fn_t, [ :votequorum_handle_t, :uint64, :uint32 ], :void)
49
+ class VotequorumCallbacksT < FFI::Struct
50
+ layout(
51
+ :votequorum_notify_fn, Callback_votequorum_notification_fn_t,
52
+ :votequorum_expectedvotes_notify_fn, Callback_votequorum_expectedvotes_notification_fn_t
53
+ )
54
+ def votequorum_notify_fn=(cb)
55
+ @votequorum_notify_fn = cb
56
+ self[:votequorum_notify_fn] = @votequorum_notify_fn
57
+ end
58
+ def votequorum_notify_fn
59
+ @votequorum_notify_fn
60
+ end
61
+ def votequorum_expectedvotes_notify_fn=(cb)
62
+ @votequorum_expectedvotes_notify_fn = cb
63
+ self[:votequorum_expectedvotes_notify_fn] = @votequorum_expectedvotes_notify_fn
64
+ end
65
+ def votequorum_expectedvotes_notify_fn
66
+ @votequorum_expectedvotes_notify_fn
67
+ end
68
+
69
+ end
70
+ attach_function :votequorum_initialize, :votequorum_initialize, [ :pointer, VotequorumCallbacksT.ptr ], :cs_error_t
71
+ attach_function :votequorum_finalize, :votequorum_finalize, [ :votequorum_handle_t ], :cs_error_t
72
+ attach_function :votequorum_dispatch, :votequorum_dispatch, [ :votequorum_handle_t, :cs_dispatch_flags_t ], :cs_error_t
73
+ attach_function :votequorum_fd_get, :votequorum_fd_get, [ :votequorum_handle_t, :pointer ], :cs_error_t
74
+ attach_function :votequorum_getinfo, :votequorum_getinfo, [ :votequorum_handle_t, :uint, VotequorumInfo.ptr ], :cs_error_t
75
+ attach_function :votequorum_setexpected, :votequorum_setexpected, [ :votequorum_handle_t, :uint ], :cs_error_t
76
+ attach_function :votequorum_setvotes, :votequorum_setvotes, [ :votequorum_handle_t, :uint, :uint ], :cs_error_t
77
+ attach_function :votequorum_trackstart, :votequorum_trackstart, [ :votequorum_handle_t, :uint64, :uint ], :cs_error_t
78
+ attach_function :votequorum_trackstop, :votequorum_trackstop, [ :votequorum_handle_t ], :cs_error_t
79
+ attach_function :votequorum_context_get, :votequorum_context_get, [ :votequorum_handle_t, :pointer ], :cs_error_t
80
+ attach_function :votequorum_context_set, :votequorum_context_set, [ :votequorum_handle_t, :pointer ], :cs_error_t
81
+ attach_function :votequorum_qdevice_register, :votequorum_qdevice_register, [ :votequorum_handle_t, :string ], :cs_error_t
82
+ attach_function :votequorum_qdevice_unregister, :votequorum_qdevice_unregister, [ :votequorum_handle_t, :string ], :cs_error_t
83
+ attach_function :votequorum_qdevice_update, :votequorum_qdevice_update, [ :votequorum_handle_t, :string, :string ], :cs_error_t
84
+ attach_function :votequorum_qdevice_poll, :votequorum_qdevice_poll, [ :votequorum_handle_t, :string, :uint ], :cs_error_t
85
+ attach_function :votequorum_qdevice_master_wins, :votequorum_qdevice_master_wins, [ :votequorum_handle_t, :string, :uint ], :cs_error_t
86
+
87
+ end
data/lib/corosync.rb CHANGED
@@ -1,6 +1,74 @@
1
1
  $:.unshift File.expand_path('../', __FILE__)
2
2
  require File.expand_path('../../ffi/common.rb', __FILE__)
3
- require 'version'
4
3
 
4
+ require_relative './ffi_pointer.rb'
5
5
  module Corosync
6
+ VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).chomp
7
+
8
+ require_relative 'corosync/exceptions'
9
+
10
+ # Calls a Corosync method and raises an exception on errors.
11
+ # This is a convenience method to handle error responses when calling various corosync library functions. When an error is returned, an exception is raised.
12
+ # The exceptions are programatically generated based off the `:cs_error_t` enum defined in `ffi/common.rb`. For example, `:err_try_again` maps to `Corosync::TryAgainError`.
13
+ #
14
+ # @param method [Symbol] name of the method to call
15
+ # @param args
16
+ #
17
+ # @return [TrueClass, Integer] Returns `true` on success, and an integer if the return value is not known (this should not happen unless you're running a newer version of corosync than the gem was released for).
18
+ def self.cs_send!(method, *args)
19
+ cs_error = nil
20
+
21
+ begin
22
+ cs_error = send(method, *args)
23
+ rescue Corosync::Error => e
24
+ e.depth += 1
25
+ raise e
26
+ end
27
+
28
+ return true if cs_error == :ok # short circuit the rest of the method since this should be true the majority of the time
29
+
30
+ if exception_class = Error::Map[cs_error] then
31
+ exception = exception_class.new("Received #{cs_error.to_s.upcase} during #{method}")
32
+ exception.depth = 1
33
+ raise exception
34
+ end
35
+
36
+ cs_error
37
+ end
38
+
39
+ # Calls a Corosync method and raises an exception on error while handling retries.
40
+ # This is the same as {cs_send!} except that on the event of TryAgainError, it retries for up to 3 seconds.
41
+ #
42
+ # @param method [Symbol] name of the method to call
43
+ # @param args
44
+ #
45
+ # @return [TrueClass, Integer] Returns `true` on success, and an integer if the return value is not known (this should not happen unless you're running a newer version of corosync than the gem was released for).
46
+ # @see cs_send!
47
+ def self.cs_send(method, *args)
48
+ cs_error = nil
49
+
50
+ time_start = Time.new.to_f
51
+ begin
52
+ begin
53
+ cs_error = send(method, *args)
54
+ rescue Corosync::Error => e
55
+ e.depth += 1
56
+ raise e
57
+ end
58
+
59
+ return true if cs_error == :ok # short circuit the rest of the method since this should be true the majority of the time
60
+
61
+ break if cs_error != :err_try_again
62
+ sleep 0.05
63
+ end while Time.new.to_f - time_start < 3
64
+
65
+ if exception_class = Error::Map[cs_error] then
66
+ exception = exception_class.new("Received #{cs_error.to_s.upcase} during #{method}")
67
+ exception.depth = 1
68
+ raise exception
69
+ end
70
+
71
+ cs_error
72
+ end
6
73
  end
74
+
@@ -0,0 +1,381 @@
1
+ require File.expand_path('../../corosync.rb', __FILE__)
2
+ require File.expand_path('../../../ffi/cmap.rb', __FILE__)
3
+
4
+ # CMAP is used to access the corosync configuration database for the local node.
5
+ # You can list keys, get/set/delete values, and watch for changes.
6
+ #
7
+ # Many of the methods take or return a 'type'. The type is a symbol for one of CMAP's supported types. The symbols are:
8
+ # * :int8
9
+ # * :uint8
10
+ # * :int16
11
+ # * :uint16
12
+ # * :int32
13
+ # * :uint32
14
+ # * :int64
15
+ # * :uint64
16
+ # * :float
17
+ # * :double
18
+ # * :string
19
+ #
20
+ # ----
21
+ #
22
+ # @example
23
+ # require 'corosync/cmap'
24
+ # cmap = Corosync::CMAP.new(true)
25
+ # cmap.set('mykey.foo', :int32, -1234)
26
+ # puts "mykey.foo is #{cmap.get('mykey.foo')}"
27
+
28
+ class Corosync::CMAP
29
+ # The IO object containing the file descriptor events and messages come across.
30
+ # You can use this to check for activity prior to calling {#dispatch}, but do not read anything from it.
31
+ # @return [IO]
32
+ attr_reader :fd
33
+
34
+ # Starts a new instance of CMAP.
35
+ # You can have as many instances as you like, but no real reason for more than one.
36
+ #
37
+ # @param connect [Boolean] Whether to automatically call {#connect}
38
+ def initialize(connect = true)
39
+ @handle = nil
40
+
41
+ @track_handle_callbacks = {}
42
+
43
+ self.connect if connect
44
+ end
45
+
46
+ # Connect to the CMAP service.
47
+ #
48
+ # @return [void]
49
+ def connect
50
+ handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_handle_t))
51
+
52
+ Corosync.cs_send(:cmap_initialize, handle_ptr)
53
+
54
+ @handle = handle_ptr.read_uint64
55
+
56
+ fd_ptr = FFI::MemoryPointer.new(:int)
57
+ Corosync.cs_send(:cmap_fd_get, @handle, fd_ptr)
58
+ @fd = IO.new(fd_ptr.read_int)
59
+ end
60
+
61
+ # Disconnect from the CMAP service.
62
+ #
63
+ # @return [void]
64
+ def finalize
65
+ return if @handle.nil?
66
+
67
+ Corosync.cs_send(:cmap_finalize, @handle)
68
+
69
+ @handle = nil
70
+ @fd = nil
71
+ end
72
+
73
+ # Retrieve a key by the specified name.
74
+ # Will raise {Corosync::NotExistError} if the key does not exist.
75
+ #
76
+ # @return [Array<type, value>] The type and value of the key
77
+ def get(name)
78
+ #TODO? make it just return nil if the key doesn't exist
79
+ size_ptr = FFI::MemoryPointer.new(:size_t)
80
+ type_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_value_types_t))
81
+
82
+ size = 256
83
+
84
+ begin
85
+ size_ptr.write_type(:size_t, size)
86
+ value_ptr = FFI::MemoryPointer.new(size)
87
+
88
+ Corosync.cs_send(:cmap_get, @handle, name, value_ptr, size_ptr, type_ptr)
89
+ rescue Corosync::InvalidParamError => e
90
+ # err_invalid_param is supposed to indicate our buffer was too small
91
+ value_ptr.free
92
+ size << 1
93
+ retry if size < 1024 ** 2 # 1 MB
94
+
95
+ raise e
96
+ end
97
+
98
+ type = type_ptr.read_type(Corosync.find_type(:cmap_value_types_t))
99
+ type = Corosync.enum_type(:cmap_value_types_t)[type]
100
+ if type == :binary then
101
+ raise RuntimeError, "Binary, not sure how to handle this. Corosync docs don't clearly indicate what it is"
102
+ end
103
+
104
+ [type, value_ptr.send("read_#{type}".downcase.to_sym)]
105
+ end
106
+
107
+ # Retrieve a key's value.
108
+ # This is just a conveinence wrapper around {#get} to only get the value if you don't want the type.
109
+ #
110
+ # @param name [String] The name of the key to look up
111
+ #
112
+ # @return [Number, String] The value of the key
113
+ def get_value(name)
114
+ type, value = get(name)
115
+ value
116
+ end
117
+
118
+ # Set a key to the specified type & value.
119
+ # This will create the key if it doesn't exist, and will otherwise modify it, including changing the type if it doesn't match.
120
+ #
121
+ # @param name [String] The name of the key
122
+ # @param type [Symbol] One of CMAP's supported types
123
+ # @param value [Number,String] The value to set
124
+ #
125
+ # @return [Number,String] The value as stored in the CMAP service. This will normally be the value passed in, but if you store a non-string as a string, the return will be the result of to_s
126
+ def set(name, type, value)
127
+ # get byte size
128
+ if type == :string then
129
+ size = value.bytesize
130
+ elsif SIZEMAP.keys.include?(type) then
131
+ size = SIZEMAP[type].bytes
132
+ elsif type == :float then
133
+ size = 4
134
+ elsif type == :double then
135
+ size = 8
136
+ elsif type == :binary then
137
+ size = value.bytesize
138
+ end
139
+
140
+ value_ptr = FFI::MemoryPointer.new(size)
141
+ value_ptr.write_type(type, value)
142
+ Corosync.cs_send(:cmap_set, @handle, name, value_ptr, size, type)
143
+
144
+ value
145
+ end
146
+
147
+ # @!visibility private
148
+ NumType = Struct.new(:min, :max, :bytes)
149
+ # @!visibility private
150
+ SIZEMAP = {
151
+ :int8 => NumType.new(-2 ** 7, 2 ** 7 - 1, 1),
152
+ :uint8 => NumType.new(0, 2 ** 8 - 1, 1),
153
+ :int16 => NumType.new(-2 ** 15, 2 ** 15 - 1, 2),
154
+ :uint16 => NumType.new(0, 2 ** 16 - 1, 2),
155
+ :int32 => NumType.new(-2 ** 31, 2 ** 31 - 1, 4),
156
+ :uint32 => NumType.new(0, 2 ** 32 - 1, 4),
157
+ :int64 => NumType.new(-2 ** 63, 2 ** 63 - 1, 8),
158
+ :uint64 => NumType.new(0, 2 ** 64 - 1, 8),
159
+ }
160
+ # Set a key to the specified value.
161
+ # A convenience wrapper around {#set} to automatically determine the type.
162
+ # If the value is numeric, we will use the same type as the existing value (if it already exists). Otherwise we pick the smallest type that will hold the value.
163
+ #
164
+ # @param name [String] The name of the key
165
+ # @param value [Number,String] The value to set
166
+ #
167
+ # @return [Number,String] The value as stored in the CMAP service. This will normally be the value passed in, but if you store a non-string as a string, the return will be the result of to_s
168
+ def set_value(name, value)
169
+ type = catch :type do
170
+ # strings are strings
171
+ throw :type, :string if value.is_a?(String)
172
+
173
+ # try and get existing type
174
+ begin
175
+ type_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_value_types_t))
176
+ size_ptr = FFI::MemoryPointer.new(:size_t)
177
+ Corosync.cs_send(:cmap_get, @handle, name, nil, size_ptr, type_ptr)
178
+ type = type_ptr.read_type(Corosync.find_type(:cmap_value_types_t))
179
+ type = Corosync.enum_type(:cmap_value_types_t)[type]
180
+ if SIZEMAP.keys.include(type) then
181
+ size = size_ptr.read_type(:size_t)
182
+ if size <= SIZEMAP[type].bytes then
183
+ # it fits within the existing type
184
+ throw :type, type
185
+ end
186
+ # it doesnt fit, we need to re-type it
187
+ end
188
+ rescue Corosync::NotExistError
189
+ end
190
+
191
+ # find the type that will fit
192
+ if value.is_a?(Float) then
193
+ type = :double
194
+ elsif value.is_a?(Numeric) then
195
+ if value.abs <= 2 ** 7 and value < 0 then
196
+ type = :int8
197
+ elsif value <= 2 ** 8 and value >= 0 then
198
+ type = :uint8
199
+ elsif value.abs <= 2 ** 15 and value < 0 then
200
+ type = :int16
201
+ elsif value <= 2 ** 16 and value >= 0 then
202
+ type = :uint16
203
+ elsif value.abs <= 2 ** 31 and value < 0 then
204
+ type = :int32
205
+ elsif value <= 2 ** 32 and value >= 0 then
206
+ type = :uint32
207
+ elsif value.abs <= 2 ** 63 and value < 0 then
208
+ type = :int64
209
+ elsif value < 2 ** 64 and value >= 0 then
210
+ type = :uint64
211
+ else
212
+ raise ArgumentError, "Corosync cannot handle numbers larger than 64-bit"
213
+ end
214
+
215
+ throw :type, type
216
+ end
217
+
218
+ # Unknown type, force it into a string
219
+ throw :type, :string
220
+ end
221
+
222
+ value = value.to_s if type == :string and !value.is_a?(String)
223
+
224
+ set(name, type, value)
225
+ end
226
+
227
+ # Delete the specified key.
228
+ #
229
+ # @param name [String] The name of the key
230
+ #
231
+ # @return [void]
232
+ def delete(name)
233
+ Corosync.cs_send(:cmap_delete, @handle, name)
234
+ end
235
+
236
+ # Decrement the specified key.
237
+ #
238
+ # @param name [String] The name of the key
239
+ #
240
+ # @return [void]
241
+ def dec(name)
242
+ Corosync.cs_send(:cmap_dec, @handle, name)
243
+ end
244
+
245
+ # Increment the specified key.
246
+ #
247
+ # @param name [String] The name of the key
248
+ #
249
+ # @return [void]
250
+ def inc(name)
251
+ Corosync.cs_send(:cmap_inc, @handle, name)
252
+ end
253
+
254
+ # Get a list of keys.
255
+ #
256
+ # @param prefix [String] Filter the list of keys to those starting with the specified prefix
257
+ #
258
+ # @return [Array<String>] List of matching key names
259
+ def keys(prefix = nil)
260
+ cmap_iteration_handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_iter_handle_t))
261
+ Corosync.cs_send(:cmap_iter_init, @handle, prefix, cmap_iteration_handle_ptr)
262
+ cmap_iteration_handle = cmap_iteration_handle_ptr.read_type(Corosync.find_type(:cmap_iter_handle_t))
263
+
264
+ keys = []
265
+
266
+ key_name_ptr = FFI::MemoryPointer.new(Corosync::CMAP_KEYNAME_MAXLEN)
267
+ begin
268
+ begin
269
+ loop do
270
+ Corosync.cs_send(:cmap_iter_next, @handle, cmap_iteration_handle, key_name_ptr, nil, nil)
271
+
272
+ # we really don't need to get info on the value. it doesn't help us any
273
+ #value_size = value_len_ptr.read_type(:size_t)
274
+ #value_type = value_type_ptr.read_type(Corosync.find_type(:cmap_value_types_t))
275
+ #value_type = Corosync.enum_type(:cmap_value_types_t)[value_type]
276
+ #keys[key_name_ptr.read_string] = Corosync::CMAP::ValueInfo.new(value_size, value_type)
277
+
278
+ keys << key_name_ptr.read_string
279
+ end
280
+ rescue Corosync::NoSectionsError
281
+ # signals end of iteration
282
+ end
283
+ ensure
284
+ Corosync.cs_send(:cmap_iter_finalize, @handle, cmap_iteration_handle)
285
+ end
286
+
287
+ keys
288
+ end
289
+
290
+ # Watch keys for changes.
291
+ # Calls a callback when the watched key(s) are changed.
292
+ #
293
+ # @param name [String] The specified key (or prefix)
294
+ # @param actions [Array<Symbol>] The operations to watch for
295
+ # * :add - The key is added
296
+ # * :delete - The key is deleted
297
+ # * :modify - The value/type is changed
298
+ # @param prefix [Boolean] Whether to use the name as a prefix and watch all keys under it
299
+ # @param block [Proc] The callback to call when an event is triggered.
300
+ #
301
+ # @yieldparam action [Symbol] The action that triggered the callback (:add, :delete, :modify)
302
+ # @yieldparam key [String] The name of the key which changed
303
+ # @yieldparam value_new_type [Symbol] The type of the new value. +nil+ if just deleted
304
+ # @yieldparam value_new_data [Number,String] The new value. +nil+ if just deleted
305
+ # @yieldparam value_old_type [Symbol] The type of the old value. +nil+ if just created
306
+ # @yieldparam value_old_data [Number,String] The old value. +nil+ if just created
307
+ #
308
+ # @return [Object] The handle used to identify the track session. Pass to {#track_delete} to stop tracking.
309
+ def track_add(name, actions, prefix = false, &block)
310
+ cs_track_type = 0
311
+ cs_track_type |= Corosync::CMAP_TRACK_ADD if actions.include?(:add)
312
+ cs_track_type |= Corosync::CMAP_TRACK_DELETE if actions.include?(:delete)
313
+ cs_track_type |= Corosync::CMAP_TRACK_MODIFY if actions.include?(:modify)
314
+
315
+ cs_track_type |= Corosync::CMAP_TRACK_PREFIX if prefix
316
+
317
+ track_handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cmap_track_handle_t))
318
+
319
+ @track_notify_method ||= self.method(:track_notify) # we have to keep it from being garbage collected
320
+ Corosync.cs_send(:cmap_track_add, @handle, name, cs_track_type, @track_notify_method, nil, track_handle_ptr)
321
+
322
+ track_handle = track_handle_ptr.read_type(Corosync.find_type(:cmap_track_handle_t))
323
+ @track_handle_callbacks[track_handle] = block
324
+
325
+ track_handle
326
+ end
327
+
328
+ # Stop watching for changes.
329
+ # @param track_handle [Number] The handle returned by {#track_add}
330
+ #
331
+ # @return [void]
332
+ def track_delete(track_handle)
333
+ @track_handle_callbacks.delete(track_handle)
334
+ Corosync.cs_send(:cmap_track_delete, @handle, track_handle)
335
+ end
336
+
337
+ # @!visibility private
338
+ # The callback called by the CMAP library.
339
+ def track_notify(handle, track_handle, event, key, value_new, value_old, user_data)
340
+ block = @track_handle_callbacks[track_handle]
341
+ raise RuntimeError, "Missing callback for track handle #{track_handle.inspect}" unless block # this should not have happened
342
+
343
+
344
+ action = {Corosync::CMAP_TRACK_ADD => :add, Corosync::CMAP_TRACK_DELETE => :delete, Corosync::CMAP_TRACK_MODIFY => :modify}[event]
345
+ if value_new[:type] != 0 then
346
+ #if !value_new.null? then
347
+ #value_new = Corosync::CmapNotifyValue.new(value_new)
348
+ value_new_type = value_new[:type]
349
+ value_new_data = value_new[:data].read_type(value_new_type)
350
+ end
351
+ if value_old[:type] != 0 then
352
+ #if !value_old.null? then
353
+ #value_old = Corosync::CmapNotifyValue.new(value_old)
354
+ value_old_type = value_old[:type]
355
+ value_old_data = value_old[:data].read_type(value_old_type)
356
+ end
357
+ block.call(action, key, value_new_type, value_new_data, value_old_type, value_old_data)
358
+ end
359
+
360
+ # Checks for a single pending event and triggers the appropriate callback if found.
361
+ # @param timeout [Integer] How long to wait for an event.
362
+ # * +-1+: Indefinite. Wait forever
363
+ # * +0+: Non-blocking. If there isn't a pending event, return immediately
364
+ # * +>0+: Wait the specified number of seconds.
365
+ # @return [Boolean] Returns +True+ if an event was triggered. Otherwise +False+.
366
+ def dispatch(timeout = -1)
367
+ if !timeout != 0 then
368
+ timeout = nil if timeout == -1
369
+ select([@fd], [], [], timeout)
370
+ end
371
+
372
+ begin
373
+ Corosync.cs_send!(:cmap_dispatch, @handle, Corosync::CS_DISPATCH_ONE_NONBLOCKING)
374
+ rescue Corosync::TryAgainError => e
375
+ raise e if e.depth > 1 # this exception is from a nested corosync function, not our quorum_dispatch we just called
376
+ return false
377
+ end
378
+
379
+ return true
380
+ end
381
+ end