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
@@ -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
+