corosync 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +40 -0
- data/LICENSE +21 -0
- data/README.md +34 -0
- data/Rakefile +34 -0
- data/corosync.gemspec +13 -0
- data/examples/example.rb +20 -0
- data/ffi/common.i +18 -0
- data/ffi/common.rb +120 -0
- data/ffi/cpg.i +14 -0
- data/ffi/cpg.rb +127 -0
- data/ffi/service.i.erb +14 -0
- data/lib/corosync/cpg/member.rb +50 -0
- data/lib/corosync/cpg/member_list.rb +99 -0
- data/lib/corosync/cpg.rb +277 -0
- data/lib/corosync.rb +6 -2
- data/lib/version.rb +3 -0
- data/spec/corosync/cpg/member.rb +42 -0
- data/spec/corosync/cpg/member_list.rb +59 -0
- data/spec/corosync/cpg.rb +47 -0
- data/spec/spec_helper.rb +2 -0
- metadata +51 -18
- data/lib/corosync/cluster.rb +0 -105
- data/lib/corosync/resource.rb +0 -34
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0ebdf0a21a91d835cd04ebbdccba82d69b791636
|
4
|
+
data.tar.gz: bfbc00cbe277b8bdf5fb7fc8f7e7f7286accd97b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 09db7ddafba097fde8bd040d98e667cb6f8b291020804d5c503334432c883c0792360547b66b9caa48204ee2e85131d96dafcbe8390cd12ce984245ea522d15b
|
7
|
+
data.tar.gz: 65a2cc445d24f1c7f3ac033cc485ba2457ba5e3460c237c30b30e06a3f17dc61f684990c18eaa65cd174360addc5e9456a9089efe0841a7abcd612988966881c
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.yardoc
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/ffi/ffi-swig-generator.git
|
3
|
+
revision: 818e921fc85aa58550c15e558af81edd1e8e9f69
|
4
|
+
specs:
|
5
|
+
ffi-swig-generator (0.3.3)
|
6
|
+
nokogiri (>= 1.6.0)
|
7
|
+
rake (>= 0.9.2.2)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
diff-lcs (1.2.4)
|
13
|
+
ffi (1.9.0)
|
14
|
+
json (1.8.1)
|
15
|
+
mini_portile (0.5.2)
|
16
|
+
nokogiri (1.6.0)
|
17
|
+
mini_portile (~> 0.5.0)
|
18
|
+
rake (10.1.0)
|
19
|
+
rdoc (4.0.1)
|
20
|
+
json (~> 1.4)
|
21
|
+
rspec (2.14.1)
|
22
|
+
rspec-core (~> 2.14.0)
|
23
|
+
rspec-expectations (~> 2.14.0)
|
24
|
+
rspec-mocks (~> 2.14.0)
|
25
|
+
rspec-core (2.14.6)
|
26
|
+
rspec-expectations (2.14.3)
|
27
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
28
|
+
rspec-mocks (2.14.4)
|
29
|
+
yard (0.8.7.2)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
ffi
|
36
|
+
ffi-swig-generator!
|
37
|
+
rake
|
38
|
+
rdoc
|
39
|
+
rspec
|
40
|
+
yard
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Patrick Hemmer <patrick.hemmer@gmail.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# ruby-corosync
|
2
|
+
|
3
|
+
## Description
|
4
|
+
|
5
|
+
Ruby-corosync is a gem for interfacing ruby with the corosync cluster services.
|
6
|
+
|
7
|
+
Corosync is a cluster communication engine for communication between nodes operating as a cluster. The services it offers are as follows:
|
8
|
+
|
9
|
+
* **CPG** - The CPG service which allows sending messages between nodes. Processes can join a 'group' and broadcast messages to that group. Order is preserved in that messages are guaranteed to arrive in the same order to all members.
|
10
|
+
* **SAM** - SAM is a service which is meant to ensure processes remain operational. You can instruct corosync to start a process, and should that process die, corosync can notify you, and start it back up.
|
11
|
+
* **Quorum** - The quorum service is a very basic service used for keeping track of whether the cluster has quorum or not.
|
12
|
+
* **Votequorum** - The votequorum service is a more powerful version of the quorum service. It allows you to control the number of votes provided by any one node such that you can control the logic that determines whether the cluster is quorate.
|
13
|
+
* **CMAP** - CMAP is a key/value store. It allows you to store keys & their values, and subscribe to changes made to those keys.
|
14
|
+
|
15
|
+
|
16
|
+
Corosync offers these services through a C API. This gem utilizes [ffi](http://github.com/ffi/ffi) to provide the interface to that API.
|
17
|
+
|
18
|
+
## State
|
19
|
+
|
20
|
+
Currently the only supported service is CPG. It is fully functional, though it may change as it is very young.
|
21
|
+
|
22
|
+
## Examples
|
23
|
+
|
24
|
+
### CPG
|
25
|
+
|
26
|
+
cpg = Corosync::CPG.new('mygroup')
|
27
|
+
cpg.on_message do |message, membership|
|
28
|
+
puts "Received #{message}"
|
29
|
+
end
|
30
|
+
puts "Member node IDs: #{cpg.members.map {|m| m.nodeid}.join(" ")}"
|
31
|
+
cpg.send "hello"
|
32
|
+
loop do
|
33
|
+
cpg.dispatch
|
34
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
########################################
|
2
|
+
desc "Create ffi/*.rb files"
|
3
|
+
multitask :ffi
|
4
|
+
|
5
|
+
file 'ffi/common.i' do # exempt 'ffi/common.i' from the service rule below
|
6
|
+
end
|
7
|
+
rule '.i' => [proc {'ffi/service.i.erb'}] do |t|
|
8
|
+
File.open(t.name, 'w') do |f|
|
9
|
+
puts "generating #{t.name}\n"
|
10
|
+
require 'erb'
|
11
|
+
service = t.name.split('/').last.split('.').first
|
12
|
+
erb = ERB.new(File.read("ffi/service.i.erb"), nil, '-')
|
13
|
+
f.write erb.result(binding)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
rule '.rb' => ['.i'] do |t|
|
17
|
+
puts "generating #{t.name}\n"
|
18
|
+
xml = %x{swig -I/usr/include -xml -o /dev/stdout #{t.source}}
|
19
|
+
require 'ffi-swig-generator'
|
20
|
+
File.open(t.name, 'w') do |f|
|
21
|
+
f.write FFI::Generator::Parser.new.generate(Nokogiri::XML(xml))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
corosync_services = ['cpg']
|
26
|
+
multitask :ffi => ['ffi/common.rb'] + corosync_services.map{|s| "ffi/#{s}.rb"}
|
27
|
+
|
28
|
+
########################################
|
29
|
+
desc 'Run tests'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:test) do |t|
|
32
|
+
t.pattern = 'spec/**/*.rb'
|
33
|
+
t.rspec_opts = '-c -f d --fail-fast'
|
34
|
+
end
|
data/corosync.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path('../lib/version.rb', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new 'corosync', Corosync::GEM_VERSION do |s|
|
4
|
+
s.description = 'An interface to the Corosync clustering services.'
|
5
|
+
s.summary = 'Corosync library interface'
|
6
|
+
s.homepage = 'http://github.com/phemmer/ruby-corosync/'
|
7
|
+
s.author = 'Patrick Hemmer'
|
8
|
+
s.email = 'patrick.hemmer@gmail.com'
|
9
|
+
s.license = 'MIT'
|
10
|
+
s.files = %x{git ls-files}.split("\n")
|
11
|
+
|
12
|
+
s.add_runtime_dependency 'ffi', '~> 1.9'
|
13
|
+
end
|
data/examples/example.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
require 'corosync/cpg'
|
3
|
+
|
4
|
+
cpg = Corosync::CPG.new
|
5
|
+
cpg.on_message do |message, nodeid, pid|
|
6
|
+
puts "MESSAGE: #{message.inspect}"
|
7
|
+
end
|
8
|
+
cpg.on_confchg do |member_list, left_list, joined_list|
|
9
|
+
puts "CONFCHG:"
|
10
|
+
puts " MEMBER_LIST=#{member_list.inspect}"
|
11
|
+
puts " LEFT_LIST=#{left_list.inspect}"
|
12
|
+
puts " JOINED_LIST=#{joined_list.inspect}"
|
13
|
+
end
|
14
|
+
cpg.join('mygroup')
|
15
|
+
cpg.send('my message')
|
16
|
+
loop do
|
17
|
+
puts "MEMBERS=#{cpg.members.inspect}"
|
18
|
+
cpg.dispatch
|
19
|
+
puts "LOOP"
|
20
|
+
end
|
data/ffi/common.i
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
%module Corosync
|
2
|
+
%{
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
module Corosync
|
6
|
+
extend FFI::Library
|
7
|
+
ffi_lib 'libcorosync_common'
|
8
|
+
|
9
|
+
%}
|
10
|
+
struct iovec /* from bits/uio.h */
|
11
|
+
{
|
12
|
+
void *iov_base; /* Pointer to data. */
|
13
|
+
size_t iov_len; /* Length of data. */
|
14
|
+
};
|
15
|
+
%include <corosync/corotypes.h>
|
16
|
+
%{
|
17
|
+
end
|
18
|
+
%}
|
data/ffi/common.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
module Corosync
|
5
|
+
extend FFI::Library
|
6
|
+
ffi_lib 'libcorosync_common'
|
7
|
+
|
8
|
+
class Iovec < FFI::Struct
|
9
|
+
layout(
|
10
|
+
:iov_base, :pointer,
|
11
|
+
:iov_len, :uint
|
12
|
+
)
|
13
|
+
end
|
14
|
+
typedef :int64, :cs_time_t
|
15
|
+
CS_FALSE = 0
|
16
|
+
CS_TRUE = !0
|
17
|
+
CS_MAX_NAME_LENGTH = 256
|
18
|
+
class CsNameT < FFI::Struct
|
19
|
+
layout(
|
20
|
+
:length, :uint16,
|
21
|
+
:value, [:uint8, 256]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
class CsVersionT < FFI::Struct
|
25
|
+
layout(
|
26
|
+
:releaseCode, :char,
|
27
|
+
:majorVersion, :uchar,
|
28
|
+
:minorVersion, :uchar
|
29
|
+
)
|
30
|
+
end
|
31
|
+
CS_DISPATCH_ONE = 1
|
32
|
+
CS_DISPATCH_ALL = 2
|
33
|
+
CS_DISPATCH_BLOCKING = 3
|
34
|
+
CS_DISPATCH_ONE_NONBLOCKING = 4
|
35
|
+
cs_dispatch_flags_t = enum :cs_dispatch_flags_t, [
|
36
|
+
:one, 1,
|
37
|
+
:all, 2,
|
38
|
+
:blocking, 3,
|
39
|
+
:one_nonblocking, 4,
|
40
|
+
]
|
41
|
+
|
42
|
+
CS_TRACK_CURRENT = 0x01
|
43
|
+
CS_TRACK_CHANGES = 0x02
|
44
|
+
CS_TRACK_CHANGES_ONLY = 0x04
|
45
|
+
CS_OK = 1
|
46
|
+
CS_ERR_LIBRARY = 2
|
47
|
+
CS_ERR_VERSION = 3
|
48
|
+
CS_ERR_INIT = 4
|
49
|
+
CS_ERR_TIMEOUT = 5
|
50
|
+
CS_ERR_TRY_AGAIN = 6
|
51
|
+
CS_ERR_INVALID_PARAM = 7
|
52
|
+
CS_ERR_NO_MEMORY = 8
|
53
|
+
CS_ERR_BAD_HANDLE = 9
|
54
|
+
CS_ERR_BUSY = 10
|
55
|
+
CS_ERR_ACCESS = 11
|
56
|
+
CS_ERR_NOT_EXIST = 12
|
57
|
+
CS_ERR_NAME_TOO_LONG = 13
|
58
|
+
CS_ERR_EXIST = 14
|
59
|
+
CS_ERR_NO_SPACE = 15
|
60
|
+
CS_ERR_INTERRUPT = 16
|
61
|
+
CS_ERR_NAME_NOT_FOUND = 17
|
62
|
+
CS_ERR_NO_RESOURCES = 18
|
63
|
+
CS_ERR_NOT_SUPPORTED = 19
|
64
|
+
CS_ERR_BAD_OPERATION = 20
|
65
|
+
CS_ERR_FAILED_OPERATION = 21
|
66
|
+
CS_ERR_MESSAGE_ERROR = 22
|
67
|
+
CS_ERR_QUEUE_FULL = 23
|
68
|
+
CS_ERR_QUEUE_NOT_AVAILABLE = 24
|
69
|
+
CS_ERR_BAD_FLAGS = 25
|
70
|
+
CS_ERR_TOO_BIG = 26
|
71
|
+
CS_ERR_NO_SECTIONS = 27
|
72
|
+
CS_ERR_CONTEXT_NOT_FOUND = 28
|
73
|
+
CS_ERR_TOO_MANY_GROUPS = 30
|
74
|
+
CS_ERR_SECURITY = 100
|
75
|
+
cs_error_t = enum :cs_error_t, [
|
76
|
+
:ok, 1,
|
77
|
+
:err_library, 2,
|
78
|
+
:err_version, 3,
|
79
|
+
:err_init, 4,
|
80
|
+
:err_timeout, 5,
|
81
|
+
:err_try_again, 6,
|
82
|
+
:err_invalid_param, 7,
|
83
|
+
:err_no_memory, 8,
|
84
|
+
:err_bad_handle, 9,
|
85
|
+
:err_busy, 10,
|
86
|
+
:err_access, 11,
|
87
|
+
:err_not_exist, 12,
|
88
|
+
:err_name_too_long, 13,
|
89
|
+
:err_exist, 14,
|
90
|
+
:err_no_space, 15,
|
91
|
+
:err_interrupt, 16,
|
92
|
+
:err_name_not_found, 17,
|
93
|
+
:err_no_resources, 18,
|
94
|
+
:err_not_supported, 19,
|
95
|
+
:err_bad_operation, 20,
|
96
|
+
:err_failed_operation, 21,
|
97
|
+
:err_message_error, 22,
|
98
|
+
:err_queue_full, 23,
|
99
|
+
:err_queue_not_available, 24,
|
100
|
+
:err_bad_flags, 25,
|
101
|
+
:err_too_big, 26,
|
102
|
+
:err_no_sections, 27,
|
103
|
+
:err_context_not_found, 28,
|
104
|
+
:err_too_many_groups, 30,
|
105
|
+
:err_security, 100,
|
106
|
+
]
|
107
|
+
|
108
|
+
CS_IPC_TIMEOUT_MS = -1
|
109
|
+
CS_TIME_MS_IN_SEC = 1000
|
110
|
+
CS_TIME_US_IN_SEC = 1000000
|
111
|
+
CS_TIME_NS_IN_SEC = 1000000000
|
112
|
+
CS_TIME_US_IN_MSEC = 1000
|
113
|
+
CS_TIME_NS_IN_MSEC = 1000000
|
114
|
+
CS_TIME_NS_IN_USEC = 1000
|
115
|
+
# inline function cs_timestamp_get
|
116
|
+
attach_function :qb_to_cs_error, :qb_to_cs_error, [ :int ], :cs_error_t
|
117
|
+
attach_function :cs_strerror, :cs_strerror, [ :cs_error_t ], :string
|
118
|
+
attach_function :hdb_error_to_cs, :hdb_error_to_cs, [ :int ], :cs_error_t
|
119
|
+
|
120
|
+
end
|
data/ffi/cpg.i
ADDED
data/ffi/cpg.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
|
2
|
+
require File.expand_path('../common.rb', __FILE__)
|
3
|
+
|
4
|
+
module Corosync
|
5
|
+
extend FFI::Library
|
6
|
+
ffi_lib 'libcpg'
|
7
|
+
|
8
|
+
typedef :uint64, :cpg_handle_t
|
9
|
+
typedef :uint64, :cpg_iteration_handle_t
|
10
|
+
CPG_TYPE_UNORDERED = 0
|
11
|
+
CPG_TYPE_FIFO = CPG_TYPE_UNORDERED + 1
|
12
|
+
CPG_TYPE_AGREED = CPG_TYPE_FIFO + 1
|
13
|
+
CPG_TYPE_SAFE = CPG_TYPE_AGREED + 1
|
14
|
+
cpg_guarantee_t = enum :cpg_guarantee_t, [
|
15
|
+
:unordered,
|
16
|
+
:fifo,
|
17
|
+
:agreed,
|
18
|
+
:safe,
|
19
|
+
]
|
20
|
+
|
21
|
+
CPG_FLOW_CONTROL_DISABLED = 0
|
22
|
+
CPG_FLOW_CONTROL_ENABLED = CPG_FLOW_CONTROL_DISABLED + 1
|
23
|
+
cpg_flow_control_state_t = enum :cpg_flow_control_state_t, [
|
24
|
+
:disabled,
|
25
|
+
:enabled,
|
26
|
+
]
|
27
|
+
|
28
|
+
CPG_REASON_JOIN = 1
|
29
|
+
CPG_REASON_LEAVE = 2
|
30
|
+
CPG_REASON_NODEDOWN = 3
|
31
|
+
CPG_REASON_NODEUP = 4
|
32
|
+
CPG_REASON_PROCDOWN = 5
|
33
|
+
cpg_reason_t = enum :cpg_reason_t, [
|
34
|
+
:join, 1,
|
35
|
+
:leave, 2,
|
36
|
+
:nodedown, 3,
|
37
|
+
:nodeup, 4,
|
38
|
+
:procdown, 5,
|
39
|
+
]
|
40
|
+
|
41
|
+
CPG_ITERATION_NAME_ONLY = 1
|
42
|
+
CPG_ITERATION_ONE_GROUP = 2
|
43
|
+
CPG_ITERATION_ALL = 3
|
44
|
+
cpg_iteration_type_t = enum :cpg_iteration_type_t, [
|
45
|
+
:name_only, 1,
|
46
|
+
:one_group, 2,
|
47
|
+
:all, 3,
|
48
|
+
]
|
49
|
+
|
50
|
+
CPG_MODEL_V1 = 1
|
51
|
+
cpg_model_t = enum :cpg_model_t, [
|
52
|
+
:v1, 1,
|
53
|
+
]
|
54
|
+
|
55
|
+
class CpgAddress < FFI::Struct
|
56
|
+
layout(
|
57
|
+
:nodeid, :uint32,
|
58
|
+
:pid, :uint32,
|
59
|
+
:reason, :uint32
|
60
|
+
)
|
61
|
+
end
|
62
|
+
CPG_MAX_NAME_LENGTH = 128
|
63
|
+
class CpgName < FFI::Struct
|
64
|
+
layout(
|
65
|
+
:length, :uint32,
|
66
|
+
:value, [:char, 128]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
CPG_MEMBERS_MAX = 128
|
70
|
+
class CpgIterationDescriptionT < FFI::Struct
|
71
|
+
layout(
|
72
|
+
:group, CpgName,
|
73
|
+
:nodeid, :uint32,
|
74
|
+
:pid, :uint32
|
75
|
+
)
|
76
|
+
end
|
77
|
+
class CpgRingId < FFI::Struct
|
78
|
+
layout(
|
79
|
+
:nodeid, :uint32,
|
80
|
+
:seq, :uint64
|
81
|
+
)
|
82
|
+
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)
|
86
|
+
class CpgCallbacksT < FFI::Struct
|
87
|
+
layout(
|
88
|
+
:cpg_deliver_fn, :cpg_deliver_fn_t,
|
89
|
+
:cpg_confchg_fn, :cpg_confchg_fn_t
|
90
|
+
)
|
91
|
+
end
|
92
|
+
class CpgModelDataT < FFI::Struct
|
93
|
+
layout(
|
94
|
+
:model, :cpg_model_t
|
95
|
+
)
|
96
|
+
end
|
97
|
+
CPG_MODEL_V1_DELIVER_INITIAL_TOTEM_CONF = 0x01
|
98
|
+
class CpgModelV1DataT < FFI::Struct
|
99
|
+
layout(
|
100
|
+
: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,
|
104
|
+
:flags, :uint
|
105
|
+
)
|
106
|
+
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
|
109
|
+
attach_function :cpg_finalize, :cpg_finalize, [ :cpg_handle_t ], :cs_error_t
|
110
|
+
attach_function :cpg_fd_get, :cpg_fd_get, [ :cpg_handle_t, :pointer ], :cs_error_t
|
111
|
+
attach_function :cpg_context_get, :cpg_context_get, [ :cpg_handle_t, :pointer ], :cs_error_t
|
112
|
+
attach_function :cpg_context_set, :cpg_context_set, [ :cpg_handle_t, :pointer ], :cs_error_t
|
113
|
+
attach_function :cpg_dispatch, :cpg_dispatch, [ :cpg_handle_t, :cs_dispatch_flags_t ], :cs_error_t
|
114
|
+
attach_function :cpg_join, :cpg_join, [ :cpg_handle_t, :pointer ], :cs_error_t
|
115
|
+
attach_function :cpg_leave, :cpg_leave, [ :cpg_handle_t, :pointer ], :cs_error_t
|
116
|
+
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
|
118
|
+
attach_function :cpg_local_get, :cpg_local_get, [ :cpg_handle_t, :pointer ], :cs_error_t
|
119
|
+
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
|
121
|
+
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
|
123
|
+
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
|
125
|
+
attach_function :cpg_iteration_finalize, :cpg_iteration_finalize, [ :cpg_iteration_handle_t ], :cs_error_t
|
126
|
+
|
127
|
+
end
|
data/ffi/service.i.erb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Corosync
|
2
|
+
class CPG
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
class Corosync::CPG::Member
|
7
|
+
# @return [Integer] Node ID of the member
|
8
|
+
attr_reader :nodeid
|
9
|
+
|
10
|
+
# @return [Integer] Process ID of the member
|
11
|
+
attr_reader :pid
|
12
|
+
|
13
|
+
# @overload initialize(member)
|
14
|
+
# @param member [FFI::Pointer<Corosync::CpgAddress>, Corosync::CpgAddress]
|
15
|
+
# @overload initialize(nodeid, pid)
|
16
|
+
# @param nodeid [Integer]
|
17
|
+
# @param pid [Integer]
|
18
|
+
def initialize(*args)
|
19
|
+
if args.size == 1 then
|
20
|
+
member = args.first
|
21
|
+
|
22
|
+
member = Corosync::CpgAddress.new(member) if member.is_a?(FFI::Pointer)
|
23
|
+
if member.is_a?(Corosync::CpgAddress) then
|
24
|
+
@nodeid = member[:nodeid]
|
25
|
+
@pid = member[:pid]
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Invalid argument type"
|
28
|
+
end
|
29
|
+
elsif args.size == 2 then
|
30
|
+
@nodeid = args.shift.to_i
|
31
|
+
@pid = args.shift.to_i
|
32
|
+
else
|
33
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 1..2)"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Boolean]
|
38
|
+
def ==(target)
|
39
|
+
self.class == target.class and @nodeid == target.nodeid and @pid == target.pid
|
40
|
+
end
|
41
|
+
alias_method :eql?, :==
|
42
|
+
|
43
|
+
def hash
|
44
|
+
[@nodid,@pid].hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"#{@nodeid}:#{@pid}"
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path('../member.rb', __FILE__)
|
2
|
+
|
3
|
+
module Corosync
|
4
|
+
class CPG
|
5
|
+
end
|
6
|
+
end
|
7
|
+
class Corosync::CPG::MemberList
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def self.new(*list)
|
11
|
+
# return the input list if we were passed a MemberList
|
12
|
+
return list[0] if list.size == 1 and list[0].is_a?(self)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
def initialize(*list)
|
16
|
+
@list = {}
|
17
|
+
|
18
|
+
list = list[0] if list.size <= 1
|
19
|
+
|
20
|
+
if list.is_a?(Array) then
|
21
|
+
list.each do |recipient|
|
22
|
+
self << recipient
|
23
|
+
end
|
24
|
+
elsif list.is_a?(Corosync::CPG::Member) then
|
25
|
+
self << list
|
26
|
+
elsif list.nil? then
|
27
|
+
# nothing
|
28
|
+
else
|
29
|
+
raise ArgumentError, "Invalid recipient type: #{list.class}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Add a member to the list
|
34
|
+
# @param member [FFI::Pointer<Corosync::CpgAddress>,Corosync::CPG::Member] Member to add
|
35
|
+
def << (member)
|
36
|
+
if member.is_a?(FFI::Pointer) then
|
37
|
+
cpgaddress = Corosync::CpgAddress.new(member)
|
38
|
+
@list[Corosync::CPG::Member.new(cpgaddress)] = cpgaddress[:reason]
|
39
|
+
else
|
40
|
+
@list[member] = nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Number of members in list
|
45
|
+
# @return [Integer]
|
46
|
+
def size
|
47
|
+
@list.size
|
48
|
+
end
|
49
|
+
|
50
|
+
# Iterate over all the {Corosync::CPG::Member member} objects in the list.
|
51
|
+
# @param block [Proc]
|
52
|
+
# @yieldparam member [Corosync::CPG::Member]
|
53
|
+
def each(&block)
|
54
|
+
@list.each_key &block
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get a list of all members also present in another list
|
58
|
+
# @param list [Corosync::CPG::MemberList]
|
59
|
+
# @return [Corosync::CPG::MemberList]
|
60
|
+
def &(list)
|
61
|
+
@list.keys & list.to_a
|
62
|
+
end
|
63
|
+
|
64
|
+
# Delete member from list
|
65
|
+
# @param member [Corosync::CPG::Member] Member to delete
|
66
|
+
# @return [void]
|
67
|
+
def delete(member)
|
68
|
+
@list.delete(member)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Duplicate
|
72
|
+
# @return [Corosync::CPG::MemberList]
|
73
|
+
def dup
|
74
|
+
new = self.class.new
|
75
|
+
self.each do |member|
|
76
|
+
new << member.dup
|
77
|
+
end
|
78
|
+
new
|
79
|
+
end
|
80
|
+
|
81
|
+
# In the case of join/leave lists, this gets the reason a member is in the list.
|
82
|
+
# @param member [Corosync::CPG::Member] Member look up
|
83
|
+
# @return [Symbol, Integer, NilClass] Reason for the membership.
|
84
|
+
# * :join => The member joined the group normally.
|
85
|
+
# * :leave => The member left the group normally.
|
86
|
+
# * :nodedown => The member left the group because the node left the cluster.
|
87
|
+
# * :nodeup => The member joined the group because it was already a member of a group on a node that just joined the cluster.
|
88
|
+
# * :procdown => The member left the group uncleanly (without calling {#leave})
|
89
|
+
def reason(member)
|
90
|
+
member = Corosync::CPG::Member.new if !member.is_a?(Corosync::CPG::Member)
|
91
|
+
@list[member]
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Corosync::CPG::MemberList]
|
95
|
+
def freeze
|
96
|
+
@list.freeze
|
97
|
+
self
|
98
|
+
end
|
99
|
+
end
|
data/lib/corosync/cpg.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
require File.expand_path('../../corosync.rb', __FILE__)
|
2
|
+
require File.expand_path('../../../ffi/cpg.rb', __FILE__)
|
3
|
+
|
4
|
+
require 'corosync/cpg/member_list'
|
5
|
+
require 'corosync/cpg/member'
|
6
|
+
|
7
|
+
# CPG is used for sending messages between processes (usually on multiple servers).
|
8
|
+
# The benefits offered by CPG over normal IPC is that message order is guaranteed.
|
9
|
+
# If you have 3 nodes, and both node 1 and node 2 send a message at the exact same time, all 3 nodes will receive the messages in the same order. One of the key details in this is that a node will also receive it's own message.
|
10
|
+
# You can also be notified whenever nodes join or leave the group. The order of these messages is preserved as well.
|
11
|
+
#
|
12
|
+
# This is all done through callbacks. You define a block of code to execute, and whenever a message is received, it is passed to that block.
|
13
|
+
# After registering the callbacks, you call {#dispatch} to check for any pending messages, upon which the appropriate callbacks will be executed.
|
14
|
+
#
|
15
|
+
# The simplest usage of this library is to call `Corosync::CPG.new('groupname')`. This will connect to CPG and join the specified group. Note though that upon joining a group, the library automatically calls {#dispatch} once on it's own. This is so that it can get the initial confchg message and obtain a group membership list. As such you will not have been able to establish a callback yet. The solution to this is to create the CPG object without joining, register your callbacks, and then call {#join}.
|
16
|
+
#
|
17
|
+
# == Threading notice
|
18
|
+
# With MRI Ruby 1.9.3 and older, you cannot call {#dispatch} from within a thread. Attempting to do so will result in a segfault.
|
19
|
+
# This is because the Corosync library allocates a very large buffer on the stack, and these versions of Ruby do not allocate enough memory to the thread stack.
|
20
|
+
# With MRI Ruby 2.0.0 the behavior is a bit different. There is a workaround, but without it, calling {#dispatch} will result in the thread hanging. The workaround is that you you can pass the environment variable RUBY_THREAD_MACHINE_STACK_SIZE to increase the size of the thread stack. The recommended size is 1572864.
|
21
|
+
#
|
22
|
+
# ----
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# cpg = Corosync::CPG.new('mygroup')
|
26
|
+
# cpg.on_message do |message, member|
|
27
|
+
# puts "Received #{message}"
|
28
|
+
# end
|
29
|
+
# puts "Member node IDs: #{cpg.members.map {|m| m.nodeid}.join(" ")}"
|
30
|
+
# cpg.send "hello"
|
31
|
+
# loop do
|
32
|
+
# cpg.dispatch
|
33
|
+
# end
|
34
|
+
|
35
|
+
class Corosync::CPG
|
36
|
+
# The IO object containing the file descriptor events and messages come across.
|
37
|
+
# You can use this to check for activity, but do not read anything from it.
|
38
|
+
# @return [IO]
|
39
|
+
attr_reader :fd
|
40
|
+
|
41
|
+
# Members currently in the group.
|
42
|
+
# @return [Corosync::CPG::MemberList]
|
43
|
+
attr_reader :members
|
44
|
+
|
45
|
+
# Name of the currently joined group
|
46
|
+
# @return [String]
|
47
|
+
attr_reader :group
|
48
|
+
|
49
|
+
# Creates a new CPG connection to the CPG service.
|
50
|
+
# You can spawn as many connections as you like in a single process, but each connection can only belong to a single group.
|
51
|
+
# If you get an *ERR_LIBRARY* error, corosync is likely not running.
|
52
|
+
# If you get an *EACCESS* error, you're likely not running as root (or havent set a `uidgid` directive in the config).
|
53
|
+
#
|
54
|
+
# @param group [String] The name of the group to join. If not provided, you must call {#join} later.
|
55
|
+
#
|
56
|
+
# @return [void]
|
57
|
+
def initialize(group = nil)
|
58
|
+
# The model has to be preserved so it doesn't get garbage collected.
|
59
|
+
# Apparently CPG needs to reference it long after initialization :-(
|
60
|
+
# (cpg.c:423)
|
61
|
+
@model = Corosync::CpgModelV1DataT.new
|
62
|
+
@model[:cpg_deliver_fn] = self.method(:callback_deliver)
|
63
|
+
@model[:cpg_confchg_fn] = self.method(:callback_confchg)
|
64
|
+
@model[:cpg_totem_confchg_fn] = self.method(:callback_totem_confchg)
|
65
|
+
|
66
|
+
@group = nil
|
67
|
+
@fd = nil
|
68
|
+
@handle = nil
|
69
|
+
@members = Corosync::CPG::MemberList.new
|
70
|
+
|
71
|
+
join group if group
|
72
|
+
end
|
73
|
+
|
74
|
+
# Connect to the CPG service.
|
75
|
+
# @return [void]
|
76
|
+
def connect
|
77
|
+
handle_ptr = FFI::MemoryPointer.new(Corosync.find_type(:cpg_handle_t))
|
78
|
+
cs_error = Corosync.cpg_model_initialize(handle_ptr, Corosync::CPG_MODEL_V1, @model.pointer, nil);
|
79
|
+
if cs_error != :ok then
|
80
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to connect to corosync"
|
81
|
+
end
|
82
|
+
@handle = handle_ptr.read_uint64
|
83
|
+
|
84
|
+
fd_ptr = FFI::MemoryPointer.new(:int)
|
85
|
+
cs_error = Corosync.cpg_fd_get(@handle, fd_ptr)
|
86
|
+
if cs_error != :ok then
|
87
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to get handle descriptor"
|
88
|
+
end
|
89
|
+
@fd = IO.new(fd_ptr.read_int)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Shuts down the connection to the CPG service.
|
93
|
+
# @return [void]
|
94
|
+
def finalize
|
95
|
+
return if @handle.nil?
|
96
|
+
|
97
|
+
cs_error = Corosync.cpg_finalize(@handle)
|
98
|
+
if cs_error != :ok then
|
99
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to perform finalize"
|
100
|
+
end
|
101
|
+
|
102
|
+
@group = nil
|
103
|
+
@fd = nil
|
104
|
+
@model = nil
|
105
|
+
@handle = nil
|
106
|
+
@members = Corosync::CPG::MemberList.new
|
107
|
+
|
108
|
+
true
|
109
|
+
end
|
110
|
+
alias_method :close, :finalize
|
111
|
+
|
112
|
+
# Join the specified closed process group.
|
113
|
+
# Note that the library will automatically make a call to {#dispatch} upon join. This is so that it can obtain a group membership list. If you wish to receive the initial join message, you must register the callback with {#on_confchg} before calling {#join}.
|
114
|
+
# @param name [String] Name of the group. Maximum length of 128 characters.
|
115
|
+
# @return [void]
|
116
|
+
def join(name)
|
117
|
+
connect if @handle.nil?
|
118
|
+
|
119
|
+
cpg_name = Corosync::CpgName.new
|
120
|
+
cpg_name[:value] = name
|
121
|
+
cpg_name[:length] = name.size
|
122
|
+
cs_error = Corosync.cpg_join(@handle, cpg_name)
|
123
|
+
if cs_error != :ok then
|
124
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to join group"
|
125
|
+
end
|
126
|
+
|
127
|
+
dispatch
|
128
|
+
|
129
|
+
@group = name
|
130
|
+
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
# Leave the current closed process group.
|
135
|
+
# @return [void]
|
136
|
+
def leave()
|
137
|
+
return if !@group
|
138
|
+
cpg_name = Corosync::CpgName.new
|
139
|
+
cpg_name[:value] = @group
|
140
|
+
cpg_name[:length] = @group.size
|
141
|
+
|
142
|
+
# we can't join multiple groups, so I dont know why corosync requires you to specify the group name
|
143
|
+
cs_error = Corosync.cpg_leave(@handle, cpg_name)
|
144
|
+
if cs_error != :ok then
|
145
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to leave group"
|
146
|
+
end
|
147
|
+
|
148
|
+
@group = nil
|
149
|
+
@members = Corosync::CPG::MemberList.new
|
150
|
+
end
|
151
|
+
|
152
|
+
# Checks for a single pending events and triggers the appropriate callback if found.
|
153
|
+
# @param timeout [Integer] How long to wait for an event.
|
154
|
+
# * +-1+: Indefinite. Wait forever
|
155
|
+
# * +0+: Non-blocking. If there isn't a pending event, return immediately
|
156
|
+
# * +>0+: Wait the specified number of seconds.
|
157
|
+
# @return [Boolean] Returns +True+ if an event was triggered. Otherwise +False+.
|
158
|
+
def dispatch(timeout = -1)
|
159
|
+
if !timeout != 0 then
|
160
|
+
timeout = nil if timeout == -1
|
161
|
+
select([@fd], [], [], timeout)
|
162
|
+
end
|
163
|
+
cs_error = Corosync.cpg_dispatch(@handle, Corosync::CS_DISPATCH_ONE_NONBLOCKING)
|
164
|
+
return false if cs_error == :err_try_again
|
165
|
+
if cs_error != :ok then
|
166
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting perform dispatch"
|
167
|
+
end
|
168
|
+
return true
|
169
|
+
end
|
170
|
+
|
171
|
+
# Proc to call when a message is received.
|
172
|
+
# @param block [Proc] Proc to call when a message is received. Pass +Nil+ to disable the callback.
|
173
|
+
# @yieldparam message [String] Message content.
|
174
|
+
# @yieldparam member [Corosync::CPG::Member] Member from which the message came
|
175
|
+
# @return [void]
|
176
|
+
def on_message(&block)
|
177
|
+
@callback_deliver = block
|
178
|
+
end
|
179
|
+
def callback_deliver(handle, group_name_p, nodeid, pid, message_p, message_len)
|
180
|
+
return if !@callback_deliver
|
181
|
+
message = message_p.read_bytes(message_len)
|
182
|
+
@callback_deliver.call(message, Corosync::CPG::Member.new(nodeid, pid))
|
183
|
+
end
|
184
|
+
private :callback_deliver
|
185
|
+
|
186
|
+
# Proc to call when a node joins/leaves the group.
|
187
|
+
# If this is set before calling {#join}, it will be called when joining the group.
|
188
|
+
# @param block [Proc] Proc to call when a node joins/leaves the group. Pass +Nil+ to disable the callback.
|
189
|
+
# @yieldparam member_list [Corosync::CPG::MemberList] Members in the group after the change completed.
|
190
|
+
# @yieldparam left_list [Corosync::CPG::MemberList] Members who left the group.
|
191
|
+
# @yieldparam joined_list [Corosync::CPG::MemberList] Members who joined the group.
|
192
|
+
# @return [void]
|
193
|
+
def on_confchg(&block)
|
194
|
+
@callback_confchg = block
|
195
|
+
end
|
196
|
+
def callback_confchg(handle, group_name_p, member_list_p, member_list_size, left_list_p, left_list_size, joined_list_p, joined_list_size)
|
197
|
+
member_list = Corosync::CPG::MemberList.new
|
198
|
+
member_list_size.times do |i|
|
199
|
+
member_list << (member_list_p + i * Corosync::CpgAddress.size)
|
200
|
+
end
|
201
|
+
|
202
|
+
@members = member_list.dup.freeze
|
203
|
+
|
204
|
+
return if !@callback_confchg # no point in continuing otherwise
|
205
|
+
|
206
|
+
left_list = Corosync::CPG::MemberList.new
|
207
|
+
left_list_size.times do |i|
|
208
|
+
left_list << (left_list_p + i * Corosync::CpgAddress.size)
|
209
|
+
end
|
210
|
+
|
211
|
+
joined_list = Corosync::CPG::MemberList.new
|
212
|
+
joined_list_size.times do |i|
|
213
|
+
joined_list << (joined_list_p + i * Corosync::CpgAddress.size)
|
214
|
+
end
|
215
|
+
|
216
|
+
@callback_confchg.call(member_list, left_list, joined_list)
|
217
|
+
end
|
218
|
+
private :callback_confchg
|
219
|
+
|
220
|
+
# Proc to call when a node joins/leaves the cluster.
|
221
|
+
# If this is set before calling {#connect} or {#join}, it will be called when connecting to the cluster.
|
222
|
+
# @param block [Proc] Proc to call when a node joins/leaves the cluster.
|
223
|
+
# @yieldparam ring_id [Integer] Ring ID change occurred on.
|
224
|
+
# @yieldparam member_list [Array<Integer>] Node ID of members in the cluster after the change completed.
|
225
|
+
# @return [void]
|
226
|
+
def on_totem_confchg(&block)
|
227
|
+
@callback_totem_confchg = block
|
228
|
+
end
|
229
|
+
def callback_totem_confchg(handle, ring_id, member_list_size, member_list_p)
|
230
|
+
return if !@callback_totem_confchg
|
231
|
+
member_list = member_list_size.times.collect do |i|
|
232
|
+
(member_list_p + i * Corosync.find_type(:uint32).size).read_uint32
|
233
|
+
end
|
234
|
+
@callback_totem_confchg.call(ring_id, member_list)
|
235
|
+
end
|
236
|
+
private :callback_totem_confchg
|
237
|
+
|
238
|
+
# The node ID of ourself.
|
239
|
+
# @!attribute nodeid [r]
|
240
|
+
# @return [Integer]
|
241
|
+
def nodeid
|
242
|
+
nodeid_p = FFI::MemoryPointer.new(:uint)
|
243
|
+
cs_error = Corosync.cpg_local_get(@handle, nodeid_p)
|
244
|
+
if cs_error != :ok then
|
245
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to get nodeid"
|
246
|
+
end
|
247
|
+
nodeid_p.read_uint
|
248
|
+
end
|
249
|
+
|
250
|
+
# Returns the {Corosync::CPG::Member member} object describing ourself.
|
251
|
+
# @return [Corosync::CPG::Member]
|
252
|
+
def member
|
253
|
+
Corosync::CPG::Member.new(self.nodeid, $$)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Send one or more messages to the group.
|
257
|
+
# Sending multiple messages through a single call to {#send} ensures that the messages will be delivered consecutively without another message in the middle.
|
258
|
+
# @param messages [Array<String>,String] The message(s) to send.
|
259
|
+
# @return [void]
|
260
|
+
def send(messages)
|
261
|
+
messages = [messages] if !messages.is_a?(Array)
|
262
|
+
|
263
|
+
iovec_list_p = FFI::MemoryPointer.new(Corosync::Iovec, messages.size)
|
264
|
+
iovec_list = messages.size.times.collect do |i|
|
265
|
+
iovec = Corosync::Iovec.new(iovec_list_p + i * Corosync::Iovec.size)
|
266
|
+
message = messages[i].to_s
|
267
|
+
iovec[:iov_base] = FFI::MemoryPointer.from_string(message)
|
268
|
+
iovec[:iov_len] = message.size
|
269
|
+
end
|
270
|
+
iovec_len = messages.size
|
271
|
+
|
272
|
+
cs_error = Corosync.cpg_mcast_joined(@handle, Corosync::CPG_TYPE_AGREED, iovec_list_p, iovec_len)
|
273
|
+
if cs_error != :ok then
|
274
|
+
raise StandardError, "Received #{cs_error.to_s.upcase} attempting to send a message"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
data/lib/corosync.rb
CHANGED
data/lib/version.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Corosync::CPG::Member do
|
4
|
+
before(:all) do
|
5
|
+
@member1 = Corosync::CPG::Member.new(1001,20001)
|
6
|
+
@member2 = Corosync::CPG::Member.new(1001,20002)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'checks equality' do
|
10
|
+
member = Corosync::CPG::Member.new(1001,20001)
|
11
|
+
|
12
|
+
expect(member).to eq(@member1)
|
13
|
+
expect(member).not_to eq(@member2)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'hashes consistently' do
|
17
|
+
member = Corosync::CPG::Member.new(1001,20001)
|
18
|
+
|
19
|
+
expect(member.hash).to eq(@member1.hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'converts to string' do
|
23
|
+
expect(@member1.to_s).to eq("1001:20001")
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has nodeid accessor' do
|
27
|
+
expect(@member1.nodeid).to eq(1001)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'has pid accessor' do
|
31
|
+
expect(@member1.pid).to eq(20001)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'can create from CpgAddress pointer' do
|
35
|
+
cpgaddress = Corosync::CpgAddress.new
|
36
|
+
cpgaddress[:nodeid] = 1001
|
37
|
+
cpgaddress[:pid] = 20001
|
38
|
+
member = Corosync::CPG::Member.new(cpgaddress.pointer)
|
39
|
+
|
40
|
+
expect(member).to eq(@member1)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Corosync::CPG::MemberList do
|
4
|
+
before(:all) do
|
5
|
+
@list1 = Corosync::CPG::MemberList.new
|
6
|
+
@list1 << Corosync::CPG::Member.new(1000,20000)
|
7
|
+
@list1 << Corosync::CPG::Member.new(1001,20001)
|
8
|
+
@list1 << Corosync::CPG::Member.new(1002,20002)
|
9
|
+
@list1 << Corosync::CPG::Member.new(1003,20003)
|
10
|
+
@list1 << Corosync::CPG::Member.new(1004,20004)
|
11
|
+
@list2 = Corosync::CPG::MemberList.new
|
12
|
+
@list2 << Corosync::CPG::Member.new(1000,20010)
|
13
|
+
@list2 << Corosync::CPG::Member.new(1002,20002)
|
14
|
+
@list2 << Corosync::CPG::Member.new(1005,20005)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'knows its size' do
|
18
|
+
expect(@list1.size).to eq(5)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'ands the list' do
|
22
|
+
list = @list1 & @list2
|
23
|
+
|
24
|
+
expect(list.size).to eq(1)
|
25
|
+
expect(list).to include(Corosync::CPG::Member.new(1002,20002))
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'dups the list' do
|
29
|
+
list = @list1.dup
|
30
|
+
|
31
|
+
expect(list.first).to eq(@list1.first)
|
32
|
+
expect(list.first.object_id).not_to eq(@list1.first.object_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can check inclusion' do
|
36
|
+
target = Corosync::CPG::Member.new(1001,20001)
|
37
|
+
|
38
|
+
expect(@list1).to include(target)
|
39
|
+
expect(@list2).not_to include(target)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'adds a member' do
|
43
|
+
list = @list1.dup
|
44
|
+
target = Corosync::CPG::Member.new(1006,20006)
|
45
|
+
list << target
|
46
|
+
|
47
|
+
expect(list.size).to eq(@list1.size + 1)
|
48
|
+
expect(list).to include(target)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'deletes a member' do
|
52
|
+
list = @list1.dup
|
53
|
+
target = Corosync::CPG::Member.new(1001,20001)
|
54
|
+
list.delete(target)
|
55
|
+
|
56
|
+
expect(list.size).to eq(@list1.size - 1)
|
57
|
+
expect(list).not_to include(target)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Corosync::CPG do
|
4
|
+
context '#initialize offline' do
|
5
|
+
before(:all) do
|
6
|
+
@cpg = Corosync::CPG.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'creates a CPG object' do
|
10
|
+
expect(@cpg).to be_an_instance_of(Corosync::CPG)
|
11
|
+
expect(@cpg.fd).to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'connects' do
|
15
|
+
@cpg.connect
|
16
|
+
|
17
|
+
expect(@cpg.fd).to be_an_instance_of(IO)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'joins a group' do
|
21
|
+
group_name = "RSPEC-#{Random.rand(2 ** 32)}"
|
22
|
+
@cpg.join(group_name)
|
23
|
+
|
24
|
+
expect(@cpg.group).to eq(group_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has ourself as a member' do
|
28
|
+
expect(@cpg.members).to include(@cpg.member)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context '#initialize with join' do
|
33
|
+
before(:all) do
|
34
|
+
@group_name = "RSPEC-#{Random.rand(2 ** 32)}"
|
35
|
+
@cpg = Corosync::CPG.new(@group_name)
|
36
|
+
end
|
37
|
+
it 'creates a CPG object and joins a group' do
|
38
|
+
expect(@cpg).to be_an_instance_of(Corosync::CPG)
|
39
|
+
expect(@cpg.fd).to be_an_instance_of(IO)
|
40
|
+
expect(@cpg.group).to eq(@group_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'has ourself as a member' do
|
44
|
+
expect(@cpg.members).to include(@cpg.member)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,47 +1,80 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: corosync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
-
|
7
|
+
- Patrick Hemmer
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-05
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
|
11
|
+
date: 2013-11-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
description: An interface to the Corosync clustering services.
|
28
|
+
email: patrick.hemmer@gmail.com
|
16
29
|
executables: []
|
17
30
|
extensions: []
|
18
31
|
extra_rdoc_files: []
|
19
32
|
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- Gemfile.lock
|
36
|
+
- LICENSE
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- corosync.gemspec
|
40
|
+
- examples/example.rb
|
41
|
+
- ffi/common.i
|
42
|
+
- ffi/common.rb
|
43
|
+
- ffi/cpg.i
|
44
|
+
- ffi/cpg.rb
|
45
|
+
- ffi/service.i.erb
|
20
46
|
- lib/corosync.rb
|
21
|
-
- lib/corosync/
|
22
|
-
- lib/corosync/
|
23
|
-
|
24
|
-
|
47
|
+
- lib/corosync/cpg.rb
|
48
|
+
- lib/corosync/cpg/member.rb
|
49
|
+
- lib/corosync/cpg/member_list.rb
|
50
|
+
- lib/version.rb
|
51
|
+
- spec/corosync/cpg.rb
|
52
|
+
- spec/corosync/cpg/member.rb
|
53
|
+
- spec/corosync/cpg/member_list.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
homepage: http://github.com/phemmer/ruby-corosync/
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata: {}
|
25
59
|
post_install_message:
|
26
60
|
rdoc_options: []
|
27
61
|
require_paths:
|
28
62
|
- lib
|
29
63
|
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
-
none: false
|
31
64
|
requirements:
|
32
|
-
- -
|
65
|
+
- - '>='
|
33
66
|
- !ruby/object:Gem::Version
|
34
67
|
version: '0'
|
35
68
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
-
none: false
|
37
69
|
requirements:
|
38
|
-
- -
|
70
|
+
- - '>='
|
39
71
|
- !ruby/object:Gem::Version
|
40
72
|
version: '0'
|
41
73
|
requirements: []
|
42
74
|
rubyforge_project:
|
43
|
-
rubygems_version:
|
75
|
+
rubygems_version: 2.0.3
|
44
76
|
signing_key:
|
45
|
-
specification_version:
|
46
|
-
summary:
|
77
|
+
specification_version: 4
|
78
|
+
summary: Corosync library interface
|
47
79
|
test_files: []
|
80
|
+
has_rdoc:
|
data/lib/corosync/cluster.rb
DELETED
@@ -1,105 +0,0 @@
|
|
1
|
-
module Corosync
|
2
|
-
class Cluster
|
3
|
-
COMMAND = "crm"
|
4
|
-
|
5
|
-
def initialize
|
6
|
-
parse
|
7
|
-
end
|
8
|
-
|
9
|
-
def healthy
|
10
|
-
if @online.size < @nodes_configured
|
11
|
-
false
|
12
|
-
elsif @resources_started.size < @resources_configured
|
13
|
-
false
|
14
|
-
elsif @failed_actions.size != 0
|
15
|
-
false
|
16
|
-
else
|
17
|
-
true
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def health_description
|
22
|
-
description = ""
|
23
|
-
ok = healthy
|
24
|
-
|
25
|
-
if ok
|
26
|
-
description += "OK"
|
27
|
-
else
|
28
|
-
description += "ERROR"
|
29
|
-
end
|
30
|
-
description += ", #{@nodes_configured} nodes configured"
|
31
|
-
if @online.size != 0
|
32
|
-
description += ", #{@online.size} nodes online #{@online.inspect}"
|
33
|
-
end
|
34
|
-
if !ok && @offline.size != 0
|
35
|
-
description += ", #{@offline.size} nodes offline #{@offline.inspect}"
|
36
|
-
end
|
37
|
-
description += ", #{@resources_configured} resources configured"
|
38
|
-
if @resources_started.size != 0
|
39
|
-
description += ", #{@resources_started.size} resources started #{@resources_started.inspect}"
|
40
|
-
end
|
41
|
-
if !ok && @resources_stopped.size != 0
|
42
|
-
description += ", #{@resources_stopped.size} resources stopped #{@resources_stopped.inspect}"
|
43
|
-
end
|
44
|
-
if !ok && @failed_actions.size != 0
|
45
|
-
description += ", Failed actions #{@failed_actions.inspect}"
|
46
|
-
end
|
47
|
-
description.gsub("\"", "")
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
def parse
|
53
|
-
# get status info
|
54
|
-
cmd = COMMAND + " status"
|
55
|
-
output = `#{cmd}`
|
56
|
-
|
57
|
-
# parse output
|
58
|
-
@nodes_configured = []
|
59
|
-
if /(\d+) Nodes configured/.match(output)
|
60
|
-
@nodes_configured = Regexp.last_match[1].to_i
|
61
|
-
else
|
62
|
-
raise "Can't parse output"
|
63
|
-
end
|
64
|
-
|
65
|
-
@resources_configured = []
|
66
|
-
if /(\d+) Resources configured/.match(output)
|
67
|
-
@resources_configured = Regexp.last_match[1].to_i
|
68
|
-
else
|
69
|
-
raise "Can't parse output"
|
70
|
-
end
|
71
|
-
|
72
|
-
@online = []
|
73
|
-
if /Online: \[ ([\w ]*) \]/.match(output)
|
74
|
-
@online = Regexp.last_match[1].split(' ')
|
75
|
-
end
|
76
|
-
|
77
|
-
@offline = []
|
78
|
-
if /OFFLINE: \[ ([\w ]*) \]/.match(output)
|
79
|
-
@offline = Regexp.last_match[1].split(' ')
|
80
|
-
end
|
81
|
-
|
82
|
-
raise "Can't parse output" if @online.empty? && @offline.empty?
|
83
|
-
|
84
|
-
@resources_started = []
|
85
|
-
@resources_stopped = []
|
86
|
-
resources = output.scan(/^\s*(\w+)\s*\([\w:]+\):\s*([\w ]*)$/)
|
87
|
-
resources.each do |res|
|
88
|
-
status = res[1].split(' ')
|
89
|
-
if status.include? 'Started'
|
90
|
-
@resources_started << res[0]
|
91
|
-
else
|
92
|
-
@resources_stopped << res[0]
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
raise "Can't parse output" if @resources_started.empty? && @resources_stopped.empty?
|
97
|
-
|
98
|
-
@failed_actions = []
|
99
|
-
if (/Failed actions:.*/m).match(output)
|
100
|
-
failed = Regexp.last_match[0].scan(/^\s+.*$/)
|
101
|
-
@failed_actions = failed.map { |e| e.lstrip }
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
data/lib/corosync/resource.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
module Corosync
|
2
|
-
class Resource
|
3
|
-
COMMAND = "crm resource"
|
4
|
-
|
5
|
-
attr_reader :status, :started_on, :started_locally
|
6
|
-
|
7
|
-
def initialize(name)
|
8
|
-
@name = name
|
9
|
-
parse
|
10
|
-
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def parse
|
15
|
-
# get status info
|
16
|
-
cmd = COMMAND + " status #{@name}"
|
17
|
-
output = `#{cmd}`
|
18
|
-
|
19
|
-
# parse it
|
20
|
-
if output.include? "NOT running"
|
21
|
-
@status = "stopped"
|
22
|
-
elsif /is running on: (\w*)/.match(output)
|
23
|
-
@status = "started"
|
24
|
-
@started_on = Regexp.last_match[1]
|
25
|
-
|
26
|
-
# get hostname of the current machine
|
27
|
-
current_hostname = `hostname`.gsub!("\n", "")
|
28
|
-
@started_locally = @started_on == current_hostname
|
29
|
-
else
|
30
|
-
raise "Can't parse output"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|