arborist 0.0.1.pre20160106113421

Sign up to get free protection for your applications and to get access to all the features.
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
+