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,4 @@
1
+ # v0.0.1 [YYYY-MM-DD] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ Initial release.
4
+
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2015, Michael Granger and Mahlon E. Smith
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the author/s, nor the names of the project's
15
+ contributors may be used to endorse or promote products derived from this
16
+ software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+
@@ -0,0 +1,72 @@
1
+ .document
2
+ .simplecov
3
+ ChangeLog
4
+ Events.md
5
+ History.md
6
+ LICENSE
7
+ Manifest.txt
8
+ Monitors.md
9
+ Nodes.md
10
+ Observers.md
11
+ Protocol.md
12
+ README.md
13
+ Rakefile
14
+ TODO.md
15
+ bin/amanagerd
16
+ bin/amonitord
17
+ bin/aobserverd
18
+ lib/arborist.rb
19
+ lib/arborist/client.rb
20
+ lib/arborist/event.rb
21
+ lib/arborist/event/node_acked.rb
22
+ lib/arborist/event/node_delta.rb
23
+ lib/arborist/event/node_matching.rb
24
+ lib/arborist/event/node_update.rb
25
+ lib/arborist/event/sys_reloaded.rb
26
+ lib/arborist/exceptions.rb
27
+ lib/arborist/manager.rb
28
+ lib/arborist/manager/event_publisher.rb
29
+ lib/arborist/manager/tree_api.rb
30
+ lib/arborist/mixins.rb
31
+ lib/arborist/monitor.rb
32
+ lib/arborist/monitor/socket.rb
33
+ lib/arborist/monitor_runner.rb
34
+ lib/arborist/node.rb
35
+ lib/arborist/node/host.rb
36
+ lib/arborist/node/root.rb
37
+ lib/arborist/node/service.rb
38
+ lib/arborist/observer.rb
39
+ lib/arborist/observer/action.rb
40
+ lib/arborist/observer/summarize.rb
41
+ lib/arborist/observer_runner.rb
42
+ lib/arborist/subscription.rb
43
+ spec/arborist/client_spec.rb
44
+ spec/arborist/event/node_update_spec.rb
45
+ spec/arborist/event_spec.rb
46
+ spec/arborist/manager/event_publisher_spec.rb
47
+ spec/arborist/manager/tree_api_spec.rb
48
+ spec/arborist/manager_spec.rb
49
+ spec/arborist/mixins_spec.rb
50
+ spec/arborist/monitor/socket_spec.rb
51
+ spec/arborist/monitor_runner_spec.rb
52
+ spec/arborist/monitor_spec.rb
53
+ spec/arborist/node/host_spec.rb
54
+ spec/arborist/node/root_spec.rb
55
+ spec/arborist/node/service_spec.rb
56
+ spec/arborist/node_spec.rb
57
+ spec/arborist/observer/action_spec.rb
58
+ spec/arborist/observer/summarize_spec.rb
59
+ spec/arborist/observer_spec.rb
60
+ spec/arborist/subscription_spec.rb
61
+ spec/arborist_spec.rb
62
+ spec/data/monitors/pings.rb
63
+ spec/data/monitors/port_checks.rb
64
+ spec/data/monitors/system_resources.rb
65
+ spec/data/monitors/web_services.rb
66
+ spec/data/nodes/duir.rb
67
+ spec/data/nodes/localhost.rb
68
+ spec/data/nodes/sidonie.rb
69
+ spec/data/nodes/yevaud.rb
70
+ spec/data/observers/auditor.rb
71
+ spec/data/observers/webservices.rb
72
+ spec/spec_helper.rb
@@ -0,0 +1,141 @@
1
+ # Monitors
2
+
3
+ Monitors are loaded in a fashion similar to the way nodes describing the network topology are
4
+ loaded: you provide a Enumerator that yields Arborist::Monitor objects to the #load_monitors method
5
+ of an Arborist::MonitorRunner object. The `Arborist::Monitor.each_in` method, given a path to a directory containing `.rb` files that declare one or more monitors, will return such an Enumerator, but you could also use this to load monitor descriptions from any source you prefer, e.g., LDAP, a RDBMS, etc.
6
+
7
+
8
+ ## Declaration DSL
9
+
10
+ To facilitate describing monitors to run, Arborist::Monitor also provides a DSL-like syntax for constructing them.
11
+
12
+ For example, this would declare two monitors, one which pings every 'host' node except those tagged as laptops in the network every 20 seconds, and the other which pings 'host' nodes tagged as laptops every 5 minutes.
13
+
14
+ # monitors/pings.rb
15
+ require 'arborist/monitor'
16
+
17
+ Arborist::Monitor 'ping check' do
18
+ every 20.seconds
19
+ match type: 'host'
20
+ exclude tag: :laptop
21
+ use :address
22
+ exec 'fping'
23
+ end
24
+
25
+ Arborist::Monitor 'transient host pings' do
26
+ every 5.minutes
27
+ match type: 'host', tag: 'laptop'
28
+ use :address
29
+ exec 'fping'
30
+ end
31
+
32
+ Each monitor is given a human-readable description for use in user interfaces, and one or more attributes that describe which nodes should be monitored, how they should be monitored, and how often the monitor should be run.
33
+
34
+ ### Monitor Attributes
35
+
36
+ #### every( seconds )
37
+
38
+ Declare the interval between runs of the monitor. The monitor will be skewed by a small amount from this value (unless you specify `splay 0`) to prevent many monitors from starting up simultaneously.
39
+
40
+ #### splay( seconds )
41
+
42
+ Manually set the amount of splay (random offset from the interval) the monitor should use. It defaults to `Math.logn( interval )`.
43
+
44
+ #### exec( command )
45
+ #### exec {|node_attributes| ... }
46
+ #### exec( module )
47
+
48
+ Specify what should be run to do the actual monitoring. The first form simply `spawn`s the specified command with its STDIN opened to a stream of serialized node data.
49
+
50
+ By default, the format of the serialized nodes is one node per line, and each line looks like this:
51
+
52
+ «identifier» «attribute1»=«attribute1 value» «attribute2»=«attribute2 value»
53
+
54
+ Each line should use shell-escaping semantics, so that if an attribute value contains whitespace, it should be quoted, control characters need to be escaped, etc.
55
+
56
+ For example, the ping checker might receive input like:
57
+
58
+ duir address=192.168.16.3
59
+ sidonie address="192.168.16.3"
60
+ yevaud address="192.168.16.10"
61
+
62
+ If the command you are running doesn't support this format, you can override this in one of two ways.
63
+
64
+ If your command expects the node data as command-line arguments, you can provide a custom `exec_arguments` block. It will receive an Array of Arborist::Node objects and it should generate an Array of arguments to append to the command before `spawn`ing it.
65
+
66
+ exec_arguments do |nodes|
67
+ # Build an address -> node mapping for pairing the updates back up by address
68
+ @node_map = nodes.each_with_object( {} ) do |node, hash|
69
+ address = node.address
70
+ hash[ address ] = node
71
+ end
72
+
73
+ @node_map.keys
74
+ end
75
+
76
+ If your command expects the node data via `STDIN`, but in a different format, you may declare an `exec_input` block. It will be called with the same node array, and additionally an IO open to the STDIN of the running command. This can be combined with the `exec_arguments` block, if you're dealing with something really weird.
77
+
78
+ exec_input do |nodes, writer|
79
+ # Build an address -> node mapping for pairing the updates back up by address
80
+ @node_map = nodes.each_with_object( {} ) do |node, hash|
81
+ address = node.address
82
+ hash[ address ] = node
83
+ end
84
+
85
+ writer.puts( node_map.values )
86
+ end
87
+
88
+ The monitor must write results for any of the listed identifiers that require update in the same format to its STDOUT. For the ping check above, the results might look like:
89
+
90
+ duir rtt=20ms
91
+ sidonie rtt=103ms
92
+ yevaud rtt= error=Host\ unreachable.
93
+
94
+ If the program writes its output in some other format, you can provide a `handle_results` block. It will be called with the program's `STDOUT` if the block takes one argument, and if it takes an additional argument its `STDERR` as well. It should return a Hash of update Hashes, keyed by the node identifier it should be sent to.
95
+
96
+ handle_results do |pid, out, err|
97
+ updates = {}
98
+
99
+ out.each_line do |line|
100
+ address, status = line.split( /\s+:\s+/, 2 )
101
+
102
+ # Use the @node_map we created up in the exec_arguments to map the output
103
+ # back into identifiers. Error-checking omitted for brevity.
104
+ identifier = @node_map[ address ].identifier
105
+
106
+ # 127.0.0.1 is alive (0.12 ms)
107
+ # 8.8.8.8 is alive (61.6 ms)
108
+ # 192.168.16.16 is unreachable
109
+ if status =~ /is alive \((\d+\.\d+ ms)\)/i
110
+ updates[ identifier ] = { ping: { rtt: Float($1) } }
111
+ else
112
+ updates[ identifier ] = { error: status }
113
+ end
114
+ end
115
+
116
+ updates
117
+ end
118
+
119
+ Unlisted attributes are unchanged. A listed attribute with an empty value is explicitly cleared. An identifier that isn't listed in the results means no update is necessary for that node.
120
+
121
+ If you find yourself wanting to repeat one or more of the exec callbacks, you can also wrap them in a module and call `exec_callbacks` with it.
122
+
123
+ The second and third forms can be used to implement a monitor in Ruby. In the second, the block is called with the Hash of node data, keyed by identifier, and it must return a Hash of updates keyed by identifier. The third form expects any object that responds to `#run`, which will be invoked the same way as the block.
124
+
125
+
126
+ #### use( *properties )
127
+
128
+ Specify the list of properties to provide to the monitor for each node. If this is unspecified, the input to the monitor will be just the list of identifiers.
129
+
130
+
131
+
132
+ # Does everything in Ruby; gets the Array of Nodes as arguments to the block, expected to
133
+ # return a Hash of updates keyed by node identifier
134
+ exec do |nodes|
135
+
136
+ end
137
+
138
+
139
+ # Runs an external
140
+ exec 'fping', '-e', '-t', '150'
141
+
File without changes
@@ -0,0 +1,72 @@
1
+ # Observers
2
+
3
+ subscription
4
+ * Event to subscribe to
5
+ * Node to attach subscription to. No node means 'root', which sees all subnode events.
6
+ * One or more action blocks
7
+
8
+ Actions have:
9
+ * a block to execute
10
+ * Zero or more time-periods, which are unioned together. No time periods means anytime.
11
+
12
+ Pragmas:
13
+ * Summarize:
14
+ (send a single alert summarizing every event received over x period of time, or n events)
15
+ * Squelch:
16
+
17
+
18
+ :MAHLON:
19
+ The manager should probably serialize subscriptions for its nodes. Otherwise the manager
20
+ can restart and any running observers will never again receive events because the
21
+ subscriptions will have disappeared.
22
+
23
+
24
+
25
+ ## Examples
26
+
27
+ # -*- ruby -*-
28
+ #encoding: utf-8
29
+
30
+ require 'arborist'
31
+
32
+ WORK_HOURS = 'hour {8am-6pm}'
33
+ OFF_HOURS = 'hour {6pm-8am}'
34
+
35
+ Arborist::Observer "Webservers" do
36
+ subscribe to: 'node.delta',
37
+ where: {
38
+ type: 'service',
39
+ port: 80,
40
+ delta: { status: ['up', 'down'] }
41
+ }
42
+
43
+ action( during: WORK_HOURS ) do |uuid, event|
44
+ $stderr.puts "Webserver %s is DOWN (%p)" % [ event['data']['identifier'], event['data'] ]
45
+ end
46
+ summarize( every: 5.minutes, count: 5, during: OFF_HOURS ) do |*tuples|
47
+ email to: 'ops@example.com', subject: ""
48
+ end
49
+
50
+ end
51
+
52
+
53
+
54
+ ## Schedulability stuff
55
+
56
+ schedule = Schedule.new
57
+ schedule |= Period.time( '8AM' .. '8PM' )
58
+ schedule |= Period.day( 'Mon' .. 'Fri' )
59
+
60
+
61
+ # Thymelörde! - An Amazeballs Gem for Doing Stuff With Time™
62
+
63
+
64
+ schedule = Thymelörde!.yes?( 'Tue-Thur {6am-9pm}' )
65
+ schedule.ehh? #=> false
66
+ schedule.yes? #=> false
67
+
68
+
69
+ Legba -- the gatekeeper?
70
+
71
+
72
+
@@ -0,0 +1,214 @@
1
+ # Monitors
2
+
3
+ ## Protocol
4
+
5
+ ZMQ REQ socket, msgpack message consisting of an Array of two elements:
6
+
7
+ [
8
+ header,
9
+ body
10
+ ]
11
+
12
+ Header is a Map of the form:
13
+
14
+ {
15
+ action: «verb», # required
16
+ version: 1, # required
17
+ [verb-specific attributes]
18
+ }
19
+
20
+ Body is either Nil, a Map of key-value pairs, or an Array of Maps appropriate to the `action`.
21
+
22
+
23
+ ## status
24
+
25
+ Fetch the status of the Manager.
26
+
27
+ {
28
+ action: status,
29
+ version: 1,
30
+ }
31
+
32
+ Response:
33
+
34
+ {
35
+ success: true,
36
+ version: 1
37
+ },
38
+ {
39
+ server_version: 0.0.1,
40
+ state: 'running',
41
+ uptime: 17155,
42
+ nodecount: 342
43
+ }
44
+
45
+
46
+ ## list
47
+
48
+ Request:
49
+
50
+ [
51
+ {
52
+ action: list,
53
+ version: 1
54
+ [from: «identifier»]
55
+ }
56
+ ]
57
+
58
+ Successful response:
59
+
60
+ [
61
+ {
62
+ success: true,
63
+ version: 1
64
+ },
65
+ [
66
+ {
67
+ identifier: 'foo',
68
+ status: 'up',
69
+ parent: '_',
70
+ properties: {},
71
+ },
72
+ {
73
+ identifier: 'bar',
74
+ status: 'down',
75
+ parent: 'foo',
76
+ properties: {},
77
+ }
78
+ ]
79
+ ]
80
+
81
+ failure example:
82
+
83
+ [
84
+ {
85
+ success: false,
86
+ reason: "human readable exception message or whatever",
87
+ category: either 'server' or 'client', meaning who is responsible for the error
88
+ version: 1
89
+ }
90
+ ]
91
+
92
+ Fetch a data structure describing the node tree from the node with the specified
93
+ `identifier`, or the root node if no `identifier` is specified.
94
+
95
+
96
+ ## fetch
97
+
98
+ Fetch the `address`, `description`, and `status` of all nodes.
99
+
100
+ [
101
+ {
102
+ action: fetch,
103
+ version: 1,
104
+ include_down: true,
105
+ return: [address, description, status]
106
+ },
107
+ {
108
+ 'theon' => {
109
+ address: '10.2.10.4',
110
+ description: 'no theon, reek',
111
+ status: down,
112
+ },
113
+ 'thoros' => {
114
+ address: '10.2.10.4',
115
+ description: "The Red God's champion",
116
+ status: up,
117
+ }
118
+ ]
119
+ ]
120
+
121
+
122
+ ### return
123
+
124
+ - not specified : returns everything.
125
+ - `Nil` : returns just identifiers
126
+ - array of fields : returns the values of those fields
127
+
128
+ Search for nodes that match the filter given in the request body, returning a serialized map of node identifiers to requested state.
129
+
130
+
131
+ ## update
132
+
133
+ [
134
+ {
135
+ action: update,
136
+ version: 1
137
+ },
138
+ {
139
+ duir: {
140
+ pingtime: 0.02
141
+ },
142
+ sidonie: {
143
+ pingtime: 0.28
144
+ }
145
+ }
146
+ ]
147
+
148
+ With a failure:
149
+
150
+ [
151
+ {
152
+ action: update,
153
+ version: 1
154
+ },
155
+ {
156
+ duir: {
157
+ pingtime: null,
158
+ error: "Host unreachable."
159
+ },
160
+ sidonie: {
161
+ pingtime: 0.28
162
+ }
163
+ }
164
+ ]
165
+
166
+
167
+ ## subscribe
168
+
169
+ Get node change delta events for every 'host' type node.
170
+
171
+ {
172
+ action: subscribe,
173
+ version: 1,
174
+ event_type: node.delta
175
+ },
176
+ {
177
+ type: 'host',
178
+ }
179
+
180
+ Get a snapshot of node state on every update for 'service' type nodes under
181
+ the 'bennett' node.
182
+
183
+ {
184
+ action: subscribe,
185
+ version: 1,
186
+ event_type: node.update,
187
+ identifier: 'bennett'
188
+ },
189
+ {
190
+ type: 'service',
191
+ }
192
+
193
+ Get events of state changes to services running on port 80.
194
+
195
+ {
196
+ action: subscribe,
197
+ version: 1,
198
+ event_type: node.delta
199
+ },
200
+ {
201
+ type: 'service',
202
+ port: 80
203
+ }
204
+
205
+ Get notified of every system event (startup, shutdown, reload, etc.)
206
+
207
+ {
208
+ action: subscribe,
209
+ version: 1,
210
+ event_type: sys.*
211
+ },
212
+ Nil
213
+
214
+