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