arborist 0.0.1.pre20160128152542 → 0.0.1.pre20160606141735
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/ChangeLog +426 -1
- data/Manifest.txt +17 -2
- data/Nodes.md +70 -0
- data/Protocol.md +68 -9
- data/README.md +3 -5
- data/Rakefile +4 -1
- data/TODO.md +52 -20
- data/lib/arborist.rb +19 -6
- data/lib/arborist/cli.rb +39 -25
- data/lib/arborist/client.rb +97 -4
- data/lib/arborist/command/client.rb +2 -1
- data/lib/arborist/command/start.rb +51 -5
- data/lib/arborist/dependency.rb +286 -0
- data/lib/arborist/event.rb +7 -2
- data/lib/arborist/event/{node_matching.rb → node.rb} +11 -5
- data/lib/arborist/event/node_acked.rb +5 -7
- data/lib/arborist/event/node_delta.rb +30 -3
- data/lib/arborist/event/node_disabled.rb +16 -0
- data/lib/arborist/event/node_down.rb +10 -0
- data/lib/arborist/event/node_quieted.rb +11 -0
- data/lib/arborist/event/node_unknown.rb +10 -0
- data/lib/arborist/event/node_up.rb +10 -0
- data/lib/arborist/event/node_update.rb +2 -11
- data/lib/arborist/event/sys_node_added.rb +10 -0
- data/lib/arborist/event/sys_node_removed.rb +10 -0
- data/lib/arborist/exceptions.rb +4 -0
- data/lib/arborist/manager.rb +188 -18
- data/lib/arborist/manager/event_publisher.rb +1 -1
- data/lib/arborist/manager/tree_api.rb +92 -13
- data/lib/arborist/mixins.rb +17 -0
- data/lib/arborist/monitor.rb +10 -1
- data/lib/arborist/monitor/socket.rb +123 -2
- data/lib/arborist/monitor_runner.rb +6 -5
- data/lib/arborist/node.rb +420 -94
- data/lib/arborist/node/ack.rb +72 -0
- data/lib/arborist/node/host.rb +43 -8
- data/lib/arborist/node/resource.rb +73 -0
- data/lib/arborist/node/root.rb +6 -0
- data/lib/arborist/node/service.rb +89 -22
- data/lib/arborist/observer.rb +1 -1
- data/lib/arborist/subscription.rb +11 -6
- data/spec/arborist/client_spec.rb +93 -5
- data/spec/arborist/dependency_spec.rb +375 -0
- data/spec/arborist/event/node_delta_spec.rb +66 -0
- data/spec/arborist/event/node_down_spec.rb +84 -0
- data/spec/arborist/event/node_spec.rb +59 -0
- data/spec/arborist/event/node_update_spec.rb +14 -3
- data/spec/arborist/event_spec.rb +3 -3
- data/spec/arborist/manager/tree_api_spec.rb +295 -3
- data/spec/arborist/manager_spec.rb +240 -57
- data/spec/arborist/monitor_spec.rb +26 -3
- data/spec/arborist/node/ack_spec.rb +74 -0
- data/spec/arborist/node/host_spec.rb +79 -0
- data/spec/arborist/node/resource_spec.rb +56 -0
- data/spec/arborist/node/service_spec.rb +68 -2
- data/spec/arborist/node_spec.rb +288 -11
- data/spec/arborist/subscription_spec.rb +23 -14
- data/spec/arborist_spec.rb +0 -4
- data/spec/data/observers/webservices.rb +10 -2
- data/spec/spec_helper.rb +8 -0
- metadata +58 -15
- metadata.gz.sig +0 -0
- data/LICENSE +0 -29
data/lib/arborist/event.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
|
+
require 'loggability'
|
4
5
|
require 'pluggability'
|
5
6
|
require 'arborist' unless defined?( Arborist )
|
6
7
|
|
@@ -9,12 +10,16 @@ require 'arborist' unless defined?( Arborist )
|
|
9
10
|
# node state changes, when they're updated, and when various other operational
|
10
11
|
# actions take place, e.g., the node tree gets reloaded.
|
11
12
|
class Arborist::Event
|
12
|
-
extend Pluggability
|
13
|
+
extend Pluggability,
|
14
|
+
Loggability
|
13
15
|
|
14
16
|
|
15
17
|
# Pluggability API -- look for events under the specified prefix
|
16
18
|
plugin_prefixes 'arborist/event'
|
17
19
|
|
20
|
+
# Loggability API -- log to the Arborist logger
|
21
|
+
log_to :arborist
|
22
|
+
|
18
23
|
|
19
24
|
### Create a new event with the specified +payload+ data.
|
20
25
|
def initialize( payload )
|
@@ -49,7 +54,7 @@ class Arborist::Event
|
|
49
54
|
|
50
55
|
|
51
56
|
### Return the event as a Hash.
|
52
|
-
def
|
57
|
+
def to_h
|
53
58
|
return {
|
54
59
|
'type' => self.type,
|
55
60
|
'data' => self.payload
|
@@ -4,9 +4,8 @@
|
|
4
4
|
require 'arborist/event' unless defined?( Arborist::Event )
|
5
5
|
|
6
6
|
|
7
|
-
# A
|
8
|
-
|
9
|
-
module Arborist::Event::NodeMatching
|
7
|
+
# A base class for events which are related to an Arborist::Node.
|
8
|
+
class Arborist::Event::Node < Arborist::Event
|
10
9
|
|
11
10
|
### Strip and save the node argument to the constructor.
|
12
11
|
def initialize( node, payload=nil )
|
@@ -26,12 +25,19 @@ module Arborist::Event::NodeMatching
|
|
26
25
|
### Returns +true+ if the specified +object+ matches this event.
|
27
26
|
def match( object )
|
28
27
|
return super &&
|
29
|
-
object.respond_to?( :criteria ) && self.node.matches?( object.criteria )
|
28
|
+
object.respond_to?( :criteria ) && self.node.matches?( object.criteria ) &&
|
29
|
+
( !object.respond_to?(:negative_criteria) || !self.node.matches?(object.negative_criteria) )
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
### Use the node data as this event's payload.
|
34
|
+
def payload
|
35
|
+
return self.node.to_h
|
30
36
|
end
|
31
37
|
|
32
38
|
|
33
39
|
### Inject the node identifier into the generated hash.
|
34
|
-
def
|
40
|
+
def to_h
|
35
41
|
return super.merge( identifier: self.node.identifier )
|
36
42
|
end
|
37
43
|
|
@@ -2,17 +2,15 @@
|
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
4
|
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
-
require 'arborist/event/
|
5
|
+
require 'arborist/event/node'
|
6
6
|
|
7
7
|
|
8
8
|
# An event generated when a node is manually ACKed.
|
9
|
-
class Arborist::Event::NodeAcked < Arborist::Event
|
10
|
-
include Arborist::Event::NodeMatching
|
9
|
+
class Arborist::Event::NodeAcked < Arborist::Event::Node
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
super
|
11
|
+
### Create a new NodeAcked event for the specified +node+.
|
12
|
+
def initialize( node )
|
13
|
+
super( node, node.ack.to_h )
|
16
14
|
end
|
17
15
|
|
18
16
|
end # class Arborist::Event::NodeAcked
|
@@ -2,12 +2,13 @@
|
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
4
|
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
-
require 'arborist/event/
|
5
|
+
require 'arborist/event/node'
|
6
|
+
require 'arborist/mixins'
|
6
7
|
|
7
8
|
|
8
9
|
# An event sent when one or more attributes of a node changes.
|
9
|
-
class Arborist::Event::NodeDelta < Arborist::Event
|
10
|
-
include Arborist::
|
10
|
+
class Arborist::Event::NodeDelta < Arborist::Event::Node
|
11
|
+
include Arborist::HashUtilities
|
11
12
|
|
12
13
|
|
13
14
|
### Create a new NodeDelta event for the specified +node+. The +delta+
|
@@ -17,4 +18,30 @@ class Arborist::Event::NodeDelta < Arborist::Event
|
|
17
18
|
super # Overridden for the documentation
|
18
19
|
end
|
19
20
|
|
21
|
+
|
22
|
+
### Overridden so delta events only contain the diff of attributes that changed.
|
23
|
+
def payload
|
24
|
+
return @payload
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
### Returns +true+ if the specified +object+ matches this event.
|
29
|
+
def match( object )
|
30
|
+
return super &&
|
31
|
+
object.respond_to?( :criteria ) && self.delta_matches?( object.criteria )
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
### Returns +true+ if the 'delta' value of the specified +criteria+ (which
|
36
|
+
### must respond to .all?) matches the delta this event represents.
|
37
|
+
def delta_matches?( criteria )
|
38
|
+
delta_criteria = criteria['delta'] || {}
|
39
|
+
self.log.debug "Matching event against delta criteria: %p" % [ delta_criteria ]
|
40
|
+
|
41
|
+
return delta_criteria.all? do |key, val|
|
42
|
+
self.log.debug " matching %p: %p against %p" % [ key, val, self.payload ]
|
43
|
+
hash_matches( self.payload, key, val )
|
44
|
+
end.tap {|match| self.log.debug " event delta %s match." % [ match ? "DID" : "did not"] }
|
45
|
+
end
|
46
|
+
|
20
47
|
end # class Arborist::Event::NodeDelta
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node'
|
6
|
+
|
7
|
+
|
8
|
+
# An event generated when a node is manually disabled
|
9
|
+
class Arborist::Event::NodeDisabled < Arborist::Event::Node
|
10
|
+
|
11
|
+
### Create a new NodeDisabled event for the specified +node+.
|
12
|
+
def initialize( node )
|
13
|
+
super( node, node.ack.to_h )
|
14
|
+
end
|
15
|
+
|
16
|
+
end # class Arborist::Event::NodeDisabled
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node'
|
6
|
+
|
7
|
+
|
8
|
+
# An event generated when a node goes down.
|
9
|
+
class Arborist::Event::NodeDown < Arborist::Event::Node
|
10
|
+
end # class Arborist::Event::NodeDown
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node'
|
6
|
+
|
7
|
+
|
8
|
+
# An event generated when a node is quieted by one of its dependencies going
|
9
|
+
# down.
|
10
|
+
class Arborist::Event::NodeQuieted < Arborist::Event::Node
|
11
|
+
end # class Arborist::Event::NodeQuieted
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node'
|
6
|
+
|
7
|
+
|
8
|
+
# An event generated when a node transitions to an unknown state.
|
9
|
+
class Arborist::Event::NodeUnknown < Arborist::Event::Node
|
10
|
+
end # class Arborist::Event::NodeUnknown
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node'
|
6
|
+
|
7
|
+
|
8
|
+
# An event generated when a node comes up.
|
9
|
+
class Arborist::Event::NodeUp < Arborist::Event::Node
|
10
|
+
end # class Arborist::Event::NodeUp
|
@@ -1,19 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'arborist/event' unless defined?( Arborist::Event )
|
4
|
-
require 'arborist/event/
|
4
|
+
require 'arborist/event/node'
|
5
5
|
|
6
6
|
|
7
7
|
# An event sent on every node update, regardless of whether or not the update resulted in
|
8
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
|
-
|
9
|
+
class Arborist::Event::NodeUpdate < Arborist::Event::Node
|
19
10
|
end # class Arborist::Event::NodeUpdate
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node'
|
6
|
+
|
7
|
+
|
8
|
+
# A system event generated when a node is added to the tree.
|
9
|
+
class Arborist::Event::SysNodeAdded < Arborist::Event::Node
|
10
|
+
end # class Arborist::Event::SysNodeAdded
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/event' unless defined?( Arborist::Event )
|
5
|
+
require 'arborist/event/node'
|
6
|
+
|
7
|
+
|
8
|
+
# A system event generated when a node is removed from the tree.
|
9
|
+
class Arborist::Event::SysNodeRemoved < Arborist::Event::Node
|
10
|
+
end # class Arborist::Event::SysNodeRemoved
|
data/lib/arborist/exceptions.rb
CHANGED
data/lib/arborist/manager.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
4
|
require 'pathname'
|
5
|
+
require 'tempfile'
|
5
6
|
require 'configurability'
|
6
7
|
require 'loggability'
|
7
8
|
require 'rbczmq'
|
@@ -26,11 +27,44 @@ class Arborist::Manager
|
|
26
27
|
# The number of seconds to wait between checks for incoming signals
|
27
28
|
SIGNAL_INTERVAL = 0.5
|
28
29
|
|
30
|
+
# Configurability API -- set config defaults
|
31
|
+
CONFIG_DEFAULTS = {
|
32
|
+
state_file: nil,
|
33
|
+
checkpoint_frequency: 30
|
34
|
+
}
|
35
|
+
|
29
36
|
|
30
|
-
##
|
31
37
|
# Use the Arborist logger
|
32
38
|
log_to :arborist
|
33
39
|
|
40
|
+
# Configurability API -- use the 'arborist' section
|
41
|
+
config_key :arborist
|
42
|
+
|
43
|
+
|
44
|
+
##
|
45
|
+
# The Pathname of the file the manager's node tree state is saved to
|
46
|
+
singleton_attr_accessor :state_file
|
47
|
+
|
48
|
+
##
|
49
|
+
# The number of seconds between automatic state checkpoints
|
50
|
+
singleton_attr_accessor :checkpoint_frequency
|
51
|
+
|
52
|
+
|
53
|
+
### Configurability API -- configure the manager
|
54
|
+
def self::configure( config=nil )
|
55
|
+
config ||= {}
|
56
|
+
config = self.defaults.merge( config[:manager] || {} )
|
57
|
+
|
58
|
+
self.state_file = config[:state_file] && Pathname( config[:state_file] )
|
59
|
+
|
60
|
+
interval = config[:checkpoint_frequency].to_i
|
61
|
+
if interval && interval.nonzero?
|
62
|
+
self.checkpoint_frequency = interval
|
63
|
+
else
|
64
|
+
self.checkpoint_frequency = nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
34
68
|
|
35
69
|
#
|
36
70
|
# Instance methods
|
@@ -54,6 +88,7 @@ class Arborist::Manager
|
|
54
88
|
|
55
89
|
@api_handler = nil
|
56
90
|
@event_publisher = nil
|
91
|
+
@checkpoint_timer = nil
|
57
92
|
end
|
58
93
|
|
59
94
|
|
@@ -114,8 +149,11 @@ class Arborist::Manager
|
|
114
149
|
@tree_sock.pollable.close
|
115
150
|
@zmq_loop.remove( @event_sock )
|
116
151
|
@event_sock.pollable.close
|
152
|
+
@zmq_loop.cancel_timer( @checkpoint_timer ) if @checkpoint_timer
|
117
153
|
end
|
118
154
|
|
155
|
+
self.save_node_states
|
156
|
+
|
119
157
|
self.log.debug "Resetting ZMQ context"
|
120
158
|
Arborist.reset_zmq_context
|
121
159
|
end
|
@@ -141,6 +179,9 @@ class Arborist::Manager
|
|
141
179
|
@event_sock.handler = @event_publisher
|
142
180
|
@zmq_loop.register( @event_sock )
|
143
181
|
|
182
|
+
@checkpoint_timer = self.start_state_checkpointing
|
183
|
+
@zmq_loop.register_timer( @checkpoint_timer ) if @checkpoint_timer
|
184
|
+
|
144
185
|
self.setup_signal_timer
|
145
186
|
self.start_time = Time.now
|
146
187
|
|
@@ -194,6 +235,72 @@ class Arborist::Manager
|
|
194
235
|
end
|
195
236
|
|
196
237
|
|
238
|
+
#
|
239
|
+
# :section: Node state saving/reloading
|
240
|
+
#
|
241
|
+
|
242
|
+
### Write out the state of all the manager's nodes to the state_file if one is
|
243
|
+
### configured.
|
244
|
+
def save_node_states
|
245
|
+
path = self.class.state_file or return
|
246
|
+
self.log.info "Saving current node state to %s" % [ path ]
|
247
|
+
tmpfile = Tempfile.create(
|
248
|
+
[path.basename.to_s.sub(path.extname, ''), path.extname],
|
249
|
+
path.dirname.to_s,
|
250
|
+
encoding: 'binary'
|
251
|
+
)
|
252
|
+
Marshal.dump( self.nodes, tmpfile )
|
253
|
+
tmpfile.close
|
254
|
+
|
255
|
+
File.rename( tmpfile.path, path.to_s )
|
256
|
+
|
257
|
+
rescue SystemCallError => err
|
258
|
+
self.log.error "%p while saving node state: %s" % [ err.class, err.message ]
|
259
|
+
|
260
|
+
ensure
|
261
|
+
File.unlink( tmpfile.path ) if tmpfile && File.exist?( tmpfile.path )
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
### Attempt to restore the state of loaded node from the configured state file. Returns
|
266
|
+
### true if it succeeded, or false if a state file wasn't configured, doesn't
|
267
|
+
### exist, isn't readable, or couldn't be unmarshalled.
|
268
|
+
def restore_node_states
|
269
|
+
path = self.class.state_file or return false
|
270
|
+
return false unless path.readable?
|
271
|
+
|
272
|
+
self.log.info "Restoring node state from %s" % [ path ]
|
273
|
+
nodes = Marshal.load( path.open('r:binary') )
|
274
|
+
|
275
|
+
nodes.each do |identifier, saved_node|
|
276
|
+
self.log.debug "Loaded node: %p" % [ identifier ]
|
277
|
+
if (( current_node = self.nodes[ identifier ] ))
|
278
|
+
self.log.debug "Restoring state of the %p node." % [ identifier ]
|
279
|
+
current_node.restore( saved_node )
|
280
|
+
else
|
281
|
+
self.log.info "Not restoring state for the %s node: not present in the loaded tree." %
|
282
|
+
[ identifier ]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
return true
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
### Start a timer that will save a snapshot of the node tree's state to the state
|
291
|
+
### file on a configured interval if it's configured.
|
292
|
+
def start_state_checkpointing
|
293
|
+
return nil unless self.class.state_file
|
294
|
+
interval = self.class.checkpoint_frequency or return nil
|
295
|
+
|
296
|
+
self.log.info "Setting up node state checkpoint every %ds" % [ interval ]
|
297
|
+
checkpoint_timer = ZMQ::Timer.new( interval, 0 ) do
|
298
|
+
self.save_node_states
|
299
|
+
end
|
300
|
+
return checkpoint_timer
|
301
|
+
end
|
302
|
+
|
303
|
+
|
197
304
|
#
|
198
305
|
# :section: Signal Handling
|
199
306
|
# These methods set up some behavior for starting, restarting, and stopping
|
@@ -292,6 +399,7 @@ class Arborist::Manager
|
|
292
399
|
### Handle a USR1 signal. Writes a message to the log.
|
293
400
|
def on_user1_signal( signo )
|
294
401
|
self.log.info "Checkpoint: User signal."
|
402
|
+
self.save_node_states
|
295
403
|
end
|
296
404
|
|
297
405
|
|
@@ -312,17 +420,27 @@ class Arborist::Manager
|
|
312
420
|
### Build the tree out of all the loaded nodes.
|
313
421
|
def build_tree
|
314
422
|
self.log.info "Building tree from %d loaded nodes." % [ self.nodes.length ]
|
315
|
-
|
423
|
+
|
424
|
+
# Build primary tree structure
|
425
|
+
self.nodes.each_value do |node|
|
316
426
|
next if node.operational?
|
317
427
|
self.link_node_to_parent( node )
|
318
428
|
end
|
319
429
|
self.tree_built = true
|
430
|
+
|
431
|
+
# Set up secondary dependencies
|
432
|
+
self.nodes.each_value do |node|
|
433
|
+
node.register_secondary_dependencies( self )
|
434
|
+
end
|
435
|
+
|
436
|
+
self.restore_node_states
|
320
437
|
end
|
321
438
|
|
322
439
|
|
323
440
|
### Link the specified +node+ to its parent. Raises an error if the specified +node+'s
|
324
441
|
### parent is not yet loaded.
|
325
442
|
def link_node_to_parent( node )
|
443
|
+
self.log.debug "Linking node %p to its parent" % [ node ]
|
326
444
|
parent_id = node.parent || '_'
|
327
445
|
parent_node = self.nodes[ parent_id ] or
|
328
446
|
raise "no parent '%s' node loaded for %p" % [ parent_id, node ]
|
@@ -336,13 +454,24 @@ class Arborist::Manager
|
|
336
454
|
def add_node( node )
|
337
455
|
identifier = node.identifier
|
338
456
|
|
339
|
-
unless self.nodes[identifier].equal?( node )
|
457
|
+
unless self.nodes[ identifier ].equal?( node )
|
340
458
|
self.remove_node( self.nodes[identifier] )
|
341
459
|
self.nodes[ identifier ] = node
|
342
460
|
end
|
343
461
|
|
344
|
-
self.
|
345
|
-
|
462
|
+
if self.tree_built?
|
463
|
+
self.link_node( node )
|
464
|
+
node.handle_event( Arborist::Event.create(:sys_node_added, node) )
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
### Link the node to other nodes in the tree.
|
470
|
+
def link_node( node )
|
471
|
+
raise "Tree is not built yet" unless self.tree_built?
|
472
|
+
|
473
|
+
self.link_node_to_parent( node )
|
474
|
+
node.register_secondary_dependencies( self )
|
346
475
|
end
|
347
476
|
|
348
477
|
|
@@ -355,6 +484,7 @@ class Arborist::Manager
|
|
355
484
|
raise "Can't remove an operational node" if node.operational?
|
356
485
|
|
357
486
|
self.log.info "Removing node %p" % [ node ]
|
487
|
+
node.handle_event( Arborist::Event.create(:sys_node_removed, node) )
|
358
488
|
node.children.each do |identifier, child_node|
|
359
489
|
self.remove_node( child_node )
|
360
490
|
end
|
@@ -384,7 +514,7 @@ class Arborist::Manager
|
|
384
514
|
### match the given +filter+, skipping downed nodes and all their children
|
385
515
|
### unless +include_down+ is set. If +return_values+ is set to +nil+, then all
|
386
516
|
### values from the node will be returned.
|
387
|
-
def fetch_matching_node_states( filter, return_values, include_down=false )
|
517
|
+
def fetch_matching_node_states( filter, return_values, include_down=false, negative_filter={} )
|
388
518
|
nodes_iter = if include_down
|
389
519
|
self.all_nodes
|
390
520
|
else
|
@@ -393,6 +523,7 @@ class Arborist::Manager
|
|
393
523
|
|
394
524
|
states = nodes_iter.
|
395
525
|
select {|node| node.matches?(filter) }.
|
526
|
+
reject {|node| !negative_filter.empty? && node.matches?(negative_filter) }.
|
396
527
|
each_with_object( {} ) do |node, hash|
|
397
528
|
hash[ node.identifier ] = node.fetch_values( return_values )
|
398
529
|
end
|
@@ -437,14 +568,14 @@ class Arborist::Manager
|
|
437
568
|
### an Enumerator if no block is given.
|
438
569
|
def reachable_nodes( &block )
|
439
570
|
iter = self.enumerator_for( self.root ) do |node|
|
440
|
-
!(node.down? || node.disabled?)
|
571
|
+
!(node.down? || node.disabled? || node.quieted?)
|
441
572
|
end
|
442
573
|
return iter.each( &block ) if block
|
443
574
|
return iter
|
444
575
|
end
|
445
576
|
|
446
577
|
|
447
|
-
### Return an enumerator for the specified +
|
578
|
+
### Return an enumerator for the specified +start_node+.
|
448
579
|
def enumerator_for( start_node, &filter )
|
449
580
|
return Enumerator.new do |yielder|
|
450
581
|
traverse = ->( node ) do
|
@@ -458,24 +589,64 @@ class Arborist::Manager
|
|
458
589
|
end
|
459
590
|
|
460
591
|
|
592
|
+
### Return a +depth+ limited enumerator for the specified +start_node+.
|
593
|
+
def depth_limited_enumerator_for( start_node, depth, &filter )
|
594
|
+
return Enumerator.new do |yielder|
|
595
|
+
traverse = ->( node, current_depth ) do
|
596
|
+
self.log.debug "Enumerating nodes from %s at depth: %p" %
|
597
|
+
[ node.identifier, current_depth ]
|
598
|
+
|
599
|
+
if !filter || filter.call( node )
|
600
|
+
yielder.yield( node )
|
601
|
+
node.each do |child|
|
602
|
+
traverse[ child, current_depth - 1 ]
|
603
|
+
end if current_depth > 0
|
604
|
+
end
|
605
|
+
end
|
606
|
+
traverse.call( start_node, depth )
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
|
611
|
+
### Return an Array of all nodes below the specified +node+.
|
612
|
+
def descendants_for( node )
|
613
|
+
return self.enumerator_for( node ).to_a
|
614
|
+
end
|
615
|
+
|
616
|
+
|
617
|
+
### Return the Array of all nodes above the specified +node+.
|
618
|
+
def ancestors_for( node )
|
619
|
+
parent_id = node.parent or return []
|
620
|
+
parent = self.nodes[ parent_id ]
|
621
|
+
return [ parent ] + self.ancestors_for( parent )
|
622
|
+
end
|
623
|
+
|
461
624
|
|
462
625
|
#
|
463
626
|
# Event API
|
464
627
|
#
|
465
628
|
|
466
|
-
###
|
467
|
-
|
468
|
-
def create_subscription( identifier, event_pattern, criteria )
|
629
|
+
### Add the specified +subscription+ to the node corresponding with the given +identifier+.
|
630
|
+
def subscribe( identifier, subscription )
|
469
631
|
identifier ||= '_'
|
470
|
-
|
471
632
|
node = self.nodes[ identifier ] or raise ArgumentError, "no such node %p" % [ identifier ]
|
472
|
-
sub = Arborist::Subscription.new( self.event_publisher, event_pattern, criteria )
|
473
633
|
|
474
|
-
self.log.debug "Registering subscription %p" % [
|
475
|
-
node.add_subscription(
|
476
|
-
self.log.debug " adding '%s' to the subscriptions hash." % [
|
477
|
-
self.subscriptions[
|
634
|
+
self.log.debug "Registering subscription %p" % [ subscription ]
|
635
|
+
node.add_subscription( subscription )
|
636
|
+
self.log.debug " adding '%s' to the subscriptions hash." % [ subscription.id ]
|
637
|
+
self.subscriptions[ subscription.id ] = node
|
478
638
|
self.log.debug " subscriptions hash: %#0x" % [ self.subscriptions.object_id ]
|
639
|
+
end
|
640
|
+
|
641
|
+
|
642
|
+
### Create a subscription that publishes to the Manager's event publisher for
|
643
|
+
### the node with the specified +identifier+ and +event_pattern+, using the
|
644
|
+
### given +criteria+ when considering an event.
|
645
|
+
def create_subscription( identifier, event_pattern, criteria )
|
646
|
+
sub = Arborist::Subscription.new( event_pattern, criteria ) do |*args|
|
647
|
+
self.event_publisher.publish( *args )
|
648
|
+
end
|
649
|
+
self.subscribe( identifier, sub )
|
479
650
|
|
480
651
|
return sub
|
481
652
|
end
|
@@ -492,7 +663,6 @@ class Arborist::Manager
|
|
492
663
|
### Propagate one or more +events+ to the specified +node+ and its ancestors in the tree,
|
493
664
|
### publishing them to matching subscriptions belonging to the nodes along the way.
|
494
665
|
def propagate_events( node, *events )
|
495
|
-
self.log.info "Propagating %d events to node %s" % [ events.length, node.identifier ]
|
496
666
|
node.publish_events( *events )
|
497
667
|
|
498
668
|
if node.parent
|