arborist 0.0.1.pre20160128152542 → 0.0.1.pre20160606141735

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 (66) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/ChangeLog +426 -1
  5. data/Manifest.txt +17 -2
  6. data/Nodes.md +70 -0
  7. data/Protocol.md +68 -9
  8. data/README.md +3 -5
  9. data/Rakefile +4 -1
  10. data/TODO.md +52 -20
  11. data/lib/arborist.rb +19 -6
  12. data/lib/arborist/cli.rb +39 -25
  13. data/lib/arborist/client.rb +97 -4
  14. data/lib/arborist/command/client.rb +2 -1
  15. data/lib/arborist/command/start.rb +51 -5
  16. data/lib/arborist/dependency.rb +286 -0
  17. data/lib/arborist/event.rb +7 -2
  18. data/lib/arborist/event/{node_matching.rb → node.rb} +11 -5
  19. data/lib/arborist/event/node_acked.rb +5 -7
  20. data/lib/arborist/event/node_delta.rb +30 -3
  21. data/lib/arborist/event/node_disabled.rb +16 -0
  22. data/lib/arborist/event/node_down.rb +10 -0
  23. data/lib/arborist/event/node_quieted.rb +11 -0
  24. data/lib/arborist/event/node_unknown.rb +10 -0
  25. data/lib/arborist/event/node_up.rb +10 -0
  26. data/lib/arborist/event/node_update.rb +2 -11
  27. data/lib/arborist/event/sys_node_added.rb +10 -0
  28. data/lib/arborist/event/sys_node_removed.rb +10 -0
  29. data/lib/arborist/exceptions.rb +4 -0
  30. data/lib/arborist/manager.rb +188 -18
  31. data/lib/arborist/manager/event_publisher.rb +1 -1
  32. data/lib/arborist/manager/tree_api.rb +92 -13
  33. data/lib/arborist/mixins.rb +17 -0
  34. data/lib/arborist/monitor.rb +10 -1
  35. data/lib/arborist/monitor/socket.rb +123 -2
  36. data/lib/arborist/monitor_runner.rb +6 -5
  37. data/lib/arborist/node.rb +420 -94
  38. data/lib/arborist/node/ack.rb +72 -0
  39. data/lib/arborist/node/host.rb +43 -8
  40. data/lib/arborist/node/resource.rb +73 -0
  41. data/lib/arborist/node/root.rb +6 -0
  42. data/lib/arborist/node/service.rb +89 -22
  43. data/lib/arborist/observer.rb +1 -1
  44. data/lib/arborist/subscription.rb +11 -6
  45. data/spec/arborist/client_spec.rb +93 -5
  46. data/spec/arborist/dependency_spec.rb +375 -0
  47. data/spec/arborist/event/node_delta_spec.rb +66 -0
  48. data/spec/arborist/event/node_down_spec.rb +84 -0
  49. data/spec/arborist/event/node_spec.rb +59 -0
  50. data/spec/arborist/event/node_update_spec.rb +14 -3
  51. data/spec/arborist/event_spec.rb +3 -3
  52. data/spec/arborist/manager/tree_api_spec.rb +295 -3
  53. data/spec/arborist/manager_spec.rb +240 -57
  54. data/spec/arborist/monitor_spec.rb +26 -3
  55. data/spec/arborist/node/ack_spec.rb +74 -0
  56. data/spec/arborist/node/host_spec.rb +79 -0
  57. data/spec/arborist/node/resource_spec.rb +56 -0
  58. data/spec/arborist/node/service_spec.rb +68 -2
  59. data/spec/arborist/node_spec.rb +288 -11
  60. data/spec/arborist/subscription_spec.rb +23 -14
  61. data/spec/arborist_spec.rb +0 -4
  62. data/spec/data/observers/webservices.rb +10 -2
  63. data/spec/spec_helper.rb +8 -0
  64. metadata +58 -15
  65. metadata.gz.sig +0 -0
  66. data/LICENSE +0 -29
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'timecop'
6
+ require 'arborist/dependency'
7
+
8
+
9
+ describe Arborist::Dependency do
10
+
11
+
12
+ it "can be constructed without identifiers" do
13
+ result = described_class.new( :any )
14
+ expect( result ).to be_empty
15
+ expect( result.all_identifiers ).to be_empty
16
+ end
17
+
18
+
19
+ it "can be constructed with an operator and a list of identifiers" do
20
+ result = described_class.new( :any, 'node1', 'node2' )
21
+ expect( result ).to include( 'node1', 'node2' )
22
+ end
23
+
24
+
25
+ it "can be constructed with an operator and a list of other dependencies" do
26
+ dep1 = described_class.new( :any, 'node1', 'node2' )
27
+ dep2 = described_class.new( :any, 'node2', 'node3' )
28
+
29
+ result = described_class.new( :all, dep1, dep2 )
30
+ expect( result.subdeps ).to include( dep1, dep2 )
31
+ end
32
+
33
+
34
+ it "can be constructed with an operator and a list of both dependencies and identifiers" do
35
+ dep1 = described_class.new( :any, 'node1', 'node2' )
36
+ dep2 = described_class.new( :any, 'node2', 'node3' )
37
+
38
+ result = described_class.new( :all, dep1, dep2, 'node4', 'node5' )
39
+ expect( result.subdeps ).to include( dep1, dep2 )
40
+ expect( result.identifiers ).to include( 'node4', 'node5' )
41
+ end
42
+
43
+
44
+ it "can return a Hash that describes itself" do
45
+ dep1 = described_class.new( :any, 'node1', 'node2' )
46
+ dep2 = described_class.new( :any, 'node2', 'node3' )
47
+ dep3 = described_class.new( :all, dep1, dep2, 'node4', 'node5' )
48
+
49
+ hash = dep3.to_h
50
+
51
+ expect( hash ).to include( :behavior, :identifiers, :subdeps )
52
+
53
+ expect( hash[:behavior] ).to eq( :all )
54
+ expect( hash[:identifiers] ).to be_an( Array ).and( include('node4', 'node5') )
55
+ expect( hash[:subdeps] ).to be_an( Array ).and( all be_a(Hash) )
56
+ expect( hash[:subdeps] ).to all( include(:behavior, :identifiers, :subdeps) )
57
+ end
58
+
59
+
60
+ it "can be constructed from a nested Hash" do
61
+ dep1 = described_class.new( :any, 'node1', 'node2' )
62
+ dep2 = described_class.new( :any, 'node2', 'node3' )
63
+ dep3 = described_class.new( :all, dep1, dep2, 'node4', 'node5' )
64
+
65
+ clone = described_class.from_hash( dep3.to_h )
66
+
67
+ expect( clone ).to eq( dep3 )
68
+ end
69
+
70
+
71
+ it "includes identifiers of all of its subdependencies" do
72
+ dep1 = described_class.new( :any, 'node1', 'node2' )
73
+ dep2 = described_class.new( :any, 'node2', 'node3' )
74
+
75
+ result = described_class.new( :all, dep1, dep2, 'node4', 'node5' )
76
+ expect( result.identifiers ).to include( 'node4', 'node5' )
77
+ expect( result.identifiers ).to_not include( 'node1', 'node2', 'node3' )
78
+
79
+ expect( result.all_identifiers.length ).to eq( 5 )
80
+ expect( result ).to include( 'node1', 'node2', 'node3', 'node4', 'node5' )
81
+ end
82
+
83
+
84
+ it "can return the list of identifiers that have been marked down" do
85
+ dep = described_class.new( :all, 'node1', 'node2', 'node3', 'node4', 'node5' )
86
+ dep.mark_down( 'node1' )
87
+ dep.mark_down( 'node4' )
88
+
89
+ expect( dep.down_identifiers ).to include( 'node1', 'node4' )
90
+ expect( dep.down_identifiers ).to_not include( 'node2', 'node3', 'node5' )
91
+ end
92
+
93
+
94
+ it "has a constructor for generating dependencies with a prefix" do
95
+ result = described_class.on( :all, 'node1', 'node2', prefixes: ['host1', 'host2'] )
96
+ expect( result ).to include( 'host1-node1', 'host1-node2', 'host2-node1', 'host2-node2' )
97
+ end
98
+
99
+
100
+ it "can mark one of its members as being down" do
101
+ dep = described_class.new( :all, 'node1', 'node2' )
102
+ dep.mark_down( 'node1' )
103
+
104
+ expect( dep ).to be_down
105
+ # down reason?
106
+ end
107
+
108
+ it "marks all downed dependencies with the same default timestamp" do
109
+ dep1 = described_class.new( :all, 'node1', 'node2' )
110
+ dep2 = described_class.new( :any, 'node1' )
111
+ dep3 = described_class.new( :all, dep1, dep2 )
112
+ dep3.mark_down( 'node1' )
113
+
114
+ expect( dep1.identifier_states['node1'] ).to eq( dep2.identifier_states['node1'] )
115
+ end
116
+
117
+
118
+ it "marks all downed dependencies with the provided timestamp" do
119
+ time = Time.parse( "2016-01-01 11:00:00" )
120
+ dep1 = described_class.new( :all, 'node1', 'node2' )
121
+ dep2 = described_class.new( :any, 'node1' )
122
+ dep3 = described_class.new( :all, dep1, dep2 )
123
+ dep3.mark_down( 'node1', time )
124
+
125
+ expect( dep1.identifier_states['node1'] ).to eq( time )
126
+ expect( dep2.identifier_states['node1'] ).to eq( time )
127
+ end
128
+
129
+
130
+ it "knows when the earliest dependency was marked down" do
131
+ dep = described_class.new( :all, 'node1', 'node2', 'node3' )
132
+
133
+ Timecop.freeze do
134
+ dep.mark_down( 'node1' )
135
+ earliest = Time.now
136
+
137
+ Timecop.travel( 60 ) do
138
+ dep.mark_down( 'node3' )
139
+
140
+ expect( dep.earliest_down_time ).to eq( earliest )
141
+ end
142
+ end
143
+ end
144
+
145
+
146
+ it "returns nil if asked for the earliest down mark, and no nodes are maked down" do
147
+ dep = described_class.new( :all, 'node1', 'node2', 'node3' )
148
+ expect( dep.earliest_down_time ).to be_nil
149
+ end
150
+
151
+
152
+ it "knows when the latest dependency was marked down" do
153
+ dep = described_class.new( :all, 'node1', 'node2', 'node3' )
154
+
155
+ Timecop.freeze do
156
+ dep.mark_down( 'node1' )
157
+
158
+ Timecop.freeze( 60 ) do
159
+ dep.mark_down( 'node3' )
160
+ latest = Time.now
161
+
162
+ expect( dep.latest_down_time ).to eq( latest )
163
+ end
164
+ end
165
+ end
166
+
167
+
168
+ it "returns nil if asked for the latest down mark, and no nodes are maked down" do
169
+ dep = described_class.new( :all, 'node1', 'node2', 'node3' )
170
+ expect( dep.latest_down_time ).to be_nil
171
+ end
172
+
173
+
174
+ it "propagates a node being marked down to its sub-dependencies" do
175
+ dep1 = described_class.new( :all, 'node1', 'node2' )
176
+ dep2 = described_class.new( :all, 'node2', 'node3' )
177
+ dep3 = described_class.new( :any, 'node2', 'node5' )
178
+
179
+ top_dep = described_class.new( :all, dep1, dep2, 'node4', dep3 )
180
+
181
+ top_dep.mark_down( 'node2' )
182
+
183
+ expect( dep1 ).to be_down
184
+ expect( dep2 ).to be_down
185
+ expect( dep3 ).to be_up
186
+ end
187
+
188
+
189
+ it "can return all of its sub-dependencies that are down" do
190
+ dep1 = described_class.new( :all, 'node1', 'node2' )
191
+ dep2 = described_class.new( :all, 'node2', 'node3' )
192
+ dep3 = described_class.new( :any, 'node2', 'node5' )
193
+
194
+ top_dep = described_class.new( :all, dep1, dep2, 'node4', dep3 )
195
+ top_dep.mark_down( 'node2' )
196
+
197
+ expect( top_dep.down_subdeps ).to include( dep1, dep2 )
198
+ expect( top_dep.down_subdeps ).to_not include( dep3 )
199
+ end
200
+
201
+
202
+ it "can return all of its sub-dependencies that are up" do
203
+ dep1 = described_class.new( :all, 'node1', 'node2' )
204
+ dep2 = described_class.new( :all, 'node2', 'node3' )
205
+ dep3 = described_class.new( :any, 'node2', 'node5' )
206
+
207
+ top_dep = described_class.new( :all, dep1, dep2, 'node4', dep3 )
208
+ top_dep.mark_down( 'node2' )
209
+
210
+ expect( top_dep.up_subdeps ).to include( dep3 )
211
+ expect( top_dep.up_subdeps ).to_not include( dep1, dep2 )
212
+ end
213
+
214
+
215
+ it "can iterate over its downed elements" do
216
+ dep1 = described_class.new( :all, 'node1', 'node2' )
217
+ dep2 = described_class.new( :all, 'node2', 'node3' )
218
+ dep3 = described_class.new( :any, 'node2', 'node5' )
219
+
220
+ top_dep = described_class.new( :all, 'node2', dep1, dep2, 'node4', dep3 )
221
+ top_dep.mark_down( 'node2' )
222
+ top_dep.mark_down( 'node5' )
223
+
224
+ results = top_dep.each_downed.to_a
225
+ expect( results.length ).to eq( 2 )
226
+ expect( results[0] ).to include( 'node2', an_instance_of(Time) )
227
+ expect( results[1] ).to include( 'node5', an_instance_of(Time) )
228
+ end
229
+
230
+
231
+ it "is equal to another node with the same identifiers" do
232
+ dep1 = described_class.new( :all, 'node1', 'node2', 'node3' )
233
+ dep2 = described_class.new( :all, 'node1', 'node2', 'node3' )
234
+ dep3 = described_class.new( :all, 'node1', 'node2', 'node4' )
235
+
236
+ expect( dep1 ).to eq( dep2 )
237
+ expect( dep1 ).to_not eq( dep3 )
238
+ end
239
+
240
+
241
+ it "is equal to another node with the same identifiers and subdeps" do
242
+ dep1 = described_class.new( :all, 'node1', 'node2', 'node3' )
243
+ dep2 = described_class.new( :all, 'node4', 'node5', 'node6' )
244
+ dep3 = described_class.new( :any, 'node4', 'node5', 'node6' )
245
+
246
+ dep4 = described_class.new( :all, 'node1', dep1, dep2 )
247
+ dep5 = described_class.new( :all, 'node1', dep1.dup, dep2.dup )
248
+ dep6 = described_class.new( :all, 'node1', dep1, dep3 )
249
+ dep7 = described_class.new( :all, 'node1', dep1, dep2, dep3 )
250
+
251
+ expect( dep4 ).to eq( dep5 )
252
+ expect( dep4 ).to_not eq( dep6 )
253
+ expect( dep4 ).to_not eq( dep7 )
254
+ end
255
+
256
+
257
+ it "is eql? to another node with the same identifiers, subdeps, and state" do
258
+ # The time values have to be the same too
259
+ Timecop.freeze do
260
+ dep1 = described_class.new( :all, 'node1', 'node2', 'node3' )
261
+ dep2 = described_class.new( :all, 'node3', 'node4', 'node5' )
262
+
263
+ dep4 = described_class.new( :all, 'node1', dep1, dep2 )
264
+ dep4.mark_down( 'node2' )
265
+ dep4.mark_down( 'node3' )
266
+
267
+ dep5 = described_class.new( :all, 'node1', dep1.dup, dep2.dup )
268
+ dep5.mark_down( 'node2' )
269
+ dep5.mark_down( 'node3' )
270
+
271
+ dep6 = described_class.new( :all, 'node1', dep1.dup, dep2.dup )
272
+ dep6.mark_down( 'node2' )
273
+
274
+ dep7 = described_class.new( :all, 'node1', dep1.dup, dep2.dup )
275
+ dep7.mark_down( 'node2' )
276
+ dep7.mark_down( 'node3' )
277
+ dep7.mark_down( 'node4' )
278
+
279
+ expect( dep4 ).to eql( dep5 )
280
+ expect( dep4 ).to_not eql( dep6 )
281
+ expect( dep4 ).to_not eql( dep7 )
282
+ end
283
+ end
284
+
285
+
286
+ it "knows how to Marshal itself" do
287
+ dep1 = described_class.new( :all, 'node1', 'node2' )
288
+ dep2 = described_class.new( :all, 'node2', 'node3' )
289
+ dep3 = described_class.new( :any, 'node2', 'node5' )
290
+
291
+ top_dep = described_class.new( :all, dep1, dep2, 'node4', dep3 )
292
+
293
+ expect( Marshal.load(Marshal.dump( top_dep )) ).to eq( top_dep )
294
+ end
295
+
296
+
297
+ # Mahlon's brain has a hard time with this. Note for future Mahlon!
298
+ # "This node depends on ALL of these other nodes to be up, for it to be up"
299
+ # "This node depends on ANY of these other nodes to be up, for it to be up"
300
+
301
+ # Another way to remember this:
302
+ # ANY -> any of these is sufficient
303
+ # ALL -> all of these are necessary
304
+
305
+ describe "with 'all' behavior" do
306
+
307
+ let( :dep ) { described_class.new(:all, 'node1', 'node2', 'node3') }
308
+
309
+
310
+ it "is up if none of its members have been marked down" do
311
+ expect( dep ).to be_up
312
+ end
313
+
314
+
315
+ it "is down if any of its members has been marked down" do
316
+ expect {
317
+ dep.mark_down( 'node2' )
318
+ }.to change { dep.down? }.from( false ).to( true )
319
+ end
320
+
321
+
322
+ it "can describe the reason it's down" do
323
+ dep.mark_down( 'node2' )
324
+ expect( dep.down_reason ).to match( /node2 is down as of/i )
325
+ end
326
+
327
+
328
+ it "can describe the reason if multiple nodes have been marked down" do
329
+ dep.mark_down( 'node1' )
330
+ dep.mark_down( 'node2' )
331
+ # :FIXME: Does order matter in the 'all' case? This assumes no.
332
+ expect( dep.down_reason ).to match( /node(1|2) \(and 1 other\) are down as of/i )
333
+ end
334
+
335
+ end
336
+
337
+
338
+ describe "with 'any' behavior" do
339
+
340
+ let( :dep ) { described_class.new(:any, 'node1', 'node2') }
341
+
342
+
343
+ it "is up if none of its members have been marked down" do
344
+ expect( dep ).to be_up
345
+ end
346
+
347
+
348
+ it "is up if only some of its members have been marked down" do
349
+ expect {
350
+ dep.mark_down( 'node2' )
351
+ }.to_not change { dep.down? }
352
+ end
353
+
354
+
355
+ it "is down if all of its members have been marked down" do
356
+ expect {
357
+ dep.mark_down( 'node2' )
358
+ dep.mark_down( 'node1' )
359
+ }.to change { dep.down? }.from( false ).to( true )
360
+ end
361
+
362
+
363
+ it "can describe the reason it's down" do
364
+ dep.mark_down( 'node2' )
365
+ dep.mark_down( 'node1' )
366
+
367
+ expect( dep.down_reason ).to match( /are all down as of/i ).and( include('node1', 'node2') )
368
+ end
369
+
370
+ end
371
+
372
+
373
+
374
+ end
375
+
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../../spec_helper'
4
+
5
+ require 'arborist/event/node_delta'
6
+
7
+
8
+ describe Arborist::Event::NodeDelta do
9
+
10
+ class TestNode < Arborist::Node; end
11
+
12
+
13
+ let( :node ) do
14
+ TestNode.new( 'foo' ) do
15
+ parent 'bar'
16
+ description "A testing node"
17
+ tags :tree, :try, :triage, :trip
18
+
19
+ update(
20
+ "tcp_socket_connect" => {
21
+ "time" => "2016-02-25 16:04:35 -0800",
22
+ "duration" => 0.020619
23
+ }
24
+ )
25
+ end
26
+ end
27
+
28
+
29
+ describe "subscription support" do
30
+
31
+ it "matches a subscription with only an event type if the type is the same" do
32
+ sub = Arborist::Subscription.new( 'node.delta' ) {}
33
+ event = described_class.new( node, status: ['up', 'down'] )
34
+
35
+ expect( event ).to match( sub )
36
+ end
37
+
38
+
39
+ it "matches a subscription with a matching event type and matching criteria" do
40
+ sub = Arborist::Subscription.new( 'node.delta', 'tag' => 'triage' ) {}
41
+ event = described_class.new( node, status: ['up', 'down'] )
42
+
43
+ expect( event ).to match( sub )
44
+ end
45
+
46
+
47
+ it "matches a subscription with matching event type, node criteria, and delta criteria" do
48
+ criteria = {
49
+ 'tag' => 'tree',
50
+ 'delta' => {
51
+ 'status' => [ 'up', 'down' ]
52
+ }
53
+ }
54
+
55
+ sub = Arborist::Subscription.new( 'node.delta', criteria ) {}
56
+ event = described_class.new( node, 'status' => ['up', 'down'] )
57
+
58
+ expect( event ).to match( sub )
59
+ end
60
+
61
+
62
+ end
63
+
64
+
65
+ end
66
+
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../../spec_helper'
4
+
5
+ require 'arborist/event/node_down'
6
+
7
+
8
+ describe Arborist::Event::NodeDown do
9
+
10
+ class TestNode < Arborist::Node; end
11
+
12
+
13
+ let( :node ) do
14
+ TestNode.new( 'foo' ) do
15
+ parent 'bar'
16
+ description "The prototypical node"
17
+ tags :chunker, :hunky, :flippin, :hippo
18
+
19
+ update(
20
+ 'error' => 'Something bad happened!',
21
+ 'song' => 'Around the World',
22
+ 'artist' => 'Daft Punk',
23
+ 'length' => '7:09',
24
+ 'cider' => {
25
+ 'description' => 'tasty',
26
+ 'size' => '16oz',
27
+ },
28
+ 'sausage' => {
29
+ 'description' => 'pork',
30
+ 'size' => 'monsterous',
31
+ 'price' => {
32
+ 'units' => 1200,
33
+ 'currency' => 'usd'
34
+ }
35
+ },
36
+ 'music' => '80s'
37
+ )
38
+ end
39
+ end
40
+
41
+
42
+ describe "subscription support" do
43
+
44
+ it "matches a subscription with only an event type if the type is the same" do
45
+ sub = Arborist::Subscription.new( 'node.down' ) {}
46
+ event = described_class.new( node )
47
+
48
+ expect( event ).to match( sub )
49
+ end
50
+
51
+
52
+ it "matches a subscription with a matching event type and matching criteria" do
53
+ sub = Arborist::Subscription.new( 'node.down', 'tag' => 'chunker' ) {}
54
+ event = described_class.new( node )
55
+
56
+ expect( event ).to match( sub )
57
+ end
58
+
59
+
60
+ it "doesn't match a subscription with a matching event type if the criteria don't match" do
61
+ sub = Arborist::Subscription.new( 'node.down', 'tag' => 'looper' ) {}
62
+ event = described_class.new( node )
63
+
64
+ expect( event ).to_not match( sub )
65
+ end
66
+
67
+
68
+ end
69
+
70
+
71
+ describe "payload" do
72
+
73
+ it "includes its attributes" do
74
+ event = described_class.new( node )
75
+
76
+ expect( event.payload ).to be_a( Hash )
77
+ expect( event.payload ).to include( :status, :error, :properties, :type )
78
+ end
79
+
80
+ end
81
+
82
+
83
+ end
84
+