arborist 0.0.1.pre20160106113421
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
+
|