arborist 0.0.1.pre20160106113421
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +4 -0
- data/.simplecov +9 -0
- data/ChangeLog +417 -0
- data/Events.md +20 -0
- data/History.md +4 -0
- data/LICENSE +29 -0
- data/Manifest.txt +72 -0
- data/Monitors.md +141 -0
- data/Nodes.md +0 -0
- data/Observers.md +72 -0
- data/Protocol.md +214 -0
- data/README.md +75 -0
- data/Rakefile +81 -0
- data/TODO.md +24 -0
- data/bin/amanagerd +10 -0
- data/bin/amonitord +12 -0
- data/bin/aobserverd +12 -0
- data/lib/arborist.rb +182 -0
- data/lib/arborist/client.rb +191 -0
- data/lib/arborist/event.rb +61 -0
- data/lib/arborist/event/node_acked.rb +18 -0
- data/lib/arborist/event/node_delta.rb +20 -0
- data/lib/arborist/event/node_matching.rb +34 -0
- data/lib/arborist/event/node_update.rb +19 -0
- data/lib/arborist/event/sys_reloaded.rb +15 -0
- data/lib/arborist/exceptions.rb +21 -0
- data/lib/arborist/manager.rb +508 -0
- data/lib/arborist/manager/event_publisher.rb +97 -0
- data/lib/arborist/manager/tree_api.rb +207 -0
- data/lib/arborist/mixins.rb +363 -0
- data/lib/arborist/monitor.rb +377 -0
- data/lib/arborist/monitor/socket.rb +163 -0
- data/lib/arborist/monitor_runner.rb +217 -0
- data/lib/arborist/node.rb +700 -0
- data/lib/arborist/node/host.rb +87 -0
- data/lib/arborist/node/root.rb +60 -0
- data/lib/arborist/node/service.rb +112 -0
- data/lib/arborist/observer.rb +176 -0
- data/lib/arborist/observer/action.rb +125 -0
- data/lib/arborist/observer/summarize.rb +105 -0
- data/lib/arborist/observer_runner.rb +181 -0
- data/lib/arborist/subscription.rb +82 -0
- data/spec/arborist/client_spec.rb +282 -0
- data/spec/arborist/event/node_update_spec.rb +71 -0
- data/spec/arborist/event_spec.rb +64 -0
- data/spec/arborist/manager/event_publisher_spec.rb +66 -0
- data/spec/arborist/manager/tree_api_spec.rb +458 -0
- data/spec/arborist/manager_spec.rb +442 -0
- data/spec/arborist/mixins_spec.rb +195 -0
- data/spec/arborist/monitor/socket_spec.rb +195 -0
- data/spec/arborist/monitor_runner_spec.rb +152 -0
- data/spec/arborist/monitor_spec.rb +251 -0
- data/spec/arborist/node/host_spec.rb +104 -0
- data/spec/arborist/node/root_spec.rb +29 -0
- data/spec/arborist/node/service_spec.rb +98 -0
- data/spec/arborist/node_spec.rb +552 -0
- data/spec/arborist/observer/action_spec.rb +205 -0
- data/spec/arborist/observer/summarize_spec.rb +294 -0
- data/spec/arborist/observer_spec.rb +146 -0
- data/spec/arborist/subscription_spec.rb +71 -0
- data/spec/arborist_spec.rb +146 -0
- data/spec/data/monitors/pings.rb +80 -0
- data/spec/data/monitors/port_checks.rb +27 -0
- data/spec/data/monitors/system_resources.rb +30 -0
- data/spec/data/monitors/web_services.rb +17 -0
- data/spec/data/nodes/duir.rb +20 -0
- data/spec/data/nodes/localhost.rb +15 -0
- data/spec/data/nodes/sidonie.rb +29 -0
- data/spec/data/nodes/yevaud.rb +26 -0
- data/spec/data/observers/auditor.rb +23 -0
- data/spec/data/observers/webservices.rb +18 -0
- data/spec/spec_helper.rb +117 -0
- metadata +368 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist'
|
6
|
+
require 'arborist/node/host'
|
7
|
+
require 'arborist/node/service'
|
8
|
+
require 'arborist/monitor/socket'
|
9
|
+
|
10
|
+
|
11
|
+
describe Arborist::Monitor::Socket do
|
12
|
+
|
13
|
+
describe 'TCP' do
|
14
|
+
|
15
|
+
let( :described_class ) { Arborist::Monitor::Socket::TCP }
|
16
|
+
|
17
|
+
let( :host_node ) do
|
18
|
+
Arborist::Node.create( 'host', 'test' ) do
|
19
|
+
description "Test host node with a few TCP services"
|
20
|
+
address '192.168.26.1'
|
21
|
+
|
22
|
+
tags :testing
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
let( :default_timeout ) { described_class::DEFAULT_OPTIONS[:timeout] }
|
27
|
+
|
28
|
+
let( :www_service_node ) { host_node.service('www') }
|
29
|
+
let( :ssh_service_node ) { host_node.service('ssh') }
|
30
|
+
let( :nat_pmp_service_node ) { host_node.service('nat-pmp', port: 5351) }
|
31
|
+
|
32
|
+
let( :service_nodes ) {[ www_service_node, ssh_service_node, nat_pmp_service_node ]}
|
33
|
+
let( :service_nodes_hash ) do
|
34
|
+
service_nodes.each_with_object({}) do |node, accum|
|
35
|
+
accum[ node.identifier ] = node.fetch_values
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# it_behaves_like "an Arborist Monitor"
|
41
|
+
|
42
|
+
|
43
|
+
def sockaddr_for( node )
|
44
|
+
return Socket.sockaddr_in( node.port, node.addresses.first.to_s )
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def make_successful_mock_socket( node )
|
49
|
+
address = Addrinfo.tcp( node.addresses.first.to_s, node.port )
|
50
|
+
socket = instance_double( Socket, "#{node.identifier} socket", remote_address: address )
|
51
|
+
errors = [ IO::EINPROGRESSWaitWritable, Errno::EISCONN ]
|
52
|
+
|
53
|
+
expect( socket ).to receive( :connect_nonblock ) do |addr|
|
54
|
+
expect( addr ).to eq( sockaddr_for(node) )
|
55
|
+
raise errors.shift
|
56
|
+
end.at_least( :once )
|
57
|
+
|
58
|
+
return socket
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def make_initial_error_mock_socket( node, error_class, message )
|
63
|
+
address = Addrinfo.tcp( node.addresses.first.to_s, node.port )
|
64
|
+
socket = instance_double( Socket, "#{node.identifier} socket", remote_address: address )
|
65
|
+
|
66
|
+
expect( socket ).to receive( :connect_nonblock ).
|
67
|
+
with( sockaddr_for(node) ).
|
68
|
+
and_raise( error_class.new(message) )
|
69
|
+
|
70
|
+
return socket
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def make_wait_error_mock_socket( node, error_class, message )
|
75
|
+
address = Addrinfo.tcp( node.addresses.first.to_s, node.port )
|
76
|
+
socket = instance_double( Socket, "#{node.identifier} socket", remote_address: address )
|
77
|
+
errors = [ IO::EINPROGRESSWaitWritable, error_class.new(message) ]
|
78
|
+
|
79
|
+
expect( socket ).to receive( :connect_nonblock ) do |addr|
|
80
|
+
expect( addr ).to eq( sockaddr_for(node) )
|
81
|
+
raise errors.shift
|
82
|
+
end.at_least( :once )
|
83
|
+
|
84
|
+
return socket
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
it "opens TCP connections to the ports of the nodes" do
|
89
|
+
fake_sockets = service_nodes.map do |node|
|
90
|
+
make_successful_mock_socket( node )
|
91
|
+
end
|
92
|
+
|
93
|
+
expect( Socket ).to receive( :new ).and_return( *fake_sockets )
|
94
|
+
expect( IO ).to receive( :select ).
|
95
|
+
with( nil, fake_sockets, nil, kind_of(Numeric) ).
|
96
|
+
and_return( [nil, fake_sockets, nil] )
|
97
|
+
|
98
|
+
expect( fake_sockets ).to all( receive( :close ) )
|
99
|
+
|
100
|
+
result = described_class.run( service_nodes_hash )
|
101
|
+
|
102
|
+
expect( result ).to be_a( Hash )
|
103
|
+
expect( result ).to include( *service_nodes.map(&:identifier) )
|
104
|
+
expect( result.values ).to all( include(
|
105
|
+
tcp_socket_connect: a_hash_including(:time, :duration)
|
106
|
+
) )
|
107
|
+
expect( result.map {|_, res| res[:tcp_socket_connect][:time]} ).to all( be_a(String) )
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
it "updates nodes with an error on a SocketError" do
|
112
|
+
socket = make_initial_error_mock_socket( www_service_node, SocketError,
|
113
|
+
"getaddrinfo: nodename nor servname provided, or not known" )
|
114
|
+
allow( Socket ).to receive( :new ).and_return( socket )
|
115
|
+
|
116
|
+
result = described_class.run( 'test-www' => www_service_node.fetch_values )
|
117
|
+
|
118
|
+
expect( result ).to be_a( Hash )
|
119
|
+
expect( result ).to include( 'test-www' )
|
120
|
+
expect(
|
121
|
+
result['test-www']
|
122
|
+
).to include( error: 'getaddrinfo: nodename nor servname provided, or not known' )
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
it "updates nodes with an error if the connection times out" do
|
127
|
+
socket = make_successful_mock_socket( www_service_node )
|
128
|
+
allow( Socket ).to receive( :new ).and_return( socket )
|
129
|
+
allow( socket ).to receive( :close )
|
130
|
+
allow( IO ).to receive( :select ) do
|
131
|
+
sleep 0.2
|
132
|
+
[nil, nil, nil]
|
133
|
+
end
|
134
|
+
|
135
|
+
result = described_class.new( timeout: 0.1 ).
|
136
|
+
run( 'test-www' => www_service_node.fetch_values )
|
137
|
+
|
138
|
+
expect( result ).to be_a( Hash )
|
139
|
+
expect( result ).to include( 'test-www' )
|
140
|
+
expect( result['test-www'] ).to include( error: 'Timeout after 0.100s' )
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
it "updates nodes with an error on a 'connection refused' error" do
|
145
|
+
socket = make_initial_error_mock_socket( www_service_node, Errno::ECONNREFUSED,
|
146
|
+
"the message" )
|
147
|
+
allow( Socket ).to receive( :new ).and_return( socket )
|
148
|
+
|
149
|
+
result = described_class.run( 'test-www' => www_service_node.fetch_values )
|
150
|
+
|
151
|
+
expect( result ).to be_a( Hash )
|
152
|
+
expect( result ).to include( 'test-www' )
|
153
|
+
expect( result['test-www'] ).to include( error: 'Connection refused - the message' )
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
it "updates nodes with an error on a 'host unreachable' error" do
|
158
|
+
socket = make_initial_error_mock_socket( www_service_node, Errno::EHOSTUNREACH,
|
159
|
+
"the message" )
|
160
|
+
allow( Socket ).to receive( :new ).and_return( socket )
|
161
|
+
|
162
|
+
result = described_class.run( 'test-www' => www_service_node.fetch_values )
|
163
|
+
|
164
|
+
expect( result ).to be_a( Hash )
|
165
|
+
expect( result ).to include( 'test-www' )
|
166
|
+
expect( result['test-www'] ).to include( error: 'No route to host - the message' )
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
it "updates nodes with an error on a 'getpeername' error" do
|
171
|
+
socket = make_wait_error_mock_socket( www_service_node, Errno::EINVAL, "getpeername(2)" )
|
172
|
+
allow( Socket ).to receive( :new ).and_return( socket )
|
173
|
+
allow( IO ).to receive( :select ).
|
174
|
+
with( nil, [socket], nil, kind_of(Numeric) ).
|
175
|
+
and_return( [nil, [socket], nil] )
|
176
|
+
allow( socket ).to receive( :close )
|
177
|
+
|
178
|
+
result = described_class.run( 'test-www' => www_service_node.fetch_values )
|
179
|
+
|
180
|
+
expect( result ).to be_a( Hash )
|
181
|
+
expect( result ).to include( 'test-www' )
|
182
|
+
expect( result['test-www'] ).to include( error: 'Invalid argument - getpeername(2)' )
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
it "can be instantiated to run with a different timeout" do
|
187
|
+
mon = described_class.new.with_timeout( 30 )
|
188
|
+
expect( mon.timeout ).to eq( 30 )
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
|
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/monitor_runner'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::MonitorRunner do
|
9
|
+
|
10
|
+
let( :zmq_loop ) { instance_double(ZMQ::Loop) }
|
11
|
+
let( :req_socket ) { instance_double(ZMQ::Socket::Req) }
|
12
|
+
let( :pollitem ) { instance_double(ZMQ::Pollitem) }
|
13
|
+
|
14
|
+
let( :runner ) do
|
15
|
+
obj = described_class.new
|
16
|
+
obj.reactor = zmq_loop
|
17
|
+
obj
|
18
|
+
end
|
19
|
+
|
20
|
+
let( :monitor_class ) { Class.new(Arborist::Monitor) }
|
21
|
+
|
22
|
+
let( :mon1 ) { monitor_class.new("testing monitor1") }
|
23
|
+
let( :mon2 ) { monitor_class.new("testing monitor2") { splay 10 } }
|
24
|
+
let( :mon3 ) { monitor_class.new("testing monitor3") }
|
25
|
+
let( :monitors ) {[ mon1, mon2, mon3 ]}
|
26
|
+
|
27
|
+
|
28
|
+
it "can load monitors from an enumerator that yields Arborist::Monitors" do
|
29
|
+
runner.load_monitors([ mon1, mon2, mon3 ])
|
30
|
+
expect( runner.monitors ).to include( mon1, mon2, mon3 )
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
describe "a runner with loaded monitors" do
|
35
|
+
|
36
|
+
before( :each ) do
|
37
|
+
allow( zmq_loop ).to receive( :register ).with( an_instance_of(ZMQ::Pollitem) )
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
it "registers its monitors to run on an interval and starts the ZMQ loop when run" do
|
42
|
+
runner.monitors.replace([ mon1 ])
|
43
|
+
|
44
|
+
interval_timer = instance_double( ZMQ::Timer )
|
45
|
+
expect( ZMQ::Timer ).to receive( :new ) do |i_delay, i_repeat, &i_block|
|
46
|
+
expect( i_delay ).to eq( mon1.interval )
|
47
|
+
expect( i_repeat ).to eq( 0 )
|
48
|
+
|
49
|
+
expect( runner.handler ).to receive( :run_monitor ).with( mon1 )
|
50
|
+
|
51
|
+
i_block.call
|
52
|
+
interval_timer
|
53
|
+
end
|
54
|
+
|
55
|
+
expect( zmq_loop ).to receive( :register_timer ).with( interval_timer )
|
56
|
+
expect( zmq_loop ).to receive( :start )
|
57
|
+
|
58
|
+
runner.run
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "delays registration of its interval timer if a monitor has a splay" do
|
63
|
+
runner.monitors.replace([ mon2 ])
|
64
|
+
|
65
|
+
interval_timer = instance_double( ZMQ::Timer )
|
66
|
+
expect( ZMQ::Timer ).to receive( :new ).with( mon2.interval, 0 ).
|
67
|
+
and_return( interval_timer )
|
68
|
+
|
69
|
+
timer = instance_double( ZMQ::Timer )
|
70
|
+
expect( ZMQ::Timer ).to receive( :new ) do |delay, repeat, &block|
|
71
|
+
expect( delay ).to be >= 0
|
72
|
+
expect( delay ).to be <= mon2.splay
|
73
|
+
expect( repeat ).to eq( 1 )
|
74
|
+
|
75
|
+
block.call
|
76
|
+
timer
|
77
|
+
end
|
78
|
+
|
79
|
+
expect( zmq_loop ).to receive( :register_timer ).with( interval_timer )
|
80
|
+
expect( zmq_loop ).to receive( :register_timer ).with( timer )
|
81
|
+
expect( zmq_loop ).to receive( :start )
|
82
|
+
|
83
|
+
runner.run
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
describe Arborist::MonitorRunner::Handler do
|
90
|
+
|
91
|
+
let( :tree_api_handler ) { Arborist::Manager::TreeAPI.new(:pollable, :manager) }
|
92
|
+
|
93
|
+
let( :handler ) { described_class.new( zmq_loop ) }
|
94
|
+
|
95
|
+
let( :node_tree ) {{
|
96
|
+
'router' => {
|
97
|
+
'addresses' => ['10.2.1.2', '1.2.3.4']
|
98
|
+
},
|
99
|
+
'server' => {
|
100
|
+
'addresses' => ['10.2.1.118']
|
101
|
+
}
|
102
|
+
}}
|
103
|
+
let( :ping_monitor_data ) {{
|
104
|
+
'router' => {'ping' => { 'rtt' => 22 }},
|
105
|
+
'server' => {'ping' => { 'rtt' => 8 }},
|
106
|
+
}}
|
107
|
+
|
108
|
+
|
109
|
+
it "can run a monitor using async ZMQ IO" do
|
110
|
+
expect( zmq_loop ).to receive( :register ).with( handler.pollitem )
|
111
|
+
|
112
|
+
# Queue up the monitor requests and register the socket as wanting to write
|
113
|
+
mon1.exec do |nodes|
|
114
|
+
ping_monitor_data
|
115
|
+
end
|
116
|
+
expect {
|
117
|
+
handler.run_monitor( mon1 )
|
118
|
+
}.to change { handler.registered? }.from( false ).to( true )
|
119
|
+
|
120
|
+
# Fetch
|
121
|
+
request = handler.client.make_fetch_request(
|
122
|
+
mon1.positive_criteria,
|
123
|
+
include_down: false,
|
124
|
+
properties: mon1.node_properties
|
125
|
+
)
|
126
|
+
response = tree_api_handler.successful_response( node_tree )
|
127
|
+
|
128
|
+
expect( handler.client.tree_api ).to receive( :send ).with( request )
|
129
|
+
expect( handler.client.tree_api ).to receive( :recv ).and_return( response )
|
130
|
+
|
131
|
+
expect {
|
132
|
+
handler.on_writable
|
133
|
+
}.to_not change { handler.registered? }
|
134
|
+
|
135
|
+
# Update
|
136
|
+
request = handler.client.make_update_request( ping_monitor_data )
|
137
|
+
response = tree_api_handler.successful_response( nil )
|
138
|
+
expect( handler.client.tree_api ).to receive( :send ).with( request )
|
139
|
+
expect( handler.client.tree_api ).to receive( :recv ).and_return( response )
|
140
|
+
|
141
|
+
# Unregister
|
142
|
+
expect( zmq_loop ).to receive( :remove ).with( handler.pollitem )
|
143
|
+
expect {
|
144
|
+
handler.on_writable
|
145
|
+
}.to change { handler.registered? }.from( true ).to( false )
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
@@ -0,0 +1,251 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/monitor'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::Monitor do
|
9
|
+
|
10
|
+
|
11
|
+
let( :trunk_node ) do
|
12
|
+
testing_node( 'trunk' ) do
|
13
|
+
properties['pork'] = 'nope'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
let( :branch_node ) do
|
17
|
+
testing_node( 'branch', 'trunk' ) do
|
18
|
+
properties['pork'] = 'yes'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
let( :leaf_node ) do
|
22
|
+
testing_node( 'leaf', 'branch' ) do
|
23
|
+
properties['pork'] = 'twice'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
let( :testing_nodes ) {[ trunk_node, branch_node, leaf_node ]}
|
29
|
+
|
30
|
+
|
31
|
+
it "can be created with just a description" do
|
32
|
+
mon = described_class.new( "the description" )
|
33
|
+
expect( mon ).to be_a( described_class )
|
34
|
+
expect( mon.description ).to eq( "the description" )
|
35
|
+
expect( mon.include_down? ).to be_falsey
|
36
|
+
expect( mon.interval ).to eq( Arborist::Monitor::DEFAULT_INTERVAL )
|
37
|
+
expect( mon.splay ).to eq( 0 )
|
38
|
+
expect( mon.positive_criteria ).to be_empty
|
39
|
+
expect( mon.negative_criteria ).to be_empty
|
40
|
+
expect( mon.node_properties ).to be_empty
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
it "yields itself to the provided block for the DSL" do
|
45
|
+
block_self = nil
|
46
|
+
mon = described_class.new( "testing monitor" ) do
|
47
|
+
block_self = self
|
48
|
+
end
|
49
|
+
|
50
|
+
expect( block_self ).to be( mon )
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
it "can specify an interval" do
|
55
|
+
mon = described_class.new( "testing monitor" ) do
|
56
|
+
every 30
|
57
|
+
end
|
58
|
+
|
59
|
+
expect( mon.interval ).to eq( 30 )
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
it "can specify a splay" do
|
64
|
+
mon = described_class.new( "testing monitor" ) do
|
65
|
+
splay 15
|
66
|
+
end
|
67
|
+
|
68
|
+
expect( mon.splay ).to eq( 15 )
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
it "can specify criteria for matching nodes to monitor" do
|
73
|
+
mon = described_class.new( "testing monitor" ) do
|
74
|
+
match type: 'host'
|
75
|
+
end
|
76
|
+
|
77
|
+
expect( mon.positive_criteria ).to include( type: 'host' )
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
it "can specify criteria for matching nodes not to monitor" do
|
82
|
+
mon = described_class.new( "testing monitor" ) do
|
83
|
+
exclude tag: 'laptop'
|
84
|
+
end
|
85
|
+
|
86
|
+
expect( mon.negative_criteria ).to include( tag: 'laptop' )
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
it "can specify that it will include hosts marked as 'down'" do
|
91
|
+
mon = described_class.new( "testing monitor" ) do
|
92
|
+
include_down true
|
93
|
+
end
|
94
|
+
|
95
|
+
expect( mon.include_down? ).to be_truthy
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
it "can specify one or more properties to include in the input to the monitor" do
|
100
|
+
mon = described_class.new( "testing monitor" ) do
|
101
|
+
use :address, :tags
|
102
|
+
end
|
103
|
+
|
104
|
+
expect( mon.node_properties ).to include( :address, :tags )
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
it "can specify a command to exec to do the monitor's work" do
|
109
|
+
mon = described_class.new( "the description" ) do
|
110
|
+
exec 'cat'
|
111
|
+
end
|
112
|
+
|
113
|
+
output = mon.run( testing_nodes )
|
114
|
+
expect( output ).to be_a( Hash )
|
115
|
+
expect( output ).to include( *(testing_nodes.map(&:identifier)) )
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
it "can specify a block to call to do the monitor's work" do
|
120
|
+
block_was_run = false
|
121
|
+
|
122
|
+
mon = described_class.new( "the description" )
|
123
|
+
mon.exec do |nodes|
|
124
|
+
block_was_run = true
|
125
|
+
end
|
126
|
+
|
127
|
+
mon.run( testing_nodes )
|
128
|
+
|
129
|
+
expect( block_was_run ).to be_truthy
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
it "can specify a runnable object to do the monitor's work" do
|
134
|
+
mod = Module.new do
|
135
|
+
class << self; attr_accessor :was_run ; end
|
136
|
+
@was_run = false
|
137
|
+
|
138
|
+
def self::run( nodes )
|
139
|
+
self.was_run = true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
mon = described_class.new( "the description" )
|
145
|
+
mon.exec( mod )
|
146
|
+
|
147
|
+
mon.run( testing_nodes )
|
148
|
+
|
149
|
+
expect( mod.was_run ).to be_truthy
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
it "can provide a function for building arguments for its command" do
|
154
|
+
mon = described_class.new( "the description" ) do
|
155
|
+
|
156
|
+
exec 'the_command'
|
157
|
+
|
158
|
+
handle_results {|*| }
|
159
|
+
exec_input {|*| }
|
160
|
+
exec_arguments do |nodes|
|
161
|
+
Loggability[ Arborist ].debug "In the argument-builder."
|
162
|
+
nodes.map {|n| n.identifier }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
expect( Process ).to receive( :spawn ) do |*args|
|
167
|
+
options = args.pop
|
168
|
+
|
169
|
+
expect( args ).to eq([ 'the_command', 'trunk', 'branch', 'leaf' ])
|
170
|
+
expect( options ).to be_a( Hash )
|
171
|
+
expect( options ).to include( :in, :out, :err )
|
172
|
+
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
|
176
|
+
mon.run( testing_nodes )
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
it "can provide a function for providing input to its command" do
|
181
|
+
mon = described_class.new( "the description" ) do
|
182
|
+
|
183
|
+
exec 'cat'
|
184
|
+
|
185
|
+
exec_input do |nodes, writer|
|
186
|
+
writer.puts( nodes.map(&:identifier) )
|
187
|
+
end
|
188
|
+
handle_results do |pid, out, err|
|
189
|
+
return out.readlines.map( &:chomp )
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
results = mon.run( testing_nodes )
|
194
|
+
|
195
|
+
expect( results ).to eq( testing_nodes.map(&:identifier) )
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
it "can provide a function for parsing its command's output" do
|
200
|
+
mon = described_class.new( "the description" ) do
|
201
|
+
|
202
|
+
exec 'cat'
|
203
|
+
|
204
|
+
exec_arguments {|*| }
|
205
|
+
exec_input do |nodes, writer|
|
206
|
+
writer.puts( nodes.map(&:identifier) )
|
207
|
+
end
|
208
|
+
handle_results do |pid, out, err|
|
209
|
+
out.readlines.map( &:chomp ).map( &:upcase )
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
results = mon.run( testing_nodes )
|
214
|
+
|
215
|
+
expect( results ).to eq( testing_nodes.map(&:identifier).map(&:upcase) )
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
it "can provide a Module that implements its exec callbacks" do
|
220
|
+
the_module = Module.new do
|
221
|
+
|
222
|
+
def exec_input( nodes, writer )
|
223
|
+
writer.puts( nodes.map {|n| n.identifier } )
|
224
|
+
end
|
225
|
+
|
226
|
+
def handle_results( pid, out, err )
|
227
|
+
err.flush
|
228
|
+
return out.each_line.with_object({}) do |line, accum|
|
229
|
+
accum[ line.chomp ] = { echoed: 'yep' }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
mon = described_class.new( "the description" ) do
|
236
|
+
exec 'cat'
|
237
|
+
exec_callbacks( the_module )
|
238
|
+
end
|
239
|
+
|
240
|
+
results = mon.run( testing_nodes )
|
241
|
+
|
242
|
+
expect( results ).to be_a( Hash )
|
243
|
+
expect( results.size ).to eq( 3 )
|
244
|
+
expect( results ).to include( *testing_nodes.map(&:identifier) )
|
245
|
+
expect( results['trunk'] ).to eq({ echoed: 'yep' })
|
246
|
+
expect( results['branch'] ).to eq({ echoed: 'yep' })
|
247
|
+
expect( results['leaf'] ).to eq({ echoed: 'yep' })
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|