arborist 0.0.1.pre20160606141735 → 0.0.1.pre20160829140603

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +81 -2
  3. data/Events.md +11 -11
  4. data/Manifest.txt +2 -4
  5. data/TODO.md +9 -29
  6. data/lib/arborist.rb +6 -2
  7. data/lib/arborist/command/watch.rb +59 -8
  8. data/lib/arborist/loader/file.rb +1 -1
  9. data/lib/arborist/manager.rb +139 -50
  10. data/lib/arborist/manager/event_publisher.rb +29 -0
  11. data/lib/arborist/manager/tree_api.rb +16 -1
  12. data/lib/arborist/mixins.rb +36 -1
  13. data/lib/arborist/monitor.rb +12 -0
  14. data/lib/arborist/monitor/socket.rb +8 -9
  15. data/lib/arborist/monitor_runner.rb +2 -2
  16. data/lib/arborist/node.rb +16 -3
  17. data/lib/arborist/node/host.rb +25 -22
  18. data/lib/arborist/node/resource.rb +28 -14
  19. data/lib/arborist/node/service.rb +21 -2
  20. data/lib/arborist/observer_runner.rb +68 -7
  21. data/spec/arborist/client_spec.rb +3 -3
  22. data/spec/arborist/manager/event_publisher_spec.rb +0 -1
  23. data/spec/arborist/manager/tree_api_spec.rb +6 -5
  24. data/spec/arborist/manager_spec.rb +53 -24
  25. data/spec/arborist/node/resource_spec.rb +9 -0
  26. data/spec/arborist/node/service_spec.rb +16 -1
  27. data/spec/arborist/node_spec.rb +22 -12
  28. data/spec/arborist/observer_runner_spec.rb +58 -0
  29. data/spec/arborist/observer_spec.rb +15 -15
  30. data/spec/arborist/subscription_spec.rb +1 -1
  31. data/spec/data/nodes/{duir.rb → sub/duir.rb} +0 -0
  32. data/spec/data/observers/auditor.rb +2 -2
  33. data/spec/spec_helper.rb +1 -0
  34. metadata +30 -27
  35. checksums.yaml.gz.sig +0 -0
  36. data.tar.gz.sig +0 -2
  37. data/lib/arborist/event/sys_node_added.rb +0 -10
  38. data/lib/arborist/event/sys_node_removed.rb +0 -10
  39. data/lib/arborist/event/sys_reloaded.rb +0 -15
  40. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a9424679d95a6df37569e5020831d81643fb54d
4
- data.tar.gz: c5c116c695eab33c5a81f79acfebaebcf9a8396a
3
+ metadata.gz: c13d6d8931471437945bbe16acd57fa7f17a01f8
4
+ data.tar.gz: 38d663d0f7fb9105727613bf61ff62a1496062f6
5
5
  SHA512:
6
- metadata.gz: cab6cbc4c4ed3d7a194ba614cd808155c2f3563bbe1b10b90ac33fc2513ce9d8abed321fd8ea901abd361544eeaa8a564faf904a9c46e932ef1034529fea0164
7
- data.tar.gz: ce941a82bacba31cfee78ce458df86abc9579e6763b0af249de2442fc2eea3a85f298cb2bf4828855cb5272b62648e153942b26c2c94b7513cfbc1efef960564
6
+ metadata.gz: 49431289b8d44fc52f40cdf60062905666a6f9de12c7c6ff8bbfb04d62e2f782f084fd824df08271ed264b513953b23baae0ccf9b4fd36b390a5d4f072c5b07f
7
+ data.tar.gz: 436d3fadba15758c0e9bd12656ada1ef3b7c5471c4b8ccaba431e02e338cae3576e3acbd579ea5f440937ff1af7b4717f118b6d4723691913f320c05afb82565
data/ChangeLog CHANGED
@@ -1,3 +1,82 @@
1
+ 2016-08-29 Michael Granger <ged@FaerieMUD.org>
2
+
3
+ * spec/data/nodes/duir.rb:
4
+ Merge with a5a6e5092024
5
+ [c961355d10c8] [tip]
6
+
7
+ 2016-08-29 Mahlon E. Smith <mahlon@martini.nu>
8
+
9
+ * lib/arborist/loader/file.rb, spec/data/nodes/duir.rb,
10
+ spec/data/nodes/sub/duir.rb:
11
+ Fix the file loader so it correctly crawls subdirectories.
12
+ [a5a6e5092024]
13
+
14
+ 2016-08-03 Michael Granger <ged@FaerieMUD.org>
15
+
16
+ * lib/arborist/event/sys_node_added.rb,
17
+ lib/arborist/event/sys_node_removed.rb,
18
+ lib/arborist/event/sys_reloaded.rb,
19
+ lib/arborist/event/sys_shutdown.rb:
20
+ Remove system event node classes
21
+ [a34a23ef095e]
22
+
23
+ 2016-08-02 Michael Granger <ged@FaerieMUD.org>
24
+
25
+ * Manifest.txt, TODO.md, lib/arborist/mixins.rb,
26
+ lib/arborist/monitor.rb, lib/arborist/monitor_runner.rb,
27
+ lib/arborist/node.rb, lib/arborist/node/host.rb,
28
+ lib/arborist/node/service.rb, spec/arborist/node/service_spec.rb,
29
+ spec/arborist/node_spec.rb, spec/arborist/subscription_spec.rb:
30
+ - Update the TO-DO list
31
+ - Factor out some network functions into a common mixin
32
+ - Add Arborist::Monitor#inspect
33
+ - Restore ack+status of serialized nodes
34
+ - Allow services to set their own address when not bound to INADDR_ANY
35
+ - Finish separating node events from system events
36
+ [f853508dba8c] [github/master]
37
+
38
+ * lib/arborist/monitor/socket.rb:
39
+ Fix race to calculate select timeout in socket monitor
40
+ [56a6dd2a8e40]
41
+
42
+ 2016-07-20 Michael Granger <ged@FaerieMUD.org>
43
+
44
+ * TODO.md:
45
+ Update the To-Do file
46
+ [53ca09b4d50e]
47
+
48
+ 2016-07-20 Mahlon E. Smith <mahlon@martini.nu>
49
+
50
+ * lib/arborist/node/resource.rb, spec/arborist/node/resource_spec.rb:
51
+ Add a `category` attribute to the resource node type.
52
+ [ae3aa8435e15]
53
+
54
+ 2016-07-20 Michael Granger <ged@FaerieMUD.org>
55
+
56
+ * .gems, .tm_properties, Events.md, TODO.md, arborist.gemspec,
57
+ lib/arborist.rb, lib/arborist/command/watch.rb,
58
+ lib/arborist/event/sys_shutdown.rb, lib/arborist/manager.rb,
59
+ lib/arborist/manager/event_publisher.rb,
60
+ lib/arborist/manager/tree_api.rb, lib/arborist/node.rb,
61
+ lib/arborist/observer_runner.rb, spec/arborist/client_spec.rb,
62
+ spec/arborist/manager/event_publisher_spec.rb,
63
+ spec/arborist/manager/tree_api_spec.rb,
64
+ spec/arborist/manager_spec.rb,
65
+ spec/arborist/observer_runner_spec.rb, spec/spec_helper.rb:
66
+ Send system events from the manager for status changes
67
+ [4cc9924e6b0c]
68
+
69
+ 2016-06-15 Michael Granger <ged@FaerieMUD.org>
70
+
71
+ * spec/arborist/observer_spec.rb, spec/data/observers/auditor.rb:
72
+ Fix signature of observer actions in specs
73
+ [196443e52d87]
74
+
75
+ * lib/arborist/node.rb, lib/arborist/node/host.rb,
76
+ spec/arborist/node_spec.rb:
77
+ Add a config hash to node operational metadata
78
+ [196223da8ba4]
79
+
1
80
  2016-05-19 Mahlon E. Smith <mahlon@martini.nu>
2
81
 
3
82
  * lib/arborist/node/resource.rb, spec/arborist/node/resource_spec.rb:
@@ -9,7 +88,7 @@
9
88
  isn't a service with a protocol and port, but a necessary piece of
10
89
  information for overall host and service health, that should be
11
90
  monitored (and potentially ack'ed/disabled) independently.
12
- [b4324ab8853e] [tip]
91
+ [b4324ab8853e]
13
92
 
14
93
  2016-05-18 Mahlon E. Smith <mahlon@laika.com>
15
94
 
@@ -23,7 +102,7 @@
23
102
  lib/arborist/node.rb, spec/arborist/dependency_spec.rb,
24
103
  spec/arborist/node_spec.rb:
25
104
  Fix the way nodes' dependency state is restored
26
- [325443abe6df] [github/master]
105
+ [325443abe6df]
27
106
 
28
107
  2016-04-27 Michael Granger <ged@FaerieMUD.org>
29
108
 
data/Events.md CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  ## Event Types
4
4
 
5
- «type».«subtype».«identifier»
5
+ «type».«subtype»
6
6
 
7
- sys.reload
8
- sys.shutdown
7
+ node.acked
9
8
  node.delta
9
+ node.disabled
10
+ node.down
11
+ node.quieted
12
+ node.unknown
13
+ node.up
10
14
  node.update
11
- node.ack
12
-
13
- Second part of the event name is the verb.
14
-
15
- - update
16
- - change (delta)
17
- - reload
18
- - shutdown / startup
15
+ sys.node_added
16
+ sys.node_removed
17
+ sys.reloaded
18
+ sys.hearbeat
19
19
 
20
20
 
@@ -31,9 +31,6 @@ lib/arborist/event/node_quieted.rb
31
31
  lib/arborist/event/node_unknown.rb
32
32
  lib/arborist/event/node_up.rb
33
33
  lib/arborist/event/node_update.rb
34
- lib/arborist/event/sys_node_added.rb
35
- lib/arborist/event/sys_node_removed.rb
36
- lib/arborist/event/sys_reloaded.rb
37
34
  lib/arborist/exceptions.rb
38
35
  lib/arborist/loader.rb
39
36
  lib/arborist/loader/file.rb
@@ -77,6 +74,7 @@ spec/arborist/node/service_spec.rb
77
74
  spec/arborist/node_spec.rb
78
75
  spec/arborist/observer/action_spec.rb
79
76
  spec/arborist/observer/summarize_spec.rb
77
+ spec/arborist/observer_runner_spec.rb
80
78
  spec/arborist/observer_spec.rb
81
79
  spec/arborist/subscription_spec.rb
82
80
  spec/arborist_spec.rb
@@ -84,9 +82,9 @@ spec/data/monitors/pings.rb
84
82
  spec/data/monitors/port_checks.rb
85
83
  spec/data/monitors/system_resources.rb
86
84
  spec/data/monitors/web_services.rb
87
- spec/data/nodes/duir.rb
88
85
  spec/data/nodes/localhost.rb
89
86
  spec/data/nodes/sidonie.rb
87
+ spec/data/nodes/sub/duir.rb
90
88
  spec/data/nodes/yevaud.rb
91
89
  spec/data/observers/auditor.rb
92
90
  spec/data/observers/webservices.rb
data/TODO.md CHANGED
@@ -7,43 +7,23 @@
7
7
  * Performance/profiling examination
8
8
 
9
9
 
10
- ### Manager
11
-
12
- * Only restore timestamps from serialized node dependencies, not the deps themselves.
13
-
14
- * Broadcast system events:
15
- - `sys.node.added`
16
- - `sys.node.removed`
17
- - `sys.startup`
18
- - `sys.shutdown`
19
-
20
-
21
10
  ### Observers
22
11
 
23
- * Re-subscribe on `sys.startup`, `sys.reloaded`, `sys.node.added`
24
- * Add `except` to observers DSL
12
+ * Add `exclude` to observers DSL
13
+ * modify tree api to accept negative criteria to subscribe
14
+ * pass to manager's create_subscription()
15
+ * alter subscription to no-op if event matches negative stuff
25
16
 
26
17
 
27
- ### Nodes
28
-
29
- * Allow a service node to not inherit all of its host's addresses (i.e., be bound to one address only or whatever)
30
- * Resource nodes: disk, load, process checks, etc. Anything that might
31
- be considered a problem, that you'd want to ack independantly of the
32
- Host node they are attached to.
33
-
34
18
  ### Monitor
35
19
 
36
20
  * Add some default monitor types and utilities
37
- - UDP socket check
38
- - Basic monitors for stdlib Net::* protocols/services
39
- -
40
-
41
- * Gems for monitor types that have external dependency
42
- - SNMP
43
-
44
- ### Watch Command
21
+ - ftp
22
+ - imap
23
+ - pop
24
+ - smtp
45
25
 
46
- * Re-subscribe on `sys.startup`, `sys.reloaded`, `sys.node.added`
26
+ * Write a gem for `fping` monitor
47
27
 
48
28
 
49
29
  ## Second Release (0.2)
@@ -17,7 +17,7 @@ module Arborist
17
17
  VERSION = '0.0.1'
18
18
 
19
19
  # Version control revision
20
- REVISION = %q$Revision: ed4f0655b4b6 $
20
+ REVISION = %q$Revision: 4cc9924e6b0c $
21
21
 
22
22
 
23
23
  # The name of the environment variable which can be used to set the config path
@@ -168,7 +168,11 @@ module Arborist
168
168
 
169
169
  ### Fetch the ZMQ context for Arborist.
170
170
  def self::zmq_context
171
- return @zmq_context ||= ZMQ::Context.new
171
+ return @zmq_context ||= begin
172
+ self.log.info "Using ZeroMQ %s/CZMQ %s" %
173
+ [ ZMQ.version.join('.'), ZMQ.czmq_version.join('.') ]
174
+ ZMQ::Context.new
175
+ end
172
176
  end
173
177
 
174
178
 
@@ -10,31 +10,65 @@ require 'arborist/client'
10
10
  module Arborist::CLI::Watch
11
11
  extend Arborist::CLI::Subcommand
12
12
 
13
+ HEARTBEAT_CHARACTERS = %w[💓 💗]
14
+
13
15
  desc 'Watch events in an Arborist manager'
14
16
 
15
17
  command :watch do |cmd|
16
18
  cmd.action do |globals, options, args|
17
19
  client = Arborist::Client.new
18
- subid = client.subscribe( identifier: '_' )
19
-
20
20
  sock = client.event_api
21
- sock.subscribe( subid )
21
+
22
+ subid = subscribe_to_node_events( client )
22
23
  prompt.say "Subscription %p" % [ subid ]
23
24
 
25
+ # Watch for system events as well
26
+ prompt.say "Subscribing to manager heartbeat events."
27
+ sock.subscribe( 'sys.heartbeat' )
28
+
24
29
  begin
30
+ last_runid = nil
25
31
  prompt.say "Watching for events on manager at %s" % [ client.event_api_url ]
26
32
  loop do
27
33
  msgsubid = sock.recv
28
34
  raise "Partial write?!" unless sock.rcvmore?
29
35
  raw_event = sock.recv
30
-
31
36
  event = MessagePack.unpack( raw_event )
32
- prompt.say "[%s] %s\n" % [
33
- hl(Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')).color(:dark, :white),
34
- hl(dump_event( event )).color( :dark, :white )
35
- ]
37
+
38
+ case msgsubid
39
+ when 'sys.heartbeat'
40
+ this_runid = event['run_id']
41
+
42
+ if last_runid && last_runid != this_runid
43
+ self.log.warn "Manager restart: re-subscribing."
44
+ sock.unsubscribe( subid )
45
+ subid = subscribe_to_node_events( client )
46
+ prompt.say "New subscription %p" % [ subid ]
47
+ else
48
+ self.log.debug "Manager is alive (runid: %s)" % [ this_runid ]
49
+ end
50
+
51
+ $stderr.print( heartbeat() )
52
+ last_runid = this_runid
53
+ when 'sys.node_added'
54
+ prompt.say "[%s] «Node added» %s\n" % [
55
+ hl(Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')).color(:dark, :white),
56
+ hl(event['node']).color( :bold, :cyan )
57
+ ]
58
+ when 'sys.node_removed'
59
+ prompt.say "[%s] »Node removed« %s\n" % [
60
+ hl(Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')).color(:dark, :white),
61
+ hl(event['node']).color( :dark, :cyan )
62
+ ]
63
+ else
64
+ prompt.say "[%s] %s\n" % [
65
+ hl(Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')).color(:dark, :white),
66
+ hl(dump_event( event )).color( :dark, :white )
67
+ ]
68
+ end
36
69
  end
37
70
  ensure
71
+ self.log.info "Unsubscribing from subscription %p" % [ subid ]
38
72
  client.unsubscribe( subid )
39
73
  end
40
74
  end
@@ -45,6 +79,16 @@ module Arborist::CLI::Watch
45
79
  module_function
46
80
  ###############
47
81
 
82
+ ### Establish a subscription to all node events via the specified +client+.
83
+ def subscribe_to_node_events( client )
84
+ subid = client.subscribe( identifier: '_' )
85
+ sock = client.event_api
86
+
87
+ sock.subscribe( subid )
88
+ return subid
89
+ end
90
+
91
+
48
92
  ### Return a String representation of the specified +event+.
49
93
  def dump_event( event )
50
94
  event_type = event['type']
@@ -84,5 +128,12 @@ module Arborist::CLI::Watch
84
128
  end.join( hl(", ").color(:dark, :white) )
85
129
  end
86
130
 
131
+
132
+ ### Return a heartbeat string for the current time.
133
+ def heartbeat
134
+ idx = (Time.now.to_i % HEARTBEAT_CHARACTERS.length) - 1
135
+ return " " + HEARTBEAT_CHARACTERS[ idx ] + "\x08\x08"
136
+ end
137
+
87
138
  end # module Arborist::CLI::Watch
88
139
 
@@ -27,7 +27,7 @@ class Arborist::Loader::File < Arborist::Loader
27
27
  def paths
28
28
  path = Pathname( self.directory )
29
29
  if path.directory?
30
- return Pathname.glob( directory + FILE_PATTERN ).each
30
+ return Pathname.glob( path + FILE_PATTERN ).each
31
31
  else
32
32
  return [ path ].each
33
33
  end
@@ -1,6 +1,7 @@
1
1
  # -*- ruby -*-
2
2
  #encoding: utf-8
3
3
 
4
+ require 'securerandom'
4
5
  require 'pathname'
5
6
  require 'tempfile'
6
7
  require 'configurability'
@@ -30,7 +31,9 @@ class Arborist::Manager
30
31
  # Configurability API -- set config defaults
31
32
  CONFIG_DEFAULTS = {
32
33
  state_file: nil,
33
- checkpoint_frequency: 30
34
+ checkpoint_frequency: 30000,
35
+ heartbeat_frequency: 1000,
36
+ linger: 5000
34
37
  }
35
38
 
36
39
 
@@ -46,16 +49,34 @@ class Arborist::Manager
46
49
  singleton_attr_accessor :state_file
47
50
 
48
51
  ##
49
- # The number of seconds between automatic state checkpoints
52
+ # The number of milliseconds between automatic state checkpoints
50
53
  singleton_attr_accessor :checkpoint_frequency
51
54
 
55
+ ##
56
+ # The number of milliseconds between heartbeat events
57
+ singleton_attr_accessor :heartbeat_frequency
58
+
59
+ ##
60
+ # The maximum amount of time to wait for pending events to be delivered during
61
+ # shutdown, in milliseconds.
62
+ singleton_attr_accessor :linger
63
+
52
64
 
53
65
  ### Configurability API -- configure the manager
54
66
  def self::configure( config=nil )
55
67
  config ||= {}
56
68
  config = self.defaults.merge( config[:manager] || {} )
57
69
 
70
+ self.log.debug "Config is: %p" % [ config ]
71
+
58
72
  self.state_file = config[:state_file] && Pathname( config[:state_file] )
73
+ self.linger = config[:linger].to_i
74
+ self.log.info "Linger configured to %p" % [ self.linger ]
75
+
76
+ self.heartbeat_frequency = config[:heartbeat_frequency].to_i ||
77
+ CONFIG_DEFAULTS[:heartbeat_frequency]
78
+ raise Arborist::ConfigError, "heartbeat frequency must be a positive non-zero integer" if
79
+ self.heartbeat_frequency <= 0
59
80
 
60
81
  interval = config[:checkpoint_frequency].to_i
61
82
  if interval && interval.nonzero?
@@ -72,10 +93,10 @@ class Arborist::Manager
72
93
 
73
94
  ### Create a new Arborist::Manager.
74
95
  def initialize
96
+ @run_id = SecureRandom.hex( 16 )
75
97
  @root = Arborist::Node.create( :root )
76
- @nodes = {
77
- '_' => @root,
78
- }
98
+ @nodes = { '_' => @root }
99
+
79
100
  @subscriptions = {}
80
101
  @tree_built = false
81
102
 
@@ -83,12 +104,27 @@ class Arborist::Manager
83
104
  @signal_timer = nil
84
105
  @start_time = nil
85
106
 
86
- Thread.main[:signal_queue] = []
87
- @zmq_loop = nil
88
-
89
- @api_handler = nil
90
- @event_publisher = nil
91
107
  @checkpoint_timer = nil
108
+ @linger = self.class.linger || Arborist::Manager::CONFIG_DEFAULTS[ :linger ]
109
+ self.log.info "Linger set to %p" % [ @linger ]
110
+
111
+ @zmq_loop = ZMQ::Loop.new
112
+ # @zmq_loop.verbose = true
113
+ @tree_sock = self.setup_tree_socket
114
+ @event_sock = self.setup_event_socket
115
+
116
+ @api_handler = Arborist::Manager::TreeAPI.new( @tree_sock, self )
117
+ @tree_sock.handler = @api_handler
118
+ @zmq_loop.register( @tree_sock )
119
+
120
+ @event_publisher = Arborist::Manager::EventPublisher.new( @event_sock, self, @zmq_loop )
121
+ @event_sock.handler = @event_publisher
122
+ @zmq_loop.register( @event_sock )
123
+
124
+ @heartbeat_timer = self.make_heartbeat_timer
125
+ @checkpoint_timer = self.make_checkpoint_timer
126
+
127
+ Thread.main[:signal_queue] = []
92
128
  end
93
129
 
94
130
 
@@ -96,6 +132,10 @@ class Arborist::Manager
96
132
  public
97
133
  ######
98
134
 
135
+ ##
136
+ # A unique string used to identify different runs of the Manager
137
+ attr_reader :run_id
138
+
99
139
  ##
100
140
  # The root node of the tree.
101
141
  attr_accessor :root
@@ -128,6 +168,23 @@ class Arborist::Manager
128
168
  # Flag for marking when the tree is built successfully the first time
129
169
  attr_predicate_accessor :tree_built
130
170
 
171
+ ##
172
+ # The maximum amount of time to wait for pending events to be delivered during
173
+ # shutdown, in milliseconds.
174
+ attr_reader :linger
175
+
176
+ ##
177
+ # The ZMQ::Timer that processes signals
178
+ attr_reader :signal_timer
179
+
180
+ ##
181
+ # The ZMQ::Timer that periodically checkpoints the manager's state (if it's configured to do so)
182
+ attr_reader :checkpoint_timer
183
+
184
+ ##
185
+ # The ZMQ::Timer that periodically publishes a heartbeat event
186
+ attr_reader :heartbeat_timer
187
+
131
188
 
132
189
  #
133
190
  # :section: Startup/Shutdown
@@ -136,20 +193,21 @@ class Arborist::Manager
136
193
  ### Setup sockets and start the event loop.
137
194
  def run
138
195
  self.log.info "Getting ready to start the manager."
139
- self.setup_sockets
196
+ self.publish_system_event( 'startup', start_time: Time.now.to_s, version: Arborist::VERSION )
197
+ self.register_timers
140
198
  self.set_signal_handlers
141
199
  self.start_accepting_requests
142
200
 
143
201
  return self # For chaining
144
202
  ensure
145
203
  self.restore_signal_handlers
146
- if @zmq_loop
204
+ if self.zmq_loop
147
205
  self.log.debug "Unregistering sockets."
148
- @zmq_loop.remove( @tree_sock )
206
+ self.zmq_loop.remove( @tree_sock )
149
207
  @tree_sock.pollable.close
150
- @zmq_loop.remove( @event_sock )
208
+ self.zmq_loop.remove( @event_sock )
151
209
  @event_sock.pollable.close
152
- @zmq_loop.cancel_timer( @checkpoint_timer ) if @checkpoint_timer
210
+ self.zmq_loop.cancel_timer( @checkpoint_timer ) if @checkpoint_timer
153
211
  end
154
212
 
155
213
  self.save_node_states
@@ -161,7 +219,14 @@ class Arborist::Manager
161
219
 
162
220
  ### Returns true if the Manager is running.
163
221
  def running?
164
- return @zmq_loop && @zmq_loop.running?
222
+ return self.zmq_loop && self.zmq_loop.running?
223
+ end
224
+
225
+
226
+ ### Register the Manager's timers.
227
+ def register_timers
228
+ self.zmq_loop.register_timer( self.heartbeat_timer )
229
+ self.zmq_loop.register_timer( self.checkpoint_timer ) if self.checkpoint_timer
165
230
  end
166
231
 
167
232
 
@@ -169,32 +234,11 @@ class Arborist::Manager
169
234
  def start_accepting_requests
170
235
  self.log.debug "Starting the main loop"
171
236
 
172
- @zmq_loop = ZMQ::Loop.new
173
-
174
- @api_handler = Arborist::Manager::TreeAPI.new( @tree_sock, self )
175
- @tree_sock.handler = @api_handler
176
- @zmq_loop.register( @tree_sock )
177
-
178
- @event_publisher = Arborist::Manager::EventPublisher.new( @event_sock, self, @zmq_loop )
179
- @event_sock.handler = @event_publisher
180
- @zmq_loop.register( @event_sock )
181
-
182
- @checkpoint_timer = self.start_state_checkpointing
183
- @zmq_loop.register_timer( @checkpoint_timer ) if @checkpoint_timer
184
-
185
237
  self.setup_signal_timer
186
238
  self.start_time = Time.now
187
239
 
188
240
  self.log.debug "Manager running."
189
- @zmq_loop.start
190
- end
191
-
192
-
193
- ### Create the ZMQ API socket if necessary.
194
- def setup_sockets
195
- self.log.debug "Setting up sockets"
196
- @tree_sock = self.setup_tree_socket
197
- @event_sock = self.setup_event_socket
241
+ return self.zmq_loop.start
198
242
  end
199
243
 
200
244
 
@@ -214,7 +258,7 @@ class Arborist::Manager
214
258
  sock = Arborist.zmq_context.socket( :PUB )
215
259
  self.log.debug " binding the event socket (%#0x) to %p" %
216
260
  [ sock.object_id * 2, Arborist.event_api_url ]
217
- sock.linger = 0
261
+ sock.linger = self.linger
218
262
  sock.bind( Arborist.event_api_url )
219
263
  return ZMQ::Pollitem.new( sock, ZMQ::POLLOUT )
220
264
  end
@@ -231,7 +275,11 @@ class Arborist::Manager
231
275
  self.log.info "Stopping the manager."
232
276
  self.ignore_signals
233
277
  self.cancel_signal_timer
234
- @zmq_loop.stop if @zmq_loop
278
+
279
+ @api_handler.shutdown
280
+ @event_publisher.shutdown
281
+
282
+ self.zmq_loop.stop
235
283
  end
236
284
 
237
285
 
@@ -287,14 +335,26 @@ class Arborist::Manager
287
335
  end
288
336
 
289
337
 
290
- ### Start a timer that will save a snapshot of the node tree's state to the state
338
+ ### Make a ZMQ::Timer that will publish a heartbeat event at a configurable interval.
339
+ def make_heartbeat_timer
340
+ interval = self.class.heartbeat_frequency || CONFIG_DEFAULTS[ :heartbeat_frequency ]
341
+
342
+ self.log.info "Setting up to heartbeat every %dms" % [ interval ]
343
+ heartbeat_timer = ZMQ::Timer.new( (interval/1000.0), 0 ) do
344
+ self.publish_heartbeat_event
345
+ end
346
+ return heartbeat_timer
347
+ end
348
+
349
+
350
+ ### Make a ZMQ::Timer that will save a snapshot of the node tree's state to the state
291
351
  ### file on a configured interval if it's configured.
292
- def start_state_checkpointing
352
+ def make_checkpoint_timer
293
353
  return nil unless self.class.state_file
294
354
  interval = self.class.checkpoint_frequency or return nil
295
355
 
296
- self.log.info "Setting up node state checkpoint every %ds" % [ interval ]
297
- checkpoint_timer = ZMQ::Timer.new( interval, 0 ) do
356
+ self.log.info "Setting up node state checkpoint every %dms" % [ interval ]
357
+ checkpoint_timer = ZMQ::Timer.new( (interval/1000.0), 0 ) do
298
358
  self.save_node_states
299
359
  end
300
360
  return checkpoint_timer
@@ -311,15 +371,15 @@ class Arborist::Manager
311
371
  ### Set up a periodic ZMQ timer to check for queued signals and handle them.
312
372
  def setup_signal_timer
313
373
  @signal_timer = ZMQ::Timer.new( SIGNAL_INTERVAL, 0, self.method(:process_signal_queue) )
314
- @zmq_loop.register_timer( @signal_timer )
374
+ self.zmq_loop.register_timer( @signal_timer )
315
375
  end
316
376
 
317
377
 
318
378
  ### Disable the timer that checks for incoming signals
319
379
  def cancel_signal_timer
320
- if @signal_timer
321
- @signal_timer.cancel
322
- @zmq_loop.cancel_timer( @signal_timer )
380
+ if self.signal_timer
381
+ self.signal_timer.cancel
382
+ self.zmq_loop.cancel_timer( self.signal_timer )
323
383
  end
324
384
  end
325
385
 
@@ -403,6 +463,13 @@ class Arborist::Manager
403
463
  end
404
464
 
405
465
 
466
+ ### Simulate the receipt of the specified +signal+ (probably only useful
467
+ ### in testing).
468
+ def simulate_signal( signal )
469
+ Thread.main[:signal_queue] << signal.to_sym
470
+ end
471
+
472
+
406
473
  #
407
474
  # :section: Tree API
408
475
  #
@@ -461,7 +528,7 @@ class Arborist::Manager
461
528
 
462
529
  if self.tree_built?
463
530
  self.link_node( node )
464
- node.handle_event( Arborist::Event.create(:sys_node_added, node) )
531
+ self.publish_system_event( 'node_added', node: identifier )
465
532
  end
466
533
  end
467
534
 
@@ -484,7 +551,7 @@ class Arborist::Manager
484
551
  raise "Can't remove an operational node" if node.operational?
485
552
 
486
553
  self.log.info "Removing node %p" % [ node ]
487
- node.handle_event( Arborist::Event.create(:sys_node_removed, node) )
554
+ self.publish_system_event( 'node_removed', node: node.identifier )
488
555
  node.children.each do |identifier, child_node|
489
556
  self.remove_node( child_node )
490
557
  end
@@ -555,6 +622,13 @@ class Arborist::Manager
555
622
  # Tree-traversal API
556
623
  #
557
624
 
625
+
626
+ ### Return the current root node.
627
+ def root_node
628
+ return self.nodes[ '_' ]
629
+ end
630
+
631
+
558
632
  ### Yield each node in a depth-first traversal of the manager's tree
559
633
  ### to the specified +block+, or return an Enumerator if no block is given.
560
634
  def all_nodes( &block )
@@ -639,6 +713,21 @@ class Arborist::Manager
639
713
  end
640
714
 
641
715
 
716
+ ### Publish a system event that observers can watch for to detect restarts.
717
+ def publish_heartbeat_event
718
+ self.publish_system_event( 'heartbeat', run_id: self.run_id )
719
+ end
720
+
721
+
722
+ ### Publish an event with the specified +eventname+ and +data+.
723
+ def publish_system_event( eventname, **data )
724
+ eventname = eventname.to_s
725
+ eventname = 'sys.' + eventname unless eventname.start_with?( 'sys.' )
726
+ self.log.debug "Publishing %s event: %p." % [ eventname, data ]
727
+ self.event_publisher.publish( eventname, data )
728
+ end
729
+
730
+
642
731
  ### Create a subscription that publishes to the Manager's event publisher for
643
732
  ### the node with the specified +identifier+ and +event_pattern+, using the
644
733
  ### given +criteria+ when considering an event.