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,71 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/event/node_update'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::Event::NodeUpdate do
|
9
|
+
|
10
|
+
class TestNode < Arborist::Node; end
|
11
|
+
|
12
|
+
|
13
|
+
let( :node ) do
|
14
|
+
TestNode.new( 'foo' ) do
|
15
|
+
parent 'bar'
|
16
|
+
description "The prototypical node"
|
17
|
+
tags :chunker, :hunky, :flippin, :hippo
|
18
|
+
|
19
|
+
update(
|
20
|
+
'song' => 'Around the World',
|
21
|
+
'artist' => 'Daft Punk',
|
22
|
+
'length' => '7:09',
|
23
|
+
'cider' => {
|
24
|
+
'description' => 'tasty',
|
25
|
+
'size' => '16oz',
|
26
|
+
},
|
27
|
+
'sausage' => {
|
28
|
+
'description' => 'pork',
|
29
|
+
'size' => 'monsterous',
|
30
|
+
'price' => {
|
31
|
+
'units' => 1200,
|
32
|
+
'currency' => 'usd'
|
33
|
+
}
|
34
|
+
},
|
35
|
+
'music' => '80s'
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
describe "subscription support" do
|
42
|
+
|
43
|
+
it "matches a subscription with only an event type if the type is the same" do
|
44
|
+
sub = Arborist::Subscription.new( :publisher, 'node.update' )
|
45
|
+
event = described_class.new( node )
|
46
|
+
|
47
|
+
expect( event ).to match( sub )
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
it "matches a subscription with a matching event type and matching criteria" do
|
52
|
+
sub = Arborist::Subscription.new( :publisher, 'node.update', 'tag' => 'chunker' )
|
53
|
+
event = described_class.new( node )
|
54
|
+
|
55
|
+
expect( event ).to match( sub )
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
it "doesn't match a subscription with a matching event type if the criteria don't match" do
|
60
|
+
sub = Arborist::Subscription.new( :publisher, 'node.update', 'tag' => 'looper' )
|
61
|
+
event = described_class.new( node )
|
62
|
+
|
63
|
+
expect( event ).to_not match( sub )
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/event'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::Event do
|
9
|
+
|
10
|
+
it "derives its type name from its class" do
|
11
|
+
payload = { 'status' => ['up', 'down'] }
|
12
|
+
expect( TestEvent.new(payload).type ).to eq( 'test.event' )
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
it "copies the payload it's constructed with" do
|
17
|
+
payload = { 'status' => ['up', 'down'] }
|
18
|
+
|
19
|
+
ev = TestEvent.create( TestEvent, payload )
|
20
|
+
payload.clear
|
21
|
+
|
22
|
+
expect( ev.payload ).to include( 'status' )
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
describe "subscription support" do
|
27
|
+
|
28
|
+
it "matches a subscription with only an event type if the type is the same" do
|
29
|
+
sub = Arborist::Subscription.new( :publisher, 'test.event' )
|
30
|
+
event = described_class.create( TestEvent, [] )
|
31
|
+
|
32
|
+
expect( event ).to match( sub )
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
it "always matches a subscription with a nil event type" do
|
37
|
+
sub = Arborist::Subscription.new( :publisher )
|
38
|
+
event = described_class.create( TestEvent, [] )
|
39
|
+
|
40
|
+
expect( event ).to match( sub )
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
describe "serialization support" do
|
47
|
+
|
48
|
+
it "can represent itself as a Hash" do
|
49
|
+
payload = { 'status' => ['up', 'down'] }
|
50
|
+
ev = TestEvent.create( TestEvent, payload )
|
51
|
+
|
52
|
+
result = ev.to_hash
|
53
|
+
|
54
|
+
expect( result ).to include( 'type', 'data' )
|
55
|
+
|
56
|
+
expect( result['type'] ).to eq( 'test.event' )
|
57
|
+
expect( result['data'] ).to eq( payload )
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/manager/event_publisher'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::Manager::EventPublisher do
|
9
|
+
|
10
|
+
let( :socket ) { instance_double( ZMQ::Socket::Pub ) }
|
11
|
+
let( :pollitem ) { instance_double( ZMQ::Pollitem, pollable: socket ) }
|
12
|
+
let( :zloop ) { instance_double( ZMQ::Loop ) }
|
13
|
+
|
14
|
+
let( :manager ) { Arborist::Manager.new }
|
15
|
+
let( :event ) { Arborist::Event.create(TestEvent, 'stuff') }
|
16
|
+
|
17
|
+
let( :publisher ) { described_class.new(pollitem, manager, zloop) }
|
18
|
+
|
19
|
+
|
20
|
+
it "starts out registered for writing" do
|
21
|
+
expect( publisher ).to be_registered
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
it "unregisters itself if told to write with an empty event queue" do
|
26
|
+
expect( zloop ).to receive( :remove ).with( pollitem )
|
27
|
+
expect {
|
28
|
+
publisher.on_writable
|
29
|
+
}.to change { publisher.registered? }.to( false )
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
it "registers itself if it's not already when an event is appended" do
|
34
|
+
# Cause the socket to become unregistered
|
35
|
+
allow( zloop ).to receive( :remove )
|
36
|
+
publisher.on_writable
|
37
|
+
|
38
|
+
expect( zloop ).to receive( :register ).with( pollitem )
|
39
|
+
|
40
|
+
expect {
|
41
|
+
publisher.publish( 'identifier-00aa', event )
|
42
|
+
}.to change { publisher.registered? }.to( true )
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
it "publishes events with their identifier" do
|
47
|
+
identifier = '65b2430b-6855-4961-ab46-d742cf4456a1'
|
48
|
+
|
49
|
+
expect( socket ).to receive( :sendm ).with( identifier )
|
50
|
+
expect( socket ).to receive( :send ) do |raw_data|
|
51
|
+
ev = MessagePack.unpack( raw_data )
|
52
|
+
expect( ev ).to include( 'type', 'data' )
|
53
|
+
|
54
|
+
expect( ev['type'] ).to eq( 'test.event' )
|
55
|
+
expect( ev['data'] ).to eq( 'stuff' )
|
56
|
+
end
|
57
|
+
expect( zloop ).to receive( :remove ).with( pollitem )
|
58
|
+
|
59
|
+
publisher.publish( identifier, event )
|
60
|
+
publisher.on_writable
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
@@ -0,0 +1,458 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
describe Arborist::Manager::TreeAPI, :testing_manager do
|
7
|
+
|
8
|
+
before( :each ) do
|
9
|
+
@manager = make_testing_manager()
|
10
|
+
@manager_thread = Thread.new do
|
11
|
+
Thread.current.abort_on_exception = true
|
12
|
+
manager.run
|
13
|
+
Loggability[ Arborist ].info "Stopped the test manager"
|
14
|
+
end
|
15
|
+
|
16
|
+
count = 0
|
17
|
+
until manager.running? || count > 30
|
18
|
+
sleep 0.1
|
19
|
+
count += 1
|
20
|
+
end
|
21
|
+
raise "Manager didn't start up" unless manager.running?
|
22
|
+
end
|
23
|
+
|
24
|
+
after( :each ) do
|
25
|
+
@manager.stop
|
26
|
+
unless @manager_thread.join( 5 )
|
27
|
+
$stderr.puts "Manager thread didn't exit on its own; killing it."
|
28
|
+
@manager_thread.kill
|
29
|
+
end
|
30
|
+
|
31
|
+
count = 0
|
32
|
+
while @manager.zmq_loop.running? || count > 30
|
33
|
+
sleep 0.1
|
34
|
+
Loggability[ Arborist ].info "ZMQ loop still running"
|
35
|
+
count += 1
|
36
|
+
end
|
37
|
+
raise "ZMQ Loop didn't stop" if @manager.zmq_loop.running?
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
let( :manager ) { @manager }
|
42
|
+
|
43
|
+
let!( :sock ) do
|
44
|
+
sock = Arborist.zmq_context.socket( :REQ )
|
45
|
+
sock.linger = 0
|
46
|
+
sock.connect( TESTING_API_SOCK )
|
47
|
+
sock
|
48
|
+
end
|
49
|
+
|
50
|
+
let( :api_handler ) { described_class.new( rep_sock, manager ) }
|
51
|
+
|
52
|
+
|
53
|
+
describe "malformed requests" do
|
54
|
+
|
55
|
+
it "send an error response if the request can't be deserialized" do
|
56
|
+
sock.send( "whatevs, dude!" )
|
57
|
+
resmsg = sock.recv
|
58
|
+
|
59
|
+
hdr, body = unpack_message( resmsg )
|
60
|
+
expect( hdr ).to include(
|
61
|
+
'success' => false,
|
62
|
+
'reason' => /invalid request/i,
|
63
|
+
'category' => 'client'
|
64
|
+
)
|
65
|
+
expect( body ).to be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
it "send an error response if the request isn't a tuple" do
|
70
|
+
sock.send( MessagePack.pack({ version: 1, action: 'list' }) )
|
71
|
+
resmsg = sock.recv
|
72
|
+
|
73
|
+
hdr, body = unpack_message( resmsg )
|
74
|
+
expect( hdr ).to include(
|
75
|
+
'success' => false,
|
76
|
+
'reason' => /invalid request.*not a tuple/i,
|
77
|
+
'category' => 'client'
|
78
|
+
)
|
79
|
+
expect( body ).to be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
it "send an error response if the request is empty" do
|
84
|
+
sock.send( MessagePack.pack([]) )
|
85
|
+
resmsg = sock.recv
|
86
|
+
|
87
|
+
hdr, body = unpack_message( resmsg )
|
88
|
+
expect( hdr ).to include(
|
89
|
+
'success' => false,
|
90
|
+
'reason' => /invalid request.*incorrect length/i,
|
91
|
+
'category' => 'client'
|
92
|
+
)
|
93
|
+
expect( body ).to be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
it "send an error response if the request is an incorrect length" do
|
98
|
+
sock.send( MessagePack.pack([{}, {}, {}]) )
|
99
|
+
resmsg = sock.recv
|
100
|
+
|
101
|
+
hdr, body = unpack_message( resmsg )
|
102
|
+
expect( hdr ).to include(
|
103
|
+
'success' => false,
|
104
|
+
'reason' => /invalid request.*incorrect length/i,
|
105
|
+
'category' => 'client'
|
106
|
+
)
|
107
|
+
expect( body ).to be_nil
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
it "send an error response if the request's header is not a Map" do
|
112
|
+
sock.send( MessagePack.pack([nil, {}]) )
|
113
|
+
resmsg = sock.recv
|
114
|
+
|
115
|
+
hdr, body = unpack_message( resmsg )
|
116
|
+
expect( hdr ).to include(
|
117
|
+
'success' => false,
|
118
|
+
'reason' => /invalid request.*header is not a map/i,
|
119
|
+
'category' => 'client'
|
120
|
+
)
|
121
|
+
expect( body ).to be_nil
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
it "send an error response if the request's body is not a Map or Nil" do
|
126
|
+
sock.send( MessagePack.pack([{version: 1, action: 'list'}, 18]) )
|
127
|
+
resmsg = sock.recv
|
128
|
+
|
129
|
+
hdr, body = unpack_message( resmsg )
|
130
|
+
expect( hdr ).to include(
|
131
|
+
'success' => false,
|
132
|
+
'reason' => /invalid request.*body must be a map or nil/i,
|
133
|
+
'category' => 'client'
|
134
|
+
)
|
135
|
+
expect( body ).to be_nil
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
it "send an error response if missing a version" do
|
140
|
+
sock.send( MessagePack.pack([{action: 'list'}]) )
|
141
|
+
resmsg = sock.recv
|
142
|
+
|
143
|
+
hdr, body = unpack_message( resmsg )
|
144
|
+
expect( hdr ).to include(
|
145
|
+
'success' => false,
|
146
|
+
'reason' => /invalid request.*missing required header 'version'/i,
|
147
|
+
'category' => 'client'
|
148
|
+
)
|
149
|
+
expect( body ).to be_nil
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
it "send an error response if missing an action" do
|
154
|
+
sock.send( MessagePack.pack([{version: 1}]) )
|
155
|
+
resmsg = sock.recv
|
156
|
+
|
157
|
+
hdr, body = unpack_message( resmsg )
|
158
|
+
expect( hdr ).to include(
|
159
|
+
'success' => false,
|
160
|
+
'reason' => /invalid request.*missing required header 'action'/i,
|
161
|
+
'category' => 'client'
|
162
|
+
)
|
163
|
+
expect( body ).to be_nil
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
it "send an error response for unknown actions" do
|
168
|
+
badmsg = pack_message( :slap )
|
169
|
+
sock.send( badmsg )
|
170
|
+
resmsg = sock.recv
|
171
|
+
|
172
|
+
hdr, body = unpack_message( resmsg )
|
173
|
+
expect( hdr ).to include(
|
174
|
+
'success' => false,
|
175
|
+
'reason' => /invalid request.*no such action 'slap'/i,
|
176
|
+
'category' => 'client'
|
177
|
+
)
|
178
|
+
expect( body ).to be_nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
describe "status" do
|
184
|
+
|
185
|
+
|
186
|
+
it "returns a Map describing the manager and its state" do
|
187
|
+
msg = pack_message( :status )
|
188
|
+
|
189
|
+
sock.send( msg )
|
190
|
+
resmsg = sock.recv
|
191
|
+
|
192
|
+
hdr, body = unpack_message( resmsg )
|
193
|
+
expect( hdr ).to include( 'success' => true )
|
194
|
+
expect( body.length ).to eq( 4 )
|
195
|
+
expect( body ).to include( 'server_version', 'state', 'uptime', 'nodecount' )
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
describe "fetch" do
|
202
|
+
|
203
|
+
it "returns an array of full state maps for nodes matching specified criteria" do
|
204
|
+
msg = pack_message( :fetch, type: 'service', port: 22 )
|
205
|
+
|
206
|
+
sock.send( msg )
|
207
|
+
resmsg = sock.recv
|
208
|
+
|
209
|
+
hdr, body = unpack_message( resmsg )
|
210
|
+
expect( hdr ).to include( 'success' => true )
|
211
|
+
|
212
|
+
expect( body ).to be_a( Hash )
|
213
|
+
expect( body.length ).to eq( 3 )
|
214
|
+
|
215
|
+
expect( body.values ).to all( be_a(Hash) )
|
216
|
+
expect( body.values ).to all( include('status', 'type') )
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
it "doesn't return nodes beneath downed nodes by default" do
|
221
|
+
manager.nodes['sidonie'].update( error: 'sunspots' )
|
222
|
+
msg = pack_message( :fetch, type: 'service', port: 22 )
|
223
|
+
|
224
|
+
sock.send( msg )
|
225
|
+
resmsg = sock.recv
|
226
|
+
|
227
|
+
hdr, body = unpack_message( resmsg )
|
228
|
+
expect( hdr ).to include( 'success' => true )
|
229
|
+
expect( body ).to be_a( Hash )
|
230
|
+
expect( body.length ).to eq( 2 )
|
231
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh' )
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
it "does return nodes beneath downed nodes if asked to" do
|
236
|
+
manager.nodes['sidonie'].update( error: 'plague of locusts' )
|
237
|
+
msg = pack_message( :fetch, {include_down: true}, type: 'service', port: 22 )
|
238
|
+
|
239
|
+
sock.send( msg )
|
240
|
+
resmsg = sock.recv
|
241
|
+
|
242
|
+
hdr, body = unpack_message( resmsg )
|
243
|
+
expect( hdr ).to include( 'success' => true )
|
244
|
+
expect( body ).to be_a( Hash )
|
245
|
+
expect( body.length ).to eq( 3 )
|
246
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh', 'sidonie-ssh' )
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
it "returns only identifiers if the `return` header is set to `nil`" do
|
251
|
+
msg = pack_message( :fetch, {return: nil}, type: 'service', port: 22 )
|
252
|
+
|
253
|
+
sock.send( msg )
|
254
|
+
resmsg = sock.recv
|
255
|
+
|
256
|
+
hdr, body = unpack_message( resmsg )
|
257
|
+
expect( hdr ).to include( 'success' => true )
|
258
|
+
expect( body ).to be_a( Hash )
|
259
|
+
expect( body.length ).to eq( 3 )
|
260
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh', 'sidonie-ssh' )
|
261
|
+
expect( body.values ).to all( be_empty )
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
it "returns only specified state if the `return` header is set to an Array of keys" do
|
266
|
+
msg = pack_message( :fetch, {return: %w[status tags addresses]},
|
267
|
+
type: 'service', port: 22 )
|
268
|
+
|
269
|
+
sock.send( msg )
|
270
|
+
resmsg = sock.recv
|
271
|
+
|
272
|
+
hdr, body = unpack_message( resmsg )
|
273
|
+
expect( hdr ).to include( 'success' => true )
|
274
|
+
expect( body.length ).to eq( 3 )
|
275
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh', 'sidonie-ssh' )
|
276
|
+
expect( body.values.map(&:keys) ).to all( contain_exactly('status', 'tags', 'addresses') )
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
describe "list" do
|
283
|
+
|
284
|
+
it "returns an array of node state" do
|
285
|
+
msg = pack_message( :list )
|
286
|
+
sock.send( msg )
|
287
|
+
resmsg = sock.recv
|
288
|
+
|
289
|
+
hdr, body = unpack_message( resmsg )
|
290
|
+
expect( hdr ).to include( 'success' => true )
|
291
|
+
expect( body.length ).to eq( manager.nodes.length )
|
292
|
+
expect( body ).to all( be_a(Hash) )
|
293
|
+
expect( body ).to include( hash_including('identifier' => '_') )
|
294
|
+
expect( body ).to include( hash_including('identifier' => 'duir') )
|
295
|
+
expect( body ).to include( hash_including('identifier' => 'sidonie-ssh') )
|
296
|
+
expect( body ).to include( hash_including('identifier' => 'sidonie-demon-http') )
|
297
|
+
expect( body ).to include( hash_including('identifier' => 'yevaud') )
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
describe "update" do
|
304
|
+
|
305
|
+
it "merges the properties sent with those of the targeted nodes" do
|
306
|
+
update_data = {
|
307
|
+
duir: {
|
308
|
+
ping: {
|
309
|
+
rtt: 254
|
310
|
+
}
|
311
|
+
},
|
312
|
+
sidonie: {
|
313
|
+
ping: {
|
314
|
+
rtt: 1208
|
315
|
+
}
|
316
|
+
},
|
317
|
+
yevaud: {
|
318
|
+
ping: {
|
319
|
+
rtt: 843
|
320
|
+
}
|
321
|
+
}
|
322
|
+
}
|
323
|
+
msg = pack_message( :update, update_data )
|
324
|
+
sock.send( msg )
|
325
|
+
resmsg = sock.recv
|
326
|
+
|
327
|
+
hdr, body = unpack_message( resmsg )
|
328
|
+
expect( hdr ).to include( 'success' => true )
|
329
|
+
expect( body ).to be_nil
|
330
|
+
|
331
|
+
expect( manager.nodes['duir'].properties['ping'] ).to include( 'rtt' => 254 )
|
332
|
+
expect( manager.nodes['sidonie'].properties['ping'] ).to include( 'rtt' => 1208 )
|
333
|
+
expect( manager.nodes['yevaud'].properties['ping'] ).to include( 'rtt' => 843 )
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
it "ignores unknown identifiers" do
|
338
|
+
msg = pack_message( :update, charlie_humperton: {ping: { rtt: 8 }} )
|
339
|
+
sock.send( msg )
|
340
|
+
resmsg = sock.recv
|
341
|
+
|
342
|
+
hdr, body = unpack_message( resmsg )
|
343
|
+
expect( hdr ).to include( 'success' => true )
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
describe "subscribe" do
|
350
|
+
|
351
|
+
it "adds a subscription for all event types to the root node by default" do
|
352
|
+
criteria = {
|
353
|
+
type: 'host'
|
354
|
+
}
|
355
|
+
|
356
|
+
msg = pack_message( :subscribe, criteria )
|
357
|
+
|
358
|
+
resmsg = nil
|
359
|
+
expect {
|
360
|
+
sock.send( msg )
|
361
|
+
resmsg = sock.recv
|
362
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
363
|
+
change { manager.root.subscriptions.length }.by( 1 )
|
364
|
+
)
|
365
|
+
hdr, body = unpack_message( resmsg )
|
366
|
+
|
367
|
+
sub_id = manager.subscriptions.keys.first
|
368
|
+
|
369
|
+
expect( hdr ).to include( 'success' => true )
|
370
|
+
expect( body ).to eq([ sub_id ])
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
it "adds a subscription to the specified node if an identifier is specified" do
|
375
|
+
criteria = {
|
376
|
+
type: 'host'
|
377
|
+
}
|
378
|
+
|
379
|
+
msg = pack_message( :subscribe, {identifier: 'sidonie'}, criteria )
|
380
|
+
|
381
|
+
resmsg = nil
|
382
|
+
expect {
|
383
|
+
sock.send( msg )
|
384
|
+
resmsg = sock.recv
|
385
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
386
|
+
change { manager.nodes['sidonie'].subscriptions.length }.by( 1 )
|
387
|
+
)
|
388
|
+
hdr, body = unpack_message( resmsg )
|
389
|
+
|
390
|
+
sub_id = manager.subscriptions.keys.first
|
391
|
+
|
392
|
+
expect( hdr ).to include( 'success' => true )
|
393
|
+
expect( body ).to eq([ sub_id ])
|
394
|
+
end
|
395
|
+
|
396
|
+
|
397
|
+
it "adds a subscription for node types matching a pattern if one is specified" do
|
398
|
+
criteria = {
|
399
|
+
type: 'host'
|
400
|
+
}
|
401
|
+
|
402
|
+
msg = pack_message( :subscribe, {event_type: 'node.ack'}, criteria )
|
403
|
+
|
404
|
+
resmsg = nil
|
405
|
+
expect {
|
406
|
+
sock.send( msg )
|
407
|
+
resmsg = sock.recv
|
408
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
409
|
+
change { manager.root.subscriptions.length }.by( 1 )
|
410
|
+
)
|
411
|
+
hdr, body = unpack_message( resmsg )
|
412
|
+
node = manager.subscriptions[ body.first ]
|
413
|
+
sub = node.subscriptions[ body.first ]
|
414
|
+
|
415
|
+
expect( sub.event_type ).to eq( 'node.ack' )
|
416
|
+
end
|
417
|
+
|
418
|
+
end
|
419
|
+
|
420
|
+
|
421
|
+
describe "unsubscribe" do
|
422
|
+
|
423
|
+
let( :subscription ) do
|
424
|
+
manager.create_subscription( nil, 'node.delta', {type: 'host'} )
|
425
|
+
end
|
426
|
+
|
427
|
+
it "removes the subscription with the specified ID" do
|
428
|
+
msg = pack_message( :unsubscribe, {subscription_id: subscription.id}, nil )
|
429
|
+
|
430
|
+
resmsg = nil
|
431
|
+
expect {
|
432
|
+
sock.send( msg )
|
433
|
+
resmsg = sock.recv
|
434
|
+
}.to change { manager.subscriptions.length }.by( -1 ).and(
|
435
|
+
change { manager.root.subscriptions.length }.by( -1 )
|
436
|
+
)
|
437
|
+
hdr, body = unpack_message( resmsg )
|
438
|
+
|
439
|
+
expect( body ).to include( 'event_type' => 'node.delta', 'criteria' => {'type' => 'host'} )
|
440
|
+
end
|
441
|
+
|
442
|
+
|
443
|
+
it "ignores unsubscription of a non-existant ID" do
|
444
|
+
msg = pack_message( :unsubscribe, {subscription_id: 'the bears!'}, nil )
|
445
|
+
|
446
|
+
resmsg = nil
|
447
|
+
expect {
|
448
|
+
sock.send( msg )
|
449
|
+
resmsg = sock.recv
|
450
|
+
}.to_not change { manager.subscriptions.length }
|
451
|
+
hdr, body = unpack_message( resmsg )
|
452
|
+
|
453
|
+
expect( body ).to be_nil
|
454
|
+
end
|
455
|
+
|
456
|
+
end
|
457
|
+
|
458
|
+
end
|