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 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
@@ -0,0 +1,12 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem 'ffi'
5
+
6
+ group :development do
7
+ gem 'ffi-swig-generator', :git => 'git://github.com/ffi/ffi-swig-generator.git'
8
+ gem 'rake', :require => false
9
+ gem 'yard'
10
+ gem 'rdoc'
11
+ gem 'rspec'
12
+ end
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
@@ -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
@@ -0,0 +1,14 @@
1
+ %module Corosync
2
+ %{
3
+ require File.expand_path('../common.rb', __FILE__)
4
+
5
+ module Corosync
6
+ extend FFI::Library
7
+ ffi_lib 'libcpg'
8
+
9
+ %}
10
+ %import "common.i"
11
+ %include <corosync/cpg.h>
12
+ %{
13
+ end
14
+ %}
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,14 @@
1
+ %module Corosync
2
+ %{
3
+ require File.expand_path('../common.rb', __FILE__)
4
+
5
+ module Corosync
6
+ extend FFI::Library
7
+ ffi_lib 'lib<%= service %>'
8
+
9
+ %}
10
+ %import "common.i"
11
+ %include <corosync/<%= service %>.h>
12
+ %{
13
+ end
14
+ %}
@@ -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
@@ -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
@@ -1,2 +1,6 @@
1
- require 'corosync/resource'
2
- require 'corosync/cluster'
1
+ $:.unshift File.expand_path('../', __FILE__)
2
+ require File.expand_path('../../ffi/common.rb', __FILE__)
3
+ require 'version'
4
+
5
+ module Corosync
6
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Corosync
2
+ GEM_VERSION = '0.0.2'
3
+ end
@@ -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
@@ -0,0 +1,2 @@
1
+ # we have to use the full path because rspec puts itself higher on the list, and we end up requiring `spec/corosync/cpg`
2
+ require File.expand_path('../../lib/corosync/cpg', __FILE__)
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.1
5
- prerelease:
4
+ version: 0.0.2
6
5
  platform: ruby
7
6
  authors:
8
- - Pavel Ivanov
7
+ - Patrick Hemmer
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-05-06 00:00:00.000000000 Z
13
- dependencies: []
14
- description:
15
- email: ivpavig@gmail.com
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/resource.rb
22
- - lib/corosync/cluster.rb
23
- homepage:
24
- licenses: []
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: 1.8.11
75
+ rubygems_version: 2.0.3
44
76
  signing_key:
45
- specification_version: 3
46
- summary: A simple wrapper for corosync cmd tool 'crm'
77
+ specification_version: 4
78
+ summary: Corosync library interface
47
79
  test_files: []
80
+ has_rdoc:
@@ -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
@@ -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