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
@@ -18,13 +18,17 @@ class Arborist::Client
18
18
  log_to :arborist
19
19
 
20
20
 
21
+ ### Create and return a singleton instance with configured
22
+ ### endpoints.
23
+ def self::instance
24
+ return @instance ||= new
25
+ end
26
+
27
+
21
28
  ### Create a new Client with the given API socket URIs.
22
29
  def initialize( tree_api_url: nil, event_api_url: nil )
23
30
  @tree_api_url = tree_api_url || Arborist.tree_api_url
24
31
  @event_api_url = event_api_url || Arborist.event_api_url
25
-
26
- @request_queue = nil
27
- @event_subscriptions = nil
28
32
  end
29
33
 
30
34
 
@@ -40,40 +44,35 @@ class Arborist::Client
40
44
 
41
45
 
42
46
  #
43
- # High-level methods
47
+ # Convenience methods
44
48
  #
45
49
 
46
- ### Mark a node as 'acknowledged' if it's down, or 'disabled' if
47
- ### it's up. (A pre-emptive acknowledgement.) Requires the node
48
- ### +identifier+, an acknowledgement +message+, and +sender+. You
49
- ### can optionally include a +via+ (source), and override the default
50
- ### +time+ of now.
51
- def acknowledge( node, message, sender, via=nil, time=Time.now )
52
- data = {
53
- node => {
54
- ack: {
55
- message: message,
56
- sender: sender,
57
- via: via,
58
- time: time.to_s
59
- }
60
- }
61
- }
62
50
 
63
- return self.update( data )
51
+ ### Return dependencies of the given +identifier+ as an array.
52
+ def dependencies_of( identifier, partition: nil, properties: :all )
53
+ dependencies = self.deps( identifier: identifier )[ 'deps' ]
54
+ dependencies = self.search(
55
+ criteria: { identifier: dependencies },
56
+ options: { properties: properties }
57
+ )
58
+
59
+ if partition
60
+ partition = partition.to_s
61
+ dependencies.keys.each{|id| dependencies[id]['identifier'] = id }
62
+ dependencies = dependencies.values.group_by do |node|
63
+ node[ partition ]
64
+ end
65
+ end
66
+
67
+ return dependencies
64
68
  end
65
- alias_method :ack, :acknowledge
66
69
 
67
70
 
68
- ### Clear an acknowledged/disabled +node+.
69
- def clear_acknowledgement( node )
70
- data = { node => { ack: nil } }
71
- request = self.make_update_request( data )
72
- self.send_tree_api_request( request )
73
- return true
71
+ ### Retreive a single node.
72
+ def fetch_node( identifier )
73
+ request = self.make_fetch_request( from: identifier, depth: 0 )
74
+ return self.send_tree_api_request( request ).first
74
75
  end
75
- alias_method :clear_ack, :clear_acknowledgement
76
-
77
76
 
78
77
 
79
78
  #
@@ -87,44 +86,63 @@ class Arborist::Client
87
86
  end
88
87
 
89
88
 
90
- ### Return the manager's current status as a hash.
89
+ ### Return a `status` request as a ZMQ message (a CZTop::Message).
91
90
  def make_status_request
92
91
  return Arborist::TreeAPI.request( :status )
93
92
  end
94
93
 
95
94
 
96
- ### Return the manager's current node tree.
97
- def list( **args )
98
- request = self.make_list_request( **args )
95
+ ### Fetch the manager's current node tree.
96
+ def fetch( **args )
97
+ request = self.make_fetch_request( **args )
99
98
  return self.send_tree_api_request( request )
100
99
  end
101
100
 
102
101
 
103
- ### Return the manager's current node tree.
104
- def make_list_request( from: nil, depth: nil )
102
+ ### Return a `fetch` request as a ZMQ message (a CZTop::Message) with the given
103
+ ### attributes.
104
+ def make_fetch_request( from: nil, depth: nil, tree: false )
105
105
  header = {}
106
106
  self.log.debug "From is: %p" % [ from ]
107
107
  header[:from] = from if from
108
108
  header[:depth] = depth if depth
109
+ header[:tree] = 'true' if tree
109
110
 
110
- return Arborist::TreeAPI.request( :list, header )
111
+ return Arborist::TreeAPI.request( :fetch, header, nil )
111
112
  end
112
113
 
113
114
 
114
115
  ### Return the manager's current node tree.
115
- def fetch( criteria={}, options={} )
116
- request = self.make_fetch_request( criteria, **options )
116
+ def search( criteria:{}, options:{}, **args )
117
+ criteria = args if criteria.empty?
118
+ request = self.make_search_request( criteria, **options )
117
119
  return self.send_tree_api_request( request )
118
120
  end
119
121
 
120
122
 
121
- ### Return the manager's current node tree.
122
- def make_fetch_request( criteria, include_down: false, properties: :all, exclude: {} )
123
+ ### Return a `search` request as a ZMQ message (a CZTop::Message) with the given
124
+ ### attributes.
125
+ def make_search_request( criteria, exclude_down: false, properties: :all, exclude: {} )
123
126
  header = {}
124
- header[ :include_down ] = true if include_down
127
+ header[ :exclude_down ] = true if exclude_down
125
128
  header[ :return ] = properties if properties != :all
126
129
 
127
- return Arborist::TreeAPI.request( :fetch, header, [ criteria, exclude ] )
130
+ return Arborist::TreeAPI.request( :search, header, [ criteria, exclude ] )
131
+ end
132
+
133
+
134
+ ### Return the identifiers that have a dependency on the node with the
135
+ ### specified +identifier+.
136
+ def deps( identifier: )
137
+ request = self.make_deps_request( identifier )
138
+ return self.send_tree_api_request( request )
139
+ end
140
+
141
+
142
+ ### Return a `deps` request as a ZMQ message (a CZTop::Message) with the given
143
+ ### +identifier+.
144
+ def make_deps_request( identifier )
145
+ return Arborist::TreeAPI.request( :deps, { from: identifier }, nil )
128
146
  end
129
147
 
130
148
 
@@ -136,9 +154,10 @@ class Arborist::Client
136
154
  end
137
155
 
138
156
 
139
- ### Update the identified nodes in the manager with the specified data.
140
- def make_update_request( data )
141
- return Arborist::TreeAPI.request( :update, nil, data )
157
+ ### Return an `update` request as a zmq message (a CZTop::Message) with the given
158
+ ### +data+.
159
+ def make_update_request( data, header={} )
160
+ return Arborist::TreeAPI.request( :update, header, data )
142
161
  end
143
162
 
144
163
 
@@ -150,7 +169,8 @@ class Arborist::Client
150
169
  end
151
170
 
152
171
 
153
- ### Make a subscription request for the specified +criteria+, +identifier+, and +event_type+.
172
+ ### Return a `subscribe` request as a zmq message (a CZTop::Message) with the
173
+ ### specified attributes.
154
174
  def make_subscribe_request( criteria: {}, identifier: nil, event_type: nil, exclude: {} )
155
175
  self.log.debug "Making subscription request for identifier: %p, event_type: %p, criteria: %p" %
156
176
  [ identifier, event_type, criteria ]
@@ -170,11 +190,12 @@ class Arborist::Client
170
190
  end
171
191
 
172
192
 
173
- ### Remove the subscription with the specified +subid+.
193
+ ### Return an `unsubscribe` request as a zmq message (a CZTop::Message) with the
194
+ ### specified +subid+.
174
195
  def make_unsubscribe_request( subid )
175
196
  self.log.debug "Making unsubscribe request for subid: %s" % [ subid ]
176
197
 
177
- return Arborist::TreeAPI.request( :unsubscribe, subscription_id: subid )
198
+ return Arborist::TreeAPI.request( :unsubscribe, {subscription_id: subid}, nil )
178
199
  end
179
200
 
180
201
 
@@ -186,11 +207,12 @@ class Arborist::Client
186
207
  end
187
208
 
188
209
 
189
- ### Remove the node with the specified +identfier+.
190
- def make_prune_request( identifier )
210
+ ### Return a `prune` request as a zmq message (a CZTop::Message) with the
211
+ ### specified +identifier+.
212
+ def make_prune_request( identifier: )
191
213
  self.log.debug "Making prune request for identifier: %s" % [ identifier ]
192
214
 
193
- return Arborist::TreeAPI.request( :prune, identifier: identifier )
215
+ return Arborist::TreeAPI.request( :prune, {identifier: identifier}, nil )
194
216
  end
195
217
 
196
218
 
@@ -202,13 +224,11 @@ class Arborist::Client
202
224
  end
203
225
 
204
226
 
205
- ### Add a node with the specified +identifier+ and +arguments+.
206
- def make_graft_request( identifier, attributes={} )
227
+ ### Return a `graft` request as a zmq message (a CZTop::Message) with the
228
+ ### specified attributes.
229
+ def make_graft_request( identifier:, type:, parent: nil, attributes:{} )
207
230
  self.log.debug "Making graft request for identifer: %s" % [ identifier ]
208
231
 
209
- parent = attributes.delete( :parent )
210
- type = attributes.delete( :type )
211
-
212
232
  header = {
213
233
  identifier: identifier,
214
234
  parent: parent,
@@ -227,14 +247,60 @@ class Arborist::Client
227
247
  end
228
248
 
229
249
 
230
- ### Modify the operations +attributes+ of the node with the specified +identifier+.
231
- def make_modify_request( identifier, attributes={} )
250
+ ### Return a `modify` request as a zmq message (a CZTop::Message) with the
251
+ ### specified attributes.
252
+ def make_modify_request( identifier:, attributes: )
232
253
  self.log.debug "Making modify request for identifer: %s" % [ identifier ]
233
254
 
234
255
  return Arborist::TreeAPI.request( :modify, {identifier: identifier}, attributes )
235
256
  end
236
257
 
237
258
 
259
+ ### Mark a node as 'acknowledged' if it's down, or 'disabled' if
260
+ ### it's up. (A pre-emptive acknowledgement.) Requires the node
261
+ ### +identifier+, an acknowledgement +message+, and +sender+. You
262
+ ### can optionally include a +via+ (source), and override the default
263
+ ### +time+ of now.
264
+ def acknowledge( *args )
265
+ request = self.make_acknowledge_request( *args )
266
+ response = self.send_tree_api_request( request )
267
+ return true
268
+ end
269
+ alias_method :ack, :acknowledge
270
+
271
+
272
+ ### Return an `ack` request as a zmq message (a CZTop::Message) with the specified
273
+ ### attributes.
274
+ def make_acknowledge_request( identifier:, message:, sender:, via: nil, time: Time.now )
275
+ ack = {
276
+ message: message,
277
+ sender: sender,
278
+ via: via,
279
+ time: time.to_s
280
+ }
281
+
282
+ return Arborist::TreeAPI.request( :ack, {identifier: identifier}, ack )
283
+ end
284
+
285
+
286
+ ### Clear the acknowledgement for a node.
287
+ def clear_acknowledgement( *args )
288
+ request = self.make_unack_request( *args )
289
+ response = self.send_tree_api_request( request )
290
+ return true
291
+ end
292
+ alias_method :unack, :clear_acknowledgement
293
+ alias_method :clear_ack, :clear_acknowledgement
294
+ alias_method :unacknowledge, :clear_acknowledgement
295
+
296
+
297
+ ### Return an `unack` request as a zmq message (a CZTop::Message) with the specified
298
+ ### attribute.
299
+ def make_unack_request( identifier: )
300
+ return Arborist::TreeAPI.request( :unack, {identifier: identifier}, nil )
301
+ end
302
+
303
+
238
304
  ### Send the packed +request+ via the Tree API socket, raise an error on
239
305
  ### unsuccessful response, and return the response body.
240
306
  def send_tree_api_request( request )
@@ -0,0 +1,86 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'etc'
5
+
6
+ require 'arborist/cli' unless defined?( Arborist::CLI )
7
+ require 'arborist/client'
8
+
9
+ # Command to ack a node
10
+ module Arborist::CLI::Ack
11
+ extend Arborist::CLI::Subcommand
12
+
13
+ desc 'Ack/disable one or more nodes in the Arborist tree'
14
+
15
+ arg :IDENTIFIER, optional: true, multiple: true
16
+
17
+ command :ack do |cmd|
18
+
19
+ cmd.switch :clear, default: false,
20
+ desc: "Clear the ack instead of setting it.",
21
+ negatable: false
22
+
23
+ cmd.flag [ :u, :user ],
24
+ desc: "The user to mark the nodes with."
25
+ cmd.flag [ :m, :message ],
26
+ desc: "The acknowledgement message."
27
+
28
+ cmd.action do |globals, options, args|
29
+ identifiers = get_identifiers( args )
30
+ help_now!( "No node identifiers supplied." ) if identifiers.empty?
31
+
32
+ client = Arborist::Client.new
33
+ res = {}
34
+
35
+ if options[ :clear ]
36
+ identifiers.each do |id|
37
+ res[ id ] = client.clear_ack( identifier: id )
38
+ end
39
+
40
+ else
41
+ message = options[ :message ] || prompt.ask( "Ack/disable message:" )
42
+ help_now!( "A acknowlegement/disable message is required." ) unless message
43
+
44
+ userid = options[ :user ] || prompt.ask( "Your name?", default: Etc.getpwuid.name )
45
+ help_now!( "Unable to determine ack user." ) unless userid
46
+
47
+ identifiers.each do |id|
48
+ res[ id ] = unless_dryrun( "Acking #{id}...", true ) do
49
+ client.ack(
50
+ identifier: id,
51
+ message: message,
52
+ sender: userid,
53
+ via: "command line"
54
+ )
55
+ end
56
+ end
57
+ end
58
+
59
+ res.each_pair do |identifier, result|
60
+ prompt.say "%s: %s" % [
61
+ hl.bold.bright_blue( identifier ),
62
+ result == true ? "Okay." : hl.red( res.to_s )
63
+ ]
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+ ###############
70
+ module_function
71
+ ###############
72
+
73
+ ### Parse a list of identifiers from the command line or from
74
+ ### a multiline prompt.
75
+ def get_identifiers( args )
76
+ identifiers = args
77
+ if args.empty?
78
+ identifiers = prompt.multiline( "Enter node identifiers, separated with newlines or commas:" )
79
+ identifiers = identifiers.map( &:chomp ).map{|id| id.split(/,\s*/) }.flatten
80
+ end
81
+
82
+ return identifiers.uniq
83
+ end
84
+
85
+ end # module Arborist::CLI::Ack
86
+
@@ -0,0 +1,48 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'arborist/cli' unless defined?( Arborist::CLI )
5
+ require 'arborist/node'
6
+ require 'arborist/manager'
7
+
8
+
9
+ # Command to reset node states while retaining acknowledgement state.
10
+ module Arborist::CLI::Reset
11
+ extend Arborist::CLI::Subcommand
12
+
13
+ desc 'Reset all state except for acknowledged/disabled'
14
+ long_desc <<-EOF
15
+ If you've rearranged the dependencies in the tree, existing states may no
16
+ longer be valid. This command forcefully resets all states to "unknown" so
17
+ they can be re-checked, but retains existing acknowledgements and intentionally
18
+ disabled nodes. (If you don't care about those you should instead simply delete
19
+ the state file.)
20
+
21
+ It's pointless to use this command while the Manager is running.
22
+ EOF
23
+
24
+ arg :SOURCE
25
+
26
+ command :reset do |cmd|
27
+
28
+ cmd.flag :loader, desc: "Specify a loader type to use.",
29
+ default_value: 'file'
30
+
31
+ cmd.action do |globals, options, args|
32
+ source = args.shift
33
+ loader = Arborist::Loader.create( options[:loader], source )
34
+ manager = Arborist.manager_for( loader )
35
+
36
+ manager.nodes.each_pair do |identifier, node|
37
+ next if node.ack || node.operational?
38
+ node.status = 'unknown'
39
+ end
40
+
41
+ unless_dryrun( "Resetting node states." ) do
42
+ manager.save_node_states
43
+ end
44
+ end
45
+ end
46
+
47
+ end # module Arborist::CLI::Reset
48
+
@@ -8,6 +8,12 @@ require 'arborist/cli' unless defined?( Arborist::CLI )
8
8
  module Arborist::CLI::Start
9
9
  extend Arborist::CLI::Subcommand
10
10
 
11
+ VALID_DAEMONS = %w[
12
+ manager
13
+ monitors
14
+ observers
15
+ ]
16
+
11
17
  desc 'Start an Arborist daemon'
12
18
  long_desc <<-EOF
13
19
  Start the Arborist manager, observers, or monitors. The SOURCE is
@@ -30,6 +36,10 @@ module Arborist::CLI::Start
30
36
  appname = args.shift
31
37
  source = args.shift
32
38
 
39
+ unless VALID_DAEMONS.include?( appname )
40
+ raise "Unknown daemon component. Should be one of: %s" % [ VALID_DAEMONS.join(', ') ]
41
+ end
42
+
33
43
  loader = Arborist::Loader.create( options[:loader], source )
34
44
  runner = case appname
35
45
  when 'manager'
@@ -42,7 +52,7 @@ module Arborist::CLI::Start
42
52
  raise "Don't know how to start %p" % [ appname ]
43
53
  end
44
54
 
45
- unless_dryrun( "starting #{appname}" ) do
55
+ unless_dryrun( "starting #{appname} using #{runner.inspect}" ) do
46
56
  start( runner, options[:p] )
47
57
  end
48
58
  end