arborist 0.0.1.pre20160128152542 → 0.0.1.pre20160606141735

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/ChangeLog +426 -1
  5. data/Manifest.txt +17 -2
  6. data/Nodes.md +70 -0
  7. data/Protocol.md +68 -9
  8. data/README.md +3 -5
  9. data/Rakefile +4 -1
  10. data/TODO.md +52 -20
  11. data/lib/arborist.rb +19 -6
  12. data/lib/arborist/cli.rb +39 -25
  13. data/lib/arborist/client.rb +97 -4
  14. data/lib/arborist/command/client.rb +2 -1
  15. data/lib/arborist/command/start.rb +51 -5
  16. data/lib/arborist/dependency.rb +286 -0
  17. data/lib/arborist/event.rb +7 -2
  18. data/lib/arborist/event/{node_matching.rb → node.rb} +11 -5
  19. data/lib/arborist/event/node_acked.rb +5 -7
  20. data/lib/arborist/event/node_delta.rb +30 -3
  21. data/lib/arborist/event/node_disabled.rb +16 -0
  22. data/lib/arborist/event/node_down.rb +10 -0
  23. data/lib/arborist/event/node_quieted.rb +11 -0
  24. data/lib/arborist/event/node_unknown.rb +10 -0
  25. data/lib/arborist/event/node_up.rb +10 -0
  26. data/lib/arborist/event/node_update.rb +2 -11
  27. data/lib/arborist/event/sys_node_added.rb +10 -0
  28. data/lib/arborist/event/sys_node_removed.rb +10 -0
  29. data/lib/arborist/exceptions.rb +4 -0
  30. data/lib/arborist/manager.rb +188 -18
  31. data/lib/arborist/manager/event_publisher.rb +1 -1
  32. data/lib/arborist/manager/tree_api.rb +92 -13
  33. data/lib/arborist/mixins.rb +17 -0
  34. data/lib/arborist/monitor.rb +10 -1
  35. data/lib/arborist/monitor/socket.rb +123 -2
  36. data/lib/arborist/monitor_runner.rb +6 -5
  37. data/lib/arborist/node.rb +420 -94
  38. data/lib/arborist/node/ack.rb +72 -0
  39. data/lib/arborist/node/host.rb +43 -8
  40. data/lib/arborist/node/resource.rb +73 -0
  41. data/lib/arborist/node/root.rb +6 -0
  42. data/lib/arborist/node/service.rb +89 -22
  43. data/lib/arborist/observer.rb +1 -1
  44. data/lib/arborist/subscription.rb +11 -6
  45. data/spec/arborist/client_spec.rb +93 -5
  46. data/spec/arborist/dependency_spec.rb +375 -0
  47. data/spec/arborist/event/node_delta_spec.rb +66 -0
  48. data/spec/arborist/event/node_down_spec.rb +84 -0
  49. data/spec/arborist/event/node_spec.rb +59 -0
  50. data/spec/arborist/event/node_update_spec.rb +14 -3
  51. data/spec/arborist/event_spec.rb +3 -3
  52. data/spec/arborist/manager/tree_api_spec.rb +295 -3
  53. data/spec/arborist/manager_spec.rb +240 -57
  54. data/spec/arborist/monitor_spec.rb +26 -3
  55. data/spec/arborist/node/ack_spec.rb +74 -0
  56. data/spec/arborist/node/host_spec.rb +79 -0
  57. data/spec/arborist/node/resource_spec.rb +56 -0
  58. data/spec/arborist/node/service_spec.rb +68 -2
  59. data/spec/arborist/node_spec.rb +288 -11
  60. data/spec/arborist/subscription_spec.rb +23 -14
  61. data/spec/arborist_spec.rb +0 -4
  62. data/spec/data/observers/webservices.rb +10 -2
  63. data/spec/spec_helper.rb +8 -0
  64. metadata +58 -15
  65. metadata.gz.sig +0 -0
  66. data/LICENSE +0 -29
@@ -4,10 +4,16 @@ require_relative '../spec_helper'
4
4
 
5
5
  require 'timecop'
6
6
  require 'arborist/manager'
7
-
7
+ require 'arborist/node/host'
8
8
 
9
9
  describe Arborist::Manager do
10
10
 
11
+ after( :all ) do
12
+ Arborist::Manager.state_file = nil
13
+ end
14
+ before( :each ) do
15
+ Arborist::Manager.configure
16
+ end
11
17
  after( :each ) do
12
18
  Arborist::Node::Root.reset
13
19
  end
@@ -49,6 +55,148 @@ describe Arborist::Manager do
49
55
  end
50
56
 
51
57
 
58
+ describe "state-saving" do
59
+
60
+ before( :each ) do
61
+ Arborist::Manager.state_file = nil
62
+ end
63
+
64
+ let( :router_node ) { Arborist::Host('router') }
65
+ let( :host_node ) { Arborist::Host( 'host-a', router_node ) }
66
+ let( :tree ) {[ router_node, host_node ]}
67
+
68
+ let( :manager ) do
69
+ instance = described_class.new
70
+ instance.load_tree( tree )
71
+ instance
72
+ end
73
+
74
+
75
+ it "saves the state of its node tree if the state file is configured" do
76
+ statefile = Pathname( './arborist.tree' )
77
+ Arborist::Manager.state_file = statefile
78
+
79
+ tempfile = instance_double( Tempfile,
80
+ path: './arborist20160224-31449-zevoz2.tree', unlink: nil )
81
+
82
+ expect( Tempfile ).to receive( :create ).
83
+ with( ['arborist', '.tree'], '.', encoding: 'binary' ).
84
+ and_return( tempfile )
85
+ expect( Marshal ).to receive( :dump ).with( manager.nodes, tempfile )
86
+ expect( tempfile ).to receive( :close )
87
+ expect( File ).to receive( :rename ).
88
+ with( './arborist20160224-31449-zevoz2.tree', './arborist.tree' )
89
+
90
+ manager.save_node_states
91
+ end
92
+
93
+
94
+ it "cleans up the tempfile created by checkpointing if renaming the file fails" do
95
+ statefile = Pathname( './arborist.tree' )
96
+ Arborist::Manager.state_file = statefile
97
+
98
+ tempfile = instance_double( Tempfile, path: './arborist20160224-31449-zevoz2.tree' )
99
+
100
+ expect( Tempfile ).to receive( :create ).
101
+ with( ['arborist', '.tree'], '.', encoding: 'binary' ).
102
+ and_return( tempfile )
103
+ expect( Marshal ).to receive( :dump ).with( manager.nodes, tempfile )
104
+ expect( tempfile ).to receive( :close )
105
+ expect( File ).to receive( :rename ).
106
+ and_raise( Errno::ENOENT.new("no such file or directory") )
107
+ expect( File ).to receive( :exist? ).with( tempfile.path ).and_return( true )
108
+ expect( File ).to receive( :unlink ).with( tempfile.path )
109
+
110
+ manager.save_node_states
111
+ end
112
+
113
+
114
+ it "doesn't try to save state if the state file is not configured" do
115
+ Arborist::Manager.state_file = nil
116
+
117
+ expect( Tempfile ).to_not receive( :create )
118
+ expect( Marshal ).to_not receive( :dump )
119
+ expect( File ).to_not receive( :rename )
120
+
121
+ manager.save_node_states
122
+ end
123
+
124
+
125
+ it "restores the state of loaded nodes if the state file is configured" do
126
+ _ = manager
127
+
128
+ statefile = Pathname( './arborist.tree' )
129
+ Arborist::Manager.state_file = statefile
130
+ state_file_io = instance_double( File )
131
+
132
+ saved_router_node = Marshal.load( Marshal.dump(router_node) )
133
+ saved_router_node.instance_variable_set( :@status, 'up' )
134
+ saved_host_node = Marshal.load( Marshal.dump(host_node) )
135
+ saved_host_node.instance_variable_set( :@status, 'down' )
136
+ saved_host_node.error = 'Stuff happened and it was not good.'
137
+
138
+ expect( statefile ).to receive( :readable? ).and_return( true )
139
+ expect( statefile ).to receive( :open ).with( 'r:binary' ).
140
+ and_return( state_file_io )
141
+ expect( Marshal ).to receive( :load ).with( state_file_io ).
142
+ and_return({ 'router' => saved_router_node, 'host-a' => saved_host_node })
143
+
144
+ expect( manager.restore_node_states ).to be_truthy
145
+
146
+ expect( manager.nodes['router'].status ).to eq( 'up' )
147
+ expect( manager.nodes['host-a'].status ).to eq( 'down' )
148
+ expect( manager.nodes['host-a'].error ).to eq( 'Stuff happened and it was not good.' )
149
+
150
+ end
151
+
152
+
153
+ it "doesn't error if the configured state file isn't readable" do
154
+ _ = manager
155
+
156
+ statefile = Pathname( './arborist.tree' )
157
+ Arborist::Manager.state_file = statefile
158
+
159
+ expect( statefile ).to receive( :readable? ).and_return( false )
160
+ expect( statefile ).to_not receive( :open )
161
+
162
+ expect( manager.restore_node_states ).to be_falsey
163
+ end
164
+
165
+
166
+ it "checkpoints the state file periodically if an interval is configured" do
167
+ described_class.configure( manager: {checkpoint_frequency: 20, state_file: 'arb.tree'} )
168
+
169
+ timer = instance_double( ZMQ::Timer, "checkpoint timer" )
170
+ expect( ZMQ::Timer ).to receive( :new ).with( 20, 0 ).and_return( timer )
171
+
172
+ expect( manager.start_state_checkpointing ).to eq( timer )
173
+ end
174
+
175
+
176
+ it "doesn't checkpoint if no interval is configured" do
177
+ described_class.configure( manager: {checkpoint_frequency: nil, state_file: 'arb.tree'} )
178
+
179
+ expect( ZMQ::Timer ).to_not receive( :new )
180
+
181
+ expect( manager.start_state_checkpointing ).to be_nil
182
+ end
183
+
184
+
185
+ it "doesn't checkpoint if no state file is configured" do
186
+ described_class.configure( manager: {checkpoint_frequency: 20, state_file: nil} )
187
+
188
+ expect( ZMQ::Timer ).to_not receive( :new )
189
+
190
+ expect( manager.start_state_checkpointing ).to be_nil
191
+ end
192
+
193
+
194
+ it "writes a checkpoint if it receives a SIGUSR1"
195
+
196
+
197
+ end
198
+
199
+
52
200
  context "a new empty manager" do
53
201
 
54
202
  let( :node ) do
@@ -206,16 +354,21 @@ describe Arborist::Manager do
206
354
 
207
355
  [
208
356
  testing_node( 'router' ),
209
- testing_node( 'host_a', 'router' ),
210
- testing_node( 'host_a_www', 'host_a' ),
211
- testing_node( 'host_a_smtp', 'host_a' ),
212
- testing_node( 'host_a_imap', 'host_a' ),
213
- testing_node( 'host_b', 'router' ),
214
- testing_node( 'host_b_www', 'host_b' ),
215
- testing_node( 'host_b_nfs', 'host_b' ),
216
- testing_node( 'host_b_ssh', 'host_b' ),
217
- testing_node( 'host_c', 'router' ),
218
- testing_node( 'host_c_www', 'host_c' ),
357
+ testing_node( 'host-a', 'router' ),
358
+ testing_node( 'host-a-www', 'host-a' ),
359
+ testing_node( 'host-a-smtp', 'host-a' ),
360
+ testing_node( 'host-a-imap', 'host-a' ),
361
+ testing_node( 'host-b', 'router' ),
362
+ testing_node( 'host-b-www', 'host-b' ),
363
+ testing_node( 'host-b-nfs', 'host-b' ),
364
+ testing_node( 'host-b-ssh', 'host-b' ),
365
+ testing_node( 'host-c', 'router' ),
366
+ testing_node( 'host-c-www', 'host-c' ),
367
+ testing_node( 'host-d', 'router' ),
368
+ testing_node( 'host-d-ssh', 'host-d' ),
369
+ testing_node( 'host-d-amqp', 'host-d' ),
370
+ testing_node( 'host-d-database', 'host-d' ),
371
+ testing_node( 'host-d-memcached', 'host-d' ),
219
372
  ]
220
373
  end
221
374
 
@@ -234,15 +387,11 @@ describe Arborist::Manager do
234
387
 
235
388
 
236
389
  it "can traverse all nodes whose status is 'up'" do
237
- manager.nodes.each {|_, node| node.status = :up }
238
- manager.nodes[ 'host_a' ].update( error: "ping failed" )
239
- expect( manager.nodes[ 'host_a' ] ).to be_down
240
- manager.nodes[ 'host_c' ].update( error: "gamma rays" )
241
- expect( manager.nodes[ 'host_c' ] ).to be_down
242
- manager.nodes[ 'host_b_nfs' ].
243
- update( ack: {sender: 'nancy_kerrigan', message: 'bad case of disk rot'} )
244
- expect( manager.nodes[ 'host_b_nfs' ] ).to be_disabled
245
- expect( manager.nodes[ 'host_b_nfs' ] ).to_not be_down
390
+ manager.nodes.each {|_, node| node.status = 'up' }
391
+ manager.nodes[ 'host-a' ].status = 'down'
392
+ manager.nodes[ 'host-c' ].status = 'down'
393
+ manager.nodes[ 'host-b-nfs' ].status = 'disabled'
394
+ manager.nodes[ 'host-b-www' ].status = 'quieted'
246
395
 
247
396
  iter = manager.reachable_nodes
248
397
 
@@ -252,23 +401,32 @@ describe Arborist::Manager do
252
401
  expect( nodes ).to include(
253
402
  "_",
254
403
  "router",
255
- "host_b",
256
- "host_b_www",
257
- "host_b_ssh"
404
+ "host-b",
405
+ "host-b-ssh",
406
+ "host-d",
407
+ "host-d-ssh",
408
+ "host-d-amqp",
409
+ "host-d-database",
410
+ "host-d-memcached",
258
411
  )
259
412
  expect( nodes ).to_not include(
260
- "host_b_nfs",
261
- "host_c",
262
- "host_c_www",
263
- "host_a",
264
- 'host_a_www',
265
- 'host_a_smtp',
266
- 'host_a_imap'
413
+ "host-b-www",
414
+ "host-b-nfs",
415
+ "host-c",
416
+ "host-c-www",
417
+ "host-a",
418
+ "host-a-www",
419
+ "host-a-smtp",
420
+ "host-a-imap",
267
421
  )
268
422
  end
269
423
 
270
424
 
271
- it "can create an Enumerator for all of a node's parents from leaf to root"
425
+ it "can create an Enumerator for all of its children to a specified depth" do
426
+ nodes = manager.depth_limited_enumerator_for( manager.nodes['_'], 2 ).to_a
427
+ expect( nodes.length ).to eq( 6 )
428
+ expect( nodes.map(&:identifier) ).to eq( %w[_ router host-a host-b host-c host-d] )
429
+ end
272
430
 
273
431
  end
274
432
 
@@ -282,16 +440,16 @@ describe Arborist::Manager do
282
440
 
283
441
  [
284
442
  testing_node( 'router' ),
285
- testing_node( 'host_a', 'router' ),
286
- testing_node( 'host_a_www', 'host_a' ),
287
- testing_node( 'host_a_smtp', 'host_a' ),
288
- testing_node( 'host_a_imap', 'host_a' ),
289
- testing_node( 'host_b', 'router' ),
290
- testing_node( 'host_b_www', 'host_b' ),
291
- testing_node( 'host_b_nfs', 'host_b' ),
292
- testing_node( 'host_b_ssh', 'host_b' ),
293
- testing_node( 'host_c', 'router' ),
294
- testing_node( 'host_c_www', 'host_c' ),
443
+ testing_node( 'host-a', 'router' ),
444
+ testing_node( 'host-a-www', 'host-a' ) { tags :home, :church },
445
+ testing_node( 'host-a-smtp', 'host-a' ) { tags :home },
446
+ testing_node( 'host-a-imap', 'host-a' ),
447
+ testing_node( 'host-b', 'router' ),
448
+ testing_node( 'host-b-www', 'host-b' ) { tags :church },
449
+ testing_node( 'host-b-nfs', 'host-b' ),
450
+ testing_node( 'host-b-ssh', 'host-b' ) { tags :work },
451
+ testing_node( 'host-c', 'router' ),
452
+ testing_node( 'host-c-www', 'host-c' ) { tags :work, :home },
295
453
  ]
296
454
  end
297
455
 
@@ -305,24 +463,50 @@ describe Arborist::Manager do
305
463
  it "can fetch a Hash of node states" do
306
464
  states = manager.fetch_matching_node_states( {}, [] )
307
465
  expect( states.size ).to eq( manager.nodes.size )
308
- expect( states ).to include( 'host_b_nfs', 'host_c', 'router' )
309
- expect( states['host_b_nfs'] ).to be_a( Hash )
310
- expect( states['host_c'] ).to be_a( Hash )
466
+ expect( states ).to include( 'host-b-nfs', 'host-c', 'router' )
467
+ expect( states['host-b-nfs'] ).to be_a( Hash )
468
+ expect( states['host-c'] ).to be_a( Hash )
311
469
  expect( states['router'] ).to be_a( Hash )
312
470
  end
313
471
 
314
472
 
473
+ it "can fetch a Hash of node states for nodes which match specified criteria" do
474
+ states = manager.fetch_matching_node_states( {'identifier' => 'host-c'}, [] )
475
+ expect( states.size ).to eq( 1 )
476
+ expect( states.keys.first ).to eq( 'host-c' )
477
+ expect( states['host-c'] ).to be_a( Hash )
478
+ end
479
+
480
+
481
+ it "can fetch a Hash of node states for nodes which don't match specified negative criteria" do
482
+ states = manager.fetch_matching_node_states( {}, [], false, {'identifier' => 'host-c'} )
483
+ expect( states.size ).to eq( manager.nodes.size - 1 )
484
+ expect( states ).to_not include( 'host-c' )
485
+ end
486
+
487
+
488
+ it "can fetch a Hash of node states for nodes combining positive and negative criteria" do
489
+ positive = {'tag' => 'home'}
490
+ negative = {'identifier' => 'host-a-www'}
491
+
492
+ states = manager.fetch_matching_node_states( positive, [], false, negative )
493
+
494
+ expect( states.size ).to eq( 2 )
495
+ expect( states ).to_not include( 'host-a-www' )
496
+ end
497
+
498
+
315
499
  it "can update an event by identifier" do
316
- manager.update_node( 'host_b_www', http: { status: 200 } )
500
+ manager.update_node( 'host-b-www', http: { status: 200 } )
317
501
  expect(
318
- manager.nodes['host_b_www'].properties
502
+ manager.nodes['host-b-www'].properties
319
503
  ).to include( 'http' => { 'status' => 200 } )
320
504
  end
321
505
 
322
506
 
323
507
  it "ignores updates to an identifier that is not (any longer) in the tree" do
324
508
  expect {
325
- manager.update_node( 'host_y', asset_tag: '2by-n86y7t' )
509
+ manager.update_node( 'host-y', asset_tag: '2by-n86y7t' )
326
510
  }.to_not raise_error
327
511
  end
328
512
 
@@ -331,10 +515,10 @@ describe Arborist::Manager do
331
515
  expect( manager.root ).to receive( :publish_events ).
332
516
  at_least( :once ).
333
517
  and_call_original
334
- expect( manager.nodes['host_c'] ).to receive( :publish_events ).
518
+ expect( manager.nodes['host-c'] ).to receive( :publish_events ).
335
519
  at_least( :once ).
336
520
  and_call_original
337
- manager.update_node( 'host_c_www', response_status: 504, error: 'Timeout talking to web service.' )
521
+ manager.update_node( 'host-c-www', response_status: 504, error: 'Timeout talking to web service.' )
338
522
  end
339
523
 
340
524
 
@@ -342,9 +526,9 @@ describe Arborist::Manager do
342
526
  expect( manager.root ).to receive( :publish_events ).
343
527
  at_least( :once ).
344
528
  and_call_original
345
- expect( manager.nodes['host_c'] ).to_not receive( :publish_events )
529
+ expect( manager.nodes['host-c'] ).to_not receive( :publish_events )
346
530
 
347
- manager.update_node( 'host_b_www', response_status: 504, error: 'Timeout talking to web service.' )
531
+ manager.update_node( 'host-b-www', response_status: 504, error: 'Timeout talking to web service.' )
348
532
  end
349
533
 
350
534
  end
@@ -352,7 +536,7 @@ describe Arborist::Manager do
352
536
 
353
537
  describe "subscriptions" do
354
538
 
355
- let( :tree ) {[ testing_node('host_c') ]}
539
+ let( :tree ) {[ testing_node('host-c') ]}
356
540
  let( :manager ) do
357
541
  instance = described_class.new
358
542
  instance.load_tree( tree )
@@ -363,24 +547,24 @@ describe Arborist::Manager do
363
547
  it "can attach subscriptions to a node by its identifier" do
364
548
  sub = subid = nil
365
549
  expect {
366
- sub = manager.create_subscription( 'host_c', 'node.update', type: 'host' )
550
+ sub = manager.create_subscription( 'host-c', 'node.update', type: 'host' )
367
551
  }.to change { manager.subscriptions.size }.by( 1 )
368
552
 
369
553
  node = manager.subscriptions[ sub.id ]
370
554
 
371
555
  expect( sub ).to be_a( Arborist::Subscription )
372
- expect( node ).to be( manager.nodes['host_c'] )
556
+ expect( node ).to be( manager.nodes['host-c'] )
373
557
  end
374
558
 
375
559
 
376
560
  it "can detach subscriptions from a node given the subscription ID" do
377
- sub = manager.create_subscription( 'host_c', 'node.ack', type: 'service' )
561
+ sub = manager.create_subscription( 'host-c', 'node.ack', type: 'service' )
378
562
  rval = nil
379
563
 
380
564
  expect {
381
565
  rval = manager.remove_subscription( sub.id )
382
566
  }.to change { manager.subscriptions.size }.by( -1 ).and(
383
- change { manager.nodes['host_c'].subscriptions.size }.by( -1 )
567
+ change { manager.nodes['host-c'].subscriptions.size }.by( -1 )
384
568
  )
385
569
 
386
570
  expect( rval ).to be( sub )
@@ -415,7 +599,6 @@ describe Arborist::Manager do
415
599
  end
416
600
 
417
601
 
418
-
419
602
  it "sets up its sockets with handlers and starts the ZMQ loop when started" do
420
603
  expect( tree_sock ).to receive( :bind ).with( Arborist.tree_api_url )
421
604
  expect( tree_sock ).to receive( :linger= ).with( 0 )
@@ -26,9 +26,9 @@ describe Arborist::Monitor do
26
26
 
27
27
 
28
28
  let( :testing_nodes ) {{
29
- 'trunk' => trunk_node.to_hash,
30
- 'branch' => branch_node.to_hash,
31
- 'leaf' => leaf_node.to_hash
29
+ 'trunk' => trunk_node.to_h,
30
+ 'branch' => branch_node.to_h,
31
+ 'leaf' => leaf_node.to_h
32
32
  }}
33
33
 
34
34
 
@@ -181,6 +181,29 @@ describe Arborist::Monitor do
181
181
  end
182
182
 
183
183
 
184
+ it "handles system call errors while running the monitor command" do
185
+ mon = described_class.new( "the description" ) do
186
+
187
+ exec 'the_command'
188
+
189
+ handle_results {|*| }
190
+ exec_input {|*| }
191
+ exec_arguments do |nodes|
192
+ Loggability[ Arborist ].debug "In the argument-builder."
193
+ nodes.keys
194
+ end
195
+ end
196
+
197
+ expect( Process ).to receive( :spawn ) do |*args|
198
+ raise Errno::EPIPE, "broken pipe"
199
+ end
200
+
201
+ expect {
202
+ mon.run( testing_nodes )
203
+ }.to_not raise_error
204
+ end
205
+
206
+
184
207
  it "can provide a function for providing input to its command" do
185
208
  mon = described_class.new( "the description" ) do
186
209