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