arborist 0.2.0.pre20170519125456 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +670 -1
- data/History.md +67 -0
- data/Manifest.txt +9 -6
- data/README.md +1 -3
- data/Rakefile +39 -4
- data/TODO.md +22 -31
- data/lib/arborist.rb +9 -2
- data/lib/arborist/cli.rb +67 -85
- data/lib/arborist/client.rb +125 -59
- data/lib/arborist/command/ack.rb +86 -0
- data/lib/arborist/command/reset.rb +48 -0
- data/lib/arborist/command/start.rb +11 -1
- data/lib/arborist/command/summary.rb +173 -0
- data/lib/arborist/command/tree.rb +215 -0
- data/lib/arborist/command/watch.rb +22 -22
- data/lib/arborist/dependency.rb +24 -4
- data/lib/arborist/event.rb +18 -2
- data/lib/arborist/event/node.rb +6 -2
- data/lib/arborist/event/node_warn.rb +16 -0
- data/lib/arborist/manager.rb +179 -48
- data/lib/arborist/mixins.rb +11 -0
- data/lib/arborist/monitor.rb +29 -17
- data/lib/arborist/monitor/connection_batching.rb +293 -0
- data/lib/arborist/monitor/socket.rb +101 -167
- data/lib/arborist/monitor_runner.rb +101 -24
- data/lib/arborist/node.rb +297 -68
- data/lib/arborist/node/ack.rb +1 -1
- data/lib/arborist/node/host.rb +26 -5
- data/lib/arborist/node/resource.rb +14 -5
- data/lib/arborist/node/root.rb +12 -3
- data/lib/arborist/node/service.rb +29 -26
- data/lib/arborist/node_subscription.rb +65 -0
- data/lib/arborist/observer.rb +8 -0
- data/lib/arborist/observer/action.rb +6 -0
- data/lib/arborist/subscription.rb +22 -16
- data/lib/arborist/tree_api.rb +7 -2
- data/spec/arborist/client_spec.rb +157 -51
- data/spec/arborist/dependency_spec.rb +21 -0
- data/spec/arborist/event/node_spec.rb +5 -0
- data/spec/arborist/event_spec.rb +3 -3
- data/spec/arborist/manager_spec.rb +626 -347
- data/spec/arborist/mixins_spec.rb +19 -0
- data/spec/arborist/monitor/socket_spec.rb +1 -2
- data/spec/arborist/monitor_runner_spec.rb +81 -29
- data/spec/arborist/monitor_spec.rb +89 -14
- data/spec/arborist/node/host_spec.rb +68 -0
- data/spec/arborist/node/resource_spec.rb +2 -0
- data/spec/arborist/node/root_spec.rb +13 -0
- data/spec/arborist/node/service_spec.rb +9 -0
- data/spec/arborist/node_spec.rb +673 -111
- data/spec/arborist/node_subscription_spec.rb +54 -0
- data/spec/arborist/observer/action_spec.rb +6 -0
- data/spec/arborist/observer_runner_spec.rb +8 -1
- data/spec/arborist/tree_api_spec.rb +111 -8
- data/spec/data/monitors/pings.rb +0 -11
- data/spec/data/monitors/port_checks.rb +0 -9
- data/spec/data/nodes/sidonie.rb +1 -0
- data/spec/data/nodes/vhosts.rb +23 -0
- data/spec/data/nodes/yevaud.rb +4 -2
- data/spec/spec_helper.rb +71 -1
- metadata +91 -28
- metadata.gz.sig +0 -0
- data/Events.md +0 -35
- data/Monitors.md +0 -155
- data/Nodes.md +0 -70
- data/Observers.md +0 -72
- data/Protocol.md +0 -276
- data/Tutorial.md +0 -8
@@ -58,6 +58,8 @@ describe Arborist::Node::Resource do
|
|
58
58
|
|
59
59
|
it "can be matched with a category" do
|
60
60
|
expect( node ).to match_criteria( category: 'disk' )
|
61
|
+
expect( node ).to match_criteria( category: [ 'chungwatch', 'disk' ] )
|
62
|
+
expect( node ).to_not match_criteria( category: [ 'chungwatch', 'snippersnapper' ] )
|
61
63
|
expect( node ).to_not match_criteria( category: 'processes' )
|
62
64
|
end
|
63
65
|
end
|
@@ -25,5 +25,18 @@ describe Arborist::Node::Root do
|
|
25
25
|
expect( node.parent ).to be_nil
|
26
26
|
end
|
27
27
|
|
28
|
+
|
29
|
+
it "immediately transitions to up when re-enabled" do
|
30
|
+
expect( node ).to be_up
|
31
|
+
|
32
|
+
node.acknowledge(
|
33
|
+
message: 'METEOR COMING DISABLE ALERTS THAT WILL BE 100K KPLZTHX',
|
34
|
+
sender: 'SunGuard'
|
35
|
+
)
|
36
|
+
expect( node ).to be_disabled
|
37
|
+
|
38
|
+
node.unacknowledge
|
39
|
+
expect( node ).to be_up
|
40
|
+
end
|
28
41
|
end
|
29
42
|
|
@@ -121,6 +121,10 @@ describe Arborist::Node::Service do
|
|
121
121
|
described_class.new( 'ssh', host )
|
122
122
|
end
|
123
123
|
|
124
|
+
let( :node2 ) do
|
125
|
+
described_class.new( 'ntp', host ) { protocol 'udp' }
|
126
|
+
end
|
127
|
+
|
124
128
|
|
125
129
|
it "inherits its host's addresses" do
|
126
130
|
expect( node ).to match_criteria( address: '192.168.66.12' )
|
@@ -153,6 +157,7 @@ describe Arborist::Node::Service do
|
|
153
157
|
|
154
158
|
it "can be matched with a port" do
|
155
159
|
expect( node ).to match_criteria( port: 22 )
|
160
|
+
expect( node ).to match_criteria( port: [ 22, 123 ] )
|
156
161
|
expect( node ).to match_criteria( port: 'ssh' )
|
157
162
|
expect( node ).to_not match_criteria( port: 80 )
|
158
163
|
expect( node ).to_not match_criteria( port: 'www' )
|
@@ -163,6 +168,7 @@ describe Arborist::Node::Service do
|
|
163
168
|
it "can be matched with a protocol" do
|
164
169
|
expect( node ).to match_criteria( protocol: 'tcp' )
|
165
170
|
expect( node ).to_not match_criteria( protocol: 'udp' )
|
171
|
+
expect( node ).to match_criteria( protocol: [ 'udp', 'tcp' ] )
|
166
172
|
end
|
167
173
|
|
168
174
|
|
@@ -171,6 +177,9 @@ describe Arborist::Node::Service do
|
|
171
177
|
expect( node ).to match_criteria( app: 'ssh' )
|
172
178
|
expect( node ).to_not match_criteria( app_protocol: 'http' )
|
173
179
|
expect( node ).to_not match_criteria( app: 'http' )
|
180
|
+
expect( node ).to match_criteria( app: [ 'ntp', 'ssh' ] )
|
181
|
+
expect( node2 ).to match_criteria( app: [ 'ntp', 'ssh' ] )
|
182
|
+
expect( node2 ).to_not match_criteria( app: [ 'http' ] )
|
174
183
|
end
|
175
184
|
|
176
185
|
end
|
data/spec/arborist/node_spec.rb
CHANGED
@@ -11,15 +11,42 @@ describe Arborist::Node do
|
|
11
11
|
before( :all ) do
|
12
12
|
Arborist::Event.load_all
|
13
13
|
end
|
14
|
+
before( :each ) do
|
15
|
+
@real_derivatives = described_class.derivatives.dup
|
16
|
+
end
|
17
|
+
after( :each ) do
|
18
|
+
described_class.derivatives.replace( @real_derivatives )
|
19
|
+
end
|
14
20
|
|
15
21
|
|
16
|
-
let( :concrete_class )
|
17
|
-
|
22
|
+
let( :concrete_class ) do
|
23
|
+
Class.new( described_class )
|
24
|
+
end
|
18
25
|
|
19
26
|
let( :identifier ) { 'the_identifier' }
|
20
27
|
let( :identifier2 ) { 'the_other_identifier' }
|
21
28
|
|
22
29
|
|
30
|
+
shared_examples_for "a reachable node" do
|
31
|
+
|
32
|
+
it "is still 'reachable'" do
|
33
|
+
expect( node ).to be_reachable
|
34
|
+
expect( node ).to_not be_unreachable
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
shared_examples_for "an unreachable node" do
|
41
|
+
|
42
|
+
it "is not 'reachable'" do
|
43
|
+
expect( node ).to_not be_reachable
|
44
|
+
expect( node ).to be_unreachable
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
|
23
50
|
it "can be loaded from a file" do
|
24
51
|
concrete_instance = nil
|
25
52
|
expect( Kernel ).to receive( :load ).with( "a/path/to/a/node.rb" ) do
|
@@ -81,15 +108,56 @@ describe Arborist::Node do
|
|
81
108
|
context "subnode classes" do
|
82
109
|
|
83
110
|
it "can declare the type of node they live under" do
|
84
|
-
|
111
|
+
subnode_class = Class.new( described_class )
|
112
|
+
subnode_class.parent_type( concrete_class )
|
113
|
+
|
114
|
+
expect( subnode_class.parent_types ).to include( concrete_class )
|
85
115
|
end
|
86
116
|
|
87
117
|
|
88
118
|
it "can be constructed via a factory method on instances of their parent type" do
|
119
|
+
subnode_class = Class.new( described_class ) do
|
120
|
+
def self::name; "TestSubNode"; end
|
121
|
+
def self::plugin_name; "testsub"; end
|
122
|
+
end
|
123
|
+
described_class.derivatives['testsub'] = subnode_class
|
124
|
+
|
125
|
+
subnode_class.parent_type( concrete_class )
|
89
126
|
parent = concrete_class.new( 'branch' )
|
90
127
|
node = parent.testsub( 'leaf' )
|
128
|
+
|
129
|
+
expect( node ).to be_an_instance_of( subnode_class )
|
130
|
+
expect( node.identifier ).to eq( 'leaf' )
|
131
|
+
expect( node.parent ).to eq( 'branch' )
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
it "can pre-process the factory method arguments" do
|
136
|
+
subnode_class = Class.new( described_class ) do
|
137
|
+
def self::name; "TestSubNode"; end
|
138
|
+
def self::plugin_name; "testsub"; end
|
139
|
+
def args( new_args=nil )
|
140
|
+
@args = new_args if new_args
|
141
|
+
return @args
|
142
|
+
end
|
143
|
+
def modify( attributes )
|
144
|
+
attributes = stringify_keys( attributes )
|
145
|
+
super
|
146
|
+
self.args( attributes['args'] )
|
147
|
+
end
|
148
|
+
end
|
149
|
+
described_class.derivatives['testsub'] = subnode_class
|
150
|
+
|
151
|
+
subnode_class.parent_type( concrete_class ) do |arg1, id, *args|
|
152
|
+
[ id, {args: [arg1] + args} ]
|
153
|
+
end
|
154
|
+
|
155
|
+
parent = concrete_class.new( 'branch' )
|
156
|
+
node = parent.testsub( :arg1, 'leaf', :arg2, :arg3 )
|
157
|
+
|
91
158
|
expect( node ).to be_an_instance_of( subnode_class )
|
92
159
|
expect( node.parent ).to eq( parent.identifier )
|
160
|
+
expect( node.args ).to eq([ :arg1, :arg2, :arg3 ])
|
93
161
|
end
|
94
162
|
|
95
163
|
end
|
@@ -97,187 +165,452 @@ describe Arborist::Node do
|
|
97
165
|
|
98
166
|
context "an instance of a concrete subclass" do
|
99
167
|
|
100
|
-
let( :
|
101
|
-
let( :
|
102
|
-
concrete_class.new(
|
168
|
+
let( :parent_node ) { concrete_class.new(identifier) }
|
169
|
+
let( :sibling_node ) do
|
170
|
+
concrete_class.new( 'sibling' ) do
|
171
|
+
parent 'the_identifier'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
let( :node ) do
|
175
|
+
concrete_class.new( identifier2 ) do
|
103
176
|
parent 'the_identifier'
|
104
177
|
end
|
105
178
|
end
|
106
179
|
|
107
180
|
|
108
181
|
it "can declare what its parent is by identifier" do
|
109
|
-
expect(
|
182
|
+
expect( node.parent ).to eq( identifier )
|
110
183
|
end
|
111
184
|
|
112
185
|
|
113
186
|
it "can have child nodes added to it" do
|
114
|
-
|
115
|
-
expect(
|
187
|
+
parent_node.add_child( node )
|
188
|
+
expect( parent_node.children ).to include( node.identifier )
|
116
189
|
end
|
117
190
|
|
118
191
|
|
119
192
|
it "can have child nodes appended to it" do
|
120
|
-
|
121
|
-
expect(
|
193
|
+
parent_node << node
|
194
|
+
expect( parent_node.children ).to include( node.identifier )
|
122
195
|
end
|
123
196
|
|
124
197
|
|
125
198
|
it "raises an error if a node which specifies a different parent is added to it" do
|
126
|
-
|
199
|
+
stranger_node = concrete_class.new( identifier2 ) do
|
127
200
|
parent 'youre_not_my_mother'
|
128
201
|
end
|
129
202
|
expect {
|
130
|
-
|
203
|
+
parent_node.add_child( stranger_node )
|
131
204
|
}.to raise_error( /not a child of/i )
|
132
205
|
end
|
133
206
|
|
134
207
|
|
135
208
|
it "doesn't add the same child more than once" do
|
136
|
-
|
137
|
-
|
138
|
-
expect(
|
209
|
+
parent_node.add_child( node )
|
210
|
+
parent_node.add_child( node )
|
211
|
+
expect( parent_node.children.size ).to eq( 1 )
|
139
212
|
end
|
140
213
|
|
141
214
|
|
142
215
|
it "knows it doesn't have any children if it's empty" do
|
143
|
-
expect(
|
216
|
+
expect( parent_node ).to_not have_children
|
144
217
|
end
|
145
218
|
|
146
219
|
|
147
220
|
it "knows it has children if subnodes have been added" do
|
148
|
-
|
149
|
-
expect(
|
221
|
+
parent_node.add_child( node )
|
222
|
+
expect( parent_node ).to have_children
|
150
223
|
end
|
151
224
|
|
152
225
|
|
153
226
|
it "knows how to remove one of its children" do
|
154
|
-
|
155
|
-
|
156
|
-
expect(
|
227
|
+
parent_node.add_child( node )
|
228
|
+
parent_node.remove_child( node )
|
229
|
+
expect( parent_node ).to_not have_children
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
it "starts out in `unknown` status" do
|
234
|
+
expect( parent_node ).to be_unknown
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
it "remembers status time changes" do
|
239
|
+
expect( node.status_changed ).to eq( Time.at(0) )
|
240
|
+
|
241
|
+
time = Time.at( 1523900910 )
|
242
|
+
allow( Time ).to receive( :now ).and_return( time )
|
243
|
+
|
244
|
+
node.update( { error: 'boom' } )
|
245
|
+
expect( node ).to be_down
|
246
|
+
expect( node.status_changed ).to eq( time )
|
247
|
+
expect( node.status_last_changed ).to eq( Time.at(0) )
|
248
|
+
|
249
|
+
|
250
|
+
node.update( {} )
|
251
|
+
expect( node ).to be_up
|
252
|
+
expect( node.status_last_changed ).to eq( time )
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
it "groups errors from separate monitors by their key" do
|
257
|
+
expect( node ).to be_unknown
|
258
|
+
|
259
|
+
node.update( {error: 'ded'}, 'MonitorTron2000' )
|
260
|
+
node.update( {error: 'moar ded'}, 'MonitorTron5000' )
|
261
|
+
expect( node ).to be_down
|
262
|
+
|
263
|
+
expect( node.errors.length ).to eq( 2 )
|
264
|
+
node.update( {}, 'MonitorTron5000' )
|
265
|
+
|
266
|
+
expect( node ).to be_down
|
267
|
+
expect( node.errors.length ).to eq( 1 )
|
268
|
+
|
269
|
+
node.update( {}, 'MonitorTron2000' )
|
270
|
+
expect( node ).to be_up
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
it "sets a default monitor key" do
|
275
|
+
node.update( error: 'ded' )
|
276
|
+
expect( node ).to be_down
|
277
|
+
expect( node.errors ).to eq({ '_' => 'ded' })
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
describe "in `unknown` status" do
|
282
|
+
|
283
|
+
let( :node ) do
|
284
|
+
obj = super()
|
285
|
+
obj.status = 'unknown'
|
286
|
+
obj
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
it_behaves_like "a reachable node"
|
291
|
+
|
292
|
+
|
293
|
+
it "transitions to `up` status if doesn't have any errors after an update" do
|
294
|
+
expect {
|
295
|
+
node.update( tested: true )
|
296
|
+
}.to change { node.status }.from( 'unknown' ).to( 'up' )
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
it "transitions to `down` status if its state is updated with an `error` property" do
|
301
|
+
expect {
|
302
|
+
node.update( error: "Couldn't talk to it!" )
|
303
|
+
}.to change { node.status }.from( 'unknown' ).to( 'down' )
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
it "transitions to `warn` status if its state is updated with a `warning` property" do
|
308
|
+
expect {
|
309
|
+
node.update( warning: "Things are starting to look bad!" )
|
310
|
+
}.to change { node.status }.from( 'unknown' ).to( 'warn' )
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
it "transitions to `disabled` if it's acknowledged" do
|
315
|
+
expect {
|
316
|
+
node.acknowledge( message: "Maintenance", sender: 'mahlon' )
|
317
|
+
}.to change { node.status }.from( 'unknown' ).to( 'disabled' )
|
318
|
+
end
|
319
|
+
|
157
320
|
end
|
158
321
|
|
159
|
-
describe "status" do
|
160
322
|
|
161
|
-
|
162
|
-
|
323
|
+
describe "in `up` status" do
|
324
|
+
|
325
|
+
let( :node ) do
|
326
|
+
obj = super()
|
327
|
+
obj.status = 'up'
|
328
|
+
obj
|
163
329
|
end
|
164
330
|
|
165
331
|
|
166
|
-
|
167
|
-
|
168
|
-
|
332
|
+
it_behaves_like "a reachable node"
|
333
|
+
|
334
|
+
|
335
|
+
it "stays in `up` status if doesn't have any errors after an update" do
|
336
|
+
expect {
|
337
|
+
node.update( tested: true )
|
338
|
+
}.to_not change { node.status }.from( 'up' )
|
169
339
|
end
|
170
340
|
|
171
341
|
|
172
342
|
it "transitions to `down` status if its state is updated with an `error` property" do
|
173
|
-
|
174
|
-
|
343
|
+
expect {
|
344
|
+
node.update( error: "Couldn't talk to it!" )
|
345
|
+
}.to change { node.status }.from( 'up' ).to( 'down' )
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
it "transitions to `down` status if it's updated with both an `error` and `warning` property" do
|
350
|
+
expect {
|
351
|
+
node.update( error: "Couldn't talk to it!", warning: "Above configured levels!" )
|
352
|
+
}.to change { node.status }.from( 'up' ).to( 'down' )
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
it "transitions to `warn` status if its state is updated with a `warning` property" do
|
357
|
+
expect {
|
358
|
+
node.update( warning: "Things are starting to look bad!" )
|
359
|
+
}.to change { node.status }.from( 'up' ).to( 'warn' )
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
it "transitions to `disabled` if it's acknowledged" do
|
364
|
+
expect {
|
365
|
+
node.acknowledge( message: "Maintenance", sender: 'mahlon' )
|
366
|
+
}.to change { node.status }.from( 'up' ).to( 'disabled' )
|
367
|
+
end
|
368
|
+
|
369
|
+
|
370
|
+
it "transitions to `quieted` if it's notified that its parent has gone down" do
|
371
|
+
down_event = Arborist::Event.create( :node_down, parent_node )
|
372
|
+
expect {
|
373
|
+
node.handle_event( down_event )
|
374
|
+
}.to change { node.status }.from( 'up' ).to( 'quieted' )
|
175
375
|
end
|
176
376
|
|
177
|
-
|
178
|
-
|
179
|
-
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
describe "in `down` status" do
|
381
|
+
|
382
|
+
let( :node ) do
|
383
|
+
obj = super()
|
384
|
+
obj.status = 'down'
|
385
|
+
obj.errors['moldovia'] = 'Something is wrong | he falls | betraying the trust | "\
|
180
386
|
"there is a disaster in his life.'
|
181
|
-
|
182
|
-
|
387
|
+
obj
|
388
|
+
end
|
389
|
+
|
390
|
+
|
391
|
+
it_behaves_like "an unreachable node"
|
392
|
+
|
393
|
+
|
394
|
+
it "transitions to `acked` status if it's acknowledged" do
|
395
|
+
expect {
|
396
|
+
node.acknowledge( message: "Leitmotiv", sender: 'ged' )
|
397
|
+
}.to change { node.status }.from( 'down' ).to( 'acked' )
|
398
|
+
end
|
399
|
+
|
400
|
+
|
401
|
+
it "transitions to `up` status if all of its errors are cleared" do
|
402
|
+
expect {
|
403
|
+
node.update( {error: nil}, 'moldovia' )
|
404
|
+
}.to change { node.status }.from( 'down' ).to( 'up' )
|
405
|
+
end
|
406
|
+
|
407
|
+
|
408
|
+
it "transitions to `warn` status if errors are cleared but warnings remain" do
|
409
|
+
expect {
|
410
|
+
node.update( {error: nil, warning: 'squirt!'}, 'moldovia' )
|
411
|
+
}.to change { node.status }.from( 'down' ).to( 'warn' )
|
412
|
+
end
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
|
417
|
+
describe "in `warn` status" do
|
418
|
+
|
419
|
+
let( :node ) do
|
420
|
+
obj = super()
|
421
|
+
obj.status = 'warn'
|
422
|
+
obj.warnings = { 'beach' => 'Sweaty but functional servers.' }
|
423
|
+
obj
|
183
424
|
end
|
184
425
|
|
185
|
-
it "transitions from `acked` to `up` status if its error is cleared" do
|
186
|
-
node.status = 'down'
|
187
|
-
node.errors = { '_' => 'Something is wrong | he falls | betraying the trust | "\
|
188
|
-
"there is a disaster in his life.' }
|
189
|
-
node.update( ack: {message: "Leitmotiv", sender: 'ged'} )
|
190
|
-
node.update( error: nil )
|
191
426
|
|
192
|
-
|
427
|
+
it_behaves_like "a reachable node"
|
428
|
+
|
429
|
+
|
430
|
+
it "transitions to `up` if its warnings are cleared" do
|
431
|
+
expect {
|
432
|
+
node.update( {warning: nil}, 'beach' )
|
433
|
+
}.to change { node.status }.from( 'warn' ).to( 'up' )
|
193
434
|
end
|
194
435
|
|
195
|
-
it "stays `up` if its error is cleared and stays cleared" do
|
196
|
-
node.status = 'down'
|
197
|
-
node.errors = { '_' => 'stay up damn you!' }
|
198
|
-
node.update( ack: {message: "Leitmotiv", sender: 'ged'} )
|
199
|
-
node.update( error: nil )
|
200
|
-
node.update( error: nil )
|
201
436
|
|
202
|
-
|
437
|
+
it "transitions to `down` if has an error set" do
|
438
|
+
expect {
|
439
|
+
node.update( {error: "Shark warning."}, 'beach' )
|
440
|
+
}.to change { node.status }.from( 'warn' ).to( 'down' )
|
203
441
|
end
|
204
442
|
|
205
|
-
it "transitions to `disabled` from `up` status if it's updated with an `ack` property" do
|
206
|
-
node.status = 'up'
|
207
|
-
node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
|
208
443
|
|
209
|
-
|
444
|
+
it "transitions to `disabled` if it's acknowledged" do
|
445
|
+
expect {
|
446
|
+
node.acknowledge( message: "Chill", sender: 'ged' )
|
447
|
+
}.to change { node.status }.from( 'warn' ).to( 'disabled' )
|
210
448
|
end
|
211
449
|
|
212
|
-
|
213
|
-
|
214
|
-
node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
|
450
|
+
end
|
451
|
+
|
215
452
|
|
216
|
-
|
453
|
+
describe "in `acked` status" do
|
454
|
+
|
455
|
+
let( :node ) do
|
456
|
+
obj = super()
|
457
|
+
obj.status = 'acked'
|
458
|
+
obj.errors['moldovia'] = 'Something is wrong | he falls | betraying the trust | "\
|
459
|
+
"there is a disaster in his life.'
|
460
|
+
obj.acknowledge( message: "Leitmotiv", sender: 'ged' )
|
461
|
+
obj
|
217
462
|
end
|
218
463
|
|
464
|
+
|
465
|
+
it_behaves_like "a reachable node"
|
466
|
+
|
467
|
+
|
468
|
+
it "transitions to `up` status if its error is cleared" do
|
469
|
+
expect {
|
470
|
+
node.update( {error: nil}, 'moldovia' )
|
471
|
+
}.to change { node.status }.from( 'acked' ).to( 'up' )
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
it "stays `up` if it is updated twice with an error key" do
|
476
|
+
node.update( {error: nil}, 'moldovia' )
|
477
|
+
|
478
|
+
expect {
|
479
|
+
node.update( {error: nil}, 'moldovia' ) # make sure it stays cleared
|
480
|
+
}.to_not change { node.status }.from( 'up' )
|
481
|
+
end
|
482
|
+
|
483
|
+
end
|
484
|
+
|
485
|
+
|
486
|
+
describe "in `disabled` status" do
|
487
|
+
|
488
|
+
let( :node ) do
|
489
|
+
obj = super()
|
490
|
+
obj.acknowledge( message: "Bikini models", sender: 'ged' )
|
491
|
+
obj
|
492
|
+
end
|
493
|
+
|
494
|
+
|
495
|
+
it_behaves_like "an unreachable node"
|
496
|
+
|
497
|
+
|
219
498
|
it "stays `disabled` if it gets an error" do
|
220
|
-
|
221
|
-
|
222
|
-
|
499
|
+
expect {
|
500
|
+
node.update( error: "take me to the virus hospital" )
|
501
|
+
}.to_not change { node.status }.from( 'disabled' )
|
502
|
+
|
503
|
+
expect( node.ack ).to_not be_nil
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
it "stays `disabled` if it gets a warning" do
|
508
|
+
expect {
|
509
|
+
node.update( warning: "heartbone" )
|
510
|
+
}.to_not change { node.status }.from( 'disabled' )
|
223
511
|
|
224
|
-
expect( node ).to be_disabled
|
225
512
|
expect( node.ack ).to_not be_nil
|
226
513
|
end
|
227
514
|
|
515
|
+
|
228
516
|
it "stays `disabled` if it gets a successful update" do
|
229
|
-
|
230
|
-
|
231
|
-
|
517
|
+
expect {
|
518
|
+
node.update( ping: {time: 0.02} )
|
519
|
+
}.to_not change { node.status }.from( 'disabled' )
|
232
520
|
|
233
|
-
expect( node ).to be_disabled
|
234
521
|
expect( node.ack ).to_not be_nil
|
235
522
|
end
|
236
523
|
|
237
|
-
it "transitions to `unknown` from `disabled` status if its ack is cleared" do
|
238
|
-
node.status = 'up'
|
239
|
-
node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
|
240
|
-
node.update( ack: nil )
|
241
524
|
|
242
|
-
|
243
|
-
expect
|
525
|
+
it "transitions to `unknown` if its acknowledgment is cleared" do
|
526
|
+
expect {
|
527
|
+
node.unacknowledge
|
528
|
+
}.to change { node.status }.from( 'disabled' ).to( 'unknown' )
|
529
|
+
|
244
530
|
expect( node.ack ).to be_nil
|
245
531
|
end
|
246
532
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
533
|
+
end
|
534
|
+
|
535
|
+
|
536
|
+
describe "in `quieted` status because its parent is down" do
|
537
|
+
|
538
|
+
let( :down_event ) { Arborist::Event.create(:node_down, parent_node) }
|
539
|
+
let( :up_event ) { Arborist::Event.create(:node_up, parent_node) }
|
540
|
+
|
541
|
+
let( :node ) do
|
542
|
+
obj = super()
|
543
|
+
obj.handle_event( down_event )
|
544
|
+
obj
|
251
545
|
end
|
252
546
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
547
|
+
|
548
|
+
it_behaves_like "an unreachable node"
|
549
|
+
|
550
|
+
|
551
|
+
it "remains `quieted` even if updated with an error" do
|
552
|
+
expect {
|
553
|
+
node.update( {error: "Internal error"}, 'webservice' )
|
554
|
+
}.to_not change { node.status }.from( 'quieted' )
|
257
555
|
end
|
258
556
|
|
259
|
-
it "groups errors from separate monitor by their key" do
|
260
|
-
expect( node ).to be_unknown
|
261
557
|
|
262
|
-
|
263
|
-
|
264
|
-
|
558
|
+
it "transitions to `unknown` if its parent transitions to up" do
|
559
|
+
up_event = Arborist::Event.create( :node_up, parent_node )
|
560
|
+
|
561
|
+
expect {
|
562
|
+
node.handle_event( up_event )
|
563
|
+
}.to change { node.status }.from( 'quieted' ).to( 'unknown' )
|
564
|
+
end
|
265
565
|
|
266
|
-
expect( node.errors.length ).to eq( 2 )
|
267
|
-
node.update( _monitor_key: 'MonitorTron5000' )
|
268
566
|
|
269
|
-
|
270
|
-
|
567
|
+
it "transitions to `unknown` if its parent transitions to warn" do
|
568
|
+
warn_event = Arborist::Event.create( :node_warn, parent_node )
|
271
569
|
|
272
|
-
|
273
|
-
|
570
|
+
expect {
|
571
|
+
node.handle_event( warn_event )
|
572
|
+
}.to change { node.status }.from( 'quieted' ).to( 'unknown' )
|
274
573
|
end
|
275
574
|
|
276
|
-
|
277
|
-
|
278
|
-
expect
|
279
|
-
|
575
|
+
|
576
|
+
it "transitions to `disabled` if it's acknowledged" do
|
577
|
+
expect {
|
578
|
+
node.acknowledge( message: 'Turning this off for now.', sender: 'ged' )
|
579
|
+
}.to change { node.status }.from( 'quieted' ).to( 'disabled' )
|
280
580
|
end
|
581
|
+
|
582
|
+
end
|
583
|
+
|
584
|
+
|
585
|
+
describe "in `quieted` status because one of its dependencies is down" do
|
586
|
+
|
587
|
+
let( :down_event ) { Arborist::Event.create(:node_down, sibling_node) }
|
588
|
+
let( :up_event ) { Arborist::Event.create(:node_up, sibling_node) }
|
589
|
+
|
590
|
+
let( :node ) do
|
591
|
+
obj = super()
|
592
|
+
obj.depends_on( 'sibling' )
|
593
|
+
obj.handle_event( down_event )
|
594
|
+
obj
|
595
|
+
end
|
596
|
+
|
597
|
+
|
598
|
+
it_behaves_like "an unreachable node"
|
599
|
+
|
600
|
+
|
601
|
+
it "transitions to `unknown` if its reasons for being quieted are cleared" do
|
602
|
+
expect {
|
603
|
+
node.handle_event( up_event )
|
604
|
+
}.to change { node.status }.from( 'quieted' ).to( 'unknown' )
|
605
|
+
end
|
606
|
+
|
607
|
+
|
608
|
+
it "transitions to `disabled` if it's acknowledged" do
|
609
|
+
expect {
|
610
|
+
node.acknowledge( message: 'Turning this off for now.', sender: 'ged' )
|
611
|
+
}.to change { node.status }.from( 'quieted' ).to( 'disabled' )
|
612
|
+
end
|
613
|
+
|
281
614
|
end
|
282
615
|
|
283
616
|
|
@@ -376,13 +709,13 @@ describe Arborist::Node do
|
|
376
709
|
describe "Enumeration" do
|
377
710
|
|
378
711
|
it "iterates over its children for #each" do
|
379
|
-
parent =
|
712
|
+
parent = parent_node
|
380
713
|
parent <<
|
381
714
|
concrete_class.new('child1') { parent 'the_identifier' } <<
|
382
715
|
concrete_class.new('child2') { parent 'the_identifier' } <<
|
383
716
|
concrete_class.new('child3') { parent 'the_identifier' }
|
384
717
|
|
385
|
-
expect(
|
718
|
+
expect( parent_node.map(&:identifier) ).to eq([ 'child1', 'child2', 'child3' ])
|
386
719
|
end
|
387
720
|
|
388
721
|
end
|
@@ -390,6 +723,8 @@ describe Arborist::Node do
|
|
390
723
|
|
391
724
|
describe "Serialization" do
|
392
725
|
|
726
|
+
# From spec_helper.rb
|
727
|
+
let( :concrete_class ) { TestNode }
|
393
728
|
let( :node ) do
|
394
729
|
concrete_class.new( 'foo' ) do
|
395
730
|
parent 'bar'
|
@@ -407,12 +742,37 @@ describe Arborist::Node do
|
|
407
742
|
end
|
408
743
|
end
|
409
744
|
|
745
|
+
let( :tree ) do
|
746
|
+
node_hierarchy( node,
|
747
|
+
node_hierarchy( 'host-a',
|
748
|
+
testing_node( 'host-a-www' ),
|
749
|
+
testing_node( 'host-a-smtp' ),
|
750
|
+
testing_node( 'host-a-imap' )
|
751
|
+
),
|
752
|
+
node_hierarchy( 'host-b',
|
753
|
+
testing_node( 'host-b-www' ),
|
754
|
+
testing_node( 'host-b-nfs' ),
|
755
|
+
testing_node( 'host-b-ssh' )
|
756
|
+
),
|
757
|
+
node_hierarchy( 'host-c',
|
758
|
+
testing_node( 'host-c-www' )
|
759
|
+
),
|
760
|
+
node_hierarchy( 'host-d',
|
761
|
+
testing_node( 'host-d-ssh' ),
|
762
|
+
testing_node( 'host-d-amqp' ),
|
763
|
+
testing_node( 'host-d-database' ),
|
764
|
+
testing_node( 'host-d-memcached' )
|
765
|
+
)
|
766
|
+
)
|
767
|
+
end
|
768
|
+
|
410
769
|
|
411
770
|
it "can restore saved state from an older copy of the node" do
|
412
771
|
old_node = Marshal.load( Marshal.dump(node) )
|
413
772
|
|
414
773
|
old_node.status = 'down'
|
415
774
|
old_node.status_changed = Time.now - 400
|
775
|
+
old_node.status_last_changed = Time.now - 800
|
416
776
|
old_node.errors = "Host unreachable"
|
417
777
|
old_node.update(
|
418
778
|
ack: {
|
@@ -433,6 +793,7 @@ describe Arborist::Node do
|
|
433
793
|
|
434
794
|
expect( node.status ).to eq( old_node.status )
|
435
795
|
expect( node.status_changed ).to eq( old_node.status_changed )
|
796
|
+
expect( node.status_last_changed ).to eq( old_node.status_last_changed )
|
436
797
|
expect( node.errors ).to eq( old_node.errors )
|
437
798
|
expect( node.ack ).to eq( old_node.ack )
|
438
799
|
expect( node.properties ).to include( old_node.properties )
|
@@ -482,14 +843,14 @@ describe Arborist::Node do
|
|
482
843
|
|
483
844
|
|
484
845
|
it "can return a Hash of serializable node data" do
|
485
|
-
result =
|
846
|
+
result = tree.to_h
|
486
847
|
|
487
848
|
expect( result ).to be_a( Hash )
|
488
849
|
expect( result ).to include(
|
489
850
|
:identifier,
|
490
851
|
:parent, :description, :tags, :properties, :ack, :status,
|
491
852
|
:last_contacted, :status_changed, :errors, :quieted_reasons,
|
492
|
-
:dependencies
|
853
|
+
:dependencies, :status_last_changed
|
493
854
|
)
|
494
855
|
expect( result[:identifier] ).to eq( 'foo' )
|
495
856
|
expect( result[:type] ).to eq( 'testnode' )
|
@@ -500,13 +861,45 @@ describe Arborist::Node do
|
|
500
861
|
expect( result[:ack] ).to be_nil
|
501
862
|
expect( result[:last_contacted] ).to eq( node.last_contacted.iso8601 )
|
502
863
|
expect( result[:status_changed] ).to eq( node.status_changed.iso8601 )
|
864
|
+
expect( result[:status_last_changed] ).to eq( node.status_last_changed.iso8601 )
|
503
865
|
expect( result[:errors] ).to be_a( Hash )
|
504
866
|
expect( result[:errors] ).to be_empty
|
505
867
|
expect( result[:dependencies] ).to be_a( Hash )
|
506
868
|
expect( result[:quieted_reasons] ).to be_a( Hash )
|
869
|
+
|
870
|
+
expect( result[:children] ).to be_empty
|
507
871
|
end
|
508
872
|
|
509
873
|
|
874
|
+
it "can include all of its serialized children" do
|
875
|
+
result = tree.to_h( depth: -1 )
|
876
|
+
|
877
|
+
expect( result ).to be_a( Hash )
|
878
|
+
expect( result ).to include(
|
879
|
+
:identifier,
|
880
|
+
:parent, :description, :tags, :properties, :ack, :status,
|
881
|
+
:last_contacted, :status_changed, :errors, :quieted_reasons,
|
882
|
+
:dependencies
|
883
|
+
)
|
884
|
+
|
885
|
+
expect( result[:children] ).to be_a( Hash )
|
886
|
+
expect( result[:children].length ).to eq( 4 )
|
887
|
+
|
888
|
+
host_a = result[:children]['host-a']
|
889
|
+
expect( host_a ).to be_a( Hash )
|
890
|
+
expect( host_a ).to include(
|
891
|
+
:identifier,
|
892
|
+
:parent, :description, :tags, :properties, :ack, :status,
|
893
|
+
:last_contacted, :status_changed, :errors, :quieted_reasons,
|
894
|
+
:dependencies
|
895
|
+
)
|
896
|
+
expect( host_a[:children].length ).to eq( 3 )
|
897
|
+
end
|
898
|
+
|
899
|
+
|
900
|
+
it "can include a specific depth of its children"
|
901
|
+
|
902
|
+
|
510
903
|
it "can be reconstituted from a serialized Hash of node data" do
|
511
904
|
hash = node.to_h
|
512
905
|
cloned_node = concrete_class.from_hash( hash )
|
@@ -525,10 +918,10 @@ describe Arborist::Node do
|
|
525
918
|
|
526
919
|
it "an ACKed node stays ACKed when serialized and restored" do
|
527
920
|
node.update( error: "there's a fire" )
|
528
|
-
node.
|
921
|
+
node.acknowledge(
|
529
922
|
message: 'We know about the fire. It rages on.',
|
530
923
|
sender: '1986 Labyrinth David Bowie'
|
531
|
-
|
924
|
+
)
|
532
925
|
expect( node ).to be_acked
|
533
926
|
|
534
927
|
restored_node = Marshal.load( Marshal.dump(node) )
|
@@ -620,20 +1013,78 @@ describe Arborist::Node do
|
|
620
1013
|
end
|
621
1014
|
|
622
1015
|
|
623
|
-
it "
|
624
|
-
node.
|
625
|
-
events = node.update(ack: {
|
1016
|
+
it "includes the original ack in delta events" do
|
1017
|
+
events = node.acknowledge(
|
626
1018
|
message: "I have a poisonous friend. She's living in the house.",
|
627
1019
|
sender: 'Seabound'
|
628
|
-
|
1020
|
+
)
|
1021
|
+
delta_event = events.find {|ev| ev.type == 'node.delta' }
|
1022
|
+
expect( delta_event.payload ).to include( 'status' => ['up', 'disabled'] )
|
1023
|
+
expect( delta_event.payload ).to include( 'ack' => [ nil, a_hash_including(sender: 'Seabound') ] )
|
1024
|
+
|
1025
|
+
events = node.unacknowledge
|
1026
|
+
delta_event = events.find {|ev| ev.type == 'node.delta' }
|
1027
|
+
|
1028
|
+
expect( delta_event.payload ).to include( 'status' => ['disabled', 'unknown'] )
|
1029
|
+
expect( delta_event.payload ).to include( 'ack' => [ a_hash_including(sender: 'Seabound'), nil ] )
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
|
1033
|
+
it "generates a node.delta event when a node ack is updated" do
|
1034
|
+
node.update( error: 'ping failed ')
|
1035
|
+
node.acknowledge(
|
1036
|
+
message: "The last one was dead. This one is on her way.",
|
1037
|
+
sender: 'Average Trigram'
|
1038
|
+
)
|
1039
|
+
|
1040
|
+
events = node.acknowledge(
|
1041
|
+
message: "000100101011111",
|
1042
|
+
sender: 'Robots'
|
1043
|
+
)
|
1044
|
+
expect( events.size ).to eq( 2 )
|
629
1045
|
|
630
|
-
|
631
|
-
|
1046
|
+
delta = events.last
|
1047
|
+
expect( delta ).to be_a( Arborist::Event::NodeDelta )
|
632
1048
|
|
633
|
-
expect(
|
634
|
-
|
1049
|
+
expect( delta.payload ).
|
1050
|
+
to include( 'ack' => [
|
1051
|
+
a_hash_including(sender: 'Average Trigram'), a_hash_including(sender: 'Robots')
|
1052
|
+
]
|
1053
|
+
)
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
|
1057
|
+
it "generates a node.acked and node.delta event when a node is acked" do
|
1058
|
+
node.update( error: 'ping failed ')
|
1059
|
+
events = node.acknowledge(
|
1060
|
+
message: "The last one was dead. This one is on her way.",
|
1061
|
+
sender: 'Average Trigram'
|
1062
|
+
)
|
1063
|
+
|
1064
|
+
expect( events.size ).to eq( 2 )
|
1065
|
+
|
1066
|
+
expect( events.first ).to be_a( Arborist::Event::NodeAcked )
|
1067
|
+
expect( events.last ).to be_a( Arborist::Event::NodeDelta )
|
1068
|
+
expect( events.first.payload ).
|
1069
|
+
to include( ack: a_hash_including(sender: 'Average Trigram') )
|
1070
|
+
expect( events.last.payload ).
|
1071
|
+
to include( 'ack' => [ nil, a_hash_including(sender: 'Average Trigram') ])
|
1072
|
+
expect( events.last.payload ).to include( 'status' => ['down', 'acked'] )
|
635
1073
|
end
|
636
1074
|
|
1075
|
+
|
1076
|
+
it "generates a node.down and node.delta event when a node is unacked" do
|
1077
|
+
node.update( error: 'ping failed ')
|
1078
|
+
node.acknowledge(
|
1079
|
+
message: "The humans are dead. I poked one. It's dead.",
|
1080
|
+
sender: 'Jermaine and Brit'
|
1081
|
+
)
|
1082
|
+
|
1083
|
+
events = node.unacknowledge
|
1084
|
+
expect( events.last.payload ).
|
1085
|
+
to include( 'ack' => [ a_hash_including(sender: 'Jermaine and Brit'), nil ])
|
1086
|
+
expect( events.last.payload ).to include( 'status' => ['acked', 'down'] )
|
1087
|
+
end
|
637
1088
|
end
|
638
1089
|
|
639
1090
|
|
@@ -648,6 +1099,7 @@ describe Arborist::Node do
|
|
648
1099
|
end
|
649
1100
|
|
650
1101
|
|
1102
|
+
|
651
1103
|
it "allows the addition of a Subscription" do
|
652
1104
|
sub = Arborist::Subscription.new {}
|
653
1105
|
node.add_subscription( sub )
|
@@ -655,7 +1107,6 @@ describe Arborist::Node do
|
|
655
1107
|
expect( node.subscriptions[sub.id] ).to be( sub )
|
656
1108
|
end
|
657
1109
|
|
658
|
-
|
659
1110
|
it "allows the removal of a Subscription" do
|
660
1111
|
sub = Arborist::Subscription.new {}
|
661
1112
|
node.add_subscription( sub )
|
@@ -678,11 +1129,23 @@ describe Arborist::Node do
|
|
678
1129
|
expect( results.first ).to be( sub )
|
679
1130
|
end
|
680
1131
|
|
1132
|
+
|
1133
|
+
it "can return the identifiers of all other nodes that subscribe to it" do
|
1134
|
+
|
1135
|
+
end
|
1136
|
+
|
681
1137
|
end
|
682
1138
|
|
683
1139
|
|
684
1140
|
describe "matching" do
|
685
1141
|
|
1142
|
+
let( :concrete_class ) do
|
1143
|
+
cls = Class.new( described_class ) do
|
1144
|
+
def self::name; "TestNode"; end
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
|
686
1149
|
let( :node ) do
|
687
1150
|
concrete_class.new( 'foo' ) do
|
688
1151
|
parent 'bar'
|
@@ -717,6 +1180,12 @@ describe Arborist::Node do
|
|
717
1180
|
expect( node ).to_not match_criteria( status: 'down' )
|
718
1181
|
end
|
719
1182
|
|
1183
|
+
it "can be matched with multiple statuses" do
|
1184
|
+
expect( node ).to match_criteria( status: ['up','warn'] )
|
1185
|
+
expect( node ).to_not match_criteria( status: 'down' )
|
1186
|
+
expect( node ).to match_criteria( status: 'up' )
|
1187
|
+
end
|
1188
|
+
|
720
1189
|
|
721
1190
|
it "can be matched with its type" do
|
722
1191
|
expect( node ).to match_criteria( type: 'testnode' )
|
@@ -726,7 +1195,9 @@ describe Arborist::Node do
|
|
726
1195
|
|
727
1196
|
it "can be matched with its parent" do
|
728
1197
|
expect( node ).to match_criteria( parent: 'bar' )
|
1198
|
+
expect( node ).to match_criteria( parent: [ 'bar', 'hooowat' ] )
|
729
1199
|
expect( node ).to_not match_criteria( parent: 'hooowat' )
|
1200
|
+
expect( node ).to_not match_criteria( parent: [ 'hooowat', 'wathoooo' ] )
|
730
1201
|
end
|
731
1202
|
|
732
1203
|
|
@@ -902,7 +1373,7 @@ describe Arborist::Node do
|
|
902
1373
|
mgr.load_tree([ vmhost01, vm01, memcache ])
|
903
1374
|
|
904
1375
|
events = vmhost01.
|
905
|
-
|
1376
|
+
acknowledge( message: "Imma gonna f up yo' sash", sender: "GOD" )
|
906
1377
|
vmhost01.publish_events( *events )
|
907
1378
|
|
908
1379
|
expect( memcache ).to be_quieted
|
@@ -913,10 +1384,10 @@ describe Arborist::Node do
|
|
913
1384
|
|
914
1385
|
describe "operational attribute modification" do
|
915
1386
|
|
916
|
-
|
917
1387
|
let( :node ) do
|
918
1388
|
concrete_class.new( 'foo' ) do
|
919
1389
|
parent 'bar'
|
1390
|
+
config boop: false
|
920
1391
|
description "The prototypical node"
|
921
1392
|
tags :chunker, :hunky, :flippin, :hippo
|
922
1393
|
end
|
@@ -935,6 +1406,12 @@ describe Arborist::Node do
|
|
935
1406
|
end
|
936
1407
|
|
937
1408
|
|
1409
|
+
it "can change any custom configuration values" do
|
1410
|
+
node.modify( config: { boop: true } )
|
1411
|
+
expect( node.config ).to eq({ 'boop' => true })
|
1412
|
+
end
|
1413
|
+
|
1414
|
+
|
938
1415
|
it "can change its tags" do
|
939
1416
|
node.modify( tags: %w[dew dairy daisy dilettante] )
|
940
1417
|
expect( node.tags ).to eq( %w[dew dairy daisy dilettante] )
|
@@ -945,6 +1422,91 @@ describe Arborist::Node do
|
|
945
1422
|
node.modify( tags: 'single' )
|
946
1423
|
expect( node.tags ).to eq( %w[single] )
|
947
1424
|
end
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
|
1428
|
+
describe "reparenting" do
|
1429
|
+
|
1430
|
+
before( :each ) do
|
1431
|
+
@old_parent = concrete_class.new( 'router1' ) do
|
1432
|
+
description "The first router"
|
1433
|
+
end
|
1434
|
+
@new_parent = concrete_class.new( 'router2' ) do
|
1435
|
+
description "The second router"
|
1436
|
+
end
|
1437
|
+
@node = concrete_class.new( 'foo' ) do
|
1438
|
+
parent 'router1'
|
1439
|
+
description "The prototypical node"
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
@old_parent.add_child( @node )
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
let( :node ) { @node }
|
1446
|
+
let( :old_parent ) { @old_parent }
|
1447
|
+
let( :new_parent ) { @new_parent }
|
1448
|
+
|
1449
|
+
|
1450
|
+
it "moves itself to the new node and removes itself from its old parent" do
|
1451
|
+
expect( old_parent.children ).to include( node.identifier )
|
1452
|
+
expect( new_parent.children ).to_not include( node.identifier )
|
1453
|
+
|
1454
|
+
node.reparent( old_parent, new_parent )
|
1455
|
+
|
1456
|
+
expect( old_parent.children ).to_not include( node.identifier )
|
1457
|
+
expect( new_parent.children ).to include( node.identifier )
|
1458
|
+
end
|
1459
|
+
|
1460
|
+
|
1461
|
+
it "sets its state to unknown if it was down prior to the move" do
|
1462
|
+
node.update( error: 'Rock and Roll McDonalds' )
|
1463
|
+
|
1464
|
+
node.reparent( old_parent, new_parent )
|
1465
|
+
|
1466
|
+
expect( node ).to be_unknown
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
|
1470
|
+
it "sets its state to unknown if it was quieted by its parent prior to the move" do
|
1471
|
+
node.quieted_reasons[ :primary ] = "Timex takes a licking and... well, broke, it looks like."
|
1472
|
+
node.status = 'quieted'
|
1473
|
+
|
1474
|
+
node.reparent( old_parent, new_parent )
|
1475
|
+
|
1476
|
+
expect( node ).to be_unknown
|
1477
|
+
end
|
1478
|
+
|
1479
|
+
|
1480
|
+
it "keeps its quieted state if it was quieted by secondary dependency prior to the move" do
|
1481
|
+
node.quieted_reasons[ :primary ] = "Timex takes a licking and... well, broke, it looks like."
|
1482
|
+
node.quieted_reasons[ :secondary ] = "Western Union: The fastest way to send money"
|
1483
|
+
node.status = 'quieted'
|
1484
|
+
|
1485
|
+
node.reparent( old_parent, new_parent )
|
1486
|
+
|
1487
|
+
expect( node ).to be_quieted
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
|
1491
|
+
it "keeps its disabled state" do
|
1492
|
+
node.acknowledge( message: 'Moving the machine', sender: 'Me' )
|
1493
|
+
expect( node ).to be_disabled
|
1494
|
+
|
1495
|
+
node.reparent( old_parent, new_parent )
|
1496
|
+
|
1497
|
+
expect( node ).to be_disabled
|
1498
|
+
end
|
1499
|
+
|
1500
|
+
|
1501
|
+
it "keeps its acked state" do
|
1502
|
+
node.update( {error: 'Batman whooped my ass.'}, 'gotham' )
|
1503
|
+
node.acknowledge( message: 'Moving the machine', sender: 'Me' )
|
1504
|
+
expect( node ).to be_acked
|
1505
|
+
|
1506
|
+
node.reparent( old_parent, new_parent )
|
1507
|
+
|
1508
|
+
expect( node ).to be_acked
|
1509
|
+
end
|
948
1510
|
|
949
1511
|
end
|
950
1512
|
|