arborist 0.2.0.pre20170519125456 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +670 -1
  5. data/History.md +67 -0
  6. data/Manifest.txt +9 -6
  7. data/README.md +1 -3
  8. data/Rakefile +39 -4
  9. data/TODO.md +22 -31
  10. data/lib/arborist.rb +9 -2
  11. data/lib/arborist/cli.rb +67 -85
  12. data/lib/arborist/client.rb +125 -59
  13. data/lib/arborist/command/ack.rb +86 -0
  14. data/lib/arborist/command/reset.rb +48 -0
  15. data/lib/arborist/command/start.rb +11 -1
  16. data/lib/arborist/command/summary.rb +173 -0
  17. data/lib/arborist/command/tree.rb +215 -0
  18. data/lib/arborist/command/watch.rb +22 -22
  19. data/lib/arborist/dependency.rb +24 -4
  20. data/lib/arborist/event.rb +18 -2
  21. data/lib/arborist/event/node.rb +6 -2
  22. data/lib/arborist/event/node_warn.rb +16 -0
  23. data/lib/arborist/manager.rb +179 -48
  24. data/lib/arborist/mixins.rb +11 -0
  25. data/lib/arborist/monitor.rb +29 -17
  26. data/lib/arborist/monitor/connection_batching.rb +293 -0
  27. data/lib/arborist/monitor/socket.rb +101 -167
  28. data/lib/arborist/monitor_runner.rb +101 -24
  29. data/lib/arborist/node.rb +297 -68
  30. data/lib/arborist/node/ack.rb +1 -1
  31. data/lib/arborist/node/host.rb +26 -5
  32. data/lib/arborist/node/resource.rb +14 -5
  33. data/lib/arborist/node/root.rb +12 -3
  34. data/lib/arborist/node/service.rb +29 -26
  35. data/lib/arborist/node_subscription.rb +65 -0
  36. data/lib/arborist/observer.rb +8 -0
  37. data/lib/arborist/observer/action.rb +6 -0
  38. data/lib/arborist/subscription.rb +22 -16
  39. data/lib/arborist/tree_api.rb +7 -2
  40. data/spec/arborist/client_spec.rb +157 -51
  41. data/spec/arborist/dependency_spec.rb +21 -0
  42. data/spec/arborist/event/node_spec.rb +5 -0
  43. data/spec/arborist/event_spec.rb +3 -3
  44. data/spec/arborist/manager_spec.rb +626 -347
  45. data/spec/arborist/mixins_spec.rb +19 -0
  46. data/spec/arborist/monitor/socket_spec.rb +1 -2
  47. data/spec/arborist/monitor_runner_spec.rb +81 -29
  48. data/spec/arborist/monitor_spec.rb +89 -14
  49. data/spec/arborist/node/host_spec.rb +68 -0
  50. data/spec/arborist/node/resource_spec.rb +2 -0
  51. data/spec/arborist/node/root_spec.rb +13 -0
  52. data/spec/arborist/node/service_spec.rb +9 -0
  53. data/spec/arborist/node_spec.rb +673 -111
  54. data/spec/arborist/node_subscription_spec.rb +54 -0
  55. data/spec/arborist/observer/action_spec.rb +6 -0
  56. data/spec/arborist/observer_runner_spec.rb +8 -1
  57. data/spec/arborist/tree_api_spec.rb +111 -8
  58. data/spec/data/monitors/pings.rb +0 -11
  59. data/spec/data/monitors/port_checks.rb +0 -9
  60. data/spec/data/nodes/sidonie.rb +1 -0
  61. data/spec/data/nodes/vhosts.rb +23 -0
  62. data/spec/data/nodes/yevaud.rb +4 -2
  63. data/spec/spec_helper.rb +71 -1
  64. metadata +91 -28
  65. metadata.gz.sig +0 -0
  66. data/Events.md +0 -35
  67. data/Monitors.md +0 -155
  68. data/Nodes.md +0 -70
  69. data/Observers.md +0 -72
  70. data/Protocol.md +0 -276
  71. data/Tutorial.md +0 -8
@@ -51,7 +51,7 @@ class Arborist::Node::Ack
51
51
 
52
52
 
53
53
  ### Return the Ack as a Hash.
54
- def to_h
54
+ def to_h( * )
55
55
  return {
56
56
  message: self.message,
57
57
  sender: self.sender,
@@ -48,6 +48,10 @@ class Arborist::Node::Host < Arborist::Node
48
48
  # The network address(es) of this Host as an Array of IPAddr objects
49
49
  attr_reader :addresses
50
50
 
51
+ ##
52
+ # An optional hostname.
53
+ dsl_accessor :hostname
54
+
51
55
 
52
56
  ### Set one or more node +attributes+. Supported attributes (in addition to
53
57
  ### those supported by Node) are: +addresses+.
@@ -56,7 +60,8 @@ class Arborist::Node::Host < Arborist::Node
56
60
 
57
61
  super
58
62
 
59
- if attributes['addresses']
63
+ self.hostname( attributes['hostname'] ) if attributes[ 'hostname' ]
64
+ if attributes[ 'addresses' ]
60
65
  self.addresses.clear
61
66
  Array( attributes['addresses'] ).each do |addr|
62
67
  self.address( addr )
@@ -68,20 +73,30 @@ class Arborist::Node::Host < Arborist::Node
68
73
  ### Return the host's operational attributes.
69
74
  def operational_values
70
75
  properties = super
71
- return properties.merge( addresses: self.addresses.map(&:to_s) )
76
+ return properties.merge(
77
+ hostname: @hostname,
78
+ addresses: self.addresses.map(&:to_s)
79
+ )
72
80
  end
73
81
 
74
82
 
75
83
  ### Set an IP address of the host.
76
84
  def address( new_address )
77
85
  self.log.debug "Adding address %p to %p" % [ new_address, self ]
86
+
87
+ if new_address =~ /^[[:alnum:]][a-z0-9\-]+/i && ! @hostname
88
+ @hostname = new_address
89
+ end
90
+
78
91
  @addresses += normalize_address( new_address )
92
+ @addresses.uniq!
79
93
  end
80
94
 
81
95
 
82
96
  ### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
83
97
  def match_criteria?( key, val )
84
98
  return case key
99
+ when 'hostname' then @hostname == val
85
100
  when 'address'
86
101
  search_addr = IPAddr.new( val )
87
102
  @addresses.any? {|a| search_addr.include?(a) }
@@ -103,8 +118,11 @@ class Arborist::Node::Host < Arborist::Node
103
118
  #
104
119
 
105
120
  ### Return a Hash of the host node's state.
106
- def to_h
107
- return super.merge( addresses: self.addresses.map(&:to_s) )
121
+ def to_h( * )
122
+ return super.merge(
123
+ hostname: @hostname,
124
+ addresses: self.addresses.map(&:to_s)
125
+ )
108
126
  end
109
127
 
110
128
 
@@ -113,13 +131,16 @@ class Arborist::Node::Host < Arborist::Node
113
131
  def marshal_load( hash )
114
132
  super
115
133
  @addresses = hash[:addresses].map {|addr| IPAddr.new(addr) }
134
+ @hostname = hash[:hostname]
116
135
  end
117
136
 
118
137
 
119
138
  ### Equality operator -- returns +true+ if +other_node+ is equal to the
120
139
  ### receiver. Overridden to also compare addresses.
121
140
  def ==( other_host )
122
- return super && other_host.addresses == self.addresses
141
+ return super &&
142
+ other_host.addresses == self.addresses &&
143
+ other_host.hostname == @hostname
123
144
  end
124
145
 
125
146
  end # class Arborist::Node::Host
@@ -27,7 +27,7 @@ class Arborist::Node::Resource < Arborist::Node
27
27
  ### Set service +attributes+.
28
28
  def modify( attributes )
29
29
  attributes = stringify_keys( attributes )
30
- super
30
+ super
31
31
  self.category( attributes['category'] )
32
32
  end
33
33
 
@@ -36,7 +36,9 @@ class Arborist::Node::Resource < Arborist::Node
36
36
  ### monitor state.
37
37
  def operational_values
38
38
  return super.merge(
39
- addresses: self.addresses.map( &:to_s )
39
+ addresses: self.addresses.map( &:to_s ),
40
+ hostname: self.hostname,
41
+ category: self.category
40
42
  )
41
43
  end
42
44
 
@@ -54,6 +56,12 @@ class Arborist::Node::Resource < Arborist::Node
54
56
  end
55
57
 
56
58
 
59
+ ### Delegate the resource's hostname to it's parent host.
60
+ def hostname
61
+ return @host.hostname
62
+ end
63
+
64
+
57
65
  ### Overridden to disallow modification of a Resource parent, as it needs a
58
66
  ### reference to the Host node for delegation.
59
67
  def parent( new_parent=nil )
@@ -63,9 +71,10 @@ class Arborist::Node::Resource < Arborist::Node
63
71
 
64
72
 
65
73
  ### Serialize the resource node. Return a Hash of the host node's state.
66
- def to_h
74
+ def to_h( * )
67
75
  return super.merge(
68
- addresses: self.addresses.map( &:to_s )
76
+ addresses: self.addresses.map( &:to_s ),
77
+ category: self.category
69
78
  )
70
79
  end
71
80
 
@@ -78,7 +87,7 @@ class Arborist::Node::Resource < Arborist::Node
78
87
  search_addr = IPAddr.new( val )
79
88
  self.addresses.any? {|a| search_addr.include?(a) }
80
89
  when 'category'
81
- self.category == val
90
+ Array( val ).include?( self.category )
82
91
  else
83
92
  super
84
93
  end
@@ -51,9 +51,18 @@ class Arborist::Node::Root < Arborist::Node
51
51
  end
52
52
 
53
53
 
54
- ### Ignore updates to the root node.
55
- def update( properties )
56
- self.log.warn "Update to the root node ignored."
54
+ ### Don't allow properties to be set on the root node.
55
+ def update( properties, monitor_key='_' )
56
+ return super( {} )
57
+ end
58
+
59
+
60
+ ### Callback for when a node goes from disabled to unknown.
61
+ ### Override, so we immediately transition from unknown to up.
62
+ def on_node_enabled( transition )
63
+ super
64
+ events = self.update( {} ) # up!
65
+ self.publish_events( events )
57
66
  end
58
67
 
59
68
 
@@ -54,6 +54,19 @@ class Arborist::Node::Service < Arborist::Node
54
54
  public
55
55
  ######
56
56
 
57
+ ##
58
+ # Get/set the port the service binds to
59
+ dsl_accessor :port
60
+
61
+ ##
62
+ # Get/set the application protocol the service uses
63
+ dsl_accessor :app_protocol
64
+
65
+ ##
66
+ # Get/set the network protocol the service uses
67
+ dsl_accessor :protocol
68
+
69
+
57
70
  ### Set service +attributes+.
58
71
  def modify( attributes )
59
72
  attributes = stringify_keys( attributes )
@@ -66,27 +79,6 @@ class Arborist::Node::Service < Arborist::Node
66
79
  end
67
80
 
68
81
 
69
- ### Get/set the port the service is bound to.
70
- def port( new_port=nil )
71
- return @port unless new_port
72
- @port = new_port
73
- end
74
-
75
-
76
- ### Get/set the (layer 7) protocol used by the service
77
- def app_protocol( new_proto=nil )
78
- return @app_protocol unless new_proto
79
- @app_protocol = new_proto
80
- end
81
-
82
-
83
- ### Get/set the transport layer protocol the service uses
84
- def protocol( new_proto=nil )
85
- return @protocol unless new_proto
86
- @protocol = new_proto
87
- end
88
-
89
-
90
82
  ### Set an IP address of the service. This must be one of the addresses of its
91
83
  ### containing host.
92
84
  def address( new_address )
@@ -109,18 +101,28 @@ class Arborist::Node::Service < Arborist::Node
109
101
  end
110
102
 
111
103
 
104
+ ### Delegate the service's hostname to it's parent host.
105
+ def hostname
106
+ return @host.hostname
107
+ end
108
+
109
+
112
110
  ### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
113
111
  def match_criteria?( key, val )
114
112
  self.log.debug "Matching %p: %p against %p" % [ key, val, self ]
113
+ array_val = Array( val )
115
114
  return case key
116
115
  when 'port'
117
- val = default_port_for( val, @protocol ) unless val.is_a?( Fixnum )
118
- self.port == val.to_i
116
+ vals = array_val.collect do |port|
117
+ port = default_port_for( port, @protocol ) unless port.is_a?( Integer )
118
+ port.to_i
119
+ end
120
+ vals.include?( self.port )
119
121
  when 'address'
120
122
  search_addr = IPAddr.new( val )
121
123
  self.addresses.any? {|a| search_addr.include?(a) }
122
- when 'protocol' then self.protocol == val.downcase
123
- when 'app', 'app_protocol' then self.app_protocol == val
124
+ when 'protocol' then array_val.include?( self.protocol )
125
+ when 'app', 'app_protocol' then array_val.include?( self.app_protocol )
124
126
  else
125
127
  super
126
128
  end
@@ -132,6 +134,7 @@ class Arborist::Node::Service < Arborist::Node
132
134
  def operational_values
133
135
  return super.merge(
134
136
  addresses: self.addresses.map( &:to_s ),
137
+ hostname: self.hostname,
135
138
  port: self.port,
136
139
  protocol: self.protocol,
137
140
  app_protocol: self.app_protocol,
@@ -161,7 +164,7 @@ class Arborist::Node::Service < Arborist::Node
161
164
  #
162
165
 
163
166
  ### Return a Hash of the host node's state.
164
- def to_h
167
+ def to_h( * )
165
168
  return super.merge(
166
169
  addresses: self.addresses.map(&:to_s),
167
170
  protocol: self.protocol,
@@ -0,0 +1,65 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'arborist' unless defined?( Arborist )
5
+ require 'arborist/subscription'
6
+
7
+
8
+ # An inter-node event subscription
9
+ class Arborist::NodeSubscription < Arborist::Subscription
10
+
11
+ ### Create a new subscription object that will send events to the given
12
+ ### +node+.
13
+ def initialize( node )
14
+ @node = node
15
+ super()
16
+ end
17
+
18
+
19
+ ######
20
+ public
21
+ ######
22
+
23
+ ##
24
+ # The target node
25
+ attr_reader :node
26
+
27
+
28
+ ### Return the identifier of the subscribed node.
29
+ def node_identifier
30
+ return self.node.identifier
31
+ end
32
+
33
+
34
+ ### Return an ID derived from the node's identifier.
35
+ def generate_id
36
+ return "%s-subscription" % [ self.node_identifier ]
37
+ end
38
+
39
+
40
+ ### Check the node to make sure it can handle published events.
41
+ def check_callback
42
+ raise NameError, "node doesn't implement handle_event" unless
43
+ self.node.respond_to?( :handle_event )
44
+ end
45
+
46
+
47
+ ### Publish any of the specified +events+ which match the subscription.
48
+ def on_events( *events )
49
+ events.flatten.each do |event|
50
+ self.node.handle_event( event )
51
+ end
52
+ end
53
+
54
+
55
+ ### Return a String representation of the object suitable for debugging.
56
+ def inspect
57
+ return "#<%p:%#x for the %s node>" % [
58
+ self.class,
59
+ self.object_id * 2,
60
+ self.node.identifier,
61
+ ]
62
+ end
63
+
64
+
65
+ end # class Arborist::NodeSubscription
@@ -2,6 +2,7 @@
2
2
  #encoding: utf-8
3
3
 
4
4
  require 'arborist' unless defined?( Arborist )
5
+ require 'arborist/client'
5
6
 
6
7
 
7
8
  # The Arborist entity responsible for observing changes to the tree and
@@ -124,6 +125,13 @@ class Arborist::Observer
124
125
  end
125
126
 
126
127
 
128
+ ### Return a client singleton for optional observer callbacks to the
129
+ ### manager.
130
+ def client
131
+ return Arborist::Client.instance
132
+ end
133
+
134
+
127
135
  #
128
136
  # Observe Methods
129
137
  #
@@ -78,6 +78,12 @@ class Arborist::Observer::Action
78
78
  else
79
79
  self.block.call( event.dup )
80
80
  end
81
+ rescue => err
82
+ self.log.error "Exception while running observer: %s: %s\n%s" % [
83
+ err.class.name,
84
+ err.message,
85
+ err.backtrace.join("\n ")
86
+ ]
81
87
  ensure
82
88
  self.event_history.clear
83
89
  end
@@ -21,13 +21,13 @@ class Arborist::Subscription
21
21
  ### Instantiate a new Subscription object given an +event+ pattern
22
22
  ### and event +criteria+.
23
23
  def initialize( event_type=nil, criteria={}, negative_criteria={}, &callback )
24
- raise LocalJumpError, "requires a callback block" unless callback
25
-
26
24
  @callback = callback
27
25
  @event_type = event_type
28
26
  @criteria = stringify_keys( criteria )
29
27
  @negative_criteria = stringify_keys( negative_criteria )
30
28
 
29
+ self.check_callback
30
+
31
31
  @id = self.generate_id
32
32
  end
33
33
 
@@ -59,9 +59,9 @@ class Arborist::Subscription
59
59
  end
60
60
 
61
61
 
62
- ### Create an identifier for this subscription object.
63
- def generate_id
64
- return SecureRandom.uuid
62
+ ### Check to make sure the subscription will function as it's set up.
63
+ def check_callback
64
+ raise LocalJumpError, "requires a callback block" unless self.callback
65
65
  end
66
66
 
67
67
 
@@ -76,17 +76,6 @@ class Arborist::Subscription
76
76
  end
77
77
 
78
78
 
79
- ### Returns +true+ if the receiver is interested in publishing the specified +event+.
80
- def interested_in?( event )
81
- self.log.debug "Testing %p against type = %p and criteria = %p but not %p" %
82
- [ event, self.event_type, self.criteria, self.negative_criteria ]
83
- rval = event.match( self )
84
- self.log.debug " event %s match." % [ rval ? "did" : "did NOT" ]
85
- return rval
86
- end
87
- alias_method :is_interested_in?, :interested_in?
88
-
89
-
90
79
  ### Return a String representation of the object suitable for debugging.
91
80
  def inspect
92
81
  return "#<%p:%#x [%s] for %s events matching: %p %s-> %p>" % [
@@ -100,4 +89,21 @@ class Arborist::Subscription
100
89
  ]
101
90
  end
102
91
 
92
+
93
+ ### Create an identifier for this subscription object.
94
+ def generate_id
95
+ return SecureRandom.uuid
96
+ end
97
+
98
+
99
+ ### Returns +true+ if the receiver is interested in publishing the specified +event+.
100
+ def interested_in?( event )
101
+ self.log.debug "Testing %p against type = %p and criteria = %p but not %p" %
102
+ [ event, self.event_type, self.criteria, self.negative_criteria ]
103
+ rval = event.match( self )
104
+ self.log.debug " event %s match." % [ rval ? "did" : "did NOT" ]
105
+ return rval
106
+ end
107
+ alias_method :is_interested_in?, :interested_in?
108
+
103
109
  end # class Arborist::Subscription
@@ -23,6 +23,10 @@ module Arborist::TreeAPI
23
23
 
24
24
  ### Return a CZTop::Message with a payload containing the specified +header+ and +body+.
25
25
  def self::encode( header, body=nil )
26
+ raise Arborist::MessageError, "header is not a Map" unless
27
+ header.is_a?( Hash )
28
+
29
+ self.log.debug "Encoding header: %p with body: %p" % [ header, body ]
26
30
  header = stringify_keys( header )
27
31
  header['version'] = PROTOCOL_VERSION
28
32
 
@@ -31,6 +35,7 @@ module Arborist::TreeAPI
31
35
 
32
36
  payload = MessagePack.pack([ header, body ])
33
37
 
38
+ self.log.debug "Making zmq message with payload: %p" % [ payload ]
34
39
  return CZTop::Message.new( payload )
35
40
  end
36
41
 
@@ -65,8 +70,8 @@ module Arborist::TreeAPI
65
70
  ### Return a CZTop::Message containing a TreeAPI request with the specified
66
71
  ### +verb+ and +data+.
67
72
  def self::request( verb, *data )
68
- header = data.shift || {}
69
- body = data.shift
73
+ body = data.pop
74
+ header = data.pop || {}
70
75
 
71
76
  header.merge!( action: verb )
72
77