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