arborist 0.0.1.pre20160606141735 → 0.0.1.pre20160829140603

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.
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.