arborist 0.1.0 → 0.2.0.pre20170519125456

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.
@@ -126,6 +126,15 @@ module Arborist
126
126
 
127
127
  # Refinements to Numeric and Time to add convenience methods
128
128
  module TimeRefinements
129
+
130
+ # Approximate Time Constants (in seconds)
131
+ MINUTES = 60
132
+ HOURS = 60 * MINUTES
133
+ DAYS = 24 * HOURS
134
+ WEEKS = 7 * DAYS
135
+ MONTHS = 30 * DAYS
136
+ YEARS = 365.25 * DAYS
137
+
129
138
  refine Numeric do
130
139
 
131
140
  ### Number of seconds (returns receiver unmodified)
@@ -208,15 +217,6 @@ module Arborist
208
217
 
209
218
  refine Time do
210
219
 
211
- # Approximate Time Constants (in seconds)
212
- MINUTES = 60
213
- HOURS = 60 * MINUTES
214
- DAYS = 24 * HOURS
215
- WEEKS = 7 * DAYS
216
- MONTHS = 30 * DAYS
217
- YEARS = 365.25 * DAYS
218
-
219
-
220
220
  ### Returns +true+ if the receiver is a Time in the future.
221
221
  def future?
222
222
  return self > Time.now
@@ -1,135 +1,99 @@
1
1
  # -*- ruby -*-
2
2
  #encoding: utf-8
3
3
 
4
- require 'rbczmq'
4
+ require 'cztop'
5
+ require 'cztop/reactor'
6
+ require 'cztop/reactor/signal_handling'
5
7
  require 'loggability'
6
8
 
7
9
  require 'arborist' unless defined?( Arborist )
8
10
  require 'arborist/client'
9
11
 
10
12
 
11
- # Undo the useless scoping
12
- class ZMQ::Loop
13
- public_class_method :instance
14
- end
15
-
16
-
17
13
  # An event-driven runner for Arborist::Monitors.
18
14
  class Arborist::MonitorRunner
19
15
  extend Loggability
16
+ include CZTop::Reactor::SignalHandling
20
17
 
21
- log_to :arborist
22
-
23
-
24
- # A ZMQ::Handler object for managing IO for all running monitors.
25
- class Handler < ZMQ::Handler
26
- extend Loggability,
27
- Arborist::MethodUtilities
28
-
29
- log_to :arborist
30
-
31
- ### Create a ZMQ::Handler that acts as the agent that runs the specified
32
- ### +monitor+.
33
- def initialize( reactor )
34
- @reactor = reactor
35
- @client = Arborist::Client.new
36
- @pollitem = ZMQ::Pollitem.new( @client.tree_api, ZMQ::POLLOUT )
37
- @pollitem.handler = self
38
-
39
- @request_queue = {}
40
- @registered = false
41
- end
42
-
43
-
44
- ######
45
- public
46
- ######
18
+ # Signals the runner handles
19
+ QUEUE_SIGS = [
20
+ :INT, :TERM, :HUP, :USR1,
21
+ # :TODO: :QUIT, :WINCH, :USR2, :TTIN, :TTOU
22
+ ] & Signal.list.keys.map( &:to_sym )
47
23
 
48
- # The ZMQ::Loop that this runner is registered with
49
- attr_reader :reactor
50
24
 
51
- # The Queue of pending requests, keyed by the callback that should be called with the
52
- # results.
53
- attr_reader :request_queue
25
+ log_to :arborist
54
26
 
55
- # The Arborist::Client that will provide the message packing and unpacking
56
- attr_reader :client
57
27
 
58
- ##
59
- # True if the Handler is registered to write one or more requests
60
- attr_predicate :registered
28
+ ### Create a new Arborist::MonitorRunner
29
+ def initialize
30
+ @monitors = []
31
+ @handler = nil
32
+ @reactor = CZTop::Reactor.new
33
+ @client = Arborist::Client.new
34
+ @request_queue = {}
35
+ end
61
36
 
62
37
 
63
- ### Run the specified +monitor+ and update nodes with the results.
64
- def run_monitor( monitor )
65
- positive = monitor.positive_criteria
66
- negative = monitor.negative_criteria
67
- include_down = monitor.include_down?
68
- props = monitor.node_properties
38
+ ######
39
+ public
40
+ ######
69
41
 
70
- self.fetch( positive, include_down, props, negative ) do |nodes|
71
- results = monitor.run( nodes )
72
- monitor_key = monitor.key
42
+ ##
43
+ # The Array of loaded Arborist::Monitors the runner should run.
44
+ attr_reader :monitors
73
45
 
74
- results.each do |ident, properties|
75
- properties['_monitor_key'] = monitor_key
76
- end
46
+ ##
47
+ # The ZMQ::Handler subclass that handles all async IO
48
+ attr_accessor :handler
77
49
 
78
- self.update( results ) do
79
- self.log.debug "Updated %d via the '%s' monitor" %
80
- [ results.length, monitor.description ]
81
- end
82
- end
83
- end
50
+ ##
51
+ # The reactor (a ZMQ::Loop) the runner uses to drive everything
52
+ attr_accessor :reactor
84
53
 
54
+ ##
55
+ # The Queue of pending requests, keyed by the callback that should be called with the
56
+ # results.
57
+ attr_reader :request_queue
85
58
 
86
- ### Create a fetch request using the runner's client, then queue the request up
87
- ### with the specified +block+ as the callback.
88
- def fetch( criteria, include_down, properties, negative={}, &block )
89
- fetch = self.client.make_fetch_request( criteria,
90
- include_down: include_down,
91
- properties: properties,
92
- exclude: negative
93
- )
94
- self.queue_request( fetch, &block )
95
- end
59
+ ##
60
+ # The Arborist::Client that will provide the message packing and unpacking
61
+ attr_reader :client
96
62
 
97
63
 
98
- ### Create an update request using the runner's client, then queue the request up
99
- ### with the specified +block+ as the callback.
100
- def update( nodemap, &block )
101
- update = self.client.make_update_request( nodemap )
102
- self.queue_request( update, &block )
103
- end
64
+ ### Load monitors from the specified +enumerator+.
65
+ def load_monitors( enumerator )
66
+ self.monitors.concat( enumerator.to_a )
67
+ end
104
68
 
105
69
 
106
- ### Add the specified +event+ to the queue to be published to the console event
107
- ### socket
108
- def queue_request( request, &callback )
109
- self.request_queue[ callback ] = request
110
- self.register
70
+ ### Run the specified +monitors+
71
+ def run
72
+ self.with_signal_handler( self.reactor, *QUEUE_SIGS ) do
73
+ self.reactor.register( self.client.tree_api, :write, &self.method(:handle_io_event) )
74
+ self.reactor.start_polling
111
75
  end
76
+ end
112
77
 
113
78
 
114
- ### Register the handler's pollitem as being ready to write if it isn't already.
115
- def register
116
- # self.log.debug "Registering for writing."
117
- self.reactor.register( self.pollitem ) unless @registered
118
- @registered = true
119
- end
79
+ ### Restart the runner
80
+ def restart
81
+ # :TODO: Kill any running monitor children, cancel monitor timers, and reload
82
+ # monitors from the monitor enumerator
83
+ raise NotImplementedError
84
+ end
120
85
 
121
86
 
122
- ### Unregister the handler's pollitem from the reactor when there's nothing ready
123
- ### to write.
124
- def unregister
125
- # self.log.debug "Unregistering for writing."
126
- self.reactor.remove( self.pollitem ) if @registered
127
- @registered = false
128
- end
87
+ ### Stop the runner.
88
+ def stop
89
+ self.log.info "Stopping the runner."
90
+ self.reactor.stop_polling
91
+ end
129
92
 
130
93
 
131
- ### Write commands from the queue
132
- def on_writable
94
+ ### Reactor callback -- handle the client's socket becoming writable.
95
+ def handle_io_event( event )
96
+ if event.writable?
133
97
  if (( pair = self.request_queue.shift ))
134
98
  callback, request = *pair
135
99
  res = self.client.send_tree_api_request( request )
@@ -137,49 +101,82 @@ class Arborist::MonitorRunner
137
101
  end
138
102
 
139
103
  self.unregister if self.request_queue.empty?
140
- return true
104
+ else
105
+ raise "Unexpected %p on the tree API socket" % [ event ]
141
106
  end
142
107
 
143
- end # class Handler
108
+ end
144
109
 
145
110
 
146
- ### Create a new Arborist::MonitorRunner
147
- def initialize
148
- @monitors = []
149
- @handler = nil
150
- @reactor = ZMQ::Loop.new
111
+ ### Run the specified +monitor+ and update nodes with the results.
112
+ def run_monitor( monitor )
113
+ positive = monitor.positive_criteria
114
+ negative = monitor.negative_criteria
115
+ include_down = monitor.include_down?
116
+ props = monitor.node_properties
117
+
118
+ self.fetch( positive, include_down, props, negative ) do |nodes|
119
+ results = monitor.run( nodes )
120
+ monitor_key = monitor.key
121
+
122
+ results.each do |ident, properties|
123
+ properties['_monitor_key'] = monitor_key
124
+ end
125
+
126
+ self.update( results ) do
127
+ self.log.debug "Updated %d via the '%s' monitor" %
128
+ [ results.length, monitor.description ]
129
+ end
130
+ end
151
131
  end
152
132
 
153
133
 
154
- ######
155
- public
156
- ######
134
+ ### Create a fetch request using the runner's client, then queue the request up
135
+ ### with the specified +block+ as the callback.
136
+ def fetch( criteria, include_down, properties, negative={}, &block )
137
+ fetch = self.client.make_fetch_request( criteria,
138
+ include_down: include_down,
139
+ properties: properties,
140
+ exclude: negative
141
+ )
142
+ self.queue_request( fetch, &block )
143
+ end
157
144
 
158
- # The Array of loaded Arborist::Monitors the runner should run.
159
- attr_reader :monitors
160
145
 
161
- # The ZMQ::Handler subclass that handles all async IO
162
- attr_accessor :handler
146
+ ### Create an update request using the runner's client, then queue the request up
147
+ ### with the specified +block+ as the callback.
148
+ def update( nodemap, &block )
149
+ update = self.client.make_update_request( nodemap )
150
+ self.queue_request( update, &block )
151
+ end
163
152
 
164
- # The reactor (a ZMQ::Loop) the runner uses to drive everything
165
- attr_accessor :reactor
153
+
154
+ ### Add the specified +event+ to the queue to be published to the console event
155
+ ### socket
156
+ def queue_request( request, &callback )
157
+ self.request_queue[ callback ] = request
158
+ self.register
159
+ end
166
160
 
167
161
 
168
- ### Load monitors from the specified +enumerator+.
169
- def load_monitors( enumerator )
170
- @monitors += enumerator.to_a
162
+ ### Returns +true+ if the runner's client socket is currently registered for writing.
163
+ def registered?
164
+ return self.reactor.event_enabled?( self.client.tree_api, :write )
171
165
  end
172
166
 
173
167
 
174
- ### Run the specified +monitors+
175
- def run
176
- self.handler = Arborist::MonitorRunner::Handler.new( self.reactor )
168
+ ### Register the handler's pollitem as being ready to write if it isn't already.
169
+ def register
170
+ # self.log.debug "Registering for writing."
171
+ self.reactor.enable_events( self.client.tree_api, :write ) unless self.registered?
172
+ end
177
173
 
178
- self.monitors.each do |mon|
179
- self.add_timer_for( mon )
180
- end
181
174
 
182
- self.reactor.start
175
+ ### Unregister the handler's pollitem from the reactor when there's nothing ready
176
+ ### to write.
177
+ def unregister
178
+ # self.log.debug "Unregistering for writing."
179
+ self.reactor.disable_events( self.client.tree_api, :write ) if self.registered?
183
180
  end
184
181
 
185
182
 
@@ -187,38 +184,78 @@ class Arborist::MonitorRunner
187
184
  def add_timer_for( monitor )
188
185
  interval = monitor.interval
189
186
 
190
- timer = if monitor.splay.nonzero?
191
- self.splay_timer_for( monitor )
192
- else
193
- self.interval_timer_for( monitor )
194
- end
195
-
196
- self.reactor.register_timer( timer )
187
+ if monitor.splay.nonzero?
188
+ self.add_splay_timer_for( monitor )
189
+ else
190
+ self.add_interval_timer_for( monitor )
191
+ end
197
192
  end
198
193
 
199
194
 
200
195
  ### Create a repeating ZMQ::Timer that will run the specified monitor on its interval.
201
- def interval_timer_for( monitor )
196
+ def add_interval_timer_for( monitor )
202
197
  interval = monitor.interval
203
198
  self.log.info "Creating timer for %p" % [ monitor ]
204
199
 
205
- return ZMQ::Timer.new( interval, 0 ) do
206
- self.handler.run_monitor( monitor )
200
+ return self.reactor.add_periodic_timer( interval ) do
201
+ self.run_monitor( monitor )
207
202
  end
208
203
  end
209
204
 
210
205
 
211
206
  ### Create a one-shot ZMQ::Timer that will register the interval timer for the specified
212
207
  ### +monitor+ after a random number of seconds no greater than its splay.
213
- def splay_timer_for( monitor )
208
+ def add_splay_timer_for( monitor )
214
209
  delay = rand( monitor.splay )
215
210
  self.log.debug "Splaying registration of %p for %ds" % [ monitor, delay ]
216
211
 
217
- return ZMQ::Timer.new( delay, 1 ) do
218
- interval_timer = self.interval_timer_for( monitor )
219
- self.reactor.register_timer( interval_timer )
212
+ self.reactor.add_oneshot_timer( delay ) do
213
+ self.add_interval_timer_for( monitor )
220
214
  end
221
215
  end
222
216
 
217
+
218
+ #
219
+ # :section: Signal Handling
220
+ # These methods set up some behavior for starting, restarting, and stopping
221
+ # the manager when a signal is received.
222
+ #
223
+
224
+ ### Handle signals.
225
+ def handle_signal( sig )
226
+ self.log.debug "Handling signal %s" % [ sig ]
227
+ case sig
228
+ when :INT, :TERM
229
+ self.on_termination_signal( sig )
230
+
231
+ when :HUP
232
+ self.on_hangup_signal( sig )
233
+
234
+ when :USR1
235
+ self.on_user1_signal( sig )
236
+
237
+ else
238
+ self.log.warn "Unhandled signal %s" % [ sig ]
239
+ end
240
+
241
+ end
242
+
243
+
244
+ ### Handle a TERM signal. Shuts the handler down after handling any current request/s. Also
245
+ ### aliased to #on_interrupt_signal.
246
+ def on_termination_signal( signo )
247
+ self.log.warn "Terminated (%p)" % [ signo ]
248
+ self.stop
249
+ end
250
+ alias_method :on_interrupt_signal, :on_termination_signal
251
+
252
+
253
+ ### Handle a hangup by restarting the runner.
254
+ def on_hangup_signal( signo )
255
+ self.log.warn "Hangup (%p)" % [ signo ]
256
+ self.restart
257
+ end
258
+
259
+
223
260
  end # class Arborist::MonitorRunner
224
261
 
data/lib/arborist/node.rb CHANGED
@@ -180,8 +180,7 @@ class Arborist::Node
180
180
  ### them.
181
181
  def self::add_loaded_instance( new_instance )
182
182
  instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
183
- self.log.debug "Adding new instance %p to loaded instances %p" %
184
- [ new_instance, instances ]
183
+ self.log.debug "Adding new instance %p to node tree" % [ new_instance ]
185
184
  instances << new_instance
186
185
  end
187
186
 
@@ -921,8 +920,8 @@ class Arborist::Node
921
920
 
922
921
 
923
922
  ### Return a Hash of the node's state.
924
- def to_h
925
- return {
923
+ def to_h( deep: false )
924
+ hash = {
926
925
  identifier: self.identifier,
927
926
  type: self.class.name.to_s.sub( /.+::/, '' ).downcase,
928
927
  parent: self.parent,
@@ -938,6 +937,14 @@ class Arborist::Node
938
937
  dependencies: self.dependencies.to_h,
939
938
  quieted_reasons: self.quieted_reasons,
940
939
  }
940
+
941
+ if deep
942
+ hash[ :children ] = self.children.each_with_object( {} ) do |(ident, node), h|
943
+ h[ ident ] = node.to_h( deep )
944
+ end
945
+ end
946
+
947
+ return hash
941
948
  end
942
949
 
943
950