arborist 0.1.0 → 0.2.0.pre20170519125456

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