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.
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