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/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, :
|
84
|
-
Callback_cpg_confchg_fn_t = callback(:cpg_confchg_fn_t, [ :cpg_handle_t, :pointer, :pointer, :
|
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,
|
89
|
-
:cpg_confchg_fn,
|
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,
|
102
|
-
:cpg_confchg_fn,
|
103
|
-
:cpg_totem_confchg_fn,
|
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,
|
108
|
-
attach_function :cpg_model_initialize, :cpg_model_initialize, [ :pointer, :cpg_model_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,
|
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, :
|
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, :
|
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,
|
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
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
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
|