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