arborist 0.0.1.pre20160128152542 → 0.0.1.pre20160606141735

Sign up to get free protection for your applications and to get access to all the features.
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
+