arborist 0.0.1.pre20160106113421
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 +7 -0
- data/.document +4 -0
- data/.simplecov +9 -0
- data/ChangeLog +417 -0
- data/Events.md +20 -0
- data/History.md +4 -0
- data/LICENSE +29 -0
- data/Manifest.txt +72 -0
- data/Monitors.md +141 -0
- data/Nodes.md +0 -0
- data/Observers.md +72 -0
- data/Protocol.md +214 -0
- data/README.md +75 -0
- data/Rakefile +81 -0
- data/TODO.md +24 -0
- data/bin/amanagerd +10 -0
- data/bin/amonitord +12 -0
- data/bin/aobserverd +12 -0
- data/lib/arborist.rb +182 -0
- data/lib/arborist/client.rb +191 -0
- data/lib/arborist/event.rb +61 -0
- data/lib/arborist/event/node_acked.rb +18 -0
- data/lib/arborist/event/node_delta.rb +20 -0
- data/lib/arborist/event/node_matching.rb +34 -0
- data/lib/arborist/event/node_update.rb +19 -0
- data/lib/arborist/event/sys_reloaded.rb +15 -0
- data/lib/arborist/exceptions.rb +21 -0
- data/lib/arborist/manager.rb +508 -0
- data/lib/arborist/manager/event_publisher.rb +97 -0
- data/lib/arborist/manager/tree_api.rb +207 -0
- data/lib/arborist/mixins.rb +363 -0
- data/lib/arborist/monitor.rb +377 -0
- data/lib/arborist/monitor/socket.rb +163 -0
- data/lib/arborist/monitor_runner.rb +217 -0
- data/lib/arborist/node.rb +700 -0
- data/lib/arborist/node/host.rb +87 -0
- data/lib/arborist/node/root.rb +60 -0
- data/lib/arborist/node/service.rb +112 -0
- data/lib/arborist/observer.rb +176 -0
- data/lib/arborist/observer/action.rb +125 -0
- data/lib/arborist/observer/summarize.rb +105 -0
- data/lib/arborist/observer_runner.rb +181 -0
- data/lib/arborist/subscription.rb +82 -0
- data/spec/arborist/client_spec.rb +282 -0
- data/spec/arborist/event/node_update_spec.rb +71 -0
- data/spec/arborist/event_spec.rb +64 -0
- data/spec/arborist/manager/event_publisher_spec.rb +66 -0
- data/spec/arborist/manager/tree_api_spec.rb +458 -0
- data/spec/arborist/manager_spec.rb +442 -0
- data/spec/arborist/mixins_spec.rb +195 -0
- data/spec/arborist/monitor/socket_spec.rb +195 -0
- data/spec/arborist/monitor_runner_spec.rb +152 -0
- data/spec/arborist/monitor_spec.rb +251 -0
- data/spec/arborist/node/host_spec.rb +104 -0
- data/spec/arborist/node/root_spec.rb +29 -0
- data/spec/arborist/node/service_spec.rb +98 -0
- data/spec/arborist/node_spec.rb +552 -0
- data/spec/arborist/observer/action_spec.rb +205 -0
- data/spec/arborist/observer/summarize_spec.rb +294 -0
- data/spec/arborist/observer_spec.rb +146 -0
- data/spec/arborist/subscription_spec.rb +71 -0
- data/spec/arborist_spec.rb +146 -0
- data/spec/data/monitors/pings.rb +80 -0
- data/spec/data/monitors/port_checks.rb +27 -0
- data/spec/data/monitors/system_resources.rb +30 -0
- data/spec/data/monitors/web_services.rb +17 -0
- data/spec/data/nodes/duir.rb +20 -0
- data/spec/data/nodes/localhost.rb +15 -0
- data/spec/data/nodes/sidonie.rb +29 -0
- data/spec/data/nodes/yevaud.rb +26 -0
- data/spec/data/observers/auditor.rb +23 -0
- data/spec/data/observers/webservices.rb +18 -0
- data/spec/spec_helper.rb +117 -0
- metadata +368 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'schedulability'
|
5
|
+
require 'schedulability/schedule'
|
6
|
+
require 'loggability'
|
7
|
+
|
8
|
+
require 'arborist/observer' unless defined?( Arborist::Observer )
|
9
|
+
|
10
|
+
|
11
|
+
# An summarization action taken by an Observer.
|
12
|
+
class Arborist::Observer::Summarize
|
13
|
+
extend Loggability
|
14
|
+
|
15
|
+
|
16
|
+
# Loggability API -- log to the Arborist logger
|
17
|
+
log_to :arborist
|
18
|
+
|
19
|
+
|
20
|
+
### Create a new Summary that will call the specified +block+ +during+ the given schedule,
|
21
|
+
### +every+ specified number of seconds or +count+ events, whichever is sooner.
|
22
|
+
def initialize( every: 0, count: 0, during: nil, &block )
|
23
|
+
raise ArgumentError, "Summarize requires a block" unless block
|
24
|
+
raise ArgumentError, "Summarize requires a value for `every` or `count`." if
|
25
|
+
every.zero? && count.zero?
|
26
|
+
|
27
|
+
@time_threshold = every
|
28
|
+
@count_threshold = count
|
29
|
+
@schedule = Schedulability::Schedule.parse( during ) if during
|
30
|
+
@block = block
|
31
|
+
|
32
|
+
@event_history = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
######
|
37
|
+
public
|
38
|
+
######
|
39
|
+
|
40
|
+
##
|
41
|
+
# The object to #call when the action is triggered.
|
42
|
+
attr_reader :block
|
43
|
+
|
44
|
+
##
|
45
|
+
# The number of seconds between calls to the action
|
46
|
+
attr_reader :time_threshold
|
47
|
+
|
48
|
+
##
|
49
|
+
# The number of events that cause the action to be called.
|
50
|
+
attr_reader :count_threshold
|
51
|
+
|
52
|
+
##
|
53
|
+
# The schedule that applies to this action.
|
54
|
+
attr_reader :schedule
|
55
|
+
|
56
|
+
##
|
57
|
+
# The Hash of recent events, keyed by their arrival time.
|
58
|
+
attr_reader :event_history
|
59
|
+
|
60
|
+
|
61
|
+
### Call the action for the specified +event+.
|
62
|
+
def handle_event( event )
|
63
|
+
self.record_event( event )
|
64
|
+
self.call_block if self.should_run?
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
### Handle a timing event by calling the block with any events in the history.
|
69
|
+
def on_timer
|
70
|
+
self.log.debug "Timer event: %d pending event/s" % [ self.event_history.size ]
|
71
|
+
self.call_block unless self.event_history.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
### Execute the action block.
|
76
|
+
def call_block
|
77
|
+
self.block.call( self.event_history.dup )
|
78
|
+
ensure
|
79
|
+
self.event_history.clear
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
### Record the specified +event+ in the event history if within the scheduled period(s).
|
84
|
+
def record_event( event )
|
85
|
+
return if self.schedule && !self.schedule.now?
|
86
|
+
self.event_history[ Time.now ] = event
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
### Returns +true+ if the count threshold is exceeded and the current time is within the
|
91
|
+
### action's schedule.
|
92
|
+
def should_run?
|
93
|
+
return self.count_threshold_exceeded?
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
### Returns +true+ if the number of events in the event history meet or exceed the
|
98
|
+
### #count_threshold.
|
99
|
+
def count_threshold_exceeded?
|
100
|
+
return false if self.count_threshold.zero?
|
101
|
+
self.log.debug "Event history has %d events" % [ self.event_history.size ]
|
102
|
+
return self.event_history.size >= self.count_threshold
|
103
|
+
end
|
104
|
+
|
105
|
+
end # class Arborist::Observer::Summarize
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'rbczmq'
|
5
|
+
require 'loggability'
|
6
|
+
|
7
|
+
require 'arborist' unless defined?( Arborist )
|
8
|
+
require 'arborist/client'
|
9
|
+
require 'arborist/observer'
|
10
|
+
|
11
|
+
|
12
|
+
# Undo the useless scoping
|
13
|
+
class ZMQ::Loop
|
14
|
+
public_class_method :instance
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
# An event-driven runner for Arborist::Observers.
|
19
|
+
class Arborist::ObserverRunner
|
20
|
+
extend Loggability
|
21
|
+
|
22
|
+
log_to :arborist
|
23
|
+
|
24
|
+
|
25
|
+
# A ZMQ::Handler object for managing IO for all running observers.
|
26
|
+
class Handler < ZMQ::Handler
|
27
|
+
extend Loggability,
|
28
|
+
Arborist::MethodUtilities
|
29
|
+
|
30
|
+
log_to :arborist
|
31
|
+
|
32
|
+
### Create a ZMQ::Handler that acts as the agent that runs the specified
|
33
|
+
### +observer+.
|
34
|
+
def initialize( reactor )
|
35
|
+
@client = Arborist::Client.new
|
36
|
+
@pollitem = ZMQ::Pollitem.new( @client.event_api, ZMQ::POLLIN )
|
37
|
+
@pollitem.handler = self
|
38
|
+
@subscriptions = {}
|
39
|
+
|
40
|
+
reactor.register( @pollitem )
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
######
|
45
|
+
public
|
46
|
+
######
|
47
|
+
|
48
|
+
# The Arborist::Client that will be used for creating and tearing down subscriptions
|
49
|
+
attr_reader :client
|
50
|
+
|
51
|
+
# The map of subscription IDs to the Observer which it was created for.
|
52
|
+
attr_reader :subscriptions
|
53
|
+
|
54
|
+
|
55
|
+
### Add the specified +observer+ and subscribe to the events it wishes to receive.
|
56
|
+
def add_observer( observer )
|
57
|
+
self.log.info "Adding observer: %s" % [ observer.description ]
|
58
|
+
observer.subscriptions.each do |sub|
|
59
|
+
subid = self.client.subscribe( sub )
|
60
|
+
self.subscriptions[ subid ] = observer
|
61
|
+
self.client.event_api.subscribe( subid )
|
62
|
+
self.log.debug " subscribed to %p with subscription %s" % [ sub, subid ]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
### Remove the specified +observer+ after unsubscribing from its events.
|
68
|
+
def remove_observer( observer )
|
69
|
+
self.log.info "Removing observer: %s" % [ observer.description ]
|
70
|
+
|
71
|
+
self.subscriptions.keys.each do |subid|
|
72
|
+
next unless self.subscriptions[ subid ] == observer
|
73
|
+
|
74
|
+
self.client.unsubscribe( subid )
|
75
|
+
self.subscriptions.delete( subid )
|
76
|
+
self.client.event_api.unsubscribe( subid )
|
77
|
+
self.log.debug " unsubscribed from %p" % [ subid ]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
### Read events from the event socket when it becomes readable, and dispatch them to
|
83
|
+
### the correct observer.
|
84
|
+
def on_readable
|
85
|
+
subid = self.recv
|
86
|
+
raise "Partial write?!" unless self.pollitem.pollable.rcvmore?
|
87
|
+
raw_event = self.recv
|
88
|
+
|
89
|
+
if (( observer = self.subscriptions[subid] ))
|
90
|
+
event = MessagePack.unpack( raw_event )
|
91
|
+
observer.handle_event( subid, event )
|
92
|
+
else
|
93
|
+
self.log.warn "Ignoring event %p for which we have no observer." % [ subid ]
|
94
|
+
end
|
95
|
+
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
|
99
|
+
end # class Handler
|
100
|
+
|
101
|
+
|
102
|
+
### Create a new Arborist::ObserverRunner
|
103
|
+
def initialize
|
104
|
+
@observers = []
|
105
|
+
@timers = []
|
106
|
+
@handler = nil
|
107
|
+
@reactor = ZMQ::Loop.new
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
######
|
112
|
+
public
|
113
|
+
######
|
114
|
+
|
115
|
+
# The Array of loaded Arborist::Observers the runner should run.
|
116
|
+
attr_reader :observers
|
117
|
+
|
118
|
+
# The Array of registered ZMQ::Timers
|
119
|
+
attr_reader :timers
|
120
|
+
|
121
|
+
# The ZMQ::Handler subclass that handles all async IO
|
122
|
+
attr_accessor :handler
|
123
|
+
|
124
|
+
# The reactor (a ZMQ::Loop) the runner uses to drive everything
|
125
|
+
attr_accessor :reactor
|
126
|
+
|
127
|
+
|
128
|
+
### Load observers from the specified +enumerator+.
|
129
|
+
def load_observers( enumerator )
|
130
|
+
@observers += enumerator.to_a
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
### Run the specified +observers+
|
135
|
+
def run
|
136
|
+
self.handler = Arborist::ObserverRunner::Handler.new( self.reactor )
|
137
|
+
|
138
|
+
self.observers.each do |observer|
|
139
|
+
self.handler.add_observer( observer )
|
140
|
+
self.add_timers_for( observer )
|
141
|
+
end
|
142
|
+
|
143
|
+
self.reactor.start
|
144
|
+
rescue Interrupt
|
145
|
+
$stderr.puts "Interrupted!"
|
146
|
+
self.stop
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
### Stop the observer
|
151
|
+
def stop
|
152
|
+
self.observers.each do |observer|
|
153
|
+
self.remove_timers
|
154
|
+
self.handler.remove_observer( observer )
|
155
|
+
end
|
156
|
+
|
157
|
+
self.reactor.stop
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
### Register a timer for the specified +observer+.
|
162
|
+
def add_timers_for( observer )
|
163
|
+
observer.timers.each do |interval, callback|
|
164
|
+
self.log.info "Creating timer for %s observer to run %p every %ds" %
|
165
|
+
[ observer.description, callback, interval ]
|
166
|
+
timer = ZMQ::Timer.new( interval, 0, &callback )
|
167
|
+
self.reactor.register_timer( timer )
|
168
|
+
self.timers << timer
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
### Remove any registered timers.
|
174
|
+
def remove_timers
|
175
|
+
self.timers.each do |timer|
|
176
|
+
self.reactor.cancel_timer( timer )
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end # class Arborist::ObserverRunner
|
181
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
require 'arborist' unless defined?( Arborist )
|
8
|
+
require 'arborist/mixins'
|
9
|
+
|
10
|
+
|
11
|
+
# An observer subscription to node events.
|
12
|
+
class Arborist::Subscription
|
13
|
+
extend Loggability
|
14
|
+
include Arborist::HashUtilities
|
15
|
+
|
16
|
+
|
17
|
+
# Loggability API -- log to the Arborist logger
|
18
|
+
log_to :arborist
|
19
|
+
|
20
|
+
|
21
|
+
### Instantiate a new Subscription object given an +event+ pattern
|
22
|
+
### and event +criteria+.
|
23
|
+
def initialize( publisher, event_type=nil, criteria={} )
|
24
|
+
@publisher = publisher
|
25
|
+
@event_type = event_type
|
26
|
+
@criteria = stringify_keys( criteria )
|
27
|
+
@id = self.generate_id
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
######
|
32
|
+
public
|
33
|
+
######
|
34
|
+
|
35
|
+
# The Arborist::Manager::EventPublisher the subscription will use to publish matching events.
|
36
|
+
attr_reader :publisher
|
37
|
+
|
38
|
+
# A unique identifier for this subscription request.
|
39
|
+
attr_reader :id
|
40
|
+
|
41
|
+
# The Arborist event pattern that this subscription handles.
|
42
|
+
attr_reader :event_type
|
43
|
+
|
44
|
+
# Node selection attributes to match
|
45
|
+
attr_reader :criteria
|
46
|
+
|
47
|
+
|
48
|
+
### Create an identifier for this subscription object.
|
49
|
+
def generate_id
|
50
|
+
return SecureRandom.uuid
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
### Publish any of the specified +events+ which match the subscription.
|
55
|
+
def on_events( *events )
|
56
|
+
events.flatten.each do |event|
|
57
|
+
self.publisher.publish( self.id, event ) if self.interested_in?( event )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
### Returns +true+ if the receiver is interested in publishing the specified +event+.
|
63
|
+
def interested_in?( event )
|
64
|
+
self.log.debug "Testing %p against type = %p and criteria = %p" %
|
65
|
+
[ event, self.event_type, self.criteria ]
|
66
|
+
return event.match( self )
|
67
|
+
end
|
68
|
+
alias_method :is_interested_in?, :interested_in?
|
69
|
+
|
70
|
+
|
71
|
+
### Return a String representation of the object suitable for debugging.
|
72
|
+
def inspect
|
73
|
+
return "#<%p:%#x [%s] for %s events matching: %p>" % [
|
74
|
+
self.class,
|
75
|
+
self.object_id * 2,
|
76
|
+
self.id,
|
77
|
+
self.event_type,
|
78
|
+
self.criteria,
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
end # class Arborist::Subscription
|
@@ -0,0 +1,282 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/client'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::Client do
|
9
|
+
|
10
|
+
let( :client ) { described_class.new }
|
11
|
+
|
12
|
+
describe "synchronous API", :testing_manager do
|
13
|
+
|
14
|
+
before( :each ) do
|
15
|
+
@manager = make_testing_manager()
|
16
|
+
@manager_thread = Thread.new do
|
17
|
+
Thread.current.abort_on_exception = true
|
18
|
+
@manager.run
|
19
|
+
Loggability[ Arborist ].info "Stopped the test manager"
|
20
|
+
end
|
21
|
+
|
22
|
+
count = 0
|
23
|
+
until @manager.running? || count > 30
|
24
|
+
sleep 0.1
|
25
|
+
count += 1
|
26
|
+
end
|
27
|
+
raise "Manager didn't start up" unless @manager.running?
|
28
|
+
end
|
29
|
+
|
30
|
+
after( :each ) do
|
31
|
+
@manager.stop
|
32
|
+
@manager_thread.join
|
33
|
+
|
34
|
+
count = 0
|
35
|
+
while @manager.zmq_loop.running? || count > 30
|
36
|
+
sleep 0.1
|
37
|
+
Loggability[ Arborist ].info "ZMQ loop still running"
|
38
|
+
count += 1
|
39
|
+
end
|
40
|
+
raise "ZMQ Loop didn't stop" if @manager.zmq_loop.running?
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
let( :manager ) { @manager }
|
45
|
+
|
46
|
+
|
47
|
+
it "can fetch the status of the manager it's connected to" do
|
48
|
+
res = client.status
|
49
|
+
expect( res ).to include( 'server_version', 'state', 'uptime', 'nodecount' )
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it "can list the nodes of the manager it's connected to" do
|
54
|
+
res = client.list
|
55
|
+
expect( res ).to be_an( Array )
|
56
|
+
expect( res.length ).to eq( manager.nodes.length )
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
it "can list a subtree of the nodes of the manager it's connected to" do
|
61
|
+
res = client.list( from: 'duir' )
|
62
|
+
expect( res ).to be_an( Array )
|
63
|
+
expect( res.length ).to be < manager.nodes.length
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
it "can fetch all node properties for all 'up' nodes" do
|
68
|
+
res = client.fetch
|
69
|
+
expect( res ).to be_a( Hash )
|
70
|
+
expect( res.length ).to be == manager.nodes.length
|
71
|
+
expect( res.values ).to all( be_a(Hash) )
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
it "can fetch identifiers for all 'up' nodes" do
|
76
|
+
res = client.fetch( {}, properties: nil )
|
77
|
+
expect( res ).to be_a( Hash )
|
78
|
+
expect( res.length ).to be == manager.nodes.length
|
79
|
+
expect( res.values ).to all( be_empty )
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
it "can fetch a subset of properties for all 'up' nodes" do
|
84
|
+
res = client.fetch( {}, properties: [:addresses, :status] )
|
85
|
+
expect( res ).to be_a( Hash )
|
86
|
+
expect( res.length ).to be == manager.nodes.length
|
87
|
+
expect( res.values ).to all( be_a(Hash) )
|
88
|
+
expect( res.values.map(&:length) ).to all( be <= 2 )
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
it "can fetch a subset of properties for all 'up' nodes matching specified criteria" do
|
93
|
+
res = client.fetch( {type: 'host'}, properties: [:addresses, :status] )
|
94
|
+
expect( res ).to be_a( Hash )
|
95
|
+
expect( res.length ).to be == manager.nodes.values.count {|n| n.type == 'host' }
|
96
|
+
expect( res.values ).to all( include('addresses', 'status') )
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
it "can fetch all properties for all nodes regardless of their status" do
|
101
|
+
# Down a node
|
102
|
+
manager.nodes['duir'].update( error: 'something happened' )
|
103
|
+
|
104
|
+
res = client.fetch( {type: 'host'}, include_down: true )
|
105
|
+
|
106
|
+
expect( res ).to be_a( Hash )
|
107
|
+
expect( res ).to include( 'duir' )
|
108
|
+
expect( res['duir']['status'] ).to eq( 'down' )
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
it "can update the properties of managed nodes" do
|
113
|
+
client.update( duir: { ping: {rtt: 24} } )
|
114
|
+
|
115
|
+
expect( manager.nodes['duir'].properties ).to include( 'ping' )
|
116
|
+
expect( manager.nodes['duir'].properties['ping'] ).to include( 'rtt' )
|
117
|
+
expect( manager.nodes['duir'].properties['ping']['rtt'] ).to eq( 24 )
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
it "can subscribe to all events" do
|
122
|
+
sub_id = client.subscribe
|
123
|
+
expect( sub_id ).to be_a( String )
|
124
|
+
expect( sub_id ).to match( /^[\w\-]{16,}/ )
|
125
|
+
|
126
|
+
node = manager.subscriptions[ sub_id ]
|
127
|
+
sub = manager.root.subscriptions[ sub_id ]
|
128
|
+
|
129
|
+
expect( sub ).to be_a( Arborist::Subscription )
|
130
|
+
expect( sub.criteria ).to be_empty
|
131
|
+
expect( sub.event_type ).to be_nil
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
it "can subscribe to a particular kind of event" do
|
136
|
+
sub_id = client.subscribe( event_type: 'node.ack' )
|
137
|
+
expect( sub_id ).to be_a( String )
|
138
|
+
expect( sub_id ).to match( /^[\w\-]{16,}/ )
|
139
|
+
|
140
|
+
node = manager.subscriptions[ sub_id ]
|
141
|
+
sub = manager.root.subscriptions[ sub_id ]
|
142
|
+
|
143
|
+
expect( sub ).to be_a( Arborist::Subscription )
|
144
|
+
expect( sub.criteria ).to be_empty
|
145
|
+
expect( sub.event_type ).to eq( 'node.ack' )
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
it "can subscribe to events for descendants of a particular node in the tree" do
|
150
|
+
sub_id = client.subscribe( identifier: 'sidonie' )
|
151
|
+
expect( sub_id ).to be_a( String )
|
152
|
+
expect( sub_id ).to match( /^[\w\-]{16,}/ )
|
153
|
+
|
154
|
+
node = manager.subscriptions[ sub_id ]
|
155
|
+
sub = node.subscriptions[ sub_id ]
|
156
|
+
|
157
|
+
expect( node.identifier ).to eq( 'sidonie' )
|
158
|
+
expect( sub ).to be_a( Arborist::Subscription )
|
159
|
+
expect( sub.criteria ).to be_empty
|
160
|
+
expect( sub.event_type ).to be_nil
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
it "can subscribe to events of a particular type for descendants of a particular node" do
|
165
|
+
sub_id = client.subscribe( identifier: 'sidonie', event_type: 'node.delta' )
|
166
|
+
expect( sub_id ).to be_a( String )
|
167
|
+
expect( sub_id ).to match( /^[\w\-]{16,}/ )
|
168
|
+
|
169
|
+
node = manager.subscriptions[ sub_id ]
|
170
|
+
sub = node.subscriptions[ sub_id ]
|
171
|
+
|
172
|
+
expect( node.identifier ).to eq( 'sidonie' )
|
173
|
+
expect( sub ).to be_a( Arborist::Subscription )
|
174
|
+
expect( sub.criteria ).to be_empty
|
175
|
+
expect( sub.event_type ).to eq( 'node.delta' )
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
it "can subscribe to events matching one or more criteria" do
|
180
|
+
sub_id = client.subscribe( criteria: {type: 'service'} )
|
181
|
+
expect( sub_id ).to be_a( String )
|
182
|
+
expect( sub_id ).to match( /^[\w\-]{16,}/ )
|
183
|
+
|
184
|
+
node = manager.subscriptions[ sub_id ]
|
185
|
+
sub = node.subscriptions[ sub_id ]
|
186
|
+
|
187
|
+
expect( node.identifier ).to eq( '_' )
|
188
|
+
expect( sub ).to be_a( Arborist::Subscription )
|
189
|
+
expect( sub.criteria ).to eq( 'type' => 'service' )
|
190
|
+
expect( sub.event_type ).to eq( nil )
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
describe "asynchronous API" do
|
197
|
+
|
198
|
+
it "can make a raw status request" do
|
199
|
+
req = client.make_status_request
|
200
|
+
expect( req ).to be_a( String )
|
201
|
+
expect( req.encoding ).to eq( Encoding::ASCII_8BIT )
|
202
|
+
|
203
|
+
msg = unpack_message( req )
|
204
|
+
expect( msg ).to be_an( Array )
|
205
|
+
expect( msg.first ).to be_a( Hash )
|
206
|
+
expect( msg.first ).to include( 'version', 'action' )
|
207
|
+
expect( msg.first['version'] ).to eq( Arborist::Client::API_VERSION )
|
208
|
+
expect( msg.first['action'] ).to eq( 'status' )
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
it "can make a raw list request" do
|
213
|
+
req = client.make_list_request
|
214
|
+
expect( req ).to be_a( String )
|
215
|
+
expect( req.encoding ).to eq( Encoding::ASCII_8BIT )
|
216
|
+
|
217
|
+
msg = unpack_message( req )
|
218
|
+
expect( msg ).to be_an( Array )
|
219
|
+
expect( msg.first ).to be_a( Hash )
|
220
|
+
expect( msg.first ).to include( 'version', 'action' )
|
221
|
+
expect( msg.first ).to_not include( 'from' )
|
222
|
+
expect( msg.first['version'] ).to eq( Arborist::Client::API_VERSION )
|
223
|
+
expect( msg.first['action'] ).to eq( 'list' )
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
it "can make a raw fetch request" do
|
228
|
+
req = client.make_fetch_request( {} )
|
229
|
+
expect( req ).to be_a( String )
|
230
|
+
expect( req.encoding ).to eq( Encoding::ASCII_8BIT )
|
231
|
+
|
232
|
+
msg = unpack_message( req )
|
233
|
+
expect( msg ).to be_an( Array )
|
234
|
+
expect( msg.first ).to be_a( Hash )
|
235
|
+
expect( msg.first ).to include( 'version', 'action' )
|
236
|
+
expect( msg.first['version'] ).to eq( Arborist::Client::API_VERSION )
|
237
|
+
expect( msg.first['action'] ).to eq( 'fetch' )
|
238
|
+
|
239
|
+
expect( msg.last ).to eq( {} )
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
it "can make a raw fetch request with criteria" do
|
244
|
+
req = client.make_fetch_request( {type: 'host'} )
|
245
|
+
expect( req ).to be_a( String )
|
246
|
+
expect( req.encoding ).to eq( Encoding::ASCII_8BIT )
|
247
|
+
|
248
|
+
msg = unpack_message( req )
|
249
|
+
expect( msg ).to be_an( Array )
|
250
|
+
expect( msg.first ).to be_a( Hash )
|
251
|
+
expect( msg.first ).to include( 'version', 'action' )
|
252
|
+
expect( msg.first['version'] ).to eq( Arborist::Client::API_VERSION )
|
253
|
+
expect( msg.first['action'] ).to eq( 'fetch' )
|
254
|
+
|
255
|
+
expect( msg.last ).to be_a( Hash )
|
256
|
+
expect( msg.last ).to include( 'type' )
|
257
|
+
expect( msg.last['type'] ).to eq( 'host' )
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
it "can make a raw update request" do
|
262
|
+
req = client.make_update_request( duir: {error: "Something happened."} )
|
263
|
+
expect( req ).to be_a( String )
|
264
|
+
expect( req.encoding ).to eq( Encoding::ASCII_8BIT )
|
265
|
+
|
266
|
+
msg = unpack_message( req )
|
267
|
+
expect( msg ).to be_an( Array )
|
268
|
+
expect( msg.first ).to be_a( Hash )
|
269
|
+
expect( msg.first ).to include( 'version', 'action' )
|
270
|
+
expect( msg.first['version'] ).to eq( Arborist::Client::API_VERSION )
|
271
|
+
expect( msg.first['action'] ).to eq( 'update' )
|
272
|
+
|
273
|
+
expect( msg.last ).to be_a( Hash )
|
274
|
+
expect( msg.last ).to include( 'duir' )
|
275
|
+
expect( msg.last['duir'] ).to eq( 'error' => 'Something happened.' )
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
end
|
282
|
+
|