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
@@ -41,7 +41,7 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
|
|
41
41
|
|
42
42
|
### Publish the specified +event+.
|
43
43
|
def publish( identifier, event )
|
44
|
-
@event_queue << [ identifier, MessagePack.pack(event.
|
44
|
+
@event_queue << [ identifier, MessagePack.pack(event.to_h) ]
|
45
45
|
self.register
|
46
46
|
return self
|
47
47
|
end
|
@@ -43,7 +43,15 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
43
43
|
self.log.error "%p: %s" % [ err.class, err.message ]
|
44
44
|
err.backtrace.each {|frame| self.log.debug " #{frame}" }
|
45
45
|
|
46
|
-
errtype = err
|
46
|
+
errtype = case err
|
47
|
+
when Arborist::RequestError,
|
48
|
+
Arborist::ConfigError,
|
49
|
+
Arborist::NodeError
|
50
|
+
'client'
|
51
|
+
else
|
52
|
+
'server'
|
53
|
+
end
|
54
|
+
|
47
55
|
return self.error_response( errtype, err.message )
|
48
56
|
end
|
49
57
|
|
@@ -116,8 +124,9 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
116
124
|
raise Arborist::RequestError, "missing required header 'action'" unless
|
117
125
|
header.key?( 'action' )
|
118
126
|
|
119
|
-
raise Arborist::RequestError, "body must be a Map or
|
120
|
-
body.is_a?( Hash ) || body.nil?
|
127
|
+
raise Arborist::RequestError, "body must be Nil, a Map, or an Array of Maps" unless
|
128
|
+
body.is_a?( Hash ) || body.nil? ||
|
129
|
+
( body.is_a?(Array) && body.all? {|obj| obj.is_a?(Hash) } )
|
121
130
|
|
122
131
|
return header, body
|
123
132
|
end
|
@@ -125,7 +134,7 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
125
134
|
|
126
135
|
### Return a response to the `status` action.
|
127
136
|
def handle_status_request( header, body )
|
128
|
-
self.log.
|
137
|
+
self.log.debug "STATUS: %p" % [ header ]
|
129
138
|
return successful_response(
|
130
139
|
server_version: Arborist::VERSION,
|
131
140
|
state: @manager.running? ? 'running' : 'not running',
|
@@ -137,7 +146,7 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
137
146
|
|
138
147
|
### Return a response to the `subscribe` action.
|
139
148
|
def handle_subscribe_request( header, body )
|
140
|
-
self.log.
|
149
|
+
self.log.debug "SUBSCRIBE: %p" % [ header ]
|
141
150
|
event_type = header[ 'event_type' ]
|
142
151
|
node_identifier = header[ 'identifier' ]
|
143
152
|
subscription = @manager.create_subscription( node_identifier, event_type, body )
|
@@ -148,7 +157,7 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
148
157
|
|
149
158
|
### Return a response to the `unsubscribe` action.
|
150
159
|
def handle_unsubscribe_request( header, body )
|
151
|
-
self.log.
|
160
|
+
self.log.debug "UNSUBSCRIBE: %p" % [ header ]
|
152
161
|
subscription_id = header[ 'subscription_id' ] or
|
153
162
|
return error_response( 'client', 'No identifier specified for UNSUBSCRIBE.' )
|
154
163
|
subscription = @manager.remove_subscription( subscription_id ) or
|
@@ -163,13 +172,20 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
163
172
|
|
164
173
|
### Return a repsonse to the `list` action.
|
165
174
|
def handle_list_request( header, body )
|
166
|
-
self.log.
|
167
|
-
from
|
175
|
+
self.log.debug "LIST: %p" % [ header ]
|
176
|
+
from = header['from'] || '_'
|
177
|
+
depth = header['depth']
|
168
178
|
|
169
179
|
start_node = @manager.nodes[ from ]
|
170
180
|
self.log.debug " Listing nodes under %p" % [ start_node ]
|
171
|
-
iter =
|
172
|
-
|
181
|
+
iter = if depth
|
182
|
+
self.log.debug " depth limited to %d" % [ depth ]
|
183
|
+
@manager.depth_limited_enumerator_for( start_node, depth )
|
184
|
+
else
|
185
|
+
self.log.debug " no depth limit"
|
186
|
+
@manager.enumerator_for( start_node )
|
187
|
+
end
|
188
|
+
data = iter.map( &:to_h )
|
173
189
|
self.log.debug " got data for %d nodes" % [ data.length ]
|
174
190
|
|
175
191
|
return successful_response( data )
|
@@ -178,7 +194,7 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
178
194
|
|
179
195
|
### Return a response to the 'fetch' action.
|
180
196
|
def handle_fetch_request( header, body )
|
181
|
-
self.log.
|
197
|
+
self.log.debug "FETCH: %p" % [ header ]
|
182
198
|
|
183
199
|
include_down = header['include_down']
|
184
200
|
values = if header.key?( 'return' )
|
@@ -186,7 +202,11 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
186
202
|
else
|
187
203
|
nil
|
188
204
|
end
|
189
|
-
|
205
|
+
|
206
|
+
body = [ body ] unless body.is_a?( Array )
|
207
|
+
positive = body.shift
|
208
|
+
negative = body.shift || {}
|
209
|
+
states = @manager.fetch_matching_node_states( positive, values, include_down, negative )
|
190
210
|
|
191
211
|
return successful_response( states )
|
192
212
|
end
|
@@ -194,7 +214,7 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
194
214
|
|
195
215
|
### Update nodes using the data from the update request's +body+.
|
196
216
|
def handle_update_request( header, body )
|
197
|
-
self.log.
|
217
|
+
self.log.debug "UPDATE: %p" % [ header ]
|
198
218
|
|
199
219
|
body.each do |identifier, properties|
|
200
220
|
@manager.update_node( identifier, properties )
|
@@ -203,5 +223,64 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
203
223
|
return successful_response( nil )
|
204
224
|
end
|
205
225
|
|
226
|
+
|
227
|
+
### Remove a node and its children.
|
228
|
+
def handle_prune_request( header, body )
|
229
|
+
self.log.debug "PRUNE: %p" % [ header ]
|
230
|
+
|
231
|
+
identifier = header[ 'identifier' ] or
|
232
|
+
return error_response( 'client', 'No identifier specified for PRUNE.' )
|
233
|
+
node = @manager.remove_node( identifier )
|
234
|
+
|
235
|
+
return successful_response( node ? true : nil )
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
### Add a node
|
240
|
+
def handle_graft_request( header, body )
|
241
|
+
self.log.debug "GRAFT: %p" % [ header ]
|
242
|
+
|
243
|
+
identifier = header[ 'identifier' ] or
|
244
|
+
return error_response( 'client', 'No identifier specified for GRAFT.' )
|
245
|
+
type = header[ 'type' ] or
|
246
|
+
return error_response( 'client', 'No type specified for GRAFT.' )
|
247
|
+
parent = header[ 'parent' ] || '_'
|
248
|
+
parent_node = @manager.nodes[ parent ] or
|
249
|
+
return error_response( 'client', 'No parent node found for %s.' % [parent] )
|
250
|
+
|
251
|
+
self.log.debug "Grafting a new %s node under %p" % [ type, parent_node ]
|
252
|
+
|
253
|
+
# If the parent has a factory method for the node type, use it, otherwise
|
254
|
+
# use the Pluggability factory
|
255
|
+
node = if parent_node.respond_to?( type )
|
256
|
+
parent_node.method( type ).call( identifier, body )
|
257
|
+
else
|
258
|
+
body.merge!( parent: parent )
|
259
|
+
Arborist::Node.create( type, identifier, body )
|
260
|
+
end
|
261
|
+
|
262
|
+
@manager.add_node( node )
|
263
|
+
|
264
|
+
return successful_response( node ? node.identifier : nil )
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
### Modify a node's operational attributes
|
269
|
+
def handle_modify_request( header, body )
|
270
|
+
self.log.debug "MODIFY: %p" % [ header ]
|
271
|
+
|
272
|
+
identifier = header[ 'identifier' ] or
|
273
|
+
return error_response( 'client', 'No identifier specified for MODIFY.' )
|
274
|
+
return error_response( 'client', "Unable to MODIFY root node." ) if identifier == '_'
|
275
|
+
node = @manager.nodes[ identifier ] or
|
276
|
+
return error_response( 'client', "No such node %p" % [identifier] )
|
277
|
+
|
278
|
+
self.log.debug "Modifying operational attributes of the %s node: %p" % [ identifier, body ]
|
279
|
+
|
280
|
+
node.modify( body )
|
281
|
+
|
282
|
+
return successful_response( nil )
|
283
|
+
end
|
284
|
+
|
206
285
|
end # class Arborist::Manager::TreeAPI
|
207
286
|
|
data/lib/arborist/mixins.rb
CHANGED
@@ -358,6 +358,23 @@ module Arborist
|
|
358
358
|
end
|
359
359
|
|
360
360
|
|
361
|
+
### Returns true if the specified +hash+ includes the specified +key+, and the value
|
362
|
+
### associated with the +key+ either includes +val+ if it is a Hash, or equals +val+ if it's
|
363
|
+
### anything but a Hash.
|
364
|
+
def hash_matches( hash, key, val )
|
365
|
+
actual = hash[ key ] or return false
|
366
|
+
|
367
|
+
if actual.is_a?( Hash )
|
368
|
+
if val.is_a?( Hash )
|
369
|
+
return val.all? {|subkey, subval| hash_matches(actual, subkey, subval) }
|
370
|
+
else
|
371
|
+
return false
|
372
|
+
end
|
373
|
+
else
|
374
|
+
return actual == val
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
361
378
|
end # HashUtilities
|
362
379
|
|
363
380
|
end # module Arborist
|
data/lib/arborist/monitor.rb
CHANGED
@@ -35,7 +35,7 @@ class Arborist::Monitor
|
|
35
35
|
DEFAULT_SPLAY = 0
|
36
36
|
|
37
37
|
|
38
|
-
Arborist.add_dsl_constructor(
|
38
|
+
Arborist.add_dsl_constructor( self ) do |description, &block|
|
39
39
|
Arborist::Monitor.new( description, &block )
|
40
40
|
end
|
41
41
|
|
@@ -250,6 +250,15 @@ class Arborist::Monitor
|
|
250
250
|
parent_writer.close
|
251
251
|
|
252
252
|
return context.handle_results( pid, parent_reader, parent_err_reader )
|
253
|
+
rescue SystemCallError => err
|
254
|
+
self.log.error "%p while running external monitor command `%s`: %s" % [
|
255
|
+
err.class,
|
256
|
+
Shellwords.join( command ),
|
257
|
+
err.message
|
258
|
+
]
|
259
|
+
self.log.debug " %s" % [ err.backtrace.join("\n ") ]
|
260
|
+
return {}
|
261
|
+
|
253
262
|
ensure
|
254
263
|
if pid
|
255
264
|
begin
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'loggability'
|
5
5
|
require 'timeout'
|
6
|
+
require 'socket'
|
6
7
|
|
7
8
|
require 'arborist/monitor' unless defined?( Arborist::Monitor )
|
8
9
|
|
@@ -12,7 +13,6 @@ using Arborist::TimeRefinements
|
|
12
13
|
# Socket-related Arborist monitor logic
|
13
14
|
module Arborist::Monitor::Socket
|
14
15
|
|
15
|
-
|
16
16
|
# Arborist TCP socket monitor logic
|
17
17
|
class TCP
|
18
18
|
extend Loggability
|
@@ -119,6 +119,8 @@ module Arborist::Monitor::Socket
|
|
119
119
|
until connections.empty? || timeout_at.past?
|
120
120
|
self.log.debug "Waiting on %d connections for %0.3ds..." %
|
121
121
|
[ connections.values.length, timeout_at - Time.now ]
|
122
|
+
|
123
|
+
# :FIXME: Race condition: errors if timeout_at - Time.now is 0
|
122
124
|
_, ready, _ = IO.select( nil, connections.keys, nil, timeout_at - Time.now )
|
123
125
|
|
124
126
|
self.log.debug " select returned: %p" % [ ready ]
|
@@ -154,10 +156,129 @@ module Arborist::Monitor::Socket
|
|
154
156
|
|
155
157
|
return results
|
156
158
|
end
|
157
|
-
|
158
159
|
end # class TCP
|
159
160
|
|
160
161
|
|
162
|
+
# Arborist UDP socket monitor logic
|
163
|
+
class UDP
|
164
|
+
extend Loggability
|
165
|
+
log_to :arborist
|
166
|
+
|
167
|
+
|
168
|
+
# Defaults for instances of this monitor
|
169
|
+
DEFAULT_OPTIONS = {
|
170
|
+
timeout: 0.001
|
171
|
+
}
|
172
|
+
|
173
|
+
|
174
|
+
### Instantiate a monitor check and run it for the specified +nodes+.
|
175
|
+
def self::run( nodes )
|
176
|
+
return self.new.run( nodes )
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
### Create a new UDP monitor with the specified +options+. Valid options are:
|
181
|
+
###
|
182
|
+
### +:timeout+
|
183
|
+
### Set the number of seconds to wait for a connection for each node.
|
184
|
+
def initialize( options=DEFAULT_OPTIONS )
|
185
|
+
options = DEFAULT_OPTIONS.merge( options || {} )
|
186
|
+
|
187
|
+
options.each do |name, value|
|
188
|
+
self.public_send( "#{name}=", value )
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
######
|
194
|
+
public
|
195
|
+
######
|
196
|
+
|
197
|
+
# The timeout for connecting, in seconds.
|
198
|
+
attr_accessor :timeout
|
199
|
+
|
200
|
+
|
201
|
+
### Run the UDP check for each of the specified Hash of +nodes+ and return a Hash of
|
202
|
+
### updates for them based on trying to connect to them.
|
203
|
+
def run( nodes )
|
204
|
+
self.log.debug "Got nodes to UDP check: %p" % [ nodes ]
|
205
|
+
|
206
|
+
connections = self.make_connections( nodes )
|
207
|
+
return self.wait_for_connections( connections )
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
### Open a socket for each of the specified nodes and return a Hash of
|
212
|
+
### the sockets (or the error from the connection attempt) keyed by
|
213
|
+
### node identifier.
|
214
|
+
def make_connections( nodes )
|
215
|
+
return nodes.each_with_object( {} ) do |(identifier, node_data), accum|
|
216
|
+
|
217
|
+
address = node_data['addresses'].first
|
218
|
+
port = node_data['port']
|
219
|
+
|
220
|
+
self.log.debug "Creating UDP connection for %s:%d" % [ address, port ]
|
221
|
+
sock = Socket.new( :INET, :DGRAM )
|
222
|
+
|
223
|
+
conn = begin
|
224
|
+
sockaddr = Socket.sockaddr_in( port, address )
|
225
|
+
sock.connect( sockaddr )
|
226
|
+
sock.send( '', 0 )
|
227
|
+
sock
|
228
|
+
rescue SocketError => err
|
229
|
+
self.log.error " %p setting up connection: %s" % [ err.class, err.message ]
|
230
|
+
err
|
231
|
+
end
|
232
|
+
|
233
|
+
accum[ conn ] = [ identifier, sock ]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
### For any elements of +connections+ that are sockets, wait on them to complete or error
|
239
|
+
### and then return a Hash of node updates keyed by identifier based on the results.
|
240
|
+
def wait_for_connections( connections )
|
241
|
+
results = {}
|
242
|
+
start = Time.now
|
243
|
+
|
244
|
+
# First strip out all the ones that failed in the first #connect
|
245
|
+
connections.delete_if do |sock, (identifier, _)|
|
246
|
+
next false if sock.respond_to?( :recvfrom_nonblock ) # Keep sockets
|
247
|
+
self.log.debug " removing connect error for node %s" % [ identifier ]
|
248
|
+
results[ identifier ] = { error: sock.message }
|
249
|
+
end
|
250
|
+
|
251
|
+
# Test all connections
|
252
|
+
connections.each do |sock, (identifier, _)|
|
253
|
+
begin
|
254
|
+
sock.recvfrom_nonblock( 1 )
|
255
|
+
|
256
|
+
rescue IO::WaitReadable
|
257
|
+
ready, _, _ = IO.select( [sock], [], [], self.timeout )
|
258
|
+
if ready.nil?
|
259
|
+
now = Time.now
|
260
|
+
results[ identifier ] = {
|
261
|
+
udp_socket_connect: { time: now.to_s, duration: now - start }
|
262
|
+
}
|
263
|
+
self.log.debug " connection successful"
|
264
|
+
else
|
265
|
+
retry
|
266
|
+
end
|
267
|
+
|
268
|
+
rescue SocketError, SystemCallError => err
|
269
|
+
self.log.debug "%p during connection: %s" % [ err.class, err.message ]
|
270
|
+
results[ identifier ] = { error: err.message }
|
271
|
+
|
272
|
+
ensure
|
273
|
+
sock.close
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
return results
|
278
|
+
end
|
279
|
+
end # class UDP
|
280
|
+
|
281
|
+
|
161
282
|
end # module Arborist::Monitor::Socket
|
162
283
|
|
163
284
|
|
@@ -62,12 +62,12 @@ class Arborist::MonitorRunner
|
|
62
62
|
|
63
63
|
### Run the specified +monitor+ and update nodes with the results.
|
64
64
|
def run_monitor( monitor )
|
65
|
-
|
65
|
+
positive = monitor.positive_criteria
|
66
|
+
negative = monitor.negative_criteria
|
66
67
|
include_down = monitor.include_down?
|
67
68
|
props = monitor.node_properties
|
68
69
|
|
69
|
-
self.fetch(
|
70
|
-
# :FIXME: Doesn't apply negative criteria
|
70
|
+
self.fetch( positive, include_down, props, negative ) do |nodes|
|
71
71
|
results = monitor.run( nodes )
|
72
72
|
self.update( results ) do
|
73
73
|
self.log.debug "Updated %d via the '%s' monitor" %
|
@@ -79,10 +79,11 @@ class Arborist::MonitorRunner
|
|
79
79
|
|
80
80
|
### Create a fetch request using the runner's client, then queue the request up
|
81
81
|
### with the specified +block+ as the callback.
|
82
|
-
def fetch( criteria, include_down, properties, &block )
|
82
|
+
def fetch( criteria, include_down, properties, negative={}, &block )
|
83
83
|
fetch = self.client.make_fetch_request( criteria,
|
84
84
|
include_down: include_down,
|
85
|
-
properties: properties
|
85
|
+
properties: properties,
|
86
|
+
exclude: negative
|
86
87
|
)
|
87
88
|
self.queue_request( fetch, &block )
|
88
89
|
end
|
data/lib/arborist/node.rb
CHANGED
@@ -11,6 +11,8 @@ require 'loggability'
|
|
11
11
|
require 'pluggability'
|
12
12
|
require 'arborist' unless defined?( Arborist )
|
13
13
|
require 'arborist/mixins'
|
14
|
+
require 'arborist/exceptions'
|
15
|
+
require 'arborist/dependency'
|
14
16
|
|
15
17
|
using Arborist::TimeRefinements
|
16
18
|
|
@@ -24,37 +26,76 @@ class Arborist::Node
|
|
24
26
|
Arborist::MethodUtilities
|
25
27
|
|
26
28
|
|
27
|
-
##
|
28
29
|
# The key for the thread local that is used to track instances as they're
|
29
30
|
# loaded.
|
30
31
|
LOADED_INSTANCE_KEY = :loaded_node_instances
|
31
32
|
|
33
|
+
# Regex to match a valid identifier
|
34
|
+
VALID_IDENTIFIER = /^\w[\w\-]*$/
|
32
35
|
|
33
|
-
##
|
34
|
-
# The struct for the 'ack' operational property
|
35
|
-
ACK = Struct.new( 'ArboristNodeACK', :message, :via, :sender, :time )
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
ACK_REQUIRED_PROPERTIES = %w[ message sender ]
|
37
|
+
autoload :Root, 'arborist/node/root'
|
38
|
+
autoload :Ack, 'arborist/node/ack'
|
40
39
|
|
41
40
|
|
42
|
-
##
|
43
41
|
# Log via the Arborist logger
|
44
42
|
log_to :arborist
|
45
43
|
|
46
|
-
##
|
47
44
|
# Search for plugins in lib/arborist/node directories in loaded gems
|
48
45
|
plugin_prefixes 'arborist/node'
|
49
46
|
|
50
47
|
|
48
|
+
##
|
49
|
+
# :method: unknown?
|
50
|
+
# Returns +true+ if the node is in an 'unknown' state.
|
51
|
+
|
52
|
+
##
|
53
|
+
# :method: up?
|
54
|
+
# Returns +true+ if the node is in an 'up' state.
|
55
|
+
|
56
|
+
##
|
57
|
+
# :method: down?
|
58
|
+
# Returns +true+ if the node is in an 'down' state.
|
59
|
+
|
60
|
+
##
|
61
|
+
# :method: acked?
|
62
|
+
# Returns +true+ if the node is in an 'acked' state.
|
63
|
+
|
64
|
+
##
|
65
|
+
# :method: disabled?
|
66
|
+
# Returns +true+ if the node is in an 'disabled' state.
|
67
|
+
|
68
|
+
##
|
69
|
+
# :method: human_status_name
|
70
|
+
# Return the node's status as a human-readable String.
|
71
|
+
|
72
|
+
##
|
73
|
+
# :method: status
|
74
|
+
# Return the +status+ of the node. This will be one of: +unknown+, +up+, +down+, +acked+, or
|
75
|
+
# +disabled+.
|
76
|
+
|
77
|
+
##
|
78
|
+
# :method: status=
|
79
|
+
# :call-seq:
|
80
|
+
# status=( new_status )
|
81
|
+
#
|
82
|
+
# Set the status of the node to +new_status+.
|
83
|
+
|
84
|
+
##
|
85
|
+
# :method: status?
|
86
|
+
# :call-seq:
|
87
|
+
# status?( status_name )
|
88
|
+
#
|
89
|
+
# Returns +true+ if the node's status is +status_name+.
|
90
|
+
|
51
91
|
state_machine( :status, initial: :unknown ) do
|
52
92
|
|
53
93
|
state :unknown,
|
54
94
|
:up,
|
55
95
|
:down,
|
56
96
|
:acked,
|
57
|
-
:disabled
|
97
|
+
:disabled,
|
98
|
+
:quieted
|
58
99
|
|
59
100
|
event :update do
|
60
101
|
transition [:down, :unknown, :acked] => :up, if: :last_contact_successful?
|
@@ -64,14 +105,24 @@ class Arborist::Node
|
|
64
105
|
transition :disabled => :unknown, unless: :ack_set?
|
65
106
|
end
|
66
107
|
|
108
|
+
event :handle_event do
|
109
|
+
transition :unknown => :acked, if: :ack_and_error_set?
|
110
|
+
transition any - [:disabled, :quieted, :acked] => :quieted, if: :has_quieted_reason?
|
111
|
+
transition :quieted => :unknown, unless: :has_quieted_reason?
|
112
|
+
end
|
113
|
+
|
67
114
|
after_transition any => :acked, do: :on_ack
|
68
115
|
after_transition :acked => :up, do: :on_ack_cleared
|
69
116
|
after_transition :down => :up, do: :on_node_up
|
70
117
|
after_transition [:unknown, :up] => :down, do: :on_node_down
|
71
118
|
after_transition [:unknown, :up] => :disabled, do: :on_node_disabled
|
119
|
+
after_transition any => :quieted, do: :on_node_quieted
|
72
120
|
after_transition :disabled => :unknown, do: :on_node_enabled
|
121
|
+
after_transition :quieted => :unknown, do: :on_node_unquieted
|
73
122
|
|
74
123
|
after_transition any => any, do: :log_transition
|
124
|
+
after_transition any => any, do: :make_transition_event
|
125
|
+
after_transition any => any, do: :update_status_changed
|
75
126
|
|
76
127
|
after_transition do: :add_status_to_update_delta
|
77
128
|
end
|
@@ -79,7 +130,11 @@ class Arborist::Node
|
|
79
130
|
|
80
131
|
### Return a curried Proc for the ::create method for the specified +type+.
|
81
132
|
def self::curried_create( type )
|
82
|
-
|
133
|
+
if type.subnode_type?
|
134
|
+
return self.method( :create ).to_proc.curry( 3 )[ type ]
|
135
|
+
else
|
136
|
+
return self.method( :create ).to_proc.curry( 2 )[ type ]
|
137
|
+
end
|
83
138
|
end
|
84
139
|
|
85
140
|
|
@@ -113,14 +168,46 @@ class Arborist::Node
|
|
113
168
|
def self::inherited( subclass )
|
114
169
|
super
|
115
170
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
171
|
+
body = self.curried_create( subclass )
|
172
|
+
Arborist.add_dsl_constructor( subclass, &body )
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
### Get/set the node type instances of the class live under. If no parent_type is set, it
|
177
|
+
### is a top-level node type.
|
178
|
+
def self::parent_types( *types )
|
179
|
+
@parent_types ||= []
|
180
|
+
|
181
|
+
types.each do |new_type|
|
182
|
+
subclass = Arborist::Node.get_subclass( new_type )
|
183
|
+
@parent_types << subclass
|
184
|
+
subclass.add_subnode_factory_method( self )
|
122
185
|
end
|
123
186
|
|
187
|
+
return @parent_types
|
188
|
+
end
|
189
|
+
singleton_method_alias :parent_type, :parent_types
|
190
|
+
|
191
|
+
|
192
|
+
### Returns +true+ if the receiver must be created under a specific node type.
|
193
|
+
def self::subnode_type?
|
194
|
+
return ! self.parent_types.empty?
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
### Add a factory method that can be used to create subnodes of the specified +subnode_type+
|
199
|
+
### on instances of the receiving class.
|
200
|
+
def self::add_subnode_factory_method( subnode_type )
|
201
|
+
if subnode_type.name
|
202
|
+
name = subnode_type.plugin_name
|
203
|
+
body = lambda do |identifier, attributes={}, &block|
|
204
|
+
return Arborist::Node.create( name, identifier, self, attributes, &block )
|
205
|
+
end
|
206
|
+
|
207
|
+
define_method( name, &body )
|
208
|
+
else
|
209
|
+
self.log.info "Skipping factory constructor for anonymous subnode class."
|
210
|
+
end
|
124
211
|
end
|
125
212
|
|
126
213
|
|
@@ -150,31 +237,42 @@ class Arborist::Node
|
|
150
237
|
|
151
238
|
### Create a new Node with the specified +identifier+, which must be unique to the
|
152
239
|
### loaded tree.
|
153
|
-
def initialize( identifier, &block )
|
154
|
-
|
155
|
-
|
240
|
+
def initialize( identifier, *args, &block )
|
241
|
+
attributes = args.last.is_a?( Hash ) ? args.pop : {}
|
242
|
+
parent_node = args.pop
|
156
243
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
@
|
162
|
-
@
|
163
|
-
|
164
|
-
@
|
165
|
-
@
|
166
|
-
|
167
|
-
@
|
168
|
-
@
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
@
|
244
|
+
raise "Invalid identifier %p" % [identifier] unless
|
245
|
+
identifier =~ VALID_IDENTIFIER
|
246
|
+
|
247
|
+
# Attributes of the target
|
248
|
+
@identifier = identifier
|
249
|
+
@parent = parent_node ? parent_node.identifier : '_'
|
250
|
+
@description = nil
|
251
|
+
@tags = Set.new
|
252
|
+
@properties = {}
|
253
|
+
@source = nil
|
254
|
+
@children = {}
|
255
|
+
@dependencies = Arborist::Dependency.new( :all )
|
256
|
+
|
257
|
+
# Primary state
|
258
|
+
@status = 'unknown'
|
259
|
+
@status_changed = Time.at( 0 )
|
260
|
+
|
261
|
+
# Attributes that govern state
|
262
|
+
@error = nil
|
263
|
+
@ack = nil
|
264
|
+
@last_contacted = Time.at( 0 )
|
265
|
+
@quieted_reasons = {}
|
266
|
+
|
267
|
+
# Event-handling
|
268
|
+
@update_delta = Hash.new do |h,k|
|
173
269
|
h[ k ] = Hash.new( &h.default_proc )
|
174
270
|
end
|
175
271
|
@pending_update_events = []
|
176
272
|
@subscriptions = {}
|
177
273
|
|
274
|
+
self.log.debug "Setting node attributes to: %p" % [ attributes ]
|
275
|
+
self.modify( attributes )
|
178
276
|
self.instance_eval( &block ) if block
|
179
277
|
end
|
180
278
|
|
@@ -228,6 +326,16 @@ class Arborist::Node
|
|
228
326
|
# subscription ID.
|
229
327
|
attr_reader :subscriptions
|
230
328
|
|
329
|
+
##
|
330
|
+
# The node's secondary dependencies, expressed as an Arborist::Node::Sexp
|
331
|
+
attr_accessor :dependencies
|
332
|
+
|
333
|
+
|
334
|
+
##
|
335
|
+
# The reasons this node was quieted. This is a Hash of text descriptions keyed by the
|
336
|
+
# type of dependency it came from (either :primary or :secondary).
|
337
|
+
attr_reader :quieted_reasons
|
338
|
+
|
231
339
|
|
232
340
|
### Set the source of the node to +source+, which should be a valid URI.
|
233
341
|
def source=( source )
|
@@ -235,6 +343,23 @@ class Arborist::Node
|
|
235
343
|
end
|
236
344
|
|
237
345
|
|
346
|
+
### Set one or more node +attributes+. This should be overridden by subclasses which
|
347
|
+
### wish to allow their operational attributes to be set/updated via the Tree API
|
348
|
+
### (+modify+ and +graft+). Supported attributes are: +parent+, +description+, and
|
349
|
+
### +tags+.
|
350
|
+
def modify( attributes )
|
351
|
+
attributes = stringify_keys( attributes )
|
352
|
+
|
353
|
+
self.parent( attributes['parent'] )
|
354
|
+
self.description( attributes['description'] )
|
355
|
+
|
356
|
+
if attributes['tags']
|
357
|
+
@tags.clear
|
358
|
+
self.tags( attributes['tags'] )
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
|
238
363
|
#
|
239
364
|
# :section: DSLish declaration methods
|
240
365
|
# These methods are both getter and setter for a node's attributes, used
|
@@ -263,11 +388,40 @@ class Arborist::Node
|
|
263
388
|
|
264
389
|
### Declare one or more +tags+ for this node.
|
265
390
|
def tags( *tags )
|
391
|
+
tags.flatten!
|
266
392
|
@tags.merge( tags.map(&:to_s) ) unless tags.empty?
|
267
393
|
return @tags.to_a
|
268
394
|
end
|
269
395
|
|
270
396
|
|
397
|
+
### Group +identifiers+ together in an 'any of' dependency.
|
398
|
+
def any_of( *identifiers, on: nil )
|
399
|
+
return Arborist::Dependency.on( :any, *identifiers, prefixes: on )
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
### Group +identifiers+ together in an 'all of' dependency.
|
404
|
+
def all_of( *identifiers, on: nil )
|
405
|
+
return Arborist::Dependency.on( :all, *identifiers, prefixes: on )
|
406
|
+
end
|
407
|
+
|
408
|
+
|
409
|
+
### Add secondary dependencies to the receiving node.
|
410
|
+
def depends_on( *dependencies, on: nil )
|
411
|
+
dependencies = self.all_of( *dependencies, on: on )
|
412
|
+
|
413
|
+
self.log.debug "Setting secondary dependencies to: %p" % [ dependencies ]
|
414
|
+
self.dependencies = check_dependencies( dependencies )
|
415
|
+
end
|
416
|
+
|
417
|
+
|
418
|
+
### Returns +true+ if the node has one or more secondary dependencies.
|
419
|
+
def has_dependencies?
|
420
|
+
return !self.dependencies.empty?
|
421
|
+
end
|
422
|
+
|
423
|
+
|
424
|
+
|
271
425
|
#
|
272
426
|
# :section: Manager API
|
273
427
|
# Methods used by the manager to manage its nodes.
|
@@ -299,14 +453,6 @@ class Arborist::Node
|
|
299
453
|
end
|
300
454
|
|
301
455
|
|
302
|
-
### Publish the specified +events+ to any subscriptions the node has which match them.
|
303
|
-
def publish_events( *events )
|
304
|
-
self.subscriptions.each_value do |sub|
|
305
|
-
sub.on_events( *events )
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
|
310
456
|
### Update specified +properties+ for the node.
|
311
457
|
def update( new_properties )
|
312
458
|
new_properties = stringify_keys( new_properties )
|
@@ -329,6 +475,7 @@ class Arborist::Node
|
|
329
475
|
events << self.make_update_event
|
330
476
|
events << self.make_delta_event unless self.update_delta.empty?
|
331
477
|
|
478
|
+
self.broadcast_events( *events )
|
332
479
|
return events
|
333
480
|
ensure
|
334
481
|
self.update_delta.clear
|
@@ -397,6 +544,8 @@ class Arborist::Node
|
|
397
544
|
### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
|
398
545
|
def match_criteria?( key, val )
|
399
546
|
return case key
|
547
|
+
when 'delta'
|
548
|
+
true
|
400
549
|
when 'status'
|
401
550
|
self.status == val
|
402
551
|
when 'type'
|
@@ -440,6 +589,129 @@ class Arborist::Node
|
|
440
589
|
end
|
441
590
|
|
442
591
|
|
592
|
+
### Register subscriptions for secondary dependencies on the receiving node with the
|
593
|
+
### given +manager+.
|
594
|
+
def register_secondary_dependencies( manager )
|
595
|
+
self.dependencies.all_identifiers.each do |identifier|
|
596
|
+
# Check to be sure the identifier isn't a descendant or ancestor
|
597
|
+
if manager.ancestors_for( self ).any? {|node| node.identifier == identifier}
|
598
|
+
raise Arborist::ConfigError, "Can't depend on ancestor node %p." % [ identifier ]
|
599
|
+
elsif manager.descendants_for( self ).any? {|node| node.identifier == identifier }
|
600
|
+
raise Arborist::ConfigError, "Can't depend on descendant node %p." % [ identifier ]
|
601
|
+
end
|
602
|
+
|
603
|
+
sub = Arborist::Subscription.new do |_, event|
|
604
|
+
self.handle_event( event )
|
605
|
+
end
|
606
|
+
manager.subscribe( identifier, sub )
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
|
611
|
+
### Publish the specified +events+ to any subscriptions the node has which match them.
|
612
|
+
def publish_events( *events )
|
613
|
+
self.log.debug "Got published events: %p" % [ events ]
|
614
|
+
self.subscriptions.each_value do |sub|
|
615
|
+
sub.on_events( *events )
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
|
620
|
+
### Send an event to this node's immediate children.
|
621
|
+
def broadcast_events( *events )
|
622
|
+
events.flatten!
|
623
|
+
self.children.each do |identifier, child|
|
624
|
+
self.log.debug "Broadcasting %d events to %p" % [ events.length, identifier ]
|
625
|
+
events.each do |event|
|
626
|
+
child.handle_event( event )
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
|
632
|
+
### Handle the specified +event+, delivered either via broadcast or secondary
|
633
|
+
### dependency subscription.
|
634
|
+
def handle_event( event )
|
635
|
+
self.log.debug "Handling %p" % [ event ]
|
636
|
+
handler_name = "handle_%s_event" % [ event.type.gsub('.', '_') ]
|
637
|
+
if self.respond_to?( handler_name )
|
638
|
+
self.log.debug "Handling a %s event." % [ event.type ]
|
639
|
+
self.method( handler_name ).call( event )
|
640
|
+
end
|
641
|
+
super
|
642
|
+
end
|
643
|
+
|
644
|
+
|
645
|
+
### Returns +true+ if this node's dependencies are not met.
|
646
|
+
def dependencies_down?
|
647
|
+
return self.dependencies.down?
|
648
|
+
end
|
649
|
+
alias_method :has_downed_dependencies?, :dependencies_down?
|
650
|
+
|
651
|
+
|
652
|
+
### Returns +true+ if this node's dependencies are met.
|
653
|
+
def dependencies_up?
|
654
|
+
return !self.dependencies_down?
|
655
|
+
end
|
656
|
+
|
657
|
+
|
658
|
+
### Returns +true+ if any reasons have been set as to why the node has been
|
659
|
+
### quieted. Guard condition for transition to and from `quieted` state.
|
660
|
+
def has_quieted_reason?
|
661
|
+
return !self.quieted_reasons.empty?
|
662
|
+
end
|
663
|
+
|
664
|
+
|
665
|
+
### Handle a 'node.down' event received via broadcast.
|
666
|
+
def handle_node_down_event( event )
|
667
|
+
self.log.debug "Got a node.down event: %p" % [ event ]
|
668
|
+
self.dependencies.mark_down( event.node.identifier )
|
669
|
+
|
670
|
+
if self.dependencies_down?
|
671
|
+
self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
|
672
|
+
[ self.dependencies.down_reason ]
|
673
|
+
end
|
674
|
+
|
675
|
+
if event.node.identifier == self.parent
|
676
|
+
self.quieted_reasons[ :primary ] = "Parent down: %s" % [ self.parent ] # :TODO: backtrace?
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
|
681
|
+
### Handle a 'node.quieted' event received via broadcast.
|
682
|
+
def handle_node_quieted_event( event )
|
683
|
+
self.log.debug "Got a node.quieted event: %p" % [ event ]
|
684
|
+
self.dependencies.mark_down( event.node.identifier )
|
685
|
+
|
686
|
+
if self.dependencies_down?
|
687
|
+
self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
|
688
|
+
[ self.dependencies.down_reason ]
|
689
|
+
end
|
690
|
+
|
691
|
+
if event.node.identifier == self.parent
|
692
|
+
self.quieted_reasons[ :primary ] = "Parent quieted: %s" % [ self.parent ] # :TODO: backtrace?
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
|
697
|
+
### Handle a 'node.up' event received via broadcast.
|
698
|
+
def handle_node_up_event( event )
|
699
|
+
self.log.debug "Got a node.up event: %p" % [ event ]
|
700
|
+
|
701
|
+
self.dependencies.mark_up( event.node.identifier )
|
702
|
+
self.quieted_reasons.delete( :secondary ) if self.dependencies_up?
|
703
|
+
|
704
|
+
if event.node.identifier == self.parent
|
705
|
+
self.log.info "Parent of %s (%s) came back up." % [
|
706
|
+
self.identifier,
|
707
|
+
self.parent
|
708
|
+
]
|
709
|
+
self.quieted_reasons.delete( :primary )
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
|
714
|
+
|
443
715
|
#
|
444
716
|
# :section: Hierarchy API
|
445
717
|
#
|
@@ -490,15 +762,26 @@ class Arborist::Node
|
|
490
762
|
# :section: Utility methods
|
491
763
|
#
|
492
764
|
|
765
|
+
|
766
|
+
### Return a description of the ack if it's set, or a generic string otherwise.
|
767
|
+
def acked_description
|
768
|
+
return self.ack.description if self.ack
|
769
|
+
return "(unset)"
|
770
|
+
end
|
771
|
+
|
772
|
+
|
493
773
|
### Return a string describing the node's status.
|
494
774
|
def status_description
|
495
775
|
case self.status
|
496
776
|
when 'up', 'down'
|
497
777
|
return "%s as of %s" % [ self.status.upcase, self.last_contacted ]
|
498
778
|
when 'acked'
|
499
|
-
return "ACKed
|
779
|
+
return "ACKed %s" % [ self.acked_description ]
|
500
780
|
when 'disabled'
|
501
|
-
return "disabled
|
781
|
+
return "disabled %s" % [ self.acked_description ]
|
782
|
+
when 'quieted'
|
783
|
+
reasons = self.quieted_reasons.values.join( ',' )
|
784
|
+
return "quieted: %s" % [ reasons ]
|
502
785
|
else
|
503
786
|
return "in an unknown state"
|
504
787
|
end
|
@@ -514,7 +797,7 @@ class Arborist::Node
|
|
514
797
|
|
515
798
|
### Return a String representation of the object suitable for debugging.
|
516
799
|
def inspect
|
517
|
-
return "#<%p:%#x [%s] -> %s %p %s%s, %d children, %s>" % [
|
800
|
+
return "#<%p:%#x [%s] -> %s %p %s %s, %d children, %s>" % [
|
518
801
|
self.class,
|
519
802
|
self.object_id * 2,
|
520
803
|
self.identifier,
|
@@ -532,52 +815,78 @@ class Arborist::Node
|
|
532
815
|
# :section: Serialization API
|
533
816
|
#
|
534
817
|
|
818
|
+
### Restore any saved state from the +old_node+ loaded from the state file.
|
819
|
+
def restore( old_node )
|
820
|
+
@status = old_node.status
|
821
|
+
@properties = old_node.properties.dup
|
822
|
+
@ack = old_node.ack.dup if old_node.ack
|
823
|
+
@last_contacted = old_node.last_contacted
|
824
|
+
@status_changed = old_node.status_changed
|
825
|
+
@error = old_node.error
|
826
|
+
@quieted_reasons = old_node.quieted_reasons
|
827
|
+
|
828
|
+
# Only merge in downed dependencies.
|
829
|
+
old_node.dependencies.each_downed do |identifier, time|
|
830
|
+
@dependencies.mark_down( identifier, time )
|
831
|
+
end
|
832
|
+
end
|
833
|
+
|
834
|
+
|
535
835
|
### Return a Hash of the node's state.
|
536
|
-
def
|
836
|
+
def to_h
|
537
837
|
return {
|
538
838
|
identifier: self.identifier,
|
539
839
|
type: self.class.name.to_s.sub( /.+::/, '' ).downcase,
|
540
840
|
parent: self.parent,
|
541
841
|
description: self.description,
|
542
842
|
tags: self.tags,
|
543
|
-
properties: self.properties.dup,
|
544
843
|
status: self.status,
|
844
|
+
properties: self.properties.dup,
|
545
845
|
ack: self.ack ? self.ack.to_h : nil,
|
546
846
|
last_contacted: self.last_contacted ? self.last_contacted.iso8601 : nil,
|
547
847
|
status_changed: self.status_changed ? self.status_changed.iso8601 : nil,
|
548
848
|
error: self.error,
|
849
|
+
dependencies: self.dependencies.to_h,
|
850
|
+
quieted_reasons: self.quieted_reasons,
|
549
851
|
}
|
550
852
|
end
|
551
853
|
|
552
854
|
|
553
855
|
### Marshal API -- return the node as an object suitable for marshalling.
|
554
856
|
def marshal_dump
|
555
|
-
return self.
|
857
|
+
return self.to_h.merge( dependencies: self.dependencies )
|
556
858
|
end
|
557
859
|
|
558
860
|
|
559
861
|
### Marshal API -- set up the object's state using the +hash+ from a
|
560
862
|
### previously-marshalled node.
|
561
863
|
def marshal_load( hash )
|
562
|
-
@identifier
|
563
|
-
@properties
|
864
|
+
@identifier = hash[:identifier]
|
865
|
+
@properties = hash[:properties]
|
564
866
|
|
565
|
-
@parent
|
566
|
-
@description
|
567
|
-
@tags
|
568
|
-
@children
|
867
|
+
@parent = hash[:parent]
|
868
|
+
@description = hash[:description]
|
869
|
+
@tags = Set.new( hash[:tags] )
|
870
|
+
@children = {}
|
569
871
|
|
570
|
-
@status
|
571
|
-
@status_changed
|
872
|
+
@status = 'unknown'
|
873
|
+
@status_changed = Time.parse( hash[:status_changed] )
|
572
874
|
|
573
|
-
@error
|
574
|
-
@properties
|
575
|
-
@last_contacted
|
875
|
+
@error = hash[:error]
|
876
|
+
@properties = hash[:properties] || {}
|
877
|
+
@last_contacted = Time.parse( hash[:last_contacted] )
|
878
|
+
@quieted_reasons = hash[:quieted_reasons] || {}
|
879
|
+
self.log.debug "Deps are: %p" % [ hash[:dependencies] ]
|
880
|
+
@dependencies = hash[:dependencies]
|
576
881
|
|
577
|
-
|
578
|
-
|
579
|
-
@ack = Arborist::Node::ACK.new( *ack_values )
|
882
|
+
@update_delta = Hash.new do |h,k|
|
883
|
+
h[ k ] = Hash.new( &h.default_proc )
|
580
884
|
end
|
885
|
+
|
886
|
+
@pending_update_events = []
|
887
|
+
@subscriptions = {}
|
888
|
+
|
889
|
+
self.ack = hash[:ack]
|
581
890
|
end
|
582
891
|
|
583
892
|
|
@@ -588,11 +897,7 @@ class Arborist::Node
|
|
588
897
|
other_node.identifier == self.identifier &&
|
589
898
|
other_node.parent == self.parent &&
|
590
899
|
other_node.description == self.description &&
|
591
|
-
other_node.tags == self.tags
|
592
|
-
other_node.properties == self.properties &&
|
593
|
-
other_node.status == self.status &&
|
594
|
-
other_node.ack == self.ack &&
|
595
|
-
other_node.error == self.error
|
900
|
+
other_node.tags == self.tags
|
596
901
|
end
|
597
902
|
|
598
903
|
|
@@ -604,15 +909,7 @@ class Arborist::Node
|
|
604
909
|
def ack=( ack_data )
|
605
910
|
if ack_data
|
606
911
|
self.log.info "Node %s ACKed with data: %p" % [ self.identifier, ack_data ]
|
607
|
-
|
608
|
-
ack_values = ack_data.values_at( *Arborist::Node::ACK.members.map(&:to_s) )
|
609
|
-
new_ack = Arborist::Node::ACK.new( *ack_values )
|
610
|
-
|
611
|
-
if missing = ACK_REQUIRED_PROPERTIES.find {|prop| new_ack[prop].nil? }
|
612
|
-
raise "Missing required ACK attribute %s" % [ missing ]
|
613
|
-
end
|
614
|
-
|
615
|
-
@ack = new_ack
|
912
|
+
@ack = Arborist::Node::Ack.from_hash( ack_data )
|
616
913
|
else
|
617
914
|
self.log.info "Node %s ACK cleared explicitly" % [ self.identifier ]
|
618
915
|
@ack = nil
|
@@ -637,6 +934,12 @@ class Arborist::Node
|
|
637
934
|
end
|
638
935
|
|
639
936
|
|
937
|
+
### Returns +true+ if the node has been acked and also has an error set.
|
938
|
+
def ack_and_error_set?
|
939
|
+
return self.error && self.ack_set?
|
940
|
+
end
|
941
|
+
|
942
|
+
|
640
943
|
#
|
641
944
|
# :section: State Callbacks
|
642
945
|
#
|
@@ -648,18 +951,30 @@ class Arborist::Node
|
|
648
951
|
end
|
649
952
|
|
650
953
|
|
954
|
+
### Update the last status change time.
|
955
|
+
def update_status_changed( transition )
|
956
|
+
self.status_changed = Time.now
|
957
|
+
end
|
958
|
+
|
959
|
+
|
960
|
+
### Queue up a transition event whenever one happens
|
961
|
+
def make_transition_event( transition )
|
962
|
+
node_type = "node_%s" % [ transition.to ]
|
963
|
+
self.log.debug "Making a %s event for %p" % [ node_type, transition ]
|
964
|
+
self.pending_update_events << Arborist::Event.create( node_type, self )
|
965
|
+
end
|
966
|
+
|
967
|
+
|
651
968
|
### Callback for when an acknowledgement is set.
|
652
969
|
def on_ack( transition )
|
653
970
|
self.log.warn "ACKed: %s" % [ self.status_description ]
|
654
|
-
self.pending_update_events <<
|
655
|
-
Arborist::Event.create( 'node_acked', self.fetch_values, self.ack.to_h )
|
656
971
|
end
|
657
972
|
|
658
973
|
|
659
974
|
### Callback for when an acknowledgement is cleared.
|
660
975
|
def on_ack_cleared( transition )
|
661
|
-
self.error = nil
|
662
976
|
self.log.warn "ACK cleared for %s" % [ self.identifier ]
|
977
|
+
self.ack = nil
|
663
978
|
end
|
664
979
|
|
665
980
|
|
@@ -683,6 +998,18 @@ class Arborist::Node
|
|
683
998
|
end
|
684
999
|
|
685
1000
|
|
1001
|
+
### Callback for when a node goes from any state to quieted
|
1002
|
+
def on_node_quieted( transition )
|
1003
|
+
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
|
1007
|
+
### Callback for when a node transitions from quieted to unknown
|
1008
|
+
def on_node_unquieted( transition )
|
1009
|
+
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
|
686
1013
|
### Callback for when a node goes from disabled to unknown
|
687
1014
|
def on_node_enabled( transition )
|
688
1015
|
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
@@ -700,21 +1027,20 @@ class Arborist::Node
|
|
700
1027
|
private
|
701
1028
|
#######
|
702
1029
|
|
703
|
-
###
|
704
|
-
###
|
705
|
-
|
706
|
-
|
707
|
-
actual = hash[ key ] or return false
|
1030
|
+
### Check the specified +dependencies+ (an Arborist::Dependency) for illegal dependencies
|
1031
|
+
### and raise an error if any are found.
|
1032
|
+
def check_dependencies( dependencies )
|
1033
|
+
identifiers = dependencies.all_identifiers
|
708
1034
|
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
end
|
715
|
-
else
|
716
|
-
return actual == val
|
1035
|
+
self.log.debug "Checking dependency identifiers: %p" % [ identifiers ]
|
1036
|
+
if identifiers.include?( '_' )
|
1037
|
+
raise Arborist::ConfigError, "a node can't depend on the root node"
|
1038
|
+
elsif identifiers.include?( self.identifier )
|
1039
|
+
raise Arborist::ConfigError, "a node can't depend on itself"
|
717
1040
|
end
|
1041
|
+
|
1042
|
+
return dependencies
|
718
1043
|
end
|
719
1044
|
|
1045
|
+
|
720
1046
|
end # class Arborist::Node
|