arborist 0.1.0 → 0.2.0.pre20170519125456
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.
- checksums.yaml +5 -5
- data/ChangeLog +46 -2
- data/Manifest.txt +4 -4
- data/Rakefile +6 -4
- data/TODO.md +16 -0
- data/lib/arborist.rb +12 -51
- data/lib/arborist/client.rb +23 -46
- data/lib/arborist/command/client.rb +1 -0
- data/lib/arborist/command/watch.rb +4 -5
- data/lib/arborist/event_api.rb +35 -0
- data/lib/arborist/exceptions.rb +2 -2
- data/lib/arborist/manager.rb +432 -212
- data/lib/arborist/mixins.rb +9 -9
- data/lib/arborist/monitor_runner.rb +174 -137
- data/lib/arborist/node.rb +11 -4
- data/lib/arborist/observer/summarize.rb +1 -1
- data/lib/arborist/observer_runner.rb +163 -126
- data/lib/arborist/tree_api.rb +113 -0
- data/spec/arborist/client_spec.rb +63 -64
- data/spec/arborist/event_api_spec.rb +23 -0
- data/spec/arborist/manager_spec.rb +842 -66
- data/spec/arborist/monitor_runner_spec.rb +45 -85
- data/spec/arborist/observer_runner_spec.rb +86 -23
- data/spec/arborist/tree_api_spec.rb +30 -0
- data/spec/arborist_spec.rb +0 -5
- data/spec/spec_helper.rb +0 -13
- metadata +47 -45
- checksums.yaml.gz.sig +0 -3
- data.tar.gz.sig +0 -0
- data/lib/arborist/manager/event_publisher.rb +0 -126
- data/lib/arborist/manager/tree_api.rb +0 -302
- data/spec/arborist/manager/event_publisher_spec.rb +0 -65
- data/spec/arborist/manager/tree_api_spec.rb +0 -791
- metadata.gz.sig +0 -0
@@ -14,6 +14,7 @@ describe Arborist::Client do
|
|
14
14
|
before( :each ) do
|
15
15
|
@manager_thread = Thread.new do
|
16
16
|
@manager = make_testing_manager()
|
17
|
+
Loggability[ Arborist ].info "Starting a testing manager: %p" % [ @manager ]
|
17
18
|
Thread.current.abort_on_exception = true
|
18
19
|
@manager.run
|
19
20
|
Loggability[ Arborist ].info "Stopped the test manager"
|
@@ -24,27 +25,29 @@ describe Arborist::Client do
|
|
24
25
|
sleep 0.1
|
25
26
|
count += 1
|
26
27
|
end
|
27
|
-
raise "Manager didn't start up" unless @manager.running?
|
28
|
+
raise "Manager didn't start up" unless @manager && @manager.running?
|
28
29
|
end
|
29
30
|
|
30
31
|
after( :each ) do
|
31
|
-
@manager
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
if @manager
|
33
|
+
@manager.simulate_signal( :TERM )
|
34
|
+
@manager_thread.join
|
35
|
+
|
36
|
+
count = 0
|
37
|
+
while @manager.running? || count > 30
|
38
|
+
sleep 0.1
|
39
|
+
Loggability[ Arborist ].info "Manager still running"
|
40
|
+
count += 1
|
41
|
+
end
|
42
|
+
raise "Manager didn't stop" if @manager.running?
|
39
43
|
end
|
40
|
-
raise "ZMQ Loop didn't stop" if @manager.zmq_loop.running?
|
41
44
|
end
|
42
45
|
|
43
46
|
|
44
47
|
let( :manager ) { @manager }
|
45
48
|
|
46
49
|
|
47
|
-
describe "high-level
|
50
|
+
describe "high-level methods" do
|
48
51
|
|
49
52
|
it "provides a convenience method for acknowledging" do
|
50
53
|
manager.nodes['sidonie'].update( error: "Clown apocalypse" )
|
@@ -260,7 +263,8 @@ describe Arborist::Client do
|
|
260
263
|
it "can prune nodes from the tree" do
|
261
264
|
res = client.prune( 'sidonie-ssh' )
|
262
265
|
|
263
|
-
expect( res ).to
|
266
|
+
expect( res ).to be_a( Hash )
|
267
|
+
expect( res ).to include( 'identifier' => 'sidonie-ssh' )
|
264
268
|
expect( manager.nodes ).to_not include( 'sidonie-ssh' )
|
265
269
|
end
|
266
270
|
|
@@ -273,7 +277,7 @@ describe Arborist::Client do
|
|
273
277
|
|
274
278
|
it "can graft new nodes onto the tree" do
|
275
279
|
res = client.graft( 'breakfast-burrito', type: 'host' )
|
276
|
-
expect( res ).to eq( 'breakfast-burrito' )
|
280
|
+
expect( res ).to eq({ 'identifier' => 'breakfast-burrito' })
|
277
281
|
expect( manager.nodes ).to include( 'breakfast-burrito' )
|
278
282
|
expect( manager.nodes['breakfast-burrito'] ).to be_a( Arborist::Node::Host )
|
279
283
|
expect( manager.nodes['breakfast-burrito'].parent ).to eq( '_' )
|
@@ -287,7 +291,7 @@ describe Arborist::Client do
|
|
287
291
|
port: 9999,
|
288
292
|
tags: ['yusss']
|
289
293
|
)
|
290
|
-
expect( res ).to eq( 'duir-breakfast-burrito' )
|
294
|
+
expect( res ).to eq({ 'identifier' => 'duir-breakfast-burrito' })
|
291
295
|
expect( manager.nodes ).to include( 'duir-breakfast-burrito' )
|
292
296
|
expect( manager.nodes['duir-breakfast-burrito'] ).to be_a( Arborist::Node::Service )
|
293
297
|
expect( manager.nodes['duir-breakfast-burrito'].parent ).to eq( 'duir' )
|
@@ -311,62 +315,58 @@ describe Arborist::Client do
|
|
311
315
|
|
312
316
|
it "can make a raw status request" do
|
313
317
|
req = client.make_status_request
|
314
|
-
expect( req ).to be_a(
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
expect(
|
319
|
-
expect(
|
320
|
-
expect(
|
321
|
-
expect(
|
322
|
-
expect( msg.first['action'] ).to eq( 'status' )
|
318
|
+
expect( req ).to be_a( CZTop::Message )
|
319
|
+
|
320
|
+
header, body = Arborist::TreeAPI.decode( req )
|
321
|
+
|
322
|
+
expect( header ).to be_a( Hash )
|
323
|
+
expect( header ).to include( 'version', 'action' )
|
324
|
+
expect( header['version'] ).to eq( Arborist::Client::API_VERSION )
|
325
|
+
expect( header['action'] ).to eq( 'status' )
|
323
326
|
end
|
324
327
|
|
325
328
|
|
326
329
|
it "can make a raw list request" do
|
327
330
|
req = client.make_list_request
|
328
|
-
expect( req ).to be_a(
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
expect(
|
333
|
-
expect(
|
334
|
-
expect(
|
335
|
-
expect(
|
336
|
-
expect(
|
337
|
-
expect( msg.first['action'] ).to eq( 'list' )
|
331
|
+
expect( req ).to be_a( CZTop::Message )
|
332
|
+
|
333
|
+
header, body = Arborist::TreeAPI.decode( req )
|
334
|
+
|
335
|
+
expect( header ).to be_a( Hash )
|
336
|
+
expect( header ).to include( 'version', 'action' )
|
337
|
+
expect( header ).to_not include( 'from' )
|
338
|
+
expect( header['version'] ).to eq( Arborist::Client::API_VERSION )
|
339
|
+
expect( header['action'] ).to eq( 'list' )
|
338
340
|
end
|
339
341
|
|
340
342
|
|
341
343
|
it "can make a raw fetch request" do
|
342
344
|
req = client.make_fetch_request( {} )
|
343
|
-
expect( req ).to be_a(
|
344
|
-
expect( req.encoding ).to eq( Encoding::ASCII_8BIT )
|
345
|
+
expect( req ).to be_a( CZTop::Message )
|
345
346
|
|
346
|
-
|
347
|
-
expect( msg ).to be_an( Array )
|
348
|
-
expect( msg.first ).to be_a( Hash )
|
349
|
-
expect( msg.first ).to include( 'version', 'action' )
|
350
|
-
expect( msg.first['version'] ).to eq( Arborist::Client::API_VERSION )
|
351
|
-
expect( msg.first['action'] ).to eq( 'fetch' )
|
347
|
+
header, body = Arborist::TreeAPI.decode( req )
|
352
348
|
|
353
|
-
expect(
|
349
|
+
expect( header ).to be_a( Hash )
|
350
|
+
expect( header ).to include( 'version', 'action' )
|
351
|
+
expect( header['version'] ).to eq( Arborist::Client::API_VERSION )
|
352
|
+
expect( header['action'] ).to eq( 'fetch' )
|
353
|
+
|
354
|
+
expect( body ).to eq([ {}, {} ])
|
354
355
|
end
|
355
356
|
|
356
357
|
|
357
358
|
it "can make a raw fetch request with criteria" do
|
358
359
|
req = client.make_fetch_request( {type: 'host'} )
|
359
|
-
expect( req ).to be_a(
|
360
|
-
|
360
|
+
expect( req ).to be_a( CZTop::Message )
|
361
|
+
|
362
|
+
header, body = Arborist::TreeAPI.decode( req )
|
361
363
|
|
362
|
-
|
363
|
-
expect(
|
364
|
-
expect(
|
365
|
-
expect(
|
366
|
-
expect( msg.first['version'] ).to eq( Arborist::Client::API_VERSION )
|
367
|
-
expect( msg.first['action'] ).to eq( 'fetch' )
|
364
|
+
expect( header ).to be_a( Hash )
|
365
|
+
expect( header ).to include( 'version', 'action' )
|
366
|
+
expect( header['version'] ).to eq( Arborist::Client::API_VERSION )
|
367
|
+
expect( header['action'] ).to eq( 'fetch' )
|
368
368
|
|
369
|
-
body =
|
369
|
+
body = body
|
370
370
|
expect( body.first ).to be_a( Hash )
|
371
371
|
expect( body.first ).to include( 'type' )
|
372
372
|
expect( body.first['type'] ).to eq( 'host' )
|
@@ -375,19 +375,18 @@ describe Arborist::Client do
|
|
375
375
|
|
376
376
|
it "can make a raw update request" do
|
377
377
|
req = client.make_update_request( duir: {error: "Something happened."} )
|
378
|
-
expect( req ).to be_a(
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
expect(
|
383
|
-
expect(
|
384
|
-
expect(
|
385
|
-
expect(
|
386
|
-
|
387
|
-
|
388
|
-
expect(
|
389
|
-
expect(
|
390
|
-
expect( msg.last['duir'] ).to eq( 'error' => 'Something happened.' )
|
378
|
+
expect( req ).to be_a( CZTop::Message )
|
379
|
+
|
380
|
+
header, body = Arborist::TreeAPI.decode( req )
|
381
|
+
|
382
|
+
expect( header ).to be_a( Hash )
|
383
|
+
expect( header ).to include( 'version', 'action' )
|
384
|
+
expect( header['version'] ).to eq( Arborist::Client::API_VERSION )
|
385
|
+
expect( header['action'] ).to eq( 'update' )
|
386
|
+
|
387
|
+
expect( body ).to be_a( Hash )
|
388
|
+
expect( body ).to include( 'duir' )
|
389
|
+
expect( body['duir'] ).to eq( 'error' => 'Something happened.' )
|
391
390
|
end
|
392
391
|
|
393
392
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/event_api'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::EventAPI do
|
9
|
+
|
10
|
+
let( :uuid ) { '9E630B46-B0D2-4658-AFE6-ED4A1E838C69' }
|
11
|
+
|
12
|
+
it "encodes events published by the Manager" do
|
13
|
+
encoded = described_class.encode( uuid, {a: 1, b: 2} )
|
14
|
+
expect( encoded ).to be_a( CZTop::Message )
|
15
|
+
identifier, payload = described_class.decode( encoded )
|
16
|
+
expect( identifier ).to eq( uuid )
|
17
|
+
expect( payload ).to eq({ 'a' => 1, 'b' => 2 })
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative '../spec_helper'
|
4
4
|
|
5
|
+
require 'tmpdir'
|
5
6
|
require 'timecop'
|
6
7
|
require 'arborist/manager'
|
7
8
|
require 'arborist/node/host'
|
@@ -125,8 +126,8 @@ describe Arborist::Manager do
|
|
125
126
|
it "restores the state of loaded nodes if the state file is configured" do
|
126
127
|
_ = manager
|
127
128
|
|
128
|
-
|
129
|
-
Arborist::Manager.state_file
|
129
|
+
Arborist::Manager.state_file = './arborist.tree'
|
130
|
+
statefile = Arborist::Manager.state_file
|
130
131
|
state_file_io = instance_double( File )
|
131
132
|
|
132
133
|
saved_router_node = Marshal.load( Marshal.dump(router_node) )
|
@@ -153,27 +154,30 @@ describe Arborist::Manager do
|
|
153
154
|
it "doesn't error if the configured state file isn't readable" do
|
154
155
|
_ = manager
|
155
156
|
|
156
|
-
|
157
|
-
Arborist::Manager.state_file = statefile
|
157
|
+
Arborist::Manager.state_file = './arborist.tree'
|
158
158
|
|
159
|
-
expect(
|
160
|
-
expect(
|
159
|
+
expect( Arborist::Manager.state_file ).to receive( :readable? ).and_return( false )
|
160
|
+
expect( Arborist::Manager.state_file ).to_not receive( :open )
|
161
161
|
|
162
162
|
expect( manager.restore_node_states ).to be_falsey
|
163
163
|
end
|
164
164
|
|
165
165
|
|
166
166
|
it "checkpoints the state file periodically if an interval is configured" do
|
167
|
-
|
168
|
-
|
169
|
-
zloop = instance_double( ZMQ::Loop, register: nil, :verbose= => nil )
|
170
|
-
timer = instance_double( ZMQ::Timer, "checkpoint timer" )
|
171
|
-
expect( ZMQ::Loop ).to receive( :new ).and_return( zloop )
|
172
|
-
allow( ZMQ::Timer ).to receive( :new ).and_call_original
|
173
|
-
expect( ZMQ::Timer ).to receive( :new ).with( 20.0, 0 ).and_return( timer )
|
167
|
+
statefile = Pathname( Dir.tmpdir ) + Dir::Tmpname.make_tmpname( 'arb', 'tree' )
|
168
|
+
described_class.configure( checkpoint_frequency: 20_000, state_file: statefile )
|
174
169
|
|
175
170
|
manager = described_class.new
|
176
|
-
|
171
|
+
manager.register_checkpoint_timer
|
172
|
+
expect( manager.checkpoint_timer ).to be_a( Timers::Timer )
|
173
|
+
expect( statefile ).to_not exist
|
174
|
+
|
175
|
+
manager.checkpoint_timer.fire
|
176
|
+
expect( statefile ).to exist
|
177
|
+
states = Marshal.load( statefile.open('r:binary') )
|
178
|
+
|
179
|
+
expect( states ).to be_a( Hash )
|
180
|
+
expect( states.keys ).to eq( manager.nodes.keys )
|
177
181
|
end
|
178
182
|
|
179
183
|
|
@@ -204,7 +208,7 @@ describe Arborist::Manager do
|
|
204
208
|
it "errors if configured with a heartbeat of 0" do
|
205
209
|
expect {
|
206
210
|
described_class.configure( heartbeat_frequency: 0 )
|
207
|
-
}.to raise_error( Arborist::ConfigError, /positive non-zero/i )
|
211
|
+
}.to raise_error( Arborist::ConfigError, /positive and non-zero/i )
|
208
212
|
end
|
209
213
|
|
210
214
|
|
@@ -361,6 +365,651 @@ describe Arborist::Manager do
|
|
361
365
|
end
|
362
366
|
|
363
367
|
|
368
|
+
xdescribe "tree API", :testing_manager do
|
369
|
+
|
370
|
+
before( :each ) do
|
371
|
+
@manager = nil
|
372
|
+
@manager_thread = Thread.new do
|
373
|
+
@manager = make_testing_manager()
|
374
|
+
Thread.current.abort_on_exception = true
|
375
|
+
@manager.run
|
376
|
+
Loggability[ Arborist ].info "Stopped the test manager"
|
377
|
+
end
|
378
|
+
|
379
|
+
count = 0
|
380
|
+
until (@manager && @manager.running?) || count > 30
|
381
|
+
sleep 0.1
|
382
|
+
count += 1
|
383
|
+
end
|
384
|
+
raise "Manager didn't start up" unless @manager.running?
|
385
|
+
end
|
386
|
+
|
387
|
+
after( :each ) do
|
388
|
+
@manager.simulate_signal( :TERM )
|
389
|
+
unless @manager_thread.join( 5 )
|
390
|
+
$stderr.puts "Manager thread didn't exit on its own; killing it."
|
391
|
+
@manager_thread.kill
|
392
|
+
end
|
393
|
+
|
394
|
+
count = 0
|
395
|
+
while @manager.running? || count > 30
|
396
|
+
sleep 0.1
|
397
|
+
Loggability[ Arborist ].info "Manager still running"
|
398
|
+
count += 1
|
399
|
+
end
|
400
|
+
raise "Manager didn't stop" if @manager.running?
|
401
|
+
end
|
402
|
+
|
403
|
+
|
404
|
+
describe "status" do
|
405
|
+
|
406
|
+
|
407
|
+
it "returns a Map describing the manager and its state" do
|
408
|
+
msg = Arborist::TreeAPI.encode( :status )
|
409
|
+
|
410
|
+
sock.send( msg )
|
411
|
+
resmsg = sock.recv
|
412
|
+
|
413
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
414
|
+
expect( hdr ).to include( 'success' => true )
|
415
|
+
expect( body.length ).to eq( 4 )
|
416
|
+
expect( body ).to include( 'server_version', 'state', 'uptime', 'nodecount' )
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
|
421
|
+
|
422
|
+
describe "fetch" do
|
423
|
+
|
424
|
+
it "returns an array of full state maps for nodes matching specified criteria" do
|
425
|
+
msg = Arborist::TreeAPI.encode( :fetch, type: 'service', port: 22 )
|
426
|
+
|
427
|
+
sock.send( msg )
|
428
|
+
resmsg = sock.recv
|
429
|
+
|
430
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
431
|
+
expect( hdr ).to include( 'success' => true )
|
432
|
+
|
433
|
+
expect( body ).to be_a( Hash )
|
434
|
+
expect( body.length ).to eq( 3 )
|
435
|
+
|
436
|
+
expect( body.values ).to all( be_a(Hash) )
|
437
|
+
expect( body.values ).to all( include('status', 'type') )
|
438
|
+
end
|
439
|
+
|
440
|
+
|
441
|
+
it "returns an array of full state maps for nodes not matching specified negative criteria" do
|
442
|
+
msg = Arborist::TreeAPI.encode( :fetch, [ {}, {type: 'service', port: 22} ] )
|
443
|
+
|
444
|
+
sock.send( msg )
|
445
|
+
resmsg = sock.recv
|
446
|
+
|
447
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
448
|
+
expect( hdr ).to include( 'success' => true )
|
449
|
+
|
450
|
+
expect( body ).to be_a( Hash )
|
451
|
+
expect( body.length ).to eq( manager.nodes.length - 3 )
|
452
|
+
|
453
|
+
expect( body.values ).to all( be_a(Hash) )
|
454
|
+
expect( body.values ).to all( include('status', 'type') )
|
455
|
+
end
|
456
|
+
|
457
|
+
|
458
|
+
it "returns an array of full state maps for nodes combining positive and negative criteria" do
|
459
|
+
msg = Arborist::TreeAPI.encode( :fetch, [ {type: 'service'}, {port: 22} ] )
|
460
|
+
|
461
|
+
sock.send( msg )
|
462
|
+
resmsg = sock.recv
|
463
|
+
|
464
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
465
|
+
expect( hdr ).to include( 'success' => true )
|
466
|
+
|
467
|
+
expect( body ).to be_a( Hash )
|
468
|
+
expect( body.length ).to eq( 16 )
|
469
|
+
|
470
|
+
expect( body.values ).to all( be_a(Hash) )
|
471
|
+
expect( body.values ).to all( include('status', 'type') )
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
it "doesn't return nodes beneath downed nodes by default" do
|
476
|
+
manager.nodes['sidonie'].update( error: 'sunspots' )
|
477
|
+
msg = Arborist::TreeAPI.encode( :fetch, type: 'service', port: 22 )
|
478
|
+
|
479
|
+
sock.send( msg )
|
480
|
+
resmsg = sock.recv
|
481
|
+
|
482
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
483
|
+
expect( hdr ).to include( 'success' => true )
|
484
|
+
expect( body ).to be_a( Hash )
|
485
|
+
expect( body.length ).to eq( 2 )
|
486
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh' )
|
487
|
+
end
|
488
|
+
|
489
|
+
|
490
|
+
it "does return nodes beneath downed nodes if asked to" do
|
491
|
+
manager.nodes['sidonie'].update( error: 'plague of locusts' )
|
492
|
+
msg = Arborist::TreeAPI.encode( :fetch, {include_down: true}, type: 'service', port: 22 )
|
493
|
+
|
494
|
+
sock.send( msg )
|
495
|
+
resmsg = sock.recv
|
496
|
+
|
497
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
498
|
+
expect( hdr ).to include( 'success' => true )
|
499
|
+
expect( body ).to be_a( Hash )
|
500
|
+
expect( body.length ).to eq( 3 )
|
501
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh', 'sidonie-ssh' )
|
502
|
+
end
|
503
|
+
|
504
|
+
|
505
|
+
it "returns only identifiers if the `return` header is set to `nil`" do
|
506
|
+
msg = Arborist::TreeAPI.encode( :fetch, {return: nil}, type: 'service', port: 22 )
|
507
|
+
|
508
|
+
sock.send( msg )
|
509
|
+
resmsg = sock.recv
|
510
|
+
|
511
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
512
|
+
expect( hdr ).to include( 'success' => true )
|
513
|
+
expect( body ).to be_a( Hash )
|
514
|
+
expect( body.length ).to eq( 3 )
|
515
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh', 'sidonie-ssh' )
|
516
|
+
expect( body.values ).to all( be_empty )
|
517
|
+
end
|
518
|
+
|
519
|
+
|
520
|
+
it "returns only specified state if the `return` header is set to an Array of keys" do
|
521
|
+
msg = Arborist::TreeAPI.encode( :fetch, {return: %w[status tags addresses]},
|
522
|
+
type: 'service', port: 22 )
|
523
|
+
|
524
|
+
sock.send( msg )
|
525
|
+
resmsg = sock.recv
|
526
|
+
|
527
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
528
|
+
expect( hdr ).to include( 'success' => true )
|
529
|
+
expect( body.length ).to eq( 3 )
|
530
|
+
expect( body ).to include( 'duir-ssh', 'yevaud-ssh', 'sidonie-ssh' )
|
531
|
+
expect( body.values.map(&:keys) ).to all( contain_exactly('status', 'tags', 'addresses') )
|
532
|
+
end
|
533
|
+
|
534
|
+
|
535
|
+
end
|
536
|
+
|
537
|
+
|
538
|
+
describe "list" do
|
539
|
+
|
540
|
+
it "returns an array of node state" do
|
541
|
+
msg = Arborist::TreeAPI.encode( :list )
|
542
|
+
sock.send( msg )
|
543
|
+
resmsg = sock.recv
|
544
|
+
|
545
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
546
|
+
expect( hdr ).to include( 'success' => true )
|
547
|
+
expect( body.length ).to eq( manager.nodes.length )
|
548
|
+
expect( body ).to all( be_a(Hash) )
|
549
|
+
expect( body ).to include( hash_including('identifier' => '_') )
|
550
|
+
expect( body ).to include( hash_including('identifier' => 'duir') )
|
551
|
+
expect( body ).to include( hash_including('identifier' => 'sidonie') )
|
552
|
+
expect( body ).to include( hash_including('identifier' => 'sidonie-ssh') )
|
553
|
+
expect( body ).to include( hash_including('identifier' => 'sidonie-demon-http') )
|
554
|
+
expect( body ).to include( hash_including('identifier' => 'yevaud') )
|
555
|
+
end
|
556
|
+
|
557
|
+
it "can be limited by depth" do
|
558
|
+
msg = Arborist::TreeAPI.encode( :list, {depth: 1}, nil )
|
559
|
+
sock.send( msg )
|
560
|
+
resmsg = sock.recv
|
561
|
+
|
562
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
563
|
+
expect( hdr ).to include( 'success' => true )
|
564
|
+
expect( body.length ).to eq( 3 )
|
565
|
+
expect( body ).to all( be_a(Hash) )
|
566
|
+
expect( body ).to include( hash_including('identifier' => '_') )
|
567
|
+
expect( body ).to include( hash_including('identifier' => 'duir') )
|
568
|
+
expect( body ).to_not include( hash_including('identifier' => 'duir-ssh') )
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
|
573
|
+
describe "update" do
|
574
|
+
|
575
|
+
it "merges the properties sent with those of the targeted nodes" do
|
576
|
+
update_data = {
|
577
|
+
duir: {
|
578
|
+
ping: {
|
579
|
+
rtt: 254
|
580
|
+
}
|
581
|
+
},
|
582
|
+
sidonie: {
|
583
|
+
ping: {
|
584
|
+
rtt: 1208
|
585
|
+
}
|
586
|
+
},
|
587
|
+
yevaud: {
|
588
|
+
ping: {
|
589
|
+
rtt: 843
|
590
|
+
}
|
591
|
+
}
|
592
|
+
}
|
593
|
+
msg = Arborist::TreeAPI.encode( :update, update_data )
|
594
|
+
sock.send( msg )
|
595
|
+
resmsg = sock.recv
|
596
|
+
|
597
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
598
|
+
expect( hdr ).to include( 'success' => true )
|
599
|
+
expect( body ).to be_nil
|
600
|
+
|
601
|
+
expect( manager.nodes['duir'].properties['ping'] ).to include( 'rtt' => 254 )
|
602
|
+
expect( manager.nodes['sidonie'].properties['ping'] ).to include( 'rtt' => 1208 )
|
603
|
+
expect( manager.nodes['yevaud'].properties['ping'] ).to include( 'rtt' => 843 )
|
604
|
+
end
|
605
|
+
|
606
|
+
|
607
|
+
it "ignores unknown identifiers" do
|
608
|
+
msg = Arborist::TreeAPI.encode( :update, charlie_humperton: {ping: { rtt: 8 }} )
|
609
|
+
sock.send( msg )
|
610
|
+
resmsg = sock.recv
|
611
|
+
|
612
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
613
|
+
expect( hdr ).to include( 'success' => true )
|
614
|
+
end
|
615
|
+
|
616
|
+
it "fails with a client error if the body is invalid" do
|
617
|
+
msg = Arborist::TreeAPI.encode( :update, nil )
|
618
|
+
sock.send( msg )
|
619
|
+
resmsg = sock.recv
|
620
|
+
|
621
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
622
|
+
expect( hdr ).to include( 'success' => false )
|
623
|
+
expect( hdr['reason'] ).to match( /respond to #each/ )
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
|
628
|
+
describe "subscribe" do
|
629
|
+
|
630
|
+
it "adds a subscription for all event types to the root node by default" do
|
631
|
+
msg = Arborist::TreeAPI.encode( :subscribe, [{}, {}] )
|
632
|
+
|
633
|
+
resmsg = nil
|
634
|
+
expect {
|
635
|
+
sock.send( msg )
|
636
|
+
resmsg = sock.recv
|
637
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
638
|
+
change { manager.root.subscriptions.length }.by( 1 )
|
639
|
+
)
|
640
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
641
|
+
|
642
|
+
sub_id = manager.subscriptions.keys.first
|
643
|
+
|
644
|
+
expect( hdr ).to include( 'success' => true )
|
645
|
+
expect( body ).to eq([ sub_id ])
|
646
|
+
end
|
647
|
+
|
648
|
+
|
649
|
+
it "adds a subscription to the specified node if an identifier is specified" do
|
650
|
+
msg = Arborist::TreeAPI.encode( :subscribe, {identifier: 'sidonie'}, [{}, {}] )
|
651
|
+
|
652
|
+
resmsg = nil
|
653
|
+
expect {
|
654
|
+
sock.send( msg )
|
655
|
+
resmsg = sock.recv
|
656
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
657
|
+
change { manager.nodes['sidonie'].subscriptions.length }.by( 1 )
|
658
|
+
)
|
659
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
660
|
+
|
661
|
+
sub_id = manager.subscriptions.keys.first
|
662
|
+
|
663
|
+
expect( hdr ).to include( 'success' => true )
|
664
|
+
expect( body ).to eq([ sub_id ])
|
665
|
+
end
|
666
|
+
|
667
|
+
|
668
|
+
it "adds a subscription for particular event types if one is specified" do
|
669
|
+
msg = Arborist::TreeAPI.encode( :subscribe, {event_type: 'node.acked'}, [{}, {}] )
|
670
|
+
|
671
|
+
resmsg = nil
|
672
|
+
expect {
|
673
|
+
sock.send( msg )
|
674
|
+
resmsg = sock.recv
|
675
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
676
|
+
change { manager.root.subscriptions.length }.by( 1 )
|
677
|
+
)
|
678
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
679
|
+
node = manager.subscriptions[ body.first ]
|
680
|
+
sub = node.subscriptions[ body.first ]
|
681
|
+
|
682
|
+
expect( sub.event_type ).to eq( 'node.acked' )
|
683
|
+
end
|
684
|
+
|
685
|
+
|
686
|
+
it "adds a subscription for events which match a pattern if one is specified" do
|
687
|
+
criteria = { type: 'host' }
|
688
|
+
|
689
|
+
msg = Arborist::TreeAPI.encode( :subscribe, [criteria, {}] )
|
690
|
+
|
691
|
+
resmsg = nil
|
692
|
+
expect {
|
693
|
+
sock.send( msg )
|
694
|
+
resmsg = sock.recv
|
695
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
696
|
+
change { manager.root.subscriptions.length }.by( 1 )
|
697
|
+
)
|
698
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
699
|
+
node = manager.subscriptions[ body.first ]
|
700
|
+
sub = node.subscriptions[ body.first ]
|
701
|
+
|
702
|
+
expect( sub.event_type ).to be_nil
|
703
|
+
expect( sub.criteria ).to eq({ 'type' => 'host' })
|
704
|
+
end
|
705
|
+
|
706
|
+
|
707
|
+
it "adds a subscription for events which don't match a pattern if an exclusion pattern is given" do
|
708
|
+
criteria = { type: 'host' }
|
709
|
+
|
710
|
+
msg = Arborist::TreeAPI.encode( :subscribe, [{}, criteria] )
|
711
|
+
|
712
|
+
resmsg = nil
|
713
|
+
expect {
|
714
|
+
sock.send( msg )
|
715
|
+
resmsg = sock.recv
|
716
|
+
}.to change { manager.subscriptions.length }.by( 1 ).and(
|
717
|
+
change { manager.root.subscriptions.length }.by( 1 )
|
718
|
+
)
|
719
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
720
|
+
node = manager.subscriptions[ body.first ]
|
721
|
+
sub = node.subscriptions[ body.first ]
|
722
|
+
|
723
|
+
expect( sub.event_type ).to be_nil
|
724
|
+
expect( sub.negative_criteria ).to eq({ 'type' => 'host' })
|
725
|
+
end
|
726
|
+
|
727
|
+
end
|
728
|
+
|
729
|
+
|
730
|
+
describe "unsubscribe" do
|
731
|
+
|
732
|
+
let( :subscription ) do
|
733
|
+
manager.create_subscription( nil, 'node.delta', {type: 'host'} )
|
734
|
+
end
|
735
|
+
|
736
|
+
|
737
|
+
it "removes the subscription with the specified ID" do
|
738
|
+
msg = Arborist::TreeAPI.encode( :unsubscribe, {subscription_id: subscription.id}, nil )
|
739
|
+
|
740
|
+
resmsg = nil
|
741
|
+
expect {
|
742
|
+
sock.send( msg )
|
743
|
+
resmsg = sock.recv
|
744
|
+
}.to change { manager.subscriptions.length }.by( -1 ).and(
|
745
|
+
change { manager.root.subscriptions.length }.by( -1 )
|
746
|
+
)
|
747
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
748
|
+
|
749
|
+
expect( body ).to include( 'event_type' => 'node.delta', 'criteria' => {'type' => 'host'} )
|
750
|
+
end
|
751
|
+
|
752
|
+
|
753
|
+
it "ignores unsubscription of a non-existant ID" do
|
754
|
+
msg = Arborist::TreeAPI.encode( :unsubscribe, {subscription_id: 'the bears!'}, nil )
|
755
|
+
|
756
|
+
resmsg = nil
|
757
|
+
expect {
|
758
|
+
sock.send( msg )
|
759
|
+
resmsg = sock.recv
|
760
|
+
}.to_not change { manager.subscriptions.length }
|
761
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
762
|
+
|
763
|
+
expect( body ).to be_nil
|
764
|
+
end
|
765
|
+
|
766
|
+
end
|
767
|
+
|
768
|
+
|
769
|
+
describe "prune" do
|
770
|
+
|
771
|
+
it "removes a single node" do
|
772
|
+
msg = Arborist::TreeAPI.encode( :prune, {identifier: 'duir-ssh'}, nil )
|
773
|
+
sock.send( msg )
|
774
|
+
resmsg = sock.recv
|
775
|
+
|
776
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
777
|
+
expect( hdr ).to include( 'success' => true )
|
778
|
+
expect( body ).to eq( true )
|
779
|
+
expect( manager.nodes ).to_not include( 'duir-ssh' )
|
780
|
+
end
|
781
|
+
|
782
|
+
|
783
|
+
it "returns Nil without error if the node to prune didn't exist" do
|
784
|
+
msg = Arborist::TreeAPI.encode( :prune, {identifier: 'shemp-ssh'}, nil )
|
785
|
+
sock.send( msg )
|
786
|
+
resmsg = sock.recv
|
787
|
+
|
788
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
789
|
+
expect( hdr ).to include( 'success' => true )
|
790
|
+
expect( body ).to be_nil
|
791
|
+
end
|
792
|
+
|
793
|
+
|
794
|
+
it "removes children nodes along with the parent" do
|
795
|
+
msg = Arborist::TreeAPI.encode( :prune, {identifier: 'duir'}, nil )
|
796
|
+
sock.send( msg )
|
797
|
+
resmsg = sock.recv
|
798
|
+
|
799
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
800
|
+
expect( hdr ).to include( 'success' => true )
|
801
|
+
expect( body ).to eq( true )
|
802
|
+
expect( manager.nodes ).to_not include( 'duir' )
|
803
|
+
expect( manager.nodes ).to_not include( 'duir-ssh' )
|
804
|
+
end
|
805
|
+
|
806
|
+
|
807
|
+
it "returns an error to the client when missing required attributes" do
|
808
|
+
msg = Arborist::TreeAPI.encode( :prune )
|
809
|
+
sock.send( msg )
|
810
|
+
resmsg = sock.recv
|
811
|
+
|
812
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
813
|
+
expect( hdr ).to include( 'success' => false )
|
814
|
+
expect( hdr['reason'] ).to match( /no identifier/i )
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
|
819
|
+
describe "graft" do
|
820
|
+
|
821
|
+
it "can add a node with no explicit parent" do
|
822
|
+
header = {
|
823
|
+
identifier: 'guenter',
|
824
|
+
type: 'host',
|
825
|
+
}
|
826
|
+
attributes = {
|
827
|
+
description: 'The evil penguin node of doom.',
|
828
|
+
addresses: ['10.2.66.8'],
|
829
|
+
tags: ['internal', 'football']
|
830
|
+
}
|
831
|
+
msg = Arborist::TreeAPI.encode( :graft, header, attributes )
|
832
|
+
|
833
|
+
sock.send( msg )
|
834
|
+
resmsg = sock.recv
|
835
|
+
|
836
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
837
|
+
expect( hdr ).to include( 'success' => true )
|
838
|
+
expect( body ).to eq( 'guenter' )
|
839
|
+
|
840
|
+
new_node = manager.nodes[ 'guenter' ]
|
841
|
+
expect( new_node ).to be_a( Arborist::Node::Host )
|
842
|
+
expect( new_node.identifier ).to eq( header[:identifier] )
|
843
|
+
expect( new_node.description ).to eq( attributes[:description] )
|
844
|
+
expect( new_node.addresses ).to eq([ IPAddr.new(attributes[:addresses].first) ])
|
845
|
+
expect( new_node.tags ).to include( *attributes[:tags] )
|
846
|
+
end
|
847
|
+
|
848
|
+
|
849
|
+
it "can add a node with a parent specified" do
|
850
|
+
header = {
|
851
|
+
identifier: 'orgalorg',
|
852
|
+
type: 'host',
|
853
|
+
parent: 'duir'
|
854
|
+
}
|
855
|
+
attributes = {
|
856
|
+
description: 'The true form of the evil penguin node of doom.',
|
857
|
+
addresses: ['192.168.22.8'],
|
858
|
+
tags: ['evil', 'space', 'entity']
|
859
|
+
}
|
860
|
+
msg = Arborist::TreeAPI.encode( :graft, header, attributes )
|
861
|
+
|
862
|
+
sock.send( msg )
|
863
|
+
resmsg = sock.recv
|
864
|
+
|
865
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
866
|
+
expect( hdr ).to include( 'success' => true )
|
867
|
+
expect( body ).to eq( 'orgalorg' )
|
868
|
+
|
869
|
+
new_node = manager.nodes[ 'orgalorg' ]
|
870
|
+
expect( new_node ).to be_a( Arborist::Node::Host )
|
871
|
+
expect( new_node.identifier ).to eq( header[:identifier] )
|
872
|
+
expect( new_node.parent ).to eq( header[:parent] )
|
873
|
+
expect( new_node.description ).to eq( attributes[:description] )
|
874
|
+
expect( new_node.addresses ).to eq([ IPAddr.new(attributes[:addresses].first) ])
|
875
|
+
expect( new_node.tags ).to include( *attributes[:tags] )
|
876
|
+
end
|
877
|
+
|
878
|
+
|
879
|
+
it "can add a subordinate node" do
|
880
|
+
header = {
|
881
|
+
identifier: 'echo',
|
882
|
+
type: 'service',
|
883
|
+
parent: 'duir'
|
884
|
+
}
|
885
|
+
attributes = {
|
886
|
+
description: 'Mmmmm AppleTalk.'
|
887
|
+
}
|
888
|
+
msg = Arborist::TreeAPI.encode( :graft, header, attributes )
|
889
|
+
|
890
|
+
sock.send( msg )
|
891
|
+
resmsg = sock.recv
|
892
|
+
|
893
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
894
|
+
expect( hdr ).to include( 'success' => true )
|
895
|
+
expect( body ).to eq( 'duir-echo' )
|
896
|
+
|
897
|
+
new_node = manager.nodes[ 'duir-echo' ]
|
898
|
+
expect( new_node ).to be_a( Arborist::Node::Service )
|
899
|
+
expect( new_node.identifier ).to eq( 'duir-echo' )
|
900
|
+
expect( new_node.parent ).to eq( header[:parent] )
|
901
|
+
expect( new_node.description ).to eq( attributes[:description] )
|
902
|
+
expect( new_node.port ).to eq( 7 )
|
903
|
+
expect( new_node.protocol ).to eq( 'tcp' )
|
904
|
+
expect( new_node.app_protocol ).to eq( 'echo' )
|
905
|
+
end
|
906
|
+
|
907
|
+
|
908
|
+
it "errors if adding a subordinate node with no parent" do
|
909
|
+
header = {
|
910
|
+
identifier: 'echo',
|
911
|
+
type: 'service'
|
912
|
+
}
|
913
|
+
attributes = {
|
914
|
+
description: 'Mmmmm AppleTalk.'
|
915
|
+
}
|
916
|
+
msg = Arborist::TreeAPI.encode( :graft, header, attributes )
|
917
|
+
|
918
|
+
sock.send( msg )
|
919
|
+
resmsg = sock.recv
|
920
|
+
|
921
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
922
|
+
expect( hdr ).to include( 'success' => false )
|
923
|
+
expect( hdr['reason'] ).to match( /no host given/i )
|
924
|
+
end
|
925
|
+
|
926
|
+
end
|
927
|
+
|
928
|
+
|
929
|
+
describe "modify" do
|
930
|
+
|
931
|
+
it "can change operational attributes of a node" do
|
932
|
+
header = {
|
933
|
+
identifier: 'sidonie',
|
934
|
+
}
|
935
|
+
attributes = {
|
936
|
+
parent: '_',
|
937
|
+
addresses: ['192.168.32.32', '10.2.2.28']
|
938
|
+
}
|
939
|
+
msg = Arborist::TreeAPI.encode( :modify, header, attributes )
|
940
|
+
|
941
|
+
sock.send( msg )
|
942
|
+
resmsg = sock.recv
|
943
|
+
|
944
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
945
|
+
expect( hdr ).to include( 'success' => true )
|
946
|
+
|
947
|
+
node = manager.nodes[ 'sidonie' ]
|
948
|
+
expect(
|
949
|
+
node.addresses
|
950
|
+
).to eq( [IPAddr.new('192.168.32.32'), IPAddr.new('10.2.2.28')] )
|
951
|
+
expect( node.parent ).to eq( '_' )
|
952
|
+
end
|
953
|
+
|
954
|
+
|
955
|
+
it "ignores modifications to unsupported attributes" do
|
956
|
+
header = {
|
957
|
+
identifier: 'sidonie',
|
958
|
+
}
|
959
|
+
attributes = {
|
960
|
+
identifier: 'somethingelse'
|
961
|
+
}
|
962
|
+
msg = Arborist::TreeAPI.encode( :modify, header, attributes )
|
963
|
+
|
964
|
+
sock.send( msg )
|
965
|
+
resmsg = sock.recv
|
966
|
+
|
967
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
968
|
+
expect( hdr ).to include( 'success' => true )
|
969
|
+
|
970
|
+
expect( manager.nodes['sidonie'] ).to be_an( Arborist::Node )
|
971
|
+
expect( manager.nodes['sidonie'].identifier ).to eq( 'sidonie' )
|
972
|
+
end
|
973
|
+
|
974
|
+
|
975
|
+
it "errors on modifications to the root node" do
|
976
|
+
header = {
|
977
|
+
identifier: '_',
|
978
|
+
}
|
979
|
+
attributes = {
|
980
|
+
identifier: 'somethingelse'
|
981
|
+
}
|
982
|
+
msg = Arborist::TreeAPI.encode( :modify, header, attributes )
|
983
|
+
|
984
|
+
sock.send( msg )
|
985
|
+
resmsg = sock.recv
|
986
|
+
|
987
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
988
|
+
expect( hdr ).to include( 'success' => false )
|
989
|
+
expect( manager.nodes['_'].identifier ).to eq( '_' )
|
990
|
+
end
|
991
|
+
|
992
|
+
|
993
|
+
it "errors on modifications to nonexistent nodes" do
|
994
|
+
header = {
|
995
|
+
identifier: 'nopenopenope',
|
996
|
+
}
|
997
|
+
attributes = {
|
998
|
+
identifier: 'somethingelse'
|
999
|
+
}
|
1000
|
+
msg = Arborist::TreeAPI.encode( :modify, header, attributes )
|
1001
|
+
|
1002
|
+
sock.send( msg )
|
1003
|
+
resmsg = sock.recv
|
1004
|
+
|
1005
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1006
|
+
expect( hdr ).to include( 'success' => false )
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
|
364
1013
|
describe "tree traversal" do
|
365
1014
|
|
366
1015
|
let( :tree ) do
|
@@ -589,77 +1238,204 @@ describe Arborist::Manager do
|
|
589
1238
|
end
|
590
1239
|
|
591
1240
|
|
592
|
-
|
1241
|
+
end
|
1242
|
+
|
593
1243
|
|
594
|
-
|
595
|
-
let( :zmq_loop ) { instance_double(ZMQ::Loop) }
|
596
|
-
let( :tree_sock ) { instance_double(ZMQ::Socket::Rep, "tree API socket") }
|
597
|
-
let( :event_sock ) { instance_double(ZMQ::Socket::Pub, "event socket") }
|
598
|
-
let( :tree_pollitem ) { instance_double(ZMQ::Pollitem, "tree API pollitem") }
|
599
|
-
let( :event_pollitem ) { instance_double(ZMQ::Pollitem, "event API pollitem") }
|
600
|
-
let( :signal_timer ) { instance_double(ZMQ::Timer, "signal timer") }
|
1244
|
+
__END__
|
601
1245
|
|
1246
|
+
let( :socket ) { instance_double( ZMQ::Socket::Pub ) }
|
1247
|
+
let( :pollitem ) { instance_double( ZMQ::Pollitem, pollable: socket ) }
|
1248
|
+
let( :zloop ) { instance_double( ZMQ::Loop ) }
|
602
1249
|
|
603
|
-
|
604
|
-
|
1250
|
+
let( :manager ) { Arborist::Manager.new }
|
1251
|
+
let( :event ) { Arborist::Event.create(TestEvent, 'stuff') }
|
1252
|
+
|
1253
|
+
let( :publisher ) { described_class.new(pollitem, manager, zloop) }
|
1254
|
+
|
1255
|
+
|
1256
|
+
it "starts out registered for writing" do
|
1257
|
+
expect( publisher ).to be_registered
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
|
1261
|
+
it "unregisters itself if told to write with an empty event queue" do
|
1262
|
+
expect( zloop ).to receive( :remove ).with( pollitem )
|
1263
|
+
expect {
|
1264
|
+
publisher.on_writable
|
1265
|
+
}.to change { publisher.registered? }.to( false )
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
|
1269
|
+
it "registers itself if it's not already when an event is appended" do
|
1270
|
+
# Cause the socket to become unregistered
|
1271
|
+
allow( zloop ).to receive( :remove )
|
1272
|
+
publisher.on_writable
|
1273
|
+
|
1274
|
+
expect( zloop ).to receive( :register ).with( pollitem )
|
1275
|
+
|
1276
|
+
expect {
|
1277
|
+
publisher.publish( 'identifier-00aa', event )
|
1278
|
+
}.to change { publisher.registered? }.to( true )
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
|
1282
|
+
it "publishes events with their identifier" do
|
1283
|
+
identifier = '65b2430b-6855-4961-ab46-d742cf4456a1'
|
1284
|
+
|
1285
|
+
expect( socket ).to receive( :sendm ).with( identifier )
|
1286
|
+
expect( socket ).to receive( :send ) do |raw_data|
|
1287
|
+
ev = MessagePack.unpack( raw_data )
|
1288
|
+
expect( ev ).to include( 'type', 'data' )
|
1289
|
+
|
1290
|
+
expect( ev['type'] ).to eq( 'test.event' )
|
1291
|
+
expect( ev['data'] ).to eq( 'stuff' )
|
1292
|
+
end
|
1293
|
+
expect( zloop ).to receive( :remove ).with( pollitem )
|
1294
|
+
|
1295
|
+
publisher.publish( identifier, event )
|
1296
|
+
publisher.on_writable
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
|
1300
|
+
|
1301
|
+
let( :manager ) { @manager }
|
605
1302
|
|
606
|
-
|
607
|
-
|
1303
|
+
let!( :sock ) do
|
1304
|
+
sock = Arborist.zmq_context.socket( :REQ )
|
1305
|
+
sock.linger = 0
|
1306
|
+
sock.connect( TESTING_API_SOCK )
|
1307
|
+
sock
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
let( :api_handler ) { described_class.new( rep_sock, manager ) }
|
1311
|
+
|
1312
|
+
|
1313
|
+
describe "malformed requests" do
|
1314
|
+
|
1315
|
+
it "send an error response if the request can't be deserialized" do
|
1316
|
+
sock.send( "whatevs, dude!" )
|
1317
|
+
resmsg = sock.recv
|
1318
|
+
|
1319
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1320
|
+
expect( hdr ).to include(
|
1321
|
+
'success' => false,
|
1322
|
+
'reason' => /invalid request/i,
|
1323
|
+
'category' => 'client'
|
1324
|
+
)
|
1325
|
+
expect( body ).to be_nil
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
|
1329
|
+
it "send an error response if the request isn't a tuple" do
|
1330
|
+
sock.send( MessagePack.pack({ version: 1, action: 'list' }) )
|
1331
|
+
resmsg = sock.recv
|
1332
|
+
|
1333
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1334
|
+
expect( hdr ).to include(
|
1335
|
+
'success' => false,
|
1336
|
+
'reason' => /invalid request.*not a tuple/i,
|
1337
|
+
'category' => 'client'
|
1338
|
+
)
|
1339
|
+
expect( body ).to be_nil
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
|
1343
|
+
it "send an error response if the request is empty" do
|
1344
|
+
sock.send( MessagePack.pack([]) )
|
1345
|
+
resmsg = sock.recv
|
1346
|
+
|
1347
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1348
|
+
expect( hdr ).to include(
|
1349
|
+
'success' => false,
|
1350
|
+
'reason' => /invalid request.*incorrect length/i,
|
1351
|
+
'category' => 'client'
|
1352
|
+
)
|
1353
|
+
expect( body ).to be_nil
|
1354
|
+
end
|
608
1355
|
|
609
|
-
allow( zmq_loop ).to receive( :verbose= )
|
610
|
-
allow( zmq_loop ).to receive( :remove ).with( tree_pollitem )
|
611
|
-
allow( zmq_loop ).to receive( :remove ).with( event_pollitem )
|
612
1356
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
allow( event_sock ).to receive( :close )
|
1357
|
+
it "send an error response if the request is an incorrect length" do
|
1358
|
+
sock.send( MessagePack.pack([{}, {}, {}]) )
|
1359
|
+
resmsg = sock.recv
|
617
1360
|
|
618
|
-
|
619
|
-
|
1361
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1362
|
+
expect( hdr ).to include(
|
1363
|
+
'success' => false,
|
1364
|
+
'reason' => /invalid request.*incorrect length/i,
|
1365
|
+
'category' => 'client'
|
1366
|
+
)
|
1367
|
+
expect( body ).to be_nil
|
1368
|
+
end
|
620
1369
|
|
621
|
-
allow( event_sock ).to receive( :bind ).with( Arborist.event_api_url )
|
622
|
-
allow( event_sock ).to receive( :linger= )
|
623
1370
|
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
and_return( event_pollitem )
|
1371
|
+
it "send an error response if the request's header is not a Map" do
|
1372
|
+
sock.send( MessagePack.pack([nil, {}]) )
|
1373
|
+
resmsg = sock.recv
|
628
1374
|
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
1375
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1376
|
+
expect( hdr ).to include(
|
1377
|
+
'success' => false,
|
1378
|
+
'reason' => /invalid request.*header is not a map/i,
|
1379
|
+
'category' => 'client'
|
1380
|
+
)
|
1381
|
+
expect( body ).to be_nil
|
635
1382
|
end
|
636
1383
|
|
637
1384
|
|
638
|
-
it "
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
expect(
|
644
|
-
|
1385
|
+
it "send an error response if the request's body is not Nil, a Map, or an Array of Maps" do
|
1386
|
+
sock.send( MessagePack.pack([{version: 1, action: 'list'}, 18]) )
|
1387
|
+
resmsg = sock.recv
|
1388
|
+
|
1389
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1390
|
+
expect( hdr ).to include(
|
1391
|
+
'success' => false,
|
1392
|
+
'reason' => /invalid request.*body must be nil, a map, or an array of maps/i,
|
1393
|
+
'category' => 'client'
|
1394
|
+
)
|
1395
|
+
expect( body ).to be_nil
|
1396
|
+
end
|
1397
|
+
|
645
1398
|
|
646
|
-
|
647
|
-
|
1399
|
+
it "send an error response if missing a version" do
|
1400
|
+
sock.send( MessagePack.pack([{action: 'list'}]) )
|
1401
|
+
resmsg = sock.recv
|
648
1402
|
|
649
|
-
|
1403
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1404
|
+
expect( hdr ).to include(
|
1405
|
+
'success' => false,
|
1406
|
+
'reason' => /invalid request.*missing required header 'version'/i,
|
1407
|
+
'category' => 'client'
|
1408
|
+
)
|
1409
|
+
expect( body ).to be_nil
|
1410
|
+
end
|
650
1411
|
|
651
|
-
expect( manager.event_publisher.event_queue.length ).to eq( 1 )
|
652
1412
|
|
653
|
-
|
654
|
-
|
1413
|
+
it "send an error response if missing an action" do
|
1414
|
+
sock.send( MessagePack.pack([{version: 1}]) )
|
1415
|
+
resmsg = sock.recv
|
655
1416
|
|
656
|
-
|
657
|
-
expect(
|
658
|
-
'
|
659
|
-
'
|
1417
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1418
|
+
expect( hdr ).to include(
|
1419
|
+
'success' => false,
|
1420
|
+
'reason' => /invalid request.*missing required header 'action'/i,
|
1421
|
+
'category' => 'client'
|
660
1422
|
)
|
1423
|
+
expect( body ).to be_nil
|
661
1424
|
end
|
662
1425
|
|
1426
|
+
|
1427
|
+
it "send an error response for unknown actions" do
|
1428
|
+
badmsg = Arborist::TreeAPI.encode( :slap )
|
1429
|
+
sock.send( badmsg )
|
1430
|
+
resmsg = sock.recv
|
1431
|
+
|
1432
|
+
hdr, body = Arborist::TreeAPI.decode( resmsg )
|
1433
|
+
expect( hdr ).to include(
|
1434
|
+
'success' => false,
|
1435
|
+
'reason' => /invalid request.*no such action 'slap'/i,
|
1436
|
+
'category' => 'client'
|
1437
|
+
)
|
1438
|
+
expect( body ).to be_nil
|
1439
|
+
end
|
663
1440
|
end
|
664
|
-
end
|
665
1441
|
|