arborist 0.1.0 → 0.2.0.pre20170519125456
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml.gz.sig
DELETED
@@ -1,3 +0,0 @@
|
|
1
|
-
bZ_�Ov�97�~��2�+쳦�;鄀 ��#��D5ma��5�PÀ\,�G(� %A3��&���Z!�g��s�x4
|
2
|
-
���ʩ�V�da�oMʡ8��K��:���40�R����t˝&�� 3��/c����"Y�*��w�����N�;Q��#�~xZ��jw��J#�<�_���C\�X������ܘ5)a�$:��gLd)T��&�z)k��T)}�V��Dv��^�&��S�T$(� ��vE���Vn�M,]ST�x��i�OR�L���]�9qn������
|
3
|
-
�=�x=�8@fv~���c�gim))���f�@����ހh!�5^���¼Hp� �o 3ϸ�*�c���ꎛ]�c"�X��
|
data.tar.gz.sig
DELETED
Binary file
|
@@ -1,126 +0,0 @@
|
|
1
|
-
# -*- ruby -*-
|
2
|
-
#encoding: utf-8
|
3
|
-
|
4
|
-
require 'msgpack'
|
5
|
-
require 'loggability'
|
6
|
-
require 'rbczmq'
|
7
|
-
require 'arborist/manager' unless defined?( Arborist::Manager )
|
8
|
-
|
9
|
-
|
10
|
-
class Arborist::Manager::EventPublisher < ZMQ::Handler
|
11
|
-
extend Loggability,
|
12
|
-
Arborist::MethodUtilities
|
13
|
-
|
14
|
-
# Loggability API -- log to arborist's logger
|
15
|
-
log_to :arborist
|
16
|
-
|
17
|
-
|
18
|
-
### Create a new EventPublish that will publish events emitted by
|
19
|
-
### emitters on the specified +manager+ on the given +pollable+.
|
20
|
-
def initialize( pollitem, manager, reactor )
|
21
|
-
self.log.debug "Setting up a %p for %p (socket %p)" %
|
22
|
-
[ self.class, pollitem, pollitem.pollable ]
|
23
|
-
@pollable = pollitem.pollable
|
24
|
-
@pollitem = pollitem
|
25
|
-
@manager = manager
|
26
|
-
@reactor = reactor
|
27
|
-
@registered = true
|
28
|
-
@enabled = true
|
29
|
-
@event_queue = []
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
######
|
34
|
-
public
|
35
|
-
######
|
36
|
-
|
37
|
-
##
|
38
|
-
# True if the publisher is currently registered with the reactor (i.e., waiting
|
39
|
-
# to write published events).
|
40
|
-
attr_predicate :registered
|
41
|
-
|
42
|
-
##
|
43
|
-
# The Queue of pending events to publish
|
44
|
-
attr_reader :event_queue
|
45
|
-
|
46
|
-
##
|
47
|
-
# A on/off toggle for publishing events
|
48
|
-
attr_predicate :enabled
|
49
|
-
|
50
|
-
|
51
|
-
### Publish the specified +event+.
|
52
|
-
def publish( identifier, event )
|
53
|
-
return self unless self.enabled?
|
54
|
-
@event_queue << [ identifier, MessagePack.pack(event.to_h) ]
|
55
|
-
self.register
|
56
|
-
return self
|
57
|
-
end
|
58
|
-
|
59
|
-
|
60
|
-
### ZMQ::Handler API -- write events to the socket as it becomes writable.
|
61
|
-
def on_writable
|
62
|
-
unless @event_queue.empty?
|
63
|
-
tuple = @event_queue.shift
|
64
|
-
identifier, payload = *tuple
|
65
|
-
|
66
|
-
pollsocket = self.pollitem.pollable
|
67
|
-
pollsocket.sendm( identifier )
|
68
|
-
pollsocket.send( payload )
|
69
|
-
end
|
70
|
-
self.unregister if @event_queue.empty?
|
71
|
-
return true
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
|
-
### Stop accepting events to be published
|
76
|
-
def shutdown
|
77
|
-
@enabled = false
|
78
|
-
return if @event_queue.empty?
|
79
|
-
|
80
|
-
start = Time.now
|
81
|
-
timeout = start + (@manager.linger.to_f / 2000)
|
82
|
-
|
83
|
-
self.log.warn "Waiting to empty the event queue..."
|
84
|
-
until @event_queue.empty?
|
85
|
-
sleep 0.1
|
86
|
-
break if Time.now > timeout
|
87
|
-
end
|
88
|
-
self.log.warn " ... waited %0.1f seconds" % [ Time.now - start ]
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#########
|
94
|
-
protected
|
95
|
-
#########
|
96
|
-
|
97
|
-
### Register the publisher with the reactor if it's not already.
|
98
|
-
def register
|
99
|
-
count ||= 0
|
100
|
-
@reactor.register( self.pollitem ) unless @registered
|
101
|
-
# self.log.info "Publisher socket is now registered!"
|
102
|
-
@registered = true
|
103
|
-
rescue => err
|
104
|
-
# :TODO:
|
105
|
-
# This is to work around a weird error that happens sometimes when registering:
|
106
|
-
# Sys error location: loop.c:424
|
107
|
-
# which then raises a RuntimeError with "Socket operation on non-socket". This is
|
108
|
-
# probably due to a race somewhere, but we can't find it. So this works (at least
|
109
|
-
# for the specs) in the meantime.
|
110
|
-
raise unless err.message.include?( "Socket operation on non-socket" )
|
111
|
-
count += 1
|
112
|
-
raise "Gave up trying to register %p with the reactor" % [ self.pollitem ] if count > 5
|
113
|
-
warn "Retrying registration!"
|
114
|
-
retry
|
115
|
-
end
|
116
|
-
|
117
|
-
|
118
|
-
### Unregister the publisher from the reactor if it's registered.
|
119
|
-
def unregister
|
120
|
-
@reactor.remove( self.pollitem ) if @registered
|
121
|
-
@registered = false
|
122
|
-
end
|
123
|
-
|
124
|
-
|
125
|
-
end # class Arborist::Manager::EventPublisher
|
126
|
-
|
@@ -1,302 +0,0 @@
|
|
1
|
-
# -*- ruby -*-
|
2
|
-
#encoding: utf-8
|
3
|
-
|
4
|
-
require 'msgpack'
|
5
|
-
require 'loggability'
|
6
|
-
require 'rbczmq'
|
7
|
-
require 'arborist/manager' unless defined?( Arborist::Manager )
|
8
|
-
|
9
|
-
|
10
|
-
class Arborist::Manager::TreeAPI < ZMQ::Handler
|
11
|
-
extend Loggability,
|
12
|
-
Arborist::MethodUtilities
|
13
|
-
|
14
|
-
|
15
|
-
# Loggability API -- log to the arborist logger
|
16
|
-
log_to :arborist
|
17
|
-
|
18
|
-
|
19
|
-
### Create the TreeAPI handler that will read requests from the specified +pollable+
|
20
|
-
### and call into the +manager+ to respond to them.
|
21
|
-
def initialize( pollable, manager )
|
22
|
-
@pollitem = pollable
|
23
|
-
@enabled = true
|
24
|
-
@manager = manager
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
##
|
29
|
-
# True if the Tree API is accepting commands
|
30
|
-
attr_predicate :enabled
|
31
|
-
|
32
|
-
|
33
|
-
### ZMQ::Handler API -- Read and handle an incoming request.
|
34
|
-
def on_readable
|
35
|
-
request = self.recv
|
36
|
-
response = self.handle_request( request )
|
37
|
-
self.send( response )
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
### Handle the specified +raw_request+ and return a response.
|
42
|
-
def handle_request( raw_request )
|
43
|
-
raise "Manager is shutting down" unless self.enabled?
|
44
|
-
|
45
|
-
header, body = self.parse_request( raw_request )
|
46
|
-
return self.dispatch_request( header, body )
|
47
|
-
|
48
|
-
rescue => err
|
49
|
-
self.log.error "%p: %s" % [ err.class, err.message ]
|
50
|
-
err.backtrace.each {|frame| self.log.debug " #{frame}" }
|
51
|
-
|
52
|
-
errtype = case err
|
53
|
-
when Arborist::RequestError,
|
54
|
-
Arborist::ConfigError,
|
55
|
-
Arborist::NodeError
|
56
|
-
'client'
|
57
|
-
else
|
58
|
-
'server'
|
59
|
-
end
|
60
|
-
|
61
|
-
return self.error_response( errtype, err.message )
|
62
|
-
end
|
63
|
-
|
64
|
-
|
65
|
-
### Attempt to dispatch a request given its +header+ and +body+, and return the
|
66
|
-
### serialized response.
|
67
|
-
def dispatch_request( header, body )
|
68
|
-
handler = self.lookup_request_action( header ) or
|
69
|
-
raise Arborist::RequestError, "No such action '%s'" % [ header['action'] ]
|
70
|
-
|
71
|
-
response = handler.call( header, body )
|
72
|
-
|
73
|
-
return response
|
74
|
-
end
|
75
|
-
|
76
|
-
|
77
|
-
### Given a request +header+, return a #call-able object that can handle the response.
|
78
|
-
def lookup_request_action( header )
|
79
|
-
raise Arborist::RequestError, "unsupported version %d" % [ header['version'] ] unless
|
80
|
-
header['version'] == 1
|
81
|
-
|
82
|
-
handler_name = "handle_%s_request" % [ header['action'] ]
|
83
|
-
return nil unless self.respond_to?( handler_name )
|
84
|
-
|
85
|
-
return self.method( handler_name )
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
### Build an error response message for the specified +category+ and +reason+.
|
90
|
-
def error_response( category, reason )
|
91
|
-
msg = [
|
92
|
-
{ category: category, reason: reason, success: false, version: 1 }
|
93
|
-
]
|
94
|
-
return MessagePack.pack( msg )
|
95
|
-
end
|
96
|
-
|
97
|
-
|
98
|
-
### Build a successful response with the specified +body+.
|
99
|
-
def successful_response( body )
|
100
|
-
msg = [
|
101
|
-
{ success: true, version: 1 },
|
102
|
-
body
|
103
|
-
]
|
104
|
-
return MessagePack.pack( msg )
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
### Validate and return a parsed msgpack +raw_request+.
|
109
|
-
def parse_request( raw_request )
|
110
|
-
tuple = begin
|
111
|
-
MessagePack.unpack( raw_request )
|
112
|
-
rescue => err
|
113
|
-
raise Arborist::RequestError, err.message
|
114
|
-
end
|
115
|
-
|
116
|
-
raise Arborist::RequestError, 'not a tuple' unless tuple.is_a?( Array )
|
117
|
-
raise Arborist::RequestError, 'incorrect length' if tuple.length.zero? || tuple.length > 2
|
118
|
-
|
119
|
-
header, body = *tuple
|
120
|
-
raise Arborist::RequestError, "header is not a Map" unless
|
121
|
-
header.is_a?( Hash )
|
122
|
-
raise Arborist::RequestError, "missing required header 'version'" unless
|
123
|
-
header.key?( 'version' )
|
124
|
-
raise Arborist::RequestError, "missing required header 'action'" unless
|
125
|
-
header.key?( 'action' )
|
126
|
-
|
127
|
-
raise Arborist::RequestError, "body must be Nil, a Map, or an Array of Maps" unless
|
128
|
-
body.is_a?( Hash ) || body.nil? ||
|
129
|
-
( body.is_a?(Array) && body.all? {|obj| obj.is_a?(Hash) } )
|
130
|
-
|
131
|
-
return header, body
|
132
|
-
end
|
133
|
-
|
134
|
-
|
135
|
-
### Return a response to the `status` action.
|
136
|
-
def handle_status_request( header, body )
|
137
|
-
self.log.debug "STATUS: %p" % [ header ]
|
138
|
-
return successful_response(
|
139
|
-
server_version: Arborist::VERSION,
|
140
|
-
state: @manager.running? ? 'running' : 'not running',
|
141
|
-
uptime: @manager.uptime,
|
142
|
-
nodecount: @manager.nodecount
|
143
|
-
)
|
144
|
-
end
|
145
|
-
|
146
|
-
|
147
|
-
### Return a response to the `subscribe` action.
|
148
|
-
def handle_subscribe_request( header, body )
|
149
|
-
self.log.debug "SUBSCRIBE: %p" % [ header ]
|
150
|
-
event_type = header[ 'event_type' ]
|
151
|
-
node_identifier = header[ 'identifier' ]
|
152
|
-
|
153
|
-
body = [ body ] unless body.is_a?( Array )
|
154
|
-
positive = body.shift
|
155
|
-
negative = body.shift || {}
|
156
|
-
|
157
|
-
subscription = @manager.
|
158
|
-
create_subscription( node_identifier, event_type, positive, negative )
|
159
|
-
|
160
|
-
return successful_response([ subscription.id ])
|
161
|
-
end
|
162
|
-
|
163
|
-
|
164
|
-
### Return a response to the `unsubscribe` action.
|
165
|
-
def handle_unsubscribe_request( header, body )
|
166
|
-
self.log.debug "UNSUBSCRIBE: %p" % [ header ]
|
167
|
-
subscription_id = header[ 'subscription_id' ] or
|
168
|
-
return error_response( 'client', 'No identifier specified for UNSUBSCRIBE.' )
|
169
|
-
subscription = @manager.remove_subscription( subscription_id ) or
|
170
|
-
return successful_response( nil )
|
171
|
-
|
172
|
-
return successful_response(
|
173
|
-
event_type: subscription.event_type,
|
174
|
-
criteria: subscription.criteria
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
|
179
|
-
### Return a repsonse to the `list` action.
|
180
|
-
def handle_list_request( header, body )
|
181
|
-
self.log.debug "LIST: %p" % [ header ]
|
182
|
-
from = header['from'] || '_'
|
183
|
-
depth = header['depth']
|
184
|
-
|
185
|
-
start_node = @manager.nodes[ from ]
|
186
|
-
self.log.debug " Listing nodes under %p" % [ start_node ]
|
187
|
-
iter = if depth
|
188
|
-
self.log.debug " depth limited to %d" % [ depth ]
|
189
|
-
@manager.depth_limited_enumerator_for( start_node, depth )
|
190
|
-
else
|
191
|
-
self.log.debug " no depth limit"
|
192
|
-
@manager.enumerator_for( start_node )
|
193
|
-
end
|
194
|
-
data = iter.map( &:to_h )
|
195
|
-
self.log.debug " got data for %d nodes" % [ data.length ]
|
196
|
-
|
197
|
-
return successful_response( data )
|
198
|
-
end
|
199
|
-
|
200
|
-
|
201
|
-
### Return a response to the 'fetch' action.
|
202
|
-
def handle_fetch_request( header, body )
|
203
|
-
self.log.debug "FETCH: %p" % [ header ]
|
204
|
-
|
205
|
-
include_down = header['include_down']
|
206
|
-
values = if header.key?( 'return' )
|
207
|
-
header['return'] || []
|
208
|
-
else
|
209
|
-
nil
|
210
|
-
end
|
211
|
-
|
212
|
-
body = [ body ] unless body.is_a?( Array )
|
213
|
-
positive = body.shift
|
214
|
-
negative = body.shift || {}
|
215
|
-
states = @manager.fetch_matching_node_states( positive, values, include_down, negative )
|
216
|
-
|
217
|
-
return successful_response( states )
|
218
|
-
end
|
219
|
-
|
220
|
-
|
221
|
-
### Update nodes using the data from the update request's +body+.
|
222
|
-
def handle_update_request( header, body )
|
223
|
-
self.log.debug "UPDATE: %p" % [ header ]
|
224
|
-
|
225
|
-
unless body.respond_to?( :each )
|
226
|
-
return error_response( 'client', 'Malformed update: body does not respond to #each' )
|
227
|
-
end
|
228
|
-
|
229
|
-
body.each do |identifier, properties|
|
230
|
-
@manager.update_node( identifier, properties )
|
231
|
-
end
|
232
|
-
|
233
|
-
return successful_response( nil )
|
234
|
-
end
|
235
|
-
|
236
|
-
|
237
|
-
### Remove a node and its children.
|
238
|
-
def handle_prune_request( header, body )
|
239
|
-
self.log.debug "PRUNE: %p" % [ header ]
|
240
|
-
|
241
|
-
identifier = header[ 'identifier' ] or
|
242
|
-
return error_response( 'client', 'No identifier specified for PRUNE.' )
|
243
|
-
node = @manager.remove_node( identifier )
|
244
|
-
|
245
|
-
return successful_response( node ? true : nil )
|
246
|
-
end
|
247
|
-
|
248
|
-
|
249
|
-
### Add a node
|
250
|
-
def handle_graft_request( header, body )
|
251
|
-
self.log.debug "GRAFT: %p" % [ header ]
|
252
|
-
|
253
|
-
identifier = header[ 'identifier' ] or
|
254
|
-
return error_response( 'client', 'No identifier specified for GRAFT.' )
|
255
|
-
type = header[ 'type' ] or
|
256
|
-
return error_response( 'client', 'No type specified for GRAFT.' )
|
257
|
-
parent = header[ 'parent' ] || '_'
|
258
|
-
parent_node = @manager.nodes[ parent ] or
|
259
|
-
return error_response( 'client', 'No parent node found for %s.' % [parent] )
|
260
|
-
|
261
|
-
self.log.debug "Grafting a new %s node under %p" % [ type, parent_node ]
|
262
|
-
|
263
|
-
# If the parent has a factory method for the node type, use it, otherwise
|
264
|
-
# use the Pluggability factory
|
265
|
-
node = if parent_node.respond_to?( type )
|
266
|
-
parent_node.method( type ).call( identifier, body )
|
267
|
-
else
|
268
|
-
body.merge!( parent: parent )
|
269
|
-
Arborist::Node.create( type, identifier, body )
|
270
|
-
end
|
271
|
-
|
272
|
-
@manager.add_node( node )
|
273
|
-
|
274
|
-
return successful_response( node ? node.identifier : nil )
|
275
|
-
end
|
276
|
-
|
277
|
-
|
278
|
-
### Modify a node's operational attributes
|
279
|
-
def handle_modify_request( header, body )
|
280
|
-
self.log.debug "MODIFY: %p" % [ header ]
|
281
|
-
|
282
|
-
identifier = header[ 'identifier' ] or
|
283
|
-
return error_response( 'client', 'No identifier specified for MODIFY.' )
|
284
|
-
return error_response( 'client', "Unable to MODIFY root node." ) if identifier == '_'
|
285
|
-
node = @manager.nodes[ identifier ] or
|
286
|
-
return error_response( 'client', "No such node %p" % [identifier] )
|
287
|
-
|
288
|
-
self.log.debug "Modifying operational attributes of the %s node: %p" % [ identifier, body ]
|
289
|
-
|
290
|
-
node.modify( body )
|
291
|
-
|
292
|
-
return successful_response( nil )
|
293
|
-
end
|
294
|
-
|
295
|
-
|
296
|
-
### Disable the API, returning errors for any future requests.
|
297
|
-
def shutdown
|
298
|
-
@enabled = false
|
299
|
-
end
|
300
|
-
|
301
|
-
end # class Arborist::Manager::TreeAPI
|
302
|
-
|
@@ -1,65 +0,0 @@
|
|
1
|
-
#!/usr/bin/env rspec -cfd
|
2
|
-
|
3
|
-
require_relative '../../spec_helper'
|
4
|
-
|
5
|
-
require 'arborist/manager/event_publisher'
|
6
|
-
|
7
|
-
|
8
|
-
describe Arborist::Manager::EventPublisher do
|
9
|
-
|
10
|
-
let( :socket ) { instance_double( ZMQ::Socket::Pub ) }
|
11
|
-
let( :pollitem ) { instance_double( ZMQ::Pollitem, pollable: socket ) }
|
12
|
-
let( :zloop ) { instance_double( ZMQ::Loop ) }
|
13
|
-
|
14
|
-
let( :manager ) { Arborist::Manager.new }
|
15
|
-
let( :event ) { Arborist::Event.create(TestEvent, 'stuff') }
|
16
|
-
|
17
|
-
let( :publisher ) { described_class.new(pollitem, manager, zloop) }
|
18
|
-
|
19
|
-
|
20
|
-
it "starts out registered for writing" do
|
21
|
-
expect( publisher ).to be_registered
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
it "unregisters itself if told to write with an empty event queue" do
|
26
|
-
expect( zloop ).to receive( :remove ).with( pollitem )
|
27
|
-
expect {
|
28
|
-
publisher.on_writable
|
29
|
-
}.to change { publisher.registered? }.to( false )
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
it "registers itself if it's not already when an event is appended" do
|
34
|
-
# Cause the socket to become unregistered
|
35
|
-
allow( zloop ).to receive( :remove )
|
36
|
-
publisher.on_writable
|
37
|
-
|
38
|
-
expect( zloop ).to receive( :register ).with( pollitem )
|
39
|
-
|
40
|
-
expect {
|
41
|
-
publisher.publish( 'identifier-00aa', event )
|
42
|
-
}.to change { publisher.registered? }.to( true )
|
43
|
-
end
|
44
|
-
|
45
|
-
|
46
|
-
it "publishes events with their identifier" do
|
47
|
-
identifier = '65b2430b-6855-4961-ab46-d742cf4456a1'
|
48
|
-
|
49
|
-
expect( socket ).to receive( :sendm ).with( identifier )
|
50
|
-
expect( socket ).to receive( :send ) do |raw_data|
|
51
|
-
ev = MessagePack.unpack( raw_data )
|
52
|
-
expect( ev ).to include( 'type', 'data' )
|
53
|
-
|
54
|
-
expect( ev['type'] ).to eq( 'test.event' )
|
55
|
-
expect( ev['data'] ).to eq( 'stuff' )
|
56
|
-
end
|
57
|
-
expect( zloop ).to receive( :remove ).with( pollitem )
|
58
|
-
|
59
|
-
publisher.publish( identifier, event )
|
60
|
-
publisher.on_writable
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
|