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