arborist 0.0.1.pre20160128152542 → 0.0.1.pre20160606141735

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.
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
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../../spec_helper'
4
+
5
+ require 'arborist/node'
6
+ require 'arborist/subscription'
7
+ require 'arborist/event/node'
8
+
9
+
10
+ describe Arborist::Event::Node do
11
+
12
+ let( :node ) do
13
+ TestNode.new( 'foo' ) do
14
+ parent 'bar'
15
+ description "A testing node"
16
+ tags :yelp, :yank, :yore, :yandex
17
+
18
+ update(
19
+ "tcp_socket_connect" => {
20
+ "time" => "2016-02-25 16:04:35 -0800",
21
+ "duration" => 0.020619
22
+ }
23
+ )
24
+ end
25
+ end
26
+ let( :event ) { described_class.new(node) }
27
+
28
+
29
+
30
+ it "matches match-anything subscriptions" do
31
+ sub = Arborist::Subscription.new {}
32
+ expect( event ).to match( sub )
33
+ end
34
+
35
+
36
+ it "matches subscriptions which have matching criteria" do
37
+ criteria = {
38
+ tag: node.tags.last,
39
+ status: node.status
40
+ }
41
+ sub = Arborist::Subscription.new( nil, criteria ) {}
42
+
43
+ expect( event ).to match( sub )
44
+ end
45
+
46
+
47
+ it "matches subscriptions which have non-matching negative criteria" do
48
+ pending "Adding negative criteria to subscriptions"
49
+ negative_criteria = {
50
+ tag: 'nope'
51
+ }
52
+ sub = Arborist::Subscription.new( nil, {}, negative_criteria ) {}
53
+
54
+ expect( event ).to match( sub )
55
+ end
56
+
57
+
58
+ end
59
+
@@ -41,7 +41,7 @@ describe Arborist::Event::NodeUpdate do
41
41
  describe "subscription support" do
42
42
 
43
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' )
44
+ sub = Arborist::Subscription.new( 'node.update' ) {}
45
45
  event = described_class.new( node )
46
46
 
47
47
  expect( event ).to match( sub )
@@ -49,7 +49,7 @@ describe Arborist::Event::NodeUpdate do
49
49
 
50
50
 
51
51
  it "matches a subscription with a matching event type and matching criteria" do
52
- sub = Arborist::Subscription.new( :publisher, 'node.update', 'tag' => 'chunker' )
52
+ sub = Arborist::Subscription.new( 'node.update', 'tag' => 'chunker' ) {}
53
53
  event = described_class.new( node )
54
54
 
55
55
  expect( event ).to match( sub )
@@ -57,7 +57,7 @@ describe Arborist::Event::NodeUpdate do
57
57
 
58
58
 
59
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' )
60
+ sub = Arborist::Subscription.new( 'node.update', 'tag' => 'looper' ) {}
61
61
  event = described_class.new( node )
62
62
 
63
63
  expect( event ).to_not match( sub )
@@ -67,5 +67,16 @@ describe Arborist::Event::NodeUpdate do
67
67
  end
68
68
 
69
69
 
70
+ describe "payload" do
71
+
72
+ it "includes its attributes" do
73
+ event = described_class.new( node )
74
+
75
+ expect( event.payload ).to be_a( Hash )
76
+ expect( event.payload ).to include( :status, :properties, :type )
77
+ end
78
+
79
+ end
80
+
70
81
  end
71
82
 
@@ -26,7 +26,7 @@ describe Arborist::Event do
26
26
  describe "subscription support" do
27
27
 
28
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' )
29
+ sub = Arborist::Subscription.new( 'test.event' ) {}
30
30
  event = described_class.create( TestEvent, [] )
31
31
 
32
32
  expect( event ).to match( sub )
@@ -34,7 +34,7 @@ describe Arborist::Event do
34
34
 
35
35
 
36
36
  it "always matches a subscription with a nil event type" do
37
- sub = Arborist::Subscription.new( :publisher )
37
+ sub = Arborist::Subscription.new {}
38
38
  event = described_class.create( TestEvent, [] )
39
39
 
40
40
  expect( event ).to match( sub )
@@ -49,7 +49,7 @@ describe Arborist::Event do
49
49
  payload = { 'status' => ['up', 'down'] }
50
50
  ev = TestEvent.create( TestEvent, payload )
51
51
 
52
- result = ev.to_hash
52
+ result = ev.to_h
53
53
 
54
54
  expect( result ).to include( 'type', 'data' )
55
55
 
@@ -122,14 +122,14 @@ describe Arborist::Manager::TreeAPI, :testing_manager do
122
122
  end
123
123
 
124
124
 
125
- it "send an error response if the request's body is not a Map or Nil" do
125
+ it "send an error response if the request's body is not Nil, a Map, or an Array of Maps" do
126
126
  sock.send( MessagePack.pack([{version: 1, action: 'list'}, 18]) )
127
127
  resmsg = sock.recv
128
128
 
129
129
  hdr, body = unpack_message( resmsg )
130
130
  expect( hdr ).to include(
131
131
  'success' => false,
132
- 'reason' => /invalid request.*body must be a map or nil/i,
132
+ 'reason' => /invalid request.*body must be nil, a map, or an array of maps/i,
133
133
  'category' => 'client'
134
134
  )
135
135
  expect( body ).to be_nil
@@ -217,6 +217,40 @@ describe Arborist::Manager::TreeAPI, :testing_manager do
217
217
  end
218
218
 
219
219
 
220
+ it "returns an array of full state maps for nodes not matching specified negative criteria" do
221
+ msg = pack_message( :fetch, [ {}, {type: 'service', port: 22} ] )
222
+
223
+ sock.send( msg )
224
+ resmsg = sock.recv
225
+
226
+ hdr, body = unpack_message( resmsg )
227
+ expect( hdr ).to include( 'success' => true )
228
+
229
+ expect( body ).to be_a( Hash )
230
+ expect( body.length ).to eq( manager.nodes.length - 3 )
231
+
232
+ expect( body.values ).to all( be_a(Hash) )
233
+ expect( body.values ).to all( include('status', 'type') )
234
+ end
235
+
236
+
237
+ it "returns an array of full state maps for nodes combining positive and negative criteria" do
238
+ msg = pack_message( :fetch, [ {type: 'service'}, {port: 22} ] )
239
+
240
+ sock.send( msg )
241
+ resmsg = sock.recv
242
+
243
+ hdr, body = unpack_message( resmsg )
244
+ expect( hdr ).to include( 'success' => true )
245
+
246
+ expect( body ).to be_a( Hash )
247
+ expect( body.length ).to eq( 16 )
248
+
249
+ expect( body.values ).to all( be_a(Hash) )
250
+ expect( body.values ).to all( include('status', 'type') )
251
+ end
252
+
253
+
220
254
  it "doesn't return nodes beneath downed nodes by default" do
221
255
  manager.nodes['sidonie'].update( error: 'sunspots' )
222
256
  msg = pack_message( :fetch, type: 'service', port: 22 )
@@ -276,6 +310,7 @@ describe Arborist::Manager::TreeAPI, :testing_manager do
276
310
  expect( body.values.map(&:keys) ).to all( contain_exactly('status', 'tags', 'addresses') )
277
311
  end
278
312
 
313
+
279
314
  end
280
315
 
281
316
 
@@ -292,11 +327,25 @@ describe Arborist::Manager::TreeAPI, :testing_manager do
292
327
  expect( body ).to all( be_a(Hash) )
293
328
  expect( body ).to include( hash_including('identifier' => '_') )
294
329
  expect( body ).to include( hash_including('identifier' => 'duir') )
330
+ expect( body ).to include( hash_including('identifier' => 'sidonie') )
295
331
  expect( body ).to include( hash_including('identifier' => 'sidonie-ssh') )
296
332
  expect( body ).to include( hash_including('identifier' => 'sidonie-demon-http') )
297
333
  expect( body ).to include( hash_including('identifier' => 'yevaud') )
298
334
  end
299
335
 
336
+ it "can be limited by depth" do
337
+ msg = pack_message( :list, {depth: 1}, nil )
338
+ sock.send( msg )
339
+ resmsg = sock.recv
340
+
341
+ hdr, body = unpack_message( resmsg )
342
+ expect( hdr ).to include( 'success' => true )
343
+ expect( body.length ).to eq( 3 )
344
+ expect( body ).to all( be_a(Hash) )
345
+ expect( body ).to include( hash_including('identifier' => '_') )
346
+ expect( body ).to include( hash_including('identifier' => 'duir') )
347
+ expect( body ).to_not include( hash_including('identifier' => 'duir-ssh') )
348
+ end
300
349
  end
301
350
 
302
351
 
@@ -456,4 +505,247 @@ describe Arborist::Manager::TreeAPI, :testing_manager do
456
505
 
457
506
  end
458
507
 
459
- end
508
+
509
+ describe "prune" do
510
+
511
+ it "removes a single node" do
512
+ msg = pack_message( :prune, {identifier: 'duir-ssh'}, nil )
513
+ sock.send( msg )
514
+ resmsg = sock.recv
515
+
516
+ hdr, body = unpack_message( resmsg )
517
+ expect( hdr ).to include( 'success' => true )
518
+ expect( body ).to eq( true )
519
+ expect( manager.nodes ).to_not include( 'duir-ssh' )
520
+ end
521
+
522
+
523
+ it "returns Nil without error if the node to prune didn't exist" do
524
+ msg = pack_message( :prune, {identifier: 'shemp-ssh'}, nil )
525
+ sock.send( msg )
526
+ resmsg = sock.recv
527
+
528
+ hdr, body = unpack_message( resmsg )
529
+ expect( hdr ).to include( 'success' => true )
530
+ expect( body ).to be_nil
531
+ end
532
+
533
+
534
+ it "removes children nodes along with the parent" do
535
+ msg = pack_message( :prune, {identifier: 'duir'}, nil )
536
+ sock.send( msg )
537
+ resmsg = sock.recv
538
+
539
+ hdr, body = unpack_message( resmsg )
540
+ expect( hdr ).to include( 'success' => true )
541
+ expect( body ).to eq( true )
542
+ expect( manager.nodes ).to_not include( 'duir' )
543
+ expect( manager.nodes ).to_not include( 'duir-ssh' )
544
+ end
545
+
546
+
547
+ it "returns an error to the client when missing required attributes" do
548
+ msg = pack_message( :prune )
549
+ sock.send( msg )
550
+ resmsg = sock.recv
551
+
552
+ hdr, body = unpack_message( resmsg )
553
+ expect( hdr ).to include( 'success' => false )
554
+ expect( hdr['reason'] ).to match( /no identifier/i )
555
+ end
556
+ end
557
+
558
+
559
+ describe "graft" do
560
+
561
+ it "can add a node with no explicit parent" do
562
+ header = {
563
+ identifier: 'guenter',
564
+ type: 'host',
565
+ }
566
+ attributes = {
567
+ description: 'The evil penguin node of doom.',
568
+ addresses: ['10.2.66.8'],
569
+ tags: ['internal', 'football']
570
+ }
571
+ msg = pack_message( :graft, header, attributes )
572
+
573
+ sock.send( msg )
574
+ resmsg = sock.recv
575
+
576
+ hdr, body = unpack_message( resmsg )
577
+ expect( hdr ).to include( 'success' => true )
578
+ expect( body ).to eq( 'guenter' )
579
+
580
+ new_node = manager.nodes[ 'guenter' ]
581
+ expect( new_node ).to be_a( Arborist::Node::Host )
582
+ expect( new_node.identifier ).to eq( header[:identifier] )
583
+ expect( new_node.description ).to eq( attributes[:description] )
584
+ expect( new_node.addresses ).to eq([ IPAddr.new(attributes[:addresses].first) ])
585
+ expect( new_node.tags ).to include( *attributes[:tags] )
586
+ end
587
+
588
+
589
+ it "can add a node with a parent specified" do
590
+ header = {
591
+ identifier: 'orgalorg',
592
+ type: 'host',
593
+ parent: 'duir'
594
+ }
595
+ attributes = {
596
+ description: 'The true form of the evil penguin node of doom.',
597
+ addresses: ['192.168.22.8'],
598
+ tags: ['evil', 'space', 'entity']
599
+ }
600
+ msg = pack_message( :graft, header, attributes )
601
+
602
+ sock.send( msg )
603
+ resmsg = sock.recv
604
+
605
+ hdr, body = unpack_message( resmsg )
606
+ expect( hdr ).to include( 'success' => true )
607
+ expect( body ).to eq( 'orgalorg' )
608
+
609
+ new_node = manager.nodes[ 'orgalorg' ]
610
+ expect( new_node ).to be_a( Arborist::Node::Host )
611
+ expect( new_node.identifier ).to eq( header[:identifier] )
612
+ expect( new_node.parent ).to eq( header[:parent] )
613
+ expect( new_node.description ).to eq( attributes[:description] )
614
+ expect( new_node.addresses ).to eq([ IPAddr.new(attributes[:addresses].first) ])
615
+ expect( new_node.tags ).to include( *attributes[:tags] )
616
+ end
617
+
618
+
619
+ it "can add a subordinate node" do
620
+ header = {
621
+ identifier: 'echo',
622
+ type: 'service',
623
+ parent: 'duir'
624
+ }
625
+ attributes = {
626
+ description: 'Mmmmm AppleTalk.'
627
+ }
628
+ msg = pack_message( :graft, header, attributes )
629
+
630
+ sock.send( msg )
631
+ resmsg = sock.recv
632
+
633
+ hdr, body = unpack_message( resmsg )
634
+ expect( hdr ).to include( 'success' => true )
635
+ expect( body ).to eq( 'duir-echo' )
636
+
637
+ new_node = manager.nodes[ 'duir-echo' ]
638
+ expect( new_node ).to be_a( Arborist::Node::Service )
639
+ expect( new_node.identifier ).to eq( 'duir-echo' )
640
+ expect( new_node.parent ).to eq( header[:parent] )
641
+ expect( new_node.description ).to eq( attributes[:description] )
642
+ expect( new_node.port ).to eq( 7 )
643
+ expect( new_node.protocol ).to eq( 'tcp' )
644
+ expect( new_node.app_protocol ).to eq( 'echo' )
645
+ end
646
+
647
+
648
+ it "errors if adding a subordinate node with no parent" do
649
+ header = {
650
+ identifier: 'echo',
651
+ type: 'service'
652
+ }
653
+ attributes = {
654
+ description: 'Mmmmm AppleTalk.'
655
+ }
656
+ msg = pack_message( :graft, header, attributes )
657
+
658
+ sock.send( msg )
659
+ resmsg = sock.recv
660
+
661
+ hdr, body = unpack_message( resmsg )
662
+ expect( hdr ).to include( 'success' => false )
663
+ expect( hdr['reason'] ).to match( /no host given/i )
664
+ end
665
+
666
+ end
667
+
668
+
669
+ describe "modify" do
670
+
671
+ it "can change operational attributes of a node" do
672
+ header = {
673
+ identifier: 'sidonie',
674
+ }
675
+ attributes = {
676
+ parent: '_',
677
+ addresses: ['192.168.32.32', '10.2.2.28']
678
+ }
679
+ msg = pack_message( :modify, header, attributes )
680
+
681
+ sock.send( msg )
682
+ resmsg = sock.recv
683
+
684
+ hdr, body = unpack_message( resmsg )
685
+ expect( hdr ).to include( 'success' => true )
686
+
687
+ node = manager.nodes[ 'sidonie' ]
688
+ expect(
689
+ node.addresses
690
+ ).to eq( [IPAddr.new('192.168.32.32'), IPAddr.new('10.2.2.28')] )
691
+ expect( node.parent ).to eq( '_' )
692
+ end
693
+
694
+
695
+ it "ignores modifications to unsupported attributes" do
696
+ header = {
697
+ identifier: 'sidonie',
698
+ }
699
+ attributes = {
700
+ identifier: 'somethingelse'
701
+ }
702
+ msg = pack_message( :modify, header, attributes )
703
+
704
+ sock.send( msg )
705
+ resmsg = sock.recv
706
+
707
+ hdr, body = unpack_message( resmsg )
708
+ expect( hdr ).to include( 'success' => true )
709
+
710
+ expect( manager.nodes['sidonie'] ).to be_an( Arborist::Node )
711
+ expect( manager.nodes['sidonie'].identifier ).to eq( 'sidonie' )
712
+ end
713
+
714
+
715
+ it "errors on modifications to the root node" do
716
+ header = {
717
+ identifier: '_',
718
+ }
719
+ attributes = {
720
+ identifier: 'somethingelse'
721
+ }
722
+ msg = pack_message( :modify, header, attributes )
723
+
724
+ sock.send( msg )
725
+ resmsg = sock.recv
726
+
727
+ hdr, body = unpack_message( resmsg )
728
+ expect( hdr ).to include( 'success' => false )
729
+ expect( manager.nodes['_'].identifier ).to eq( '_' )
730
+ end
731
+
732
+
733
+ it "errors on modifications to nonexistent nodes" do
734
+ header = {
735
+ identifier: 'nopenopenope',
736
+ }
737
+ attributes = {
738
+ identifier: 'somethingelse'
739
+ }
740
+ msg = pack_message( :modify, header, attributes )
741
+
742
+ sock.send( msg )
743
+ resmsg = sock.recv
744
+
745
+ hdr, body = unpack_message( resmsg )
746
+ expect( hdr ).to include( 'success' => false )
747
+ end
748
+ end
749
+
750
+ end
751
+