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.
- checksums.yaml +7 -0
- data/.document +4 -0
- data/.simplecov +9 -0
- data/ChangeLog +417 -0
- data/Events.md +20 -0
- data/History.md +4 -0
- data/LICENSE +29 -0
- data/Manifest.txt +72 -0
- data/Monitors.md +141 -0
- data/Nodes.md +0 -0
- data/Observers.md +72 -0
- data/Protocol.md +214 -0
- data/README.md +75 -0
- data/Rakefile +81 -0
- data/TODO.md +24 -0
- data/bin/amanagerd +10 -0
- data/bin/amonitord +12 -0
- data/bin/aobserverd +12 -0
- data/lib/arborist.rb +182 -0
- data/lib/arborist/client.rb +191 -0
- data/lib/arborist/event.rb +61 -0
- data/lib/arborist/event/node_acked.rb +18 -0
- data/lib/arborist/event/node_delta.rb +20 -0
- data/lib/arborist/event/node_matching.rb +34 -0
- data/lib/arborist/event/node_update.rb +19 -0
- data/lib/arborist/event/sys_reloaded.rb +15 -0
- data/lib/arborist/exceptions.rb +21 -0
- data/lib/arborist/manager.rb +508 -0
- data/lib/arborist/manager/event_publisher.rb +97 -0
- data/lib/arborist/manager/tree_api.rb +207 -0
- data/lib/arborist/mixins.rb +363 -0
- data/lib/arborist/monitor.rb +377 -0
- data/lib/arborist/monitor/socket.rb +163 -0
- data/lib/arborist/monitor_runner.rb +217 -0
- data/lib/arborist/node.rb +700 -0
- data/lib/arborist/node/host.rb +87 -0
- data/lib/arborist/node/root.rb +60 -0
- data/lib/arborist/node/service.rb +112 -0
- data/lib/arborist/observer.rb +176 -0
- data/lib/arborist/observer/action.rb +125 -0
- data/lib/arborist/observer/summarize.rb +105 -0
- data/lib/arborist/observer_runner.rb +181 -0
- data/lib/arborist/subscription.rb +82 -0
- data/spec/arborist/client_spec.rb +282 -0
- data/spec/arborist/event/node_update_spec.rb +71 -0
- data/spec/arborist/event_spec.rb +64 -0
- data/spec/arborist/manager/event_publisher_spec.rb +66 -0
- data/spec/arborist/manager/tree_api_spec.rb +458 -0
- data/spec/arborist/manager_spec.rb +442 -0
- data/spec/arborist/mixins_spec.rb +195 -0
- data/spec/arborist/monitor/socket_spec.rb +195 -0
- data/spec/arborist/monitor_runner_spec.rb +152 -0
- data/spec/arborist/monitor_spec.rb +251 -0
- data/spec/arborist/node/host_spec.rb +104 -0
- data/spec/arborist/node/root_spec.rb +29 -0
- data/spec/arborist/node/service_spec.rb +98 -0
- data/spec/arborist/node_spec.rb +552 -0
- data/spec/arborist/observer/action_spec.rb +205 -0
- data/spec/arborist/observer/summarize_spec.rb +294 -0
- data/spec/arborist/observer_spec.rb +146 -0
- data/spec/arborist/subscription_spec.rb +71 -0
- data/spec/arborist_spec.rb +146 -0
- data/spec/data/monitors/pings.rb +80 -0
- data/spec/data/monitors/port_checks.rb +27 -0
- data/spec/data/monitors/system_resources.rb +30 -0
- data/spec/data/monitors/web_services.rb +17 -0
- data/spec/data/nodes/duir.rb +20 -0
- data/spec/data/nodes/localhost.rb +15 -0
- data/spec/data/nodes/sidonie.rb +29 -0
- data/spec/data/nodes/yevaud.rb +26 -0
- data/spec/data/observers/auditor.rb +23 -0
- data/spec/data/observers/webservices.rb +18 -0
- data/spec/spec_helper.rb +117 -0
- metadata +368 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'pluggability'
|
5
|
+
require 'arborist' unless defined?( Arborist )
|
6
|
+
|
7
|
+
|
8
|
+
# The representation of activity in the manager; events are broadcast when
|
9
|
+
# node state changes, when they're updated, and when various other operational
|
10
|
+
# actions take place, e.g., the node tree gets reloaded.
|
11
|
+
class Arborist::Event
|
12
|
+
extend Pluggability
|
13
|
+
|
14
|
+
|
15
|
+
# Pluggability API -- look for events under the specified prefix
|
16
|
+
plugin_prefixes 'arborist/event'
|
17
|
+
|
18
|
+
|
19
|
+
### Create a new event with the specified +payload+ data.
|
20
|
+
def initialize( payload )
|
21
|
+
payload = payload.clone unless payload.nil?
|
22
|
+
@payload = payload
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
######
|
27
|
+
public
|
28
|
+
######
|
29
|
+
|
30
|
+
# The event payload specific to the event type
|
31
|
+
attr_reader :payload
|
32
|
+
|
33
|
+
|
34
|
+
### Return the type of the event.
|
35
|
+
def type
|
36
|
+
return self.class.name.
|
37
|
+
sub( /.*::/, '' ).
|
38
|
+
gsub( /([a-z])([A-Z])/, '\1.\2' ).
|
39
|
+
downcase
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
### Match operator -- returns +true+ if the other object matches this event.
|
44
|
+
def match( object )
|
45
|
+
return object.respond_to?( :event_type ) &&
|
46
|
+
( object.event_type.nil? || object.event_type == self.type )
|
47
|
+
end
|
48
|
+
alias_method :=~, :match
|
49
|
+
|
50
|
+
|
51
|
+
### Return the event as a Hash.
|
52
|
+
def to_hash
|
53
|
+
return {
|
54
|
+
'type' => self.type,
|
55
|
+
'data' => self.payload
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
end # class Arborist::Event
|
60
|
+
|
61
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node_matching'
|
6
|
+
|
7
|
+
|
8
|
+
# An event generated when a node is manually ACKed.
|
9
|
+
class Arborist::Event::NodeAcked < Arborist::Event
|
10
|
+
include Arborist::Event::NodeMatching
|
11
|
+
|
12
|
+
|
13
|
+
### Create a new NodeAcked event for the specified +node+ and +ack_info+.
|
14
|
+
def initialize( node, ack_info )
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
end # class Arborist::Event::NodeAcked
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node_matching'
|
6
|
+
|
7
|
+
|
8
|
+
# An event sent when one or more attributes of a node changes.
|
9
|
+
class Arborist::Event::NodeDelta < Arborist::Event
|
10
|
+
include Arborist::Event::NodeMatching
|
11
|
+
|
12
|
+
|
13
|
+
### Create a new NodeDelta event for the specified +node+. The +delta+
|
14
|
+
### is a Hash of:
|
15
|
+
### attribute_name => [ old_value, new_value ]
|
16
|
+
def initialize( node, delta )
|
17
|
+
super # Overridden for the documentation
|
18
|
+
end
|
19
|
+
|
20
|
+
end # class Arborist::Event::NodeDelta
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
|
6
|
+
|
7
|
+
# A mixin which adds common functionality to events which related to an
|
8
|
+
# Arborist::Node.
|
9
|
+
module Arborist::Event::NodeMatching
|
10
|
+
|
11
|
+
### Strip and save the node argument to the constructor.
|
12
|
+
def initialize( node, payload=nil )
|
13
|
+
@node = node
|
14
|
+
super( payload )
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
######
|
19
|
+
public
|
20
|
+
######
|
21
|
+
|
22
|
+
# The node that generated the event
|
23
|
+
attr_reader :node
|
24
|
+
|
25
|
+
|
26
|
+
### Returns +true+ if the specified +object+ matches this event.
|
27
|
+
def match( object )
|
28
|
+
return super &&
|
29
|
+
object.respond_to?( :criteria ) && self.node.matches?( object.criteria )
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end # module Arborist::Event::NodeMatching
|
34
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
4
|
+
require 'arborist/event/node_matching'
|
5
|
+
|
6
|
+
|
7
|
+
# An event sent on every node update, regardless of whether or not the update resulted in
|
8
|
+
# any changes
|
9
|
+
class Arborist::Event::NodeUpdate < Arborist::Event
|
10
|
+
include Arborist::Event::NodeMatching
|
11
|
+
|
12
|
+
|
13
|
+
### Use the node data as this event's payload.
|
14
|
+
def payload
|
15
|
+
return self.node.to_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end # class Arborist::Event::NodeUpdate
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'arborist/node' unless defined?( Arborist::Node )
|
4
|
+
|
5
|
+
|
6
|
+
# An event sent when the manager reloads the node tree.
|
7
|
+
class Arborist::Event::SysReloaded < Arborist::Event
|
8
|
+
|
9
|
+
|
10
|
+
### Create a NodeUpdate event for the specified +node+.
|
11
|
+
def initialize( payload=Time.now )
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
end # class Arborist::Event::NodeUpdate
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
|
5
|
+
# Arborist namespace
|
6
|
+
module Arborist
|
7
|
+
|
8
|
+
class ClientError < RuntimeError; end
|
9
|
+
|
10
|
+
class RequestError < ClientError
|
11
|
+
|
12
|
+
def initialize( reason )
|
13
|
+
super( "Invalid request (#{reason})" )
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class ServerError < RuntimeError; end
|
19
|
+
|
20
|
+
end # module Arborist
|
21
|
+
|
@@ -0,0 +1,508 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'configurability'
|
6
|
+
require 'loggability'
|
7
|
+
require 'rbczmq'
|
8
|
+
|
9
|
+
require 'arborist' unless defined?( Arborist )
|
10
|
+
require 'arborist/node'
|
11
|
+
require 'arborist/mixins'
|
12
|
+
|
13
|
+
|
14
|
+
# The main Arborist process -- responsible for coordinating all other activity.
|
15
|
+
class Arborist::Manager
|
16
|
+
extend Configurability,
|
17
|
+
Loggability,
|
18
|
+
Arborist::MethodUtilities
|
19
|
+
|
20
|
+
# Signals the manager responds to
|
21
|
+
QUEUE_SIGS = [
|
22
|
+
:INT, :TERM, :HUP, :USR1,
|
23
|
+
# :TODO: :QUIT, :WINCH, :USR2, :TTIN, :TTOU
|
24
|
+
]
|
25
|
+
|
26
|
+
# The number of seconds to wait between checks for incoming signals
|
27
|
+
SIGNAL_INTERVAL = 0.5
|
28
|
+
|
29
|
+
|
30
|
+
##
|
31
|
+
# Use the Arborist logger
|
32
|
+
log_to :arborist
|
33
|
+
|
34
|
+
|
35
|
+
#
|
36
|
+
# Instance methods
|
37
|
+
#
|
38
|
+
|
39
|
+
### Create a new Arborist::Manager.
|
40
|
+
def initialize
|
41
|
+
@root = Arborist::Node.create( :root )
|
42
|
+
@nodes = {
|
43
|
+
'_' => @root,
|
44
|
+
}
|
45
|
+
@subscriptions = {}
|
46
|
+
@tree_built = false
|
47
|
+
|
48
|
+
@tree_sock = @event_sock = nil
|
49
|
+
@signal_timer = nil
|
50
|
+
@start_time = nil
|
51
|
+
|
52
|
+
Thread.main[:signal_queue] = []
|
53
|
+
@zmq_loop = nil
|
54
|
+
|
55
|
+
@api_handler = nil
|
56
|
+
@event_publisher = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
######
|
61
|
+
public
|
62
|
+
######
|
63
|
+
|
64
|
+
##
|
65
|
+
# The root node of the tree.
|
66
|
+
attr_accessor :root
|
67
|
+
|
68
|
+
##
|
69
|
+
# The Hash of all loaded Nodes, keyed by their identifier
|
70
|
+
attr_accessor :nodes
|
71
|
+
|
72
|
+
##
|
73
|
+
# The Hash of all Subscriptions, keyed by their subscription ID
|
74
|
+
attr_accessor :subscriptions
|
75
|
+
|
76
|
+
##
|
77
|
+
# The time at which the manager began running.
|
78
|
+
attr_accessor :start_time
|
79
|
+
|
80
|
+
##
|
81
|
+
# The ZMQ::Handler that manages the IO for the Tree API
|
82
|
+
attr_reader :api_handler
|
83
|
+
|
84
|
+
##
|
85
|
+
# The ZMQ::Handler that manages the IO for the event-publication API.
|
86
|
+
attr_reader :event_publisher
|
87
|
+
|
88
|
+
##
|
89
|
+
# The ZMQ::Loop that will/is acting as the main loop.
|
90
|
+
attr_reader :zmq_loop
|
91
|
+
|
92
|
+
##
|
93
|
+
# Flag for marking when the tree is built successfully the first time
|
94
|
+
attr_predicate_accessor :tree_built
|
95
|
+
|
96
|
+
|
97
|
+
#
|
98
|
+
# :section: Startup/Shutdown
|
99
|
+
#
|
100
|
+
|
101
|
+
### Setup sockets and start the event loop.
|
102
|
+
def run
|
103
|
+
self.log.info "Getting ready to start the manager."
|
104
|
+
self.setup_sockets
|
105
|
+
self.set_signal_handlers
|
106
|
+
self.start_accepting_requests
|
107
|
+
|
108
|
+
return self # For chaining
|
109
|
+
ensure
|
110
|
+
self.restore_signal_handlers
|
111
|
+
if @zmq_loop
|
112
|
+
self.log.debug "Unregistering sockets."
|
113
|
+
@zmq_loop.remove( @tree_sock )
|
114
|
+
@tree_sock.pollable.close
|
115
|
+
@zmq_loop.remove( @event_sock )
|
116
|
+
@event_sock.pollable.close
|
117
|
+
end
|
118
|
+
|
119
|
+
self.log.debug "Resetting ZMQ context"
|
120
|
+
Arborist.reset_zmq_context
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
### Returns true if the Manager is running.
|
125
|
+
def running?
|
126
|
+
return @zmq_loop && @zmq_loop.running?
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
### Start a loop, accepting a request and handling it.
|
131
|
+
def start_accepting_requests
|
132
|
+
self.log.debug "Starting the main loop"
|
133
|
+
|
134
|
+
@zmq_loop = ZMQ::Loop.new
|
135
|
+
|
136
|
+
@api_handler = Arborist::Manager::TreeAPI.new( @tree_sock, self )
|
137
|
+
@tree_sock.handler = @api_handler
|
138
|
+
@zmq_loop.register( @tree_sock )
|
139
|
+
|
140
|
+
@event_publisher = Arborist::Manager::EventPublisher.new( @event_sock, self, @zmq_loop )
|
141
|
+
@event_sock.handler = @event_publisher
|
142
|
+
@zmq_loop.register( @event_sock )
|
143
|
+
|
144
|
+
self.setup_signal_timer
|
145
|
+
self.start_time = Time.now
|
146
|
+
|
147
|
+
self.log.debug "Manager running."
|
148
|
+
@zmq_loop.start
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
### Create the ZMQ API socket if necessary.
|
153
|
+
def setup_sockets
|
154
|
+
self.log.debug "Setting up sockets"
|
155
|
+
@tree_sock = self.setup_tree_socket
|
156
|
+
@event_sock = self.setup_event_socket
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
### Set up the ZMQ REP socket for the Tree API.
|
161
|
+
def setup_tree_socket
|
162
|
+
sock = Arborist.zmq_context.socket( :REP )
|
163
|
+
self.log.debug " binding the tree API socket (%#0x) to %p" %
|
164
|
+
[ sock.object_id * 2, Arborist.tree_api_url ]
|
165
|
+
sock.linger = 0
|
166
|
+
sock.bind( Arborist.tree_api_url )
|
167
|
+
return ZMQ::Pollitem.new( sock, ZMQ::POLLIN|ZMQ::POLLOUT )
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
### Set up the ZMQ PUB socket for published events.
|
172
|
+
def setup_event_socket
|
173
|
+
sock = Arborist.zmq_context.socket( :PUB )
|
174
|
+
self.log.debug " binding the event socket (%#0x) to %p" %
|
175
|
+
[ sock.object_id * 2, Arborist.event_api_url ]
|
176
|
+
sock.linger = 0
|
177
|
+
sock.bind( Arborist.event_api_url )
|
178
|
+
return ZMQ::Pollitem.new( sock, ZMQ::POLLOUT )
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
### Restart the manager
|
183
|
+
def restart
|
184
|
+
raise NotImplementedError
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
### Stop the manager.
|
189
|
+
def stop
|
190
|
+
self.log.info "Stopping the manager."
|
191
|
+
self.ignore_signals
|
192
|
+
self.cancel_signal_timer
|
193
|
+
@zmq_loop.stop if @zmq_loop
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
#
|
198
|
+
# :section: Signal Handling
|
199
|
+
# These methods set up some behavior for starting, restarting, and stopping
|
200
|
+
# your application when a signal is received. If you don't want signals to
|
201
|
+
# be handled, override #set_signal_handlers with an empty method.
|
202
|
+
#
|
203
|
+
|
204
|
+
### Set up a periodic ZMQ timer to check for queued signals and handle them.
|
205
|
+
def setup_signal_timer
|
206
|
+
@signal_timer = ZMQ::Timer.new( SIGNAL_INTERVAL, 0, self.method(:process_signal_queue) )
|
207
|
+
@zmq_loop.register_timer( @signal_timer )
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
### Disable the timer that checks for incoming signals
|
212
|
+
def cancel_signal_timer
|
213
|
+
if @signal_timer
|
214
|
+
@signal_timer.cancel
|
215
|
+
@zmq_loop.cancel_timer( @signal_timer )
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
### Set up signal handlers for common signals that will shut down, restart, etc.
|
221
|
+
def set_signal_handlers
|
222
|
+
self.log.debug "Setting up deferred signal handlers."
|
223
|
+
QUEUE_SIGS.each do |sig|
|
224
|
+
Signal.trap( sig ) { Thread.main[:signal_queue] << sig }
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
### Set all signal handlers to ignore.
|
230
|
+
def ignore_signals
|
231
|
+
self.log.debug "Ignoring signals."
|
232
|
+
QUEUE_SIGS.each do |sig|
|
233
|
+
Signal.trap( sig, :IGNORE )
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
### Set the signal handlers back to their defaults.
|
239
|
+
def restore_signal_handlers
|
240
|
+
self.log.debug "Restoring default signal handlers."
|
241
|
+
QUEUE_SIGS.each do |sig|
|
242
|
+
Signal.trap( sig, :DEFAULT )
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
### Handle any queued signals.
|
247
|
+
def process_signal_queue
|
248
|
+
# Look for any signals that arrived and handle them
|
249
|
+
while sig = Thread.main[:signal_queue].shift
|
250
|
+
self.handle_signal( sig )
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
### Handle signals.
|
256
|
+
def handle_signal( sig )
|
257
|
+
self.log.debug "Handling signal %s" % [ sig ]
|
258
|
+
case sig
|
259
|
+
when :INT, :TERM
|
260
|
+
self.on_termination_signal( sig )
|
261
|
+
|
262
|
+
when :HUP
|
263
|
+
self.on_hangup_signal( sig )
|
264
|
+
|
265
|
+
when :USR1
|
266
|
+
self.on_user1_signal( sig )
|
267
|
+
|
268
|
+
else
|
269
|
+
self.log.warn "Unhandled signal %s" % [ sig ]
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
### Handle a TERM signal. Shuts the handler down after handling any current request/s. Also
|
276
|
+
### aliased to #on_interrupt_signal.
|
277
|
+
def on_termination_signal( signo )
|
278
|
+
self.log.warn "Terminated (%p)" % [ signo ]
|
279
|
+
self.stop
|
280
|
+
end
|
281
|
+
alias_method :on_interrupt_signal, :on_termination_signal
|
282
|
+
|
283
|
+
|
284
|
+
### Handle a HUP signal. The default is to restart the handler.
|
285
|
+
def on_hangup_signal( signo )
|
286
|
+
self.log.warn "Hangup (%p)" % [ signo ]
|
287
|
+
self.restart
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
### Handle a USR1 signal. Writes a message to the log by default.
|
292
|
+
def on_user1_signal( signo )
|
293
|
+
self.log.info "Checkpoint: User signal."
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
#
|
298
|
+
# :section: Tree API
|
299
|
+
#
|
300
|
+
|
301
|
+
### Add nodes yielded from the specified +enumerator+ into the manager's
|
302
|
+
### tree.
|
303
|
+
def load_tree( enumerator )
|
304
|
+
enumerator.each do |node|
|
305
|
+
self.add_node( node )
|
306
|
+
end
|
307
|
+
self.build_tree
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
### Build the tree out of all the loaded nodes.
|
312
|
+
def build_tree
|
313
|
+
self.log.info "Building tree from %d loaded nodes." % [ self.nodes.length ]
|
314
|
+
self.nodes.each do |identifier, node|
|
315
|
+
next if node.operational?
|
316
|
+
self.link_node_to_parent( node )
|
317
|
+
end
|
318
|
+
self.tree_built = true
|
319
|
+
end
|
320
|
+
|
321
|
+
|
322
|
+
### Link the specified +node+ to its parent. Raises an error if the specified +node+'s
|
323
|
+
### parent is not yet loaded.
|
324
|
+
def link_node_to_parent( node )
|
325
|
+
parent_id = node.parent || '_'
|
326
|
+
parent_node = self.nodes[ parent_id ] or
|
327
|
+
raise "no parent '%s' node loaded for %p" % [ parent_id, node ]
|
328
|
+
|
329
|
+
self.log.debug "adding %p as a child of %p" % [ node, parent_node ]
|
330
|
+
parent_node.add_child( node )
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
### Add the specified +node+ to the Manager.
|
335
|
+
def add_node( node )
|
336
|
+
identifier = node.identifier
|
337
|
+
|
338
|
+
unless self.nodes[identifier].equal?( node )
|
339
|
+
self.remove_node( self.nodes[identifier] )
|
340
|
+
self.nodes[ identifier ] = node
|
341
|
+
end
|
342
|
+
|
343
|
+
self.log.debug "Linking node %p to its parent" % [ node ]
|
344
|
+
self.link_node_to_parent( node ) if self.tree_built?
|
345
|
+
end
|
346
|
+
|
347
|
+
|
348
|
+
### Remove a +node+ from the Manager. The +node+ can either be the Arborist::Node to
|
349
|
+
### remove, or the identifier of a node.
|
350
|
+
def remove_node( node )
|
351
|
+
node = self.nodes[ node ] unless node.is_a?( Arborist::Node )
|
352
|
+
return unless node
|
353
|
+
|
354
|
+
raise "Can't remove an operational node" if node.operational?
|
355
|
+
|
356
|
+
self.log.info "Removing node %p" % [ node ]
|
357
|
+
node.children.each do |identifier, child_node|
|
358
|
+
self.remove_node( child_node )
|
359
|
+
end
|
360
|
+
|
361
|
+
if parent_node = self.nodes[ node.parent || '_' ]
|
362
|
+
parent_node.remove_child( node )
|
363
|
+
end
|
364
|
+
|
365
|
+
return self.nodes.delete( node.identifier )
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
### Update the node with the specified +identifier+ with the given +new_properties+
|
370
|
+
### and propagate any events generated by the update to the node and its ancestors.
|
371
|
+
def update_node( identifier, new_properties )
|
372
|
+
unless (( node = self.nodes[identifier] ))
|
373
|
+
self.log.warn "Update for non-existent node %p ignored." % [ identifier ]
|
374
|
+
return []
|
375
|
+
end
|
376
|
+
|
377
|
+
events = node.update( new_properties )
|
378
|
+
self.propagate_events( node, events )
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
### Traverse the node tree and fetch the specified +return_values+ from any nodes which
|
383
|
+
### match the given +filter+, skipping downed nodes and all their children
|
384
|
+
### unless +include_down+ is set. If +return_values+ is set to +nil+, then all
|
385
|
+
### values from the node will be returned.
|
386
|
+
def fetch_matching_node_states( filter, return_values, include_down=false )
|
387
|
+
nodes_iter = if include_down
|
388
|
+
self.all_nodes
|
389
|
+
else
|
390
|
+
self.reachable_nodes
|
391
|
+
end
|
392
|
+
|
393
|
+
states = nodes_iter.
|
394
|
+
select {|node| node.matches?(filter) }.
|
395
|
+
each_with_object( {} ) do |node, hash|
|
396
|
+
hash[ node.identifier ] = node.fetch_values( return_values )
|
397
|
+
end
|
398
|
+
|
399
|
+
return states
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
### Return the duration the manager has been running in seconds.
|
404
|
+
def uptime
|
405
|
+
return 0 unless self.start_time
|
406
|
+
return Time.now - self.start_time
|
407
|
+
end
|
408
|
+
|
409
|
+
|
410
|
+
### Return the number of nodes in the manager's tree.
|
411
|
+
def nodecount
|
412
|
+
return self.nodes.length
|
413
|
+
end
|
414
|
+
|
415
|
+
|
416
|
+
### Return an Array of the identifiers of all nodes in the manager's tree.
|
417
|
+
def nodelist
|
418
|
+
return self.nodes.keys
|
419
|
+
end
|
420
|
+
|
421
|
+
|
422
|
+
#
|
423
|
+
# Tree-traversal API
|
424
|
+
#
|
425
|
+
|
426
|
+
### Yield each node in a depth-first traversal of the manager's tree
|
427
|
+
### to the specified +block+, or return an Enumerator if no block is given.
|
428
|
+
def all_nodes( &block )
|
429
|
+
iter = self.enumerator_for( self.root )
|
430
|
+
return iter.each( &block ) if block
|
431
|
+
return iter
|
432
|
+
end
|
433
|
+
|
434
|
+
|
435
|
+
### Yield each node that is not down to the specified +block+, or return
|
436
|
+
### an Enumerator if no block is given.
|
437
|
+
def reachable_nodes( &block )
|
438
|
+
iter = self.enumerator_for( self.root ) do |node|
|
439
|
+
!node.down?
|
440
|
+
end
|
441
|
+
return iter.each( &block ) if block
|
442
|
+
return iter
|
443
|
+
end
|
444
|
+
|
445
|
+
|
446
|
+
### Return an enumerator for the specified +node+.
|
447
|
+
def enumerator_for( start_node, &filter )
|
448
|
+
return Enumerator.new do |yielder|
|
449
|
+
traverse = ->( node ) do
|
450
|
+
if !filter || filter.call( node )
|
451
|
+
yielder.yield( node )
|
452
|
+
node.each( &traverse )
|
453
|
+
end
|
454
|
+
end
|
455
|
+
traverse.call( start_node )
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
|
460
|
+
|
461
|
+
#
|
462
|
+
# Event API
|
463
|
+
#
|
464
|
+
|
465
|
+
### Create a subscription for the node with the specified +identifier+ and
|
466
|
+
### +event_pattern+, using the given +criteria+ when considering an event.
|
467
|
+
def create_subscription( identifier, event_pattern, criteria )
|
468
|
+
identifier ||= '_'
|
469
|
+
|
470
|
+
node = self.nodes[ identifier ] or raise ArgumentError, "no such node %p" % [ identifier ]
|
471
|
+
sub = Arborist::Subscription.new( self.event_publisher, event_pattern, criteria )
|
472
|
+
|
473
|
+
self.log.debug "Registering subscription %p" % [ sub ]
|
474
|
+
node.add_subscription( sub )
|
475
|
+
self.log.debug " adding '%s' to the subscriptions hash." % [ sub.id ]
|
476
|
+
self.subscriptions[ sub.id ] = node
|
477
|
+
self.log.debug " subscriptions hash: %#0x" % [ self.subscriptions.object_id ]
|
478
|
+
|
479
|
+
return sub
|
480
|
+
end
|
481
|
+
|
482
|
+
|
483
|
+
### Remove the subscription with the specified +subscription_identifier+ from the node
|
484
|
+
### it's attached to and from the manager, and return it.
|
485
|
+
def remove_subscription( subscription_identifier )
|
486
|
+
node = self.subscriptions.delete( subscription_identifier ) or return nil
|
487
|
+
return node.remove_subscription( subscription_identifier )
|
488
|
+
end
|
489
|
+
|
490
|
+
|
491
|
+
### Propagate one or more +events+ to the specified +node+ and its ancestors in the tree,
|
492
|
+
### publishing them to matching subscriptions belonging to the nodes along the way.
|
493
|
+
def propagate_events( node, *events )
|
494
|
+
self.log.debug "Propagating %d events to node %s" % [ events.length, node.identifier ]
|
495
|
+
node.publish_events( *events )
|
496
|
+
|
497
|
+
if node.parent
|
498
|
+
parent = self.nodes[ node.parent ] or raise "couldn't find parent %p of node %p!" %
|
499
|
+
[ node.parent, node.identifier ]
|
500
|
+
self.propagate_events( parent, *events )
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
|
505
|
+
require 'arborist/manager/tree_api'
|
506
|
+
require 'arborist/manager/event_publisher'
|
507
|
+
|
508
|
+
end # class Arborist::Manager
|