arborist 0.0.1.pre20160106113421

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.document +4 -0
  3. data/.simplecov +9 -0
  4. data/ChangeLog +417 -0
  5. data/Events.md +20 -0
  6. data/History.md +4 -0
  7. data/LICENSE +29 -0
  8. data/Manifest.txt +72 -0
  9. data/Monitors.md +141 -0
  10. data/Nodes.md +0 -0
  11. data/Observers.md +72 -0
  12. data/Protocol.md +214 -0
  13. data/README.md +75 -0
  14. data/Rakefile +81 -0
  15. data/TODO.md +24 -0
  16. data/bin/amanagerd +10 -0
  17. data/bin/amonitord +12 -0
  18. data/bin/aobserverd +12 -0
  19. data/lib/arborist.rb +182 -0
  20. data/lib/arborist/client.rb +191 -0
  21. data/lib/arborist/event.rb +61 -0
  22. data/lib/arborist/event/node_acked.rb +18 -0
  23. data/lib/arborist/event/node_delta.rb +20 -0
  24. data/lib/arborist/event/node_matching.rb +34 -0
  25. data/lib/arborist/event/node_update.rb +19 -0
  26. data/lib/arborist/event/sys_reloaded.rb +15 -0
  27. data/lib/arborist/exceptions.rb +21 -0
  28. data/lib/arborist/manager.rb +508 -0
  29. data/lib/arborist/manager/event_publisher.rb +97 -0
  30. data/lib/arborist/manager/tree_api.rb +207 -0
  31. data/lib/arborist/mixins.rb +363 -0
  32. data/lib/arborist/monitor.rb +377 -0
  33. data/lib/arborist/monitor/socket.rb +163 -0
  34. data/lib/arborist/monitor_runner.rb +217 -0
  35. data/lib/arborist/node.rb +700 -0
  36. data/lib/arborist/node/host.rb +87 -0
  37. data/lib/arborist/node/root.rb +60 -0
  38. data/lib/arborist/node/service.rb +112 -0
  39. data/lib/arborist/observer.rb +176 -0
  40. data/lib/arborist/observer/action.rb +125 -0
  41. data/lib/arborist/observer/summarize.rb +105 -0
  42. data/lib/arborist/observer_runner.rb +181 -0
  43. data/lib/arborist/subscription.rb +82 -0
  44. data/spec/arborist/client_spec.rb +282 -0
  45. data/spec/arborist/event/node_update_spec.rb +71 -0
  46. data/spec/arborist/event_spec.rb +64 -0
  47. data/spec/arborist/manager/event_publisher_spec.rb +66 -0
  48. data/spec/arborist/manager/tree_api_spec.rb +458 -0
  49. data/spec/arborist/manager_spec.rb +442 -0
  50. data/spec/arborist/mixins_spec.rb +195 -0
  51. data/spec/arborist/monitor/socket_spec.rb +195 -0
  52. data/spec/arborist/monitor_runner_spec.rb +152 -0
  53. data/spec/arborist/monitor_spec.rb +251 -0
  54. data/spec/arborist/node/host_spec.rb +104 -0
  55. data/spec/arborist/node/root_spec.rb +29 -0
  56. data/spec/arborist/node/service_spec.rb +98 -0
  57. data/spec/arborist/node_spec.rb +552 -0
  58. data/spec/arborist/observer/action_spec.rb +205 -0
  59. data/spec/arborist/observer/summarize_spec.rb +294 -0
  60. data/spec/arborist/observer_spec.rb +146 -0
  61. data/spec/arborist/subscription_spec.rb +71 -0
  62. data/spec/arborist_spec.rb +146 -0
  63. data/spec/data/monitors/pings.rb +80 -0
  64. data/spec/data/monitors/port_checks.rb +27 -0
  65. data/spec/data/monitors/system_resources.rb +30 -0
  66. data/spec/data/monitors/web_services.rb +17 -0
  67. data/spec/data/nodes/duir.rb +20 -0
  68. data/spec/data/nodes/localhost.rb +15 -0
  69. data/spec/data/nodes/sidonie.rb +29 -0
  70. data/spec/data/nodes/yevaud.rb +26 -0
  71. data/spec/data/observers/auditor.rb +23 -0
  72. data/spec/data/observers/webservices.rb +18 -0
  73. data/spec/spec_helper.rb +117 -0
  74. metadata +368 -0
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ describe Arborist::Observer do
6
+
7
+
8
+ it "can be created with just a description" do
9
+ observer = described_class.new( "the description" )
10
+ expect( observer ).to be_a( described_class )
11
+ expect( observer.description ).to eq( "the description" )
12
+ end
13
+
14
+
15
+ it "yields itself to the provided block for the DSL" do
16
+ block_self = nil
17
+ observer = described_class.new( "testing observer" ) do
18
+ block_self = self
19
+ end
20
+
21
+ expect( block_self ).to be( observer )
22
+ end
23
+
24
+
25
+ it "can specify a subscription for an event it's interested in" do
26
+ observer = described_class.new( "testing observer" ) do
27
+ subscribe to: 'node.delta'
28
+ end
29
+
30
+ expect( observer.subscriptions ).to be_an( Array )
31
+ expect( observer.subscriptions.length ).to eq( 1 )
32
+ end
33
+
34
+
35
+ it "can specify a subscription for more than one event it's interested in" do
36
+ observer = described_class.new( "testing observer" ) do
37
+ subscribe to: 'node.delta'
38
+ subscribe to: 'sys.reload'
39
+ end
40
+
41
+ expect( observer.subscriptions ).to be_an( Array )
42
+ expect( observer.subscriptions.length ).to eq( 2 )
43
+ end
44
+
45
+
46
+ it "can specify an action to run when a subscribed event is received" do
47
+ observer = described_class.new( "testing observer" ) do
48
+ action do |uuid, event|
49
+ puts( uuid )
50
+ end
51
+ end
52
+
53
+ expect( observer.actions ).to be_an( Array )
54
+ expect( observer.actions.length ).to eq( 1 )
55
+ end
56
+
57
+
58
+ it "can specify more than one action to run when a subscribed event is received" do
59
+ observer = described_class.new( "testing observer" ) do
60
+ action do |uuid, event|
61
+ puts( uuid )
62
+ end
63
+ action do |uuid, event|
64
+ $stderr.puts( uuid )
65
+ end
66
+ end
67
+
68
+ expect( observer.actions ).to be_an( Array )
69
+ expect( observer.actions.length ).to eq( 2 )
70
+ expect( observer.actions ).to all( be_a(Arborist::Observer::Action) )
71
+ end
72
+
73
+
74
+ it "can specify a summary action" do
75
+ observer = described_class.new( "testing observer" ) do
76
+ summarize( every: 5 ) do |events|
77
+ puts( events.size )
78
+ end
79
+ end
80
+
81
+ expect( observer.actions ).to be_an( Array )
82
+ expect( observer.actions.length ).to eq( 1 )
83
+ expect( observer.actions.first ).to be_a( Arborist::Observer::Summarize )
84
+ end
85
+
86
+
87
+ it "can specify a mix of regular and summary actions" do
88
+ observer = described_class.new( "testing observer" ) do
89
+ summarize( every: 5 ) do |events|
90
+ puts( events.size )
91
+ end
92
+ action do |uuid, event|
93
+ $stderr.puts( uuid )
94
+ end
95
+ end
96
+
97
+ expect( observer.actions ).to be_an( Array )
98
+ expect( observer.actions.length ).to eq( 2 )
99
+ expect( observer.actions.first ).to be_a( Arborist::Observer::Summarize )
100
+ expect( observer.actions.last ).to be_a( Arborist::Observer::Action )
101
+ end
102
+
103
+
104
+ it "passes events it is given to handle to its actions" do
105
+ observer = described_class.new( "testing observer" ) do
106
+ summarize( every: 5 ) do |events|
107
+ puts( events.size )
108
+ end
109
+ action do |uuid, event|
110
+ $stderr.puts( uuid )
111
+ end
112
+ end
113
+
114
+ event = {}
115
+ expect( observer.actions.first ).to receive( :handle_event ).with( event )
116
+ expect( observer.actions.last ).to receive( :handle_event ).with( event )
117
+
118
+ observer.handle_event( "eventid", event )
119
+ end
120
+
121
+
122
+ it "can build a list of timer callbacks for its actions" do
123
+ summarize_called = false
124
+
125
+ observer = described_class.new( "testing observer" ) do
126
+ summarize( every: 5 ) do |events|
127
+ summarize_called = true
128
+ end
129
+ action do |uuid, event|
130
+ $stderr.puts( uuid )
131
+ end
132
+ end
133
+
134
+ results = observer.timers
135
+
136
+ expect( results ).to be_an( Array )
137
+ expect( results.size ).to eq( 1 )
138
+ expect( results ).to all( be_an(Array) )
139
+ expect( results.last[0] ).to eq( 5 )
140
+
141
+ observer.handle_event( "eventid", {} )
142
+ expect { results.last[1].call }.to change { summarize_called }.to( true )
143
+ end
144
+
145
+ end
146
+
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'arborist/subscription'
6
+
7
+
8
+ describe Arborist::Subscription do
9
+
10
+ let( :host_node ) do
11
+ Arborist::Node.create( 'host', 'testnode' ) do
12
+ description "Test host"
13
+ address '192.168.1.1'
14
+ end
15
+ end
16
+ let( :service_node ) do
17
+ host_node.service( 'ssh' )
18
+ end
19
+ let( :publisher ) do
20
+ instance_double( Arborist::Manager::EventPublisher )
21
+ end
22
+ let( :subscription ) do
23
+ described_class.new( publisher, 'node.delta', type: 'host' )
24
+ end
25
+
26
+
27
+ it "generates a unique ID when it's created" do
28
+ expect( subscription.id ).to match( /^\S{16,}$/ )
29
+ end
30
+
31
+
32
+ it "publishes events which are of the desired type and have matching criteria" do
33
+ event = Arborist::Event.create( 'node_delta', host_node, status: ['up', 'down'] )
34
+
35
+ expect( publisher ).to receive( :publish ).with( subscription.id, event )
36
+
37
+ subscription.on_events( event )
38
+ end
39
+
40
+
41
+ it "publishes events which are of any type if the specified type is `nil`" do
42
+ subscription = described_class.new( publisher )
43
+ event1 = Arborist::Event.create( 'node_delta', host_node, status: ['up', 'down'] )
44
+ event2 = Arborist::Event.create( 'sys_reloaded' )
45
+
46
+ expect( publisher ).to receive( :publish ).with( subscription.id, event1 )
47
+ expect( publisher ).to receive( :publish ).with( subscription.id, event2 )
48
+
49
+ subscription.on_events( event1, event2 )
50
+ end
51
+
52
+
53
+ it "doesn't publish events which are of the desired type but don't have matching criteria" do
54
+ event = Arborist::Event.create( 'node_delta', service_node, status: ['up', 'down'] )
55
+
56
+ expect( publisher ).to_not receive( :publish )
57
+
58
+ subscription.on_events( event )
59
+ end
60
+
61
+
62
+ it "doesn't publish events which have matching criteria but aren't of the desired type" do
63
+ event = Arborist::Event.create( 'node_update', host_node )
64
+
65
+ expect( publisher ).to_not receive( :publish )
66
+
67
+ subscription.on_events( event )
68
+ end
69
+
70
+ end
71
+
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative 'spec_helper'
4
+
5
+ require 'pathname'
6
+ require 'arborist'
7
+
8
+
9
+ describe Arborist do
10
+
11
+ before( :all ) do
12
+ @original_config_env = ENV[Arborist::CONFIG_ENV]
13
+ @data_dir = Pathname( __FILE__ ).dirname + 'data'
14
+ @nodes_dir = @data_dir + 'nodes'
15
+ end
16
+
17
+ before( :each ) do
18
+ ENV.delete(Arborist::CONFIG_ENV)
19
+ end
20
+
21
+ after( :all ) do
22
+ ENV[Arborist::CONFIG_ENV] = @original_config_env
23
+ end
24
+
25
+
26
+ it "has a semantic version" do
27
+ expect( described_class::VERSION ).to match( /^\d+\.\d+\.\d+/ )
28
+ end
29
+
30
+
31
+ describe "configurability" do
32
+
33
+ before( :each ) do
34
+ Configurability.configure_objects( Configurability.default_config )
35
+ end
36
+
37
+ after( :all ) do
38
+ Configurability.reset
39
+ end
40
+
41
+
42
+ it "can return the loaded configuration" do
43
+ expect( described_class.config ).to be( Configurability.loaded_config )
44
+ end
45
+
46
+
47
+ it "knows whether or not the config has been loaded" do
48
+ expect( described_class ).to be_config_loaded
49
+ Configurability.reset
50
+ expect( described_class ).to_not be_config_loaded
51
+ end
52
+
53
+
54
+ it "will load a local config file if it exists and none is specified" do
55
+ config_object = double( "Configurability::Config object" )
56
+ allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
57
+
58
+ expect( Configurability ).to receive( :gather_defaults ).
59
+ and_return( {} )
60
+ expect( Arborist::LOCAL_CONFIG_FILE ).to receive( :exist? ).
61
+ and_return( true )
62
+ expect( Configurability::Config ).to receive( :load ).
63
+ with( Arborist::LOCAL_CONFIG_FILE, {} ).
64
+ and_return( config_object )
65
+ expect( config_object ).to receive( :install )
66
+
67
+ Arborist.load_config
68
+ end
69
+
70
+
71
+ it "will load a default config file if none is specified and there's no local config" do
72
+ config_object = double( "Configurability::Config object" )
73
+ allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
74
+
75
+ expect( Configurability ).to receive( :gather_defaults ).
76
+ and_return( {} )
77
+ expect( Arborist::LOCAL_CONFIG_FILE ).to receive( :exist? ).
78
+ and_return( false )
79
+ expect( Configurability::Config ).to receive( :load ).
80
+ with( Arborist::DEFAULT_CONFIG_FILE, {} ).
81
+ and_return( config_object )
82
+ expect( config_object ).to receive( :install )
83
+
84
+ Arborist.load_config
85
+ end
86
+
87
+
88
+ it "will load a config file given in an environment variable" do
89
+ ENV['ARBORIST_CONFIG'] = '/usr/local/etc/config.yml'
90
+
91
+ config_object = double( "Configurability::Config object" )
92
+ allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
93
+
94
+ expect( Configurability ).to receive( :gather_defaults ).
95
+ and_return( {} )
96
+ expect( Configurability::Config ).to receive( :load ).
97
+ with( '/usr/local/etc/config.yml', {} ).
98
+ and_return( config_object )
99
+ expect( config_object ).to receive( :install )
100
+
101
+ Arborist.load_config
102
+ end
103
+
104
+
105
+ it "will load a config file and install it if one is given" do
106
+ config_object = double( "Configurability::Config object" )
107
+ allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
108
+
109
+ expect( Configurability ).to receive( :gather_defaults ).
110
+ and_return( {} )
111
+ expect( Configurability::Config ).to receive( :load ).
112
+ with( 'a/configfile.yml', {} ).
113
+ and_return( config_object )
114
+ expect( config_object ).to receive( :install )
115
+
116
+ Arborist.load_config( 'a/configfile.yml' )
117
+ end
118
+
119
+
120
+ it "will override default values when loading the config if they're given" do
121
+ config_object = double( "Configurability::Config object" )
122
+ allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
123
+
124
+ expect( Configurability ).to_not receive( :gather_defaults )
125
+ expect( Configurability::Config ).to receive( :load ).
126
+ with( 'a/different/configfile.yml', {database: {dbname: 'test'}} ).
127
+ and_return( config_object )
128
+ expect( config_object ).to receive( :install )
129
+
130
+ Arborist.load_config( 'a/different/configfile.yml', database: {dbname: 'test'} )
131
+ end
132
+
133
+ end
134
+
135
+
136
+ it "can load all nodes in a directory and return a manager for them" do
137
+ expect( described_class.manager_for(@nodes_dir) ).to be_a( Arborist::Manager )
138
+ end
139
+
140
+
141
+ it "has a ZMQ context" do
142
+ expect( described_class.zmq_context ).to be_a( ZMQ::Context )
143
+ end
144
+
145
+ end
146
+
@@ -0,0 +1,80 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'loggability'
5
+ require 'arborist/monitor'
6
+ require 'arborist/mixins'
7
+
8
+ using Arborist::TimeRefinements
9
+
10
+ module FPingWrapper
11
+ extend Loggability
12
+ log_to :arborist
13
+
14
+ attr_accessor :identifiers
15
+
16
+ def exec_arguments( nodes )
17
+ self.identifiers = nodes.each_with_object({}) do |(identifier, props), hash|
18
+ next unless props.key?( 'addresses' )
19
+ address = props[ 'addresses' ].first
20
+ hash[ address ] = identifier
21
+ end
22
+
23
+ return {} if self.identifiers.empty?
24
+
25
+ return self.identifiers.keys
26
+ end
27
+
28
+ def handle_results( pid, stdout, stderr )
29
+ # 8.8.8.8 is alive (32.1 ms)
30
+ # 8.8.4.4 is alive (14.9 ms)
31
+ # 8.8.0.1 is unreachable
32
+
33
+ return stdout.each_line.with_object({}) do |line, hash|
34
+ address, remainder = line.split( ' ', 2 )
35
+ identifier = self.identifiers[ address ] or next
36
+
37
+ self.log.debug " parsing result for %s(%s): %p" % [ identifier, address, remainder ]
38
+
39
+ if remainder =~ /is alive \((\d+\.\d+) ms\)/
40
+ hash[ identifier ] = { rtt: Float( $1 ) }
41
+ else
42
+ hash[ identifier ] = { error: remainder.chomp }
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+
50
+ Arborist::Monitor 'ping check' do
51
+ every 20.seconds
52
+ splay 5.seconds
53
+ match type: 'host'
54
+ exclude tag: :laptop
55
+ use :addresses
56
+
57
+ exec 'fping', '-e', '-t', '150'
58
+ exec_callbacks( FPingWrapper )
59
+ end
60
+
61
+ Arborist::Monitor 'ping check downed hosts' do
62
+ every 40.seconds
63
+ splay 15.seconds
64
+ match type: 'host', status: 'down'
65
+ include_down true
66
+ use :addresses
67
+
68
+ exec 'fping', '-e', '-t', '150'
69
+ exec_callbacks( FPingWrapper )
70
+ end
71
+
72
+ Arborist::Monitor 'transient host pings' do
73
+ every 5.minutes
74
+ match type: 'host', tag: 'laptop'
75
+ use :addresses
76
+
77
+ exec 'fping', '-e', '-t', '500'
78
+ exec_callbacks( FPingWrapper )
79
+ end
80
+
@@ -0,0 +1,27 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'arborist/monitor'
5
+ require 'arborist/monitor/socket'
6
+
7
+ using Arborist::TimeRefinements
8
+
9
+ Arborist::Monitor 'port checks on all tcp services' do
10
+ every 5.seconds
11
+ match type: 'service', protocol: 'tcp'
12
+ use :addresses, :port
13
+ exec( Arborist::Monitor::Socket::TCP )
14
+ end
15
+
16
+ Arborist::Monitor 'port checks on downed tcp services' do
17
+ every 10.seconds
18
+ match type: 'service', protocol: 'tcp', status: 'down'
19
+ include_down true
20
+ use :addresses, :port
21
+ exec( Arborist::Monitor::Socket::TCP )
22
+ end
23
+
24
+ # :MAHLON: A possible equivalent way to provide the above as a default for some monitors:
25
+ # Arborist::Monitor::Socket::TCP.default_monitor
26
+
27
+