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
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
|
-
|