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
@@ -25,6 +25,7 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
25
25
  @manager = manager
26
26
  @reactor = reactor
27
27
  @registered = true
28
+ @enabled = true
28
29
  @event_queue = []
29
30
  end
30
31
 
@@ -38,9 +39,18 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
38
39
  # to write published events).
39
40
  attr_predicate :registered
40
41
 
42
+ ##
43
+ # The Queue of pending events to publish
44
+ attr_reader :event_queue
45
+
46
+ ##
47
+ # A on/off toggle for publishing events
48
+ attr_predicate :enabled
49
+
41
50
 
42
51
  ### Publish the specified +event+.
43
52
  def publish( identifier, event )
53
+ return self unless self.enabled?
44
54
  @event_queue << [ identifier, MessagePack.pack(event.to_h) ]
45
55
  self.register
46
56
  return self
@@ -62,6 +72,24 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
62
72
  end
63
73
 
64
74
 
75
+ ### Stop accepting events to be published
76
+ def shutdown
77
+ @enabled = false
78
+ return if @event_queue.empty?
79
+
80
+ start = Time.now
81
+ timeout = start + (@manager.linger.to_f / 2000)
82
+
83
+ self.log.warn "Waiting to empty the event queue..."
84
+ until @event_queue.empty?
85
+ sleep 0.1
86
+ break if Time.now > timeout
87
+ end
88
+ self.log.warn " ... waited %0.1f seconds" % [ Time.now - start ]
89
+ end
90
+
91
+
92
+
65
93
  #########
66
94
  protected
67
95
  #########
@@ -70,6 +98,7 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
70
98
  def register
71
99
  count ||= 0
72
100
  @reactor.register( self.pollitem ) unless @registered
101
+ # self.log.info "Publisher socket is now registered!"
73
102
  @registered = true
74
103
  rescue => err
75
104
  # :TODO:
@@ -8,7 +8,8 @@ require 'arborist/manager' unless defined?( Arborist::Manager )
8
8
 
9
9
 
10
10
  class Arborist::Manager::TreeAPI < ZMQ::Handler
11
- extend Loggability
11
+ extend Loggability,
12
+ Arborist::MethodUtilities
12
13
 
13
14
 
14
15
  # Loggability API -- log to the arborist logger
@@ -20,10 +21,16 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
20
21
  def initialize( pollable, manager )
21
22
  self.log.debug "Setting up a %p" % [ self.class ]
22
23
  @pollitem = pollable
24
+ @enabled = true
23
25
  @manager = manager
24
26
  end
25
27
 
26
28
 
29
+ ##
30
+ # True if the Tree API is accepting commands
31
+ attr_predicate :enabled
32
+
33
+
27
34
  ### ZMQ::Handler API -- Read and handle an incoming request.
28
35
  def on_readable
29
36
  request = self.recv
@@ -36,6 +43,8 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
36
43
  def handle_request( raw_request )
37
44
  self.log.debug "Handling request: %p" % [ raw_request ]
38
45
 
46
+ raise "Manager is shutting down" unless self.enabled?
47
+
39
48
  header, body = self.parse_request( raw_request )
40
49
  return self.dispatch_request( header, body )
41
50
 
@@ -282,5 +291,11 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
282
291
  return successful_response( nil )
283
292
  end
284
293
 
294
+
295
+ ### Disable the API, returning errors for any future requests.
296
+ def shutdown
297
+ @enabled = false
298
+ end
299
+
285
300
  end # class Arborist::Manager::TreeAPI
286
301
 
@@ -1,6 +1,8 @@
1
1
  # -*- ruby -*-
2
2
  #encoding: utf-8
3
3
 
4
+ require 'ipaddr'
5
+
4
6
  # A collection of generically-useful mixins
5
7
  module Arborist
6
8
 
@@ -280,7 +282,7 @@ module Arborist
280
282
  end # module TimeRefinements
281
283
 
282
284
 
283
- ### A collection of utilities for working with Hashes.
285
+ # A collection of utilities for working with Hashes.
284
286
  module HashUtilities
285
287
 
286
288
  ###############
@@ -377,4 +379,37 @@ module Arborist
377
379
 
378
380
  end # HashUtilities
379
381
 
382
+
383
+ # A collection of utilities for working with network addresses, names, etc.
384
+ module NetworkUtilities
385
+
386
+ # A union of IPv4 and IPv6 regular expressions.
387
+ IPADDR_RE = Regexp.union(
388
+ IPAddr::RE_IPV4ADDRLIKE,
389
+ IPAddr::RE_IPV6ADDRLIKE_COMPRESSED,
390
+ IPAddr::RE_IPV6ADDRLIKE_FULL
391
+ )
392
+
393
+
394
+ ### Return the specified +address+ as one or more IPAddr objects.
395
+ def normalize_address( address )
396
+ addresses = []
397
+ case address
398
+ when IPAddr
399
+ addresses << address
400
+ when IPADDR_RE
401
+ addresses << IPAddr.new( address )
402
+ when String
403
+ ip_addr = TCPSocket.gethostbyname( address )
404
+ addresses = ip_addr[3..-1].map{|ip| IPAddr.new(ip) }
405
+ else
406
+ raise "I don't know how to parse a %p host address (%p)" %
407
+ [ address.class, address ]
408
+ end
409
+
410
+ return addresses
411
+ end
412
+
413
+ end # module NetworkUtilities
414
+
380
415
  end # module Arborist
@@ -214,6 +214,18 @@ class Arborist::Monitor
214
214
  attr_accessor :source
215
215
 
216
216
 
217
+ ### Return a string representation of the object suitable for debugging.
218
+ def inspect
219
+ return "#<%p:%#x %s (every %ds ±%ds)>" % [
220
+ self.class,
221
+ self.object_id * 2,
222
+ self.description || "(no description)",
223
+ @interval,
224
+ @splay,
225
+ ]
226
+ end
227
+
228
+
217
229
  ### Run the monitor
218
230
  def run( nodes )
219
231
  if self.exec_block
@@ -116,26 +116,23 @@ module Arborist::Monitor::Socket
116
116
  end
117
117
 
118
118
  # Now wait for connections to complete
119
- until connections.empty? || timeout_at.past?
119
+ wait_seconds = timeout_at - Time.now
120
+ until connections.empty? || wait_seconds <= 0
120
121
  self.log.debug "Waiting on %d connections for %0.3ds..." %
121
- [ connections.values.length, timeout_at - Time.now ]
122
+ [ connections.values.length, wait_seconds ]
122
123
 
123
124
  # :FIXME: Race condition: errors if timeout_at - Time.now is 0
124
- _, ready, _ = IO.select( nil, connections.keys, nil, timeout_at - Time.now )
125
+ _, ready, _ = IO.select( nil, connections.keys, nil, wait_seconds )
125
126
 
126
- self.log.debug " select returned: %p" % [ ready ]
127
+ now = Time.now
127
128
  ready.each do |sock|
128
- self.log.debug " %p is ready" % [ sock ]
129
129
  identifier, sockaddr = *connections.delete( sock )
130
- self.log.debug "%p became writable: testing connection state" % [ sock ]
131
130
 
132
131
  begin
133
- self.log.debug " trying another connection to %p" % [ sockaddr ]
134
132
  sock.connect_nonblock( sockaddr )
135
133
  rescue Errno::EISCONN
136
- self.log.debug " connection successful"
137
134
  results[ identifier ] = {
138
- tcp_socket_connect: { time: Time.now.to_s, duration: Time.now - start }
135
+ tcp_socket_connect: { time: now.to_s, duration: now - start }
139
136
  }
140
137
  rescue SocketError, SystemCallError => err
141
138
  self.log.debug "%p during connection: %s" % [ err.class, err.message ]
@@ -143,8 +140,10 @@ module Arborist::Monitor::Socket
143
140
  ensure
144
141
  sock.close
145
142
  end
143
+
146
144
  end if ready
147
145
 
146
+ wait_seconds = timeout_at - Time.now
148
147
  end
149
148
 
150
149
  # Anything left is a timeout
@@ -194,7 +194,7 @@ class Arborist::MonitorRunner
194
194
  ### Create a repeating ZMQ::Timer that will run the specified monitor on its interval.
195
195
  def interval_timer_for( monitor )
196
196
  interval = monitor.interval
197
- self.log.info "Creating timer for %s monitor to run every %ds" % [ monitor, interval ]
197
+ self.log.info "Creating timer for %p" % [ monitor ]
198
198
 
199
199
  return ZMQ::Timer.new( interval, 0 ) do
200
200
  self.handler.run_monitor( monitor )
@@ -206,7 +206,7 @@ class Arborist::MonitorRunner
206
206
  ### +monitor+ after a random number of seconds no greater than its splay.
207
207
  def splay_timer_for( monitor )
208
208
  delay = rand( monitor.splay )
209
- self.log.info "Splaying registration of %s monitor for %ds" % [ monitor, delay ]
209
+ self.log.debug "Splaying registration of %p for %ds" % [ monitor, delay ]
210
210
 
211
211
  return ZMQ::Timer.new( delay, 1 ) do
212
212
  interval_timer = self.interval_timer_for( monitor )
@@ -250,6 +250,7 @@ class Arborist::Node
250
250
  @description = nil
251
251
  @tags = Set.new
252
252
  @properties = {}
253
+ @config = {}
253
254
  @source = nil
254
255
  @children = {}
255
256
  @dependencies = Arborist::Dependency.new( :all )
@@ -421,6 +422,13 @@ class Arborist::Node
421
422
  end
422
423
 
423
424
 
425
+ ### Get or set the node's configuration hash. This can be used to pass per-node
426
+ ### information to systems using the tree (e.g., monitors, subscribers).
427
+ def config( new_config=nil )
428
+ @config = stringify_keys( new_config ) if new_config
429
+ return @config
430
+ end
431
+
424
432
 
425
433
  #
426
434
  # :section: Manager API
@@ -554,6 +562,8 @@ class Arborist::Node
554
562
  when 'tag' then @tags.include?( val.to_s )
555
563
  when 'tags' then Array(val).all? {|tag| @tags.include?(tag) }
556
564
  when 'identifier' then @identifier == val
565
+ when 'config'
566
+ val.all? {|ikey, ival| hash_matches(@config, ikey, ival) }
557
567
  else
558
568
  hash_matches( @properties, key, val )
559
569
  end
@@ -638,7 +648,7 @@ class Arborist::Node
638
648
  self.log.debug "Handling a %s event." % [ event.type ]
639
649
  self.method( handler_name ).call( event )
640
650
  end
641
- super
651
+ super # to state-machine
642
652
  end
643
653
 
644
654
 
@@ -840,6 +850,7 @@ class Arborist::Node
840
850
  parent: self.parent,
841
851
  description: self.description,
842
852
  tags: self.tags,
853
+ config: self.config,
843
854
  status: self.status,
844
855
  properties: self.properties.dup,
845
856
  ack: self.ack ? self.ack.to_h : nil,
@@ -861,16 +872,19 @@ class Arborist::Node
861
872
  ### Marshal API -- set up the object's state using the +hash+ from a
862
873
  ### previously-marshalled node.
863
874
  def marshal_load( hash )
875
+ self.log.debug "Restoring from serialized hash: %p" % [ hash ]
864
876
  @identifier = hash[:identifier]
865
877
  @properties = hash[:properties]
866
878
 
867
879
  @parent = hash[:parent]
868
880
  @description = hash[:description]
869
881
  @tags = Set.new( hash[:tags] )
882
+ @config = hash[:config]
870
883
  @children = {}
871
884
 
872
- @status = 'unknown'
885
+ @status = hash[:status]
873
886
  @status_changed = Time.parse( hash[:status_changed] )
887
+ @ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack]
874
888
 
875
889
  @error = hash[:error]
876
890
  @properties = hash[:properties] || {}
@@ -886,7 +900,6 @@ class Arborist::Node
886
900
  @pending_update_events = []
887
901
  @subscriptions = {}
888
902
 
889
- self.ack = hash[:ack]
890
903
  end
891
904
 
892
905
 
@@ -2,21 +2,36 @@
2
2
  #encoding: utf-8
3
3
 
4
4
  require 'etc'
5
- require 'ipaddr'
6
5
 
7
6
  require 'arborist/node'
7
+ require 'arborist/mixins'
8
8
 
9
9
 
10
10
  # A node type for Arborist trees that represent network-connected hosts.
11
+ #
12
+ # host_node = Arborist::Node.create( :host, 'acme' ) do
13
+ # description "Public-facing webserver"
14
+ # address '93.184.216.34'
15
+ #
16
+ # tags :public, :dmz
17
+ #
18
+ # resource 'disk'
19
+ # resource 'memory' do
20
+ # config hwm: '3.4G'
21
+ # end
22
+ # resource 'loadavg'
23
+ # resource 'processes' do
24
+ # config expect: { nginx: 2 }
25
+ # end
26
+ #
27
+ # service 'ssh'
28
+ # service 'www'
29
+ #
30
+ # end
31
+ #
32
+ #
11
33
  class Arborist::Node::Host < Arborist::Node
12
-
13
- # A union of IPv4 and IPv6 regular expressions.
14
- IPADDR_RE = Regexp.union(
15
- IPAddr::RE_IPV4ADDRLIKE,
16
- IPAddr::RE_IPV6ADDRLIKE_COMPRESSED,
17
- IPAddr::RE_IPV6ADDRLIKE_FULL
18
- )
19
-
34
+ include Arborist::NetworkUtilities
20
35
 
21
36
  ### Create a new Host node.
22
37
  def initialize( identifier, attributes={}, &block )
@@ -60,19 +75,7 @@ class Arborist::Node::Host < Arborist::Node
60
75
  ### Set an IP address of the host.
61
76
  def address( new_address )
62
77
  self.log.debug "Adding address %p to %p" % [ new_address, self ]
63
- case new_address
64
- when IPAddr
65
- @addresses << new_address
66
- when IPADDR_RE
67
- @addresses << IPAddr.new( new_address )
68
- when String
69
- ip_addr = TCPSocket.gethostbyname( new_address )
70
- @addresses << IPAddr.new( ip_addr[3] )
71
- @addresses << IPAddr.new( ip_addr[4] ) if ip_addr[4]
72
- else
73
- raise "I don't know how to parse a %p host address (%p)" %
74
- [ new_address.class, new_address ]
75
- end
78
+ @addresses += normalize_address( new_address )
76
79
  end
77
80
 
78
81
 
@@ -19,20 +19,16 @@ class Arborist::Node::Resource < Arborist::Node
19
19
 
20
20
  @host = host
21
21
 
22
+ attributes[ :category ] ||= identifier
22
23
  super( qualified_identifier, host, attributes, &block )
23
24
  end
24
25
 
25
26
 
26
- ### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
27
- def match_criteria?( key, val )
28
- self.log.debug "Matching %p: %p against %p" % [ key, val, self ]
29
- return case key
30
- when 'address'
31
- search_addr = IPAddr.new( val )
32
- self.addresses.any? {|a| search_addr.include?(a) }
33
- else
27
+ ### Set service +attributes+.
28
+ def modify( attributes )
29
+ attributes = stringify_keys( attributes )
34
30
  super
35
- end
31
+ self.category( attributes['category'] )
36
32
  end
37
33
 
38
34
 
@@ -45,6 +41,13 @@ class Arborist::Node::Resource < Arborist::Node
45
41
  end
46
42
 
47
43
 
44
+ ### Get/set the resource category.
45
+ def category( new_category=nil )
46
+ return @category unless new_category
47
+ @category = new_category
48
+ end
49
+
50
+
48
51
  ### Delegate the resources's address to its host.
49
52
  def addresses
50
53
  return @host.addresses
@@ -59,15 +62,26 @@ class Arborist::Node::Resource < Arborist::Node
59
62
  end
60
63
 
61
64
 
62
- #
63
- # Serialization
64
- #
65
-
66
- ### Return a Hash of the host node's state.
65
+ ### Serialize the resource node. Return a Hash of the host node's state.
67
66
  def to_h
68
67
  return super.merge(
69
68
  addresses: self.addresses.map( &:to_s )
70
69
  )
71
70
  end
72
71
 
72
+
73
+ ### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
74
+ def match_criteria?( key, val )
75
+ self.log.debug "Matching %p: %p against %p" % [ key, val, self ]
76
+ return case key
77
+ when 'address'
78
+ search_addr = IPAddr.new( val )
79
+ self.addresses.any? {|a| search_addr.include?(a) }
80
+ when 'category'
81
+ self.category == val
82
+ else
83
+ super
84
+ end
85
+ end
86
+
73
87
  end # class Arborist::Node::Resource