carnivore 0.1.10 → 0.2.0

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.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # v0.2.0
2
+ * Remove `fog` from dependency list
3
+ * Add common spec helper for testing
4
+ * Only start sources if callbacks are defined
5
+ * Add custom supervisor with isolated registry
6
+ * Include auto-restart support
7
+ * Loop local messages internally instead of transmit/retrieve loop
8
+ * Use auto loading to clean things up
9
+
1
10
  # v0.1.10
2
11
  * Remove builtin sources
3
12
  * Allow optional auto-symbolize
data/README.md CHANGED
@@ -30,7 +30,7 @@ Under the hood, callbacks are built into `Carnivore::Callback`
30
30
  instances. This class can be subclassed and provided directly
31
31
  instead of a simple block. This has the added bonus of being
32
32
  able to define the number of worker instances to be created
33
- for the callback (blocks default to 1):
33
+ for the callback (blocks only get 1):
34
34
 
35
35
  ```ruby
36
36
  require 'carnivore'
@@ -56,7 +56,7 @@ end.start!
56
56
 
57
57
  ### Block execution
58
58
 
59
- It is important to note that when providing blocks, they will
59
+ It is important to note that when providing blocks they will
60
60
  lose all reference to the scope in which they are defined. This
61
61
  is due to how `Callback` is implemented and is by design. Simply
62
62
  ensure that blocks are fully autonomous and everything will be
data/carnivore.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.homepage = 'https://github.com/carnivore-rb/carnivore'
10
10
  s.description = 'Message processing helper'
11
11
  s.require_path = 'lib'
12
- s.add_dependency 'fog'
13
12
  s.add_dependency 'celluloid'
14
13
  s.add_dependency 'mixlib-config'
15
- s.files = Dir['**/*']
14
+ s.add_dependency 'multi_json'
15
+ s.files = Dir['lib/**/*'] + %w(carnivore.gemspec README.md CHANGELOG.md)
16
16
  end
@@ -0,0 +1,13 @@
1
+ # Register all base autoloading requirements
2
+
3
+ module Carnivore
4
+ autoload :Config, 'carnivore/config'
5
+ autoload :Callback, 'carnivore/callback'
6
+ autoload :Container, 'carnivore/container'
7
+ autoload :Error, 'carnivore/errors'
8
+ autoload :Message, 'carnivore/message'
9
+ autoload :Source, 'carnivore/source'
10
+ autoload :Supervisor, 'carnivore/supervisor'
11
+ autoload :Utils, 'carnivore/utils'
12
+ autoload :Version, 'carnivore/version'
13
+ end
@@ -1,4 +1,4 @@
1
- require 'carnivore/utils'
1
+ require 'carnivore'
2
2
 
3
3
  module Carnivore
4
4
  class Callback
@@ -9,10 +9,14 @@ module Carnivore
9
9
  end
10
10
 
11
11
  include Celluloid
12
- include Utils::Logging
12
+ include Carnivore::Utils::Logging
13
13
 
14
14
  attr_reader :name
15
15
 
16
+ # name:: Name of the callback
17
+ # block:: Optional `Proc` to define the callback behavior
18
+ # Creates a new callback. Optional block to define callback
19
+ # behavior must be passed as a `Proc` instance, not a block.
16
20
  def initialize(name, block=nil)
17
21
  @name = name
18
22
  if(block.nil? && self.class == Callback)
@@ -42,9 +46,10 @@ module Carnivore
42
46
  # Pass message to registered callbacks
43
47
  def call(message)
44
48
  if(valid?(message))
49
+ debug ">> Received message is valid for this callback (#{message})"
45
50
  execute(message)
46
51
  else
47
- debug 'Received message not valid for this callback'
52
+ debug "Invalid message for this callback #{message})"
48
53
  end
49
54
  rescue => e
50
55
  error "[callback: #{self}, source: #{message[:source]}, message: #{message[:message].object_id}]: #{e.class} - #{e}"
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'mixlib/config'
3
+ require 'carnivore'
3
4
 
4
5
  module Carnivore
5
6
  class Config
@@ -8,6 +9,8 @@ module Carnivore
8
9
 
9
10
  class << self
10
11
 
12
+ # v:: Boolean value
13
+ # Set/get automatic symbolization of hash keys
11
14
  def auto_symbolize(v=nil)
12
15
  unless(v.nil?)
13
16
  @hash_symbolizer = !!v
@@ -51,9 +54,7 @@ module Carnivore
51
54
  # Config.get(:other_app, :port) => nil
52
55
  # Config.get(:my_app, :mail, :server) => nil
53
56
  def get(*ary)
54
- value = ary.flatten.inject(self) do |memo, key|
55
- memo[key.to_s] || memo[key.to_sym] || break
56
- end
57
+ value = Carnivore::Utils.retrieve(self, *ary)
57
58
  if(value.is_a?(Hash) && auto_symbolize)
58
59
  Carnivore::Utils.symbolize_hash(value)
59
60
  else
@@ -1,4 +1,4 @@
1
- require 'carnivore/utils'
1
+ require 'carnivore'
2
2
  require 'celluloid/logger'
3
3
 
4
4
  module Carnivore
@@ -0,0 +1,7 @@
1
+ require 'carnivore'
2
+
3
+ module Carnivore
4
+ class Error < StandardError
5
+ class DeadSupervisor < Error; end
6
+ end
7
+ end
@@ -1,4 +1,4 @@
1
- require 'carnivore/source'
1
+ require 'carnivore'
2
2
 
3
3
  module Carnivore
4
4
  class Message
@@ -12,22 +12,29 @@ module Carnivore
12
12
  @args = args.dup
13
13
  end
14
14
 
15
+ # Helper method to return keys available from `args`
16
+ def keys
17
+ args.keys
18
+ end
19
+
20
+ # k:: key
21
+ # Accessor into message
15
22
  def [](k)
16
- if(k.to_sym == :source)
17
- Celluloid::Actor[@args[:source]]
18
- else
19
- @args[k.to_sym] || @args[k.to_s]
20
- end
23
+ args[k.to_sym] || args[k.to_s]
21
24
  end
22
25
 
26
+ # args:: Arguments
27
+ # Confirm message was received on source
23
28
  def confirm!(*args)
24
- self[:source].confirm(*([self] + args))
29
+ self[:source].confirm(*([self] + args).flatten(1).compact)
25
30
  end
26
31
 
32
+ # Formatted inspection string
27
33
  def inspect
28
- "<Carnivore::Message[#{self.object_id}] @args=#{args}>"
34
+ "<Carnivore::Message[#{self.object_id}] @args=#{args.inspect}>"
29
35
  end
30
36
 
37
+ # String representation
31
38
  def to_s
32
39
  "<Carnivore::Message:#{self.object_id}>"
33
40
  end
@@ -1,29 +1,48 @@
1
- require 'celluloid'
2
- require 'carnivore/config'
3
- require 'carnivore/source'
4
- require 'carnivore/container'
1
+ require 'carnivore/autoloader'
5
2
 
6
3
  module Carnivore
7
4
  class << self
5
+
6
+ # block:: Block of configuration
7
+ # Add configuration to Carnivore
8
8
  def configure(&block)
9
9
  mod = Container.new
10
10
  mod.instance_exec(mod, &block)
11
11
  self
12
12
  end
13
13
 
14
+ # Start carnivore
14
15
  def start!
15
16
  supervisor = nil
16
17
  begin
17
18
  require 'carnivore/supervisor'
18
- supervisor = Carnivore::Supervisor.run!
19
+ supervisor = Carnivore::Supervisor.build!
19
20
  Source.sources.each do |source|
21
+ source.klass.reset_comms!
20
22
  supervisor.supervise_as(
21
23
  source.source_hash[:name],
22
24
  source.klass,
23
- source.source_hash
25
+ source.source_hash.dup
24
26
  )
25
27
  end
26
- supervisor.wait(:kill_all_humans)
28
+ loop do
29
+ # We do a sleep loop so we can periodically check on the
30
+ # supervisor and ensure it is still alive. If it has died,
31
+ # raise exception to allow cleanup and restart attempt
32
+ sleep Carnivore::Config.get(:carnivore, :supervisor, :poll) || 5 while supervisor.alive?
33
+ Celluloid::Logger.error 'Carnivore supervisor has died!'
34
+ raise Carnivore::Error::DeadSupervisor.new
35
+ end
36
+ rescue Carnivore::Error::DeadSupervisor
37
+ Celluloid::Logger.warn "Received dead supervisor exception. Attempting to restart."
38
+ begin
39
+ supervisor.terminate
40
+ rescue => e
41
+ Celluloid::Logger.debug "Exception raised during supervisor termination (restart cleanup): #{e}"
42
+ end
43
+ Celluloid::Logger.debug "Pausing restart for 10 seconds to prevent restart thrashing cycles"
44
+ sleep 10
45
+ retry
27
46
  rescue Exception => e
28
47
  supervisor.terminate
29
48
  # Gracefully shut down
@@ -1,31 +1,11 @@
1
+ require 'digest/sha2'
1
2
  require 'celluloid'
2
- require 'carnivore/utils'
3
- require 'carnivore/callback'
4
- require 'carnivore/message'
3
+ require 'carnivore'
5
4
 
6
5
  module Carnivore
7
6
  class Source
8
7
 
9
- class SourceContainer
10
-
11
- attr_reader :klass
12
- attr_reader :source_hash
13
-
14
- # class_name:: Name of source class
15
- # args:: argument hash to pass to source instance
16
- def initialize(class_name, args={})
17
- @klass = class_name
18
- @source_hash = args || {}
19
- @source_hash[:callbacks] = {}
20
- end
21
-
22
- # name:: Name of callback
23
- # klass:: Class of callback (optional)
24
- # Add a callback to a source via Class or block
25
- def add_callback(name, klass=nil, &block)
26
- @source_hash[:callbacks][name] = klass || block
27
- end
28
- end
8
+ autoload :SourceContainer, 'carnivore/source_container'
29
9
 
30
10
  class << self
31
11
 
@@ -48,12 +28,17 @@ module Carnivore
48
28
  inst
49
29
  end
50
30
 
31
+ # type:: Symbol of type of source
32
+ # require_path:: Path to feed to `require`
33
+ # Registers a source
51
34
  def provide(type, require_path)
52
35
  @source_klass ||= {}
53
36
  @source_klass[type.to_sym] = require_path
54
37
  true
55
38
  end
56
39
 
40
+ # type: Symbol of source type
41
+ # Returns register path for given type of source
57
42
  def require_path(type)
58
43
  @source_klass ||= {}
59
44
  @source_klass[type.to_sym]
@@ -82,6 +67,19 @@ module Carnivore
82
67
  def sources
83
68
  @sources ? @sources.values : []
84
69
  end
70
+
71
+ def reset_comms!
72
+ self.class_eval do
73
+ unless(method_defined?(:reset_communications?))
74
+ alias_method :custom_transmit, :transmit
75
+ alias_method :transmit, :_transmit
76
+ def reset_communications?
77
+ true
78
+ end
79
+ end
80
+ end
81
+ end
82
+
85
83
  end
86
84
 
87
85
  include Celluloid
@@ -93,14 +91,22 @@ module Carnivore
93
91
  attr_reader :auto_process
94
92
  attr_reader :run_process
95
93
  attr_reader :callback_supervisor
94
+ attr_reader :message_registry
95
+ attr_reader :message_loop
96
+ attr_reader :processing
96
97
 
97
98
  def initialize(args={})
98
99
  @callbacks = []
100
+ @message_loop = []
99
101
  @callback_names = {}
100
102
  @auto_process = args.fetch(:auto_process, true)
101
- @run_process = @auto_process
103
+ @run_process = true
102
104
  @auto_confirm = !!args[:auto_confirm]
103
- @callback_supervisor = Celluloid::SupervisionGroup.run!
105
+ @callback_supervisor = Carnivore::Supervisor.create!.last
106
+ if(args[:prevent_duplicates])
107
+ init_registry
108
+ end
109
+ @processing = false
104
110
  @name = args[:name] || Celluloid.uuid
105
111
  if(args[:callbacks])
106
112
  args[:callbacks].each do |name, block|
@@ -109,45 +115,72 @@ module Carnivore
109
115
  end
110
116
  setup(args)
111
117
  connect
112
- async.process if @auto_process
118
+ if(auto_process && !callbacks.empty?)
119
+ async.process
120
+ end
113
121
  rescue => e
114
122
  debug "Failed to initialize: #{self} - #{e.class}: #{e}\n#{e.backtrace.join("\n")}"
115
123
  raise
116
124
  end
117
125
 
126
+ # Ensure we cleanup our internal supervisor before bailing out
127
+ def terminate
128
+ callback_supervisor.terminate
129
+ super
130
+ end
131
+
132
+ # Automatically confirm messages after dispatch
118
133
  def auto_confirm?
119
134
  @auto_confirm
120
135
  end
121
136
 
137
+ # Return string for inspection
122
138
  def inspect
123
139
  "<#{self.class.name}:#{object_id} @name=#{name} @callbacks=#{Hash[*callbacks.map{|k,v| [k,v.object_id]}.flatten]}>"
124
140
  end
125
141
 
142
+ # Return string of instance
126
143
  def to_s
127
144
  "<#{self.class.name}:#{object_id} @name=#{name}>"
128
145
  end
129
146
 
147
+ # args:: Argument hash used to initialize instance
148
+ # Setup called during initialization for child sources to override
130
149
  def setup(args={})
131
150
  debug 'No custom setup declared'
132
151
  end
133
152
 
153
+ # args:: Argument hash
154
+ # Connection method to be overridden in child sources
134
155
  def connect(args={})
135
156
  debug 'No custom connect declared'
136
157
  end
137
158
 
159
+ # args:: number of messages to read
160
+ # Returns messages from source
138
161
  def receive(n=1)
139
162
  raise NoMethodError.new('Abstract method not valid for runtime')
140
163
  end
141
164
 
142
- def transmit(message, original_message, args={})
165
+ # message:: Payload to transmit
166
+ # original_message:: Original `Carnivore::Message`
167
+ # args:: Custom arguments
168
+ # Transmit message on source
169
+ def transmit(message, original_message=nil, args={})
143
170
  raise NoMethodError.new('Abstract method not valid for runtime')
144
171
  end
145
172
 
173
+ # message:: Carnivore::Message
174
+ # Confirm receipt of the message on source
146
175
  def confirm(message)
147
176
  debug 'No custom confirm declared'
148
177
  end
149
178
 
150
- def add_callback(name, block_or_class)
179
+ # callback_name:: Name of callback
180
+ # block_or_class:: Carnivore::Callback class or a block
181
+ # Adds the given callback to the source for message processing
182
+ def add_callback(callback_name, block_or_class)
183
+ name = "#{self.name}:#{callback_name}"
151
184
  if(block_or_class.is_a?(Class))
152
185
  size = block_or_class.workers || 1
153
186
  if(size < 1)
@@ -155,19 +188,21 @@ module Carnivore
155
188
  return self
156
189
  elsif(size == 1)
157
190
  debug "Adding callback class (#{block_or_class}) under supervision. Name: #{callback_name(name)}"
158
- @callback_supervisor.supervise_as callback_name(name), block_or_class, name
191
+ callback_supervisor.supervise_as callback_name(name), block_or_class, name
159
192
  else
160
193
  debug "Adding callback class (#{block_or_class}) under supervision pool (#{size} workers). Name: #{callback_name(name)}"
161
- @callback_supervisor.pool block_or_class, as: callback_name(name), size: size, args: [name]
194
+ callback_supervisor.pool block_or_class, as: callback_name(name), size: size, args: [name]
162
195
  end
163
196
  else
164
197
  debug "Adding custom callback class from block (#{block_or_class}) under supervision. Name: #{callback_name(name)}"
165
- @callback_supervisor.supervise_as callback_name(name), Callback, name, block_or_class
198
+ callback_supervisor.supervise_as callback_name(name), Callback, name, block_or_class
166
199
  end
167
- @callbacks.push(name).uniq!
200
+ callbacks.push(name).uniq!
168
201
  self
169
202
  end
170
203
 
204
+ # name:: Name of callback
205
+ # Remove the named callback from the source
171
206
  def remove_callback(name)
172
207
  unless(@callbacks.include?(callback_name(name)))
173
208
  raise NameError.new("Failed to locate callback named: #{name}")
@@ -177,6 +212,8 @@ module Carnivore
177
212
  self
178
213
  end
179
214
 
215
+ # name:: Name of callback
216
+ # Returns namespaced name (prefixed with source name and instance id)
180
217
  def callback_name(name)
181
218
  unless(@callback_names[name])
182
219
  @callback_names[name] = [@name, self.object_id, name].join(':').to_sym
@@ -184,26 +221,99 @@ module Carnivore
184
221
  @callback_names[name]
185
222
  end
186
223
 
224
+ # msg:: New message received from source
225
+ # Returns formatted Carnivore::Message
187
226
  def format(msg)
188
- Message.new(
189
- :message => msg,
190
- :source => self.name
191
- )
227
+ actor = Carnivore::Supervisor.supervisor[name]
228
+ if(actor)
229
+ Message.new(
230
+ :message => msg,
231
+ :source => actor
232
+ )
233
+ else
234
+ abort "Failed to locate self in registry (#{name})"
235
+ end
192
236
  end
193
237
 
194
- def process
195
- while(run_process)
196
- msgs = Array(receive).flatten.compact.map do |m|
197
- format(m)
238
+ # m:: Carnivore::Message
239
+ # Returns true if message is valid to be processed
240
+ def valid_message?(m)
241
+ if(message_registry)
242
+ if(message_registry.valid?(m))
243
+ true
244
+ else
245
+ warn "Message was already received. Discarding: #{m.inspect}"
246
+ false
198
247
  end
199
- msgs.each do |msg|
200
- @callbacks.each do |name|
201
- debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{callback_name(name)})>"
202
- Celluloid::Actor[callback_name(name)].async.call(msg)
248
+ else
249
+ true
250
+ end
251
+ end
252
+
253
+ # args:: Arguments
254
+ # Start processing messages from source
255
+ def process(*args)
256
+ begin
257
+ while(run_process && !callbacks.empty?)
258
+ @processing = true
259
+ loop_msgs = future.loop_receive unless loop_msgs
260
+ remote_msgs = future.receive unless remote_msgs
261
+ if(loop_msgs.ready?)
262
+ msgs = loop_msgs.value
263
+ loop_msgs = nil
264
+ elsif(remote_msgs.ready?)
265
+ msgs = remote_msgs.value
266
+ remote_msgs = nil
267
+ else
268
+ msgs = []
269
+ end
270
+ msgs = [msgs].flatten.compact.map do |m|
271
+ if(valid_message?(m))
272
+ format(m)
273
+ end
274
+ end.compact
275
+ msgs.each do |msg|
276
+ @callbacks.each do |name|
277
+ debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{callback_name(name)})>"
278
+ callback_supervisor[callback_name(name)].async.call(msg)
279
+ end
203
280
  end
281
+ sleep(1) if msgs.empty?
204
282
  end
283
+ ensure
284
+ @processing = false
205
285
  end
206
286
  end
207
287
 
288
+ # args:: unused
289
+ # Return queued message from internal loop
290
+ def loop_receive(*args)
291
+ @message_loop.shift
292
+ end
293
+
294
+ # message:: Message for delivery
295
+ # original_message:: unused
296
+ # args:: unused
297
+ # Push message onto internal loop queue
298
+ def loop_transmit(message, original_message=nil, args={})
299
+ @message_loop.push message
300
+ end
301
+
302
+ # args:: transmit args
303
+ # Send to local loop if processing otherwise use regular transmit
304
+ def _transmit(*args)
305
+ if(processing)
306
+ loop_transmit(*args)
307
+ else
308
+ custom_transmit(*args)
309
+ end
310
+ end
311
+
312
+ # Load and initialize the message registry
313
+ def init_registry
314
+ require 'carnivore/message_registry'
315
+ @message_registry = MessageRegistry.new
316
+ end
317
+
208
318
  end
209
319
  end
@@ -0,0 +1,30 @@
1
+ require 'carnivore'
2
+
3
+ module Carnivore
4
+ class Source
5
+
6
+ # Container for holding source configuration. This allows setup to
7
+ # occur prior to the supervisor actually starting the sources
8
+ class SourceContainer
9
+
10
+ attr_reader :klass
11
+ attr_reader :source_hash
12
+
13
+ # class_name:: Name of source class
14
+ # args:: argument hash to pass to source instance
15
+ def initialize(class_name, args={})
16
+ @klass = class_name
17
+ @source_hash = args || {}
18
+ @source_hash[:callbacks] = {}
19
+ end
20
+
21
+ # name:: Name of callback
22
+ # klass:: Class of callback (optional)
23
+ # Add a callback to a source via Class or block
24
+ def add_callback(name, klass=nil, &block)
25
+ @source_hash[:callbacks][name] = klass || block
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ require 'carnivore'
2
+ require 'celluloid'
3
+ require 'minitest/autorun'
4
+
5
+ Celluloid.logger.level = 4
6
+
7
+ if(File.directory?(dir = File.join(Dir.pwd, 'test', 'specs')))
8
+ Dir.glob(File.join(dir, '*.rb')).each do |path|
9
+ require path
10
+ end
11
+ else
12
+ puts 'Failed to locate `test/specs` directory. Are you in project root directory?'
13
+ exit -1
14
+ end
15
+
16
+ MiniTest::Spec.before do
17
+ Celluloid.shutdown
18
+ Celluloid.boot
19
+ end
20
+
21
+ # Simple waiter method to stall testing
22
+ def source_wait(name='wait')
23
+ sleep(ENV.fetch("CARNIVORE_SOURCE_#{name.to_s.upcase}", 0.2).to_f)
24
+ end
25
+
26
+ # dummy store that should never be used for anything real
27
+ class MessageStore
28
+ class << self
29
+
30
+ def init
31
+ @messages = []
32
+ end
33
+
34
+ def messages
35
+ @messages
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ # dummy source to hold final tranmission and stuff payload in store
42
+ module Carnivore
43
+ class Source
44
+ class Spec < Source
45
+ def setup(*args)
46
+ MessageStore.init
47
+ end
48
+
49
+ def receive(*args)
50
+ wait(:forever)
51
+ end
52
+
53
+ def transmit(*args)
54
+ MessageStore.messages << args.first
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ Carnivore::Source.provide(:spec, 'carnivore/spec_helper')
@@ -1,4 +1,57 @@
1
+ require 'carnivore'
2
+ require 'celluloid/supervision_group'
3
+
1
4
  module Carnivore
2
5
  class Supervisor < Celluloid::SupervisionGroup
6
+
7
+ class << self
8
+
9
+ attr_reader :registry, :supervisor
10
+
11
+ # Build a new supervisor
12
+ def build!
13
+ @registry, @supervisor = create!
14
+ @supervisor
15
+ end
16
+
17
+ # Create a new supervisor
18
+ # Returns [registry,supervisor]
19
+ def create!
20
+ registry = Celluloid::Registry.new
21
+ [registry, run!(registry)]
22
+ end
23
+
24
+ # Destroy the registered supervisor
25
+ def terminate!
26
+ if(supervisor)
27
+ begin
28
+ supervisor.terminate
29
+ rescue Celluloid::DeadActorError
30
+ end
31
+ @supervisor = nil
32
+ @registry = nil
33
+ end
34
+ true
35
+ end
36
+
37
+ end
38
+
39
+ # name:: Name of source
40
+ # Return source
41
+ def [](name)
42
+ instance = @registry[name]
43
+ unless(instance)
44
+ if(member = @members.detect{|m| m && m.name.to_s == name.to_s})
45
+ Celluloid::Logger.warn "Found missing actor in member list. Attempting to restart manually."
46
+ restart_actor(member.actor, true)
47
+ instance = @registry[name]
48
+ unless(instance)
49
+ Celluloid::Logger.error "Actor restart failed to make it available in the registry! (#{name})"
50
+ end
51
+ end
52
+ end
53
+ instance
54
+ end
55
+
3
56
  end
4
57
  end
@@ -0,0 +1,28 @@
1
+ require 'celluloid'
2
+
3
+ module Carnivore
4
+ module Utils
5
+
6
+ module Logging
7
+
8
+ # Define base logging types
9
+ %w(debug info warn error).each do |key|
10
+ define_method(key) do |string|
11
+ log(key, string)
12
+ end
13
+ end
14
+
15
+ # Log message
16
+ def log(*args)
17
+ if(args.empty?)
18
+ Celluloid::Logger
19
+ else
20
+ severity, string = args
21
+ Celluloid::Logger.send(severity.to_sym, "#{self}: #{string}")
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ module Carnivore
2
+ module Utils
3
+ # Registry used for preventing duplicate message processing
4
+ class MessageRegistry
5
+ def initialize
6
+ @store = []
7
+ @size = 100
8
+ end
9
+
10
+ # message:: Carnivore::Message
11
+ # Returns true if message has not been processed
12
+ def valid?(message)
13
+ checksum = sha(message)
14
+ found = @store.include?(checksum)
15
+ unless(found)
16
+ push(checksum)
17
+ end
18
+ !found
19
+ end
20
+
21
+ # item:: checksum
22
+ # Pushes checksum into store
23
+ def push(item)
24
+ @store.push(item)
25
+ if(@store.size > @size)
26
+ @store.shift
27
+ end
28
+ self
29
+ end
30
+
31
+ # thing:: Instance
32
+ # Return checksum for give instance
33
+ def sha(thing)
34
+ unless(thing.is_a?(String))
35
+ thing = MultiJson.dump(thing)
36
+ end
37
+ (Digest::SHA512.new << thing).hexdigest
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ module Carnivore
2
+ module Utils
3
+
4
+ module Params
5
+
6
+ # hash:: Hash
7
+ # Symbolize keys in hash
8
+ def symbolize_hash(hash)
9
+ Hash[*(
10
+ hash.map do |k,v|
11
+ if(k.is_a?(String))
12
+ key = k.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym
13
+ else
14
+ key = k
15
+ end
16
+ [
17
+ key,
18
+ v.is_a?(Hash) ? symbolize_hash(v) : v
19
+ ]
20
+ end.flatten(1)
21
+ )]
22
+ end
23
+
24
+ # hash:: Hash
25
+ # args:: Symbols or strings
26
+ # Follow path in hash provided by args and return value or nil
27
+ # if path is not valid
28
+ def retrieve(hash, *args)
29
+ valids = [::Hash, hash.is_a?(Class) ? hash : hash.class]
30
+ args.flatten.inject(hash) do |memo, key|
31
+ break unless valids.detect{ |valid_type|
32
+ memo.is_a?(valid_type) || memo == valid_type
33
+ }
34
+ memo[key.to_s] || memo[key.to_sym] || break
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -1,47 +1,13 @@
1
- require 'celluloid'
1
+ require 'carnivore'
2
2
 
3
3
  module Carnivore
4
- module Utils
5
4
 
6
- module Params
7
- def symbolize_hash(hash)
8
- Hash[*(
9
- hash.map do |k,v|
10
- if(k.is_a?(String))
11
- key = k.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym
12
- else
13
- key = k
14
- end
15
- [
16
- key,
17
- v.is_a?(Hash) ? symbolize_hash(v) : v
18
- ]
19
- end.flatten(1)
20
- )]
21
- end
22
- end
5
+ module Utils
6
+ autoload :Params, 'carnivore/utils/params'
7
+ autoload :Logging, 'carnivore/utils/logging'
8
+ autoload :MessageRegistry, 'carnivore/utils/message_registry'
23
9
 
24
10
  extend Params
25
-
26
- module Logging
27
-
28
- %w(debug info warn error).each do |key|
29
- define_method(key) do |string|
30
- log(key, string)
31
- end
32
- end
33
-
34
- def log(*args)
35
- if(args.empty?)
36
- Celluloid::Logger
37
- else
38
- severity, string = args
39
- Celluloid::Logger.send(severity.to_sym, "#{self}: #{string}")
40
- end
41
- end
42
-
43
- end
44
-
45
11
  extend Logging
46
12
 
47
13
  end
@@ -1,5 +1,7 @@
1
1
  module Carnivore
2
2
  class Version < Gem::Version
3
3
  end
4
- VERSION = Version.new('0.1.10')
4
+ VERSION = Version.new('0.2.0')
5
5
  end
6
+
7
+ require 'carnivore'
data/lib/carnivore.rb CHANGED
@@ -1,2 +1,5 @@
1
- require 'carnivore/version'
2
1
  require 'carnivore/runner'
2
+ require 'carnivore/version'
3
+
4
+ # Load in celluloid as required
5
+ autoload :Celluloid, 'celluloid'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carnivore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,10 +9,10 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-23 00:00:00.000000000 Z
12
+ date: 2014-01-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: fog
15
+ name: celluloid
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
@@ -28,7 +28,7 @@ dependencies:
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
30
  - !ruby/object:Gem::Dependency
31
- name: celluloid
31
+ name: mixlib-config
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  none: false
34
34
  requirements:
@@ -44,7 +44,7 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: mixlib-config
47
+ name: multi_json
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
@@ -66,29 +66,26 @@ extensions: []
66
66
  extra_rdoc_files: []
67
67
  files:
68
68
  - lib/carnivore/callback.rb
69
+ - lib/carnivore/errors.rb
69
70
  - lib/carnivore/supervisor.rb
70
71
  - lib/carnivore/source/test.rb
71
72
  - lib/carnivore/source.rb
72
73
  - lib/carnivore/version.rb
73
74
  - lib/carnivore/runner.rb
75
+ - lib/carnivore/spec_helper.rb
76
+ - lib/carnivore/utils/message_registry.rb
77
+ - lib/carnivore/utils/logging.rb
78
+ - lib/carnivore/utils/params.rb
79
+ - lib/carnivore/autoloader.rb
74
80
  - lib/carnivore/config.rb
75
81
  - lib/carnivore/message.rb
82
+ - lib/carnivore/source_container.rb
76
83
  - lib/carnivore/utils.rb
77
84
  - lib/carnivore/container.rb
78
85
  - lib/carnivore.rb
79
- - test/spec.rb
80
- - test/specs/source.rb
81
- - test/specs/config.rb
82
- - test/specs/message.rb
83
- - test/specs/utils.rb
84
- - test/specs/container.rb
85
- - examples/test_http.rb
86
- - examples/test_class.rb
87
- - examples/test_block.rb
88
- - Gemfile
86
+ - carnivore.gemspec
89
87
  - README.md
90
88
  - CHANGELOG.md
91
- - carnivore.gemspec
92
89
  homepage: https://github.com/carnivore-rb/carnivore
93
90
  licenses: []
94
91
  post_install_message:
data/Gemfile DELETED
@@ -1,5 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'minitest'
4
-
5
- gemspec
@@ -1,10 +0,0 @@
1
- require 'carnivore'
2
-
3
- Carnivore.configure do
4
- s = Carnivore::Source.build(:type => :test, :args => {})
5
-
6
- s.add_callback(:printer) do |message|
7
- info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
8
- end
9
-
10
- end.start!
@@ -1,20 +0,0 @@
1
- require 'carnivore'
2
-
3
- class CustomCallback < Carnivore::Callback
4
-
5
- self.workers = 5
6
-
7
- def setup
8
- info "Custom callback setup called!"
9
- end
10
-
11
- def execute(message)
12
- info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
13
- end
14
- end
15
-
16
- Carnivore.configure do
17
- s = Carnivore::Source.build(:type => :test, :args => {})
18
- s.add_callback(:printer, CustomCallback)
19
-
20
- end.start!
@@ -1,10 +0,0 @@
1
- require 'carnivore'
2
-
3
- Carnivore.configure do
4
- s = Carnivore::Source.build(:type => :http, :args => {:bind => '0.0.0.0', :port => 3000})
5
-
6
- s.add_callback(:printer) do |message|
7
- info "GOT MESSAGE: #{message[:message][:body]} - path: #{message[:message][:request].url} - method: #{message[:message][:request].method} - source: #{message[:source]} - instance: #{self}"
8
- end
9
-
10
- end.start!
data/test/spec.rb DELETED
@@ -1,11 +0,0 @@
1
- require 'celluloid'
2
- Celluloid.logger.level = 4
3
-
4
- Dir.glob(File.join(File.expand_path(File.dirname(__FILE__)), 'specs/*.rb')).each do |path|
5
- require path
6
- end
7
-
8
- MiniTest::Spec.before do
9
- Celluloid.shutdown
10
- Celluloid.boot
11
- end
data/test/specs/config.rb DELETED
@@ -1,31 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'carnivore/config'
3
-
4
- describe 'Carnivore::Config' do
5
- describe 'Direct Configuration' do
6
- it 'allows direct configuration set' do
7
- Carnivore::Config[:direct] = true
8
- Carnivore::Config[:direct].must_equal true
9
- end
10
-
11
- it 'returns nil when configuration is undefined' do
12
- Carnivore::Config[:missing].must_be_nil
13
- end
14
-
15
- it 'raises exception when accessing nested keys that do not exist' do
16
- -> { Carnivore::Config[:missing][:key] }.must_raise NoMethodError
17
- end
18
-
19
- end
20
-
21
- describe '#get helper' do
22
- it 'returns the nested configuration value' do
23
- Carnivore::Config[:nested] = {:value => 'hello world'}
24
- Carnivore::Config.get(:nested, :value).must_equal 'hello world'
25
- end
26
-
27
- it 'returns `nil` when nested configuration does not exist' do
28
- Carnivore::Config.get(:value, :does, :not, :exist).must_be_nil
29
- end
30
- end
31
- end
@@ -1,13 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'carnivore/container'
3
-
4
- describe 'Carnivore::Container' do
5
- it 'provides logging helpers' do
6
- c = Carnivore::Container.new
7
- Carnivore::Container.must_respond_to :log
8
- c.must_respond_to :log
9
- %w(debug info warn error).each do |key|
10
- c.must_respond_to key
11
- end
12
- end
13
- end
@@ -1,29 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'carnivore/message'
3
-
4
- describe 'Carnivore::Message' do
5
-
6
- it 'requires a Source to be provided' do
7
- -> { Carnivore::Message.new(:message => 'hi') }.must_raise ArgumentError
8
- end
9
-
10
- it 'provides argument access via `[]`' do
11
- message = Carnivore::Message.new(:source => true, :message => 'hi')
12
- message[:source].must_equal true
13
- message[:message].must_equal 'hi'
14
- end
15
-
16
- it 'provides direct argument hash access via `args`' do
17
- message = Carnivore::Message.new(:source => true, :message => 'hi')
18
- message.args.must_be_kind_of Hash
19
- end
20
-
21
- it 'provides `confirm!` confirmation helper to Source' do
22
- source = MiniTest::Mock.new
23
- message = Carnivore::Message.new(:source => source, :message => 'hi')
24
- source.expect(:confirm, true, [message])
25
- message.confirm!
26
- source.verify
27
- end
28
-
29
- end
data/test/specs/source.rb DELETED
@@ -1,82 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'carnivore/source'
3
-
4
- describe 'Carnivore::Source' do
5
- describe 'Carnivore::Source::SourceContainer' do
6
- before do
7
- @src_ctn = Carnivore::Source::SourceContainer.new(:my_name, {:arg1 => true})
8
- end
9
-
10
- it 'should store name in `klass` attribute' do
11
- @src_ctn.klass.must_equal :my_name
12
- end
13
-
14
- it 'should store argument hash in `source_hash` attribute' do
15
- @src_ctn.source_hash.must_equal :arg1 => true, :callbacks => {}
16
- end
17
-
18
- describe 'callback additions' do
19
- before do
20
- @block = lambda{ 'hi' }
21
- @src_ctn.add_callback(:hi, &@block)
22
- end
23
-
24
- it 'should store callback by name in `source_hash` under `:callbacks`' do
25
- @src_ctn.source_hash.keys.must_include :callbacks
26
- @src_ctn.source_hash[:callbacks].keys.must_include :hi
27
- @src_ctn.source_hash[:callbacks].values.must_include @block
28
- end
29
- end
30
- end
31
-
32
- describe 'Custom source providers' do
33
-
34
- before do
35
- Carnivore::Source.provide(:meat_bag, 'carnivore-meat-bag/meat_bag')
36
- end
37
-
38
- it 'gives expected require path if registered' do
39
- Carnivore::Source.require_path(:meat_bag).must_equal 'carnivore-meat-bag/meat_bag'
40
- end
41
-
42
- it 'gives `nil` require path if not registered' do
43
- Carnivore::Source.require_path(:test).must_be_nil
44
- end
45
- end
46
-
47
- describe 'Source registration' do
48
- before do
49
- @inst = Object.new
50
- Carnivore::Source.register(:my_source, @inst)
51
- end
52
-
53
- it 'provides list of registered sources' do
54
- Carnivore::Source.sources.must_include @inst
55
- end
56
-
57
- it 'allows accessing registered source by name' do
58
- Carnivore::Source.source(:my_source).must_equal @inst
59
- end
60
- end
61
-
62
- describe 'Processing with base Source instance' do
63
- it 'should raise an exception' do
64
- -> {
65
- Carnivore::Source.new(:auto_process => false).process
66
- }.must_raise NoMethodError
67
- end
68
- end
69
-
70
- describe 'Source test instance' do
71
- describe 'with no name argument' do
72
- before do
73
- require 'carnivore/source/test'
74
- @source = Carnivore::Source::Test.new
75
- end
76
-
77
- it 'should generate name if none provided' do
78
- @source.name.wont_be :empty?
79
- end
80
- end
81
- end
82
- end
data/test/specs/utils.rb DELETED
@@ -1,47 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'carnivore/utils'
3
-
4
- describe 'Carnivore::Utils' do
5
- describe 'Carnivore::Utils::Logging' do
6
- before do
7
- @obj = Object.new
8
- @obj.extend(Carnivore::Utils::Logging)
9
- Celluloid.logger.level = 0
10
- end
11
-
12
- after do
13
- Celluloid.logger.level = 4
14
- end
15
-
16
- it 'adds logging methods' do
17
- %w(debug info warn error).each do |key|
18
- @obj.must_respond_to(key)
19
- end
20
- end
21
-
22
- it 'includes object information in logging output' do
23
- out, err = capture_subprocess_io do
24
- @obj.info 'hello world'
25
- end
26
- err.must_match %r{I,\s*\[.*?\]\s*INFO\s*--\s*:\s*#{Regexp.escape(@obj.inspect)}:\s*hello world\n}
27
- end
28
- end
29
-
30
- describe 'Carnivore::Utils::Params' do
31
- before do
32
- @obj = Object.new
33
- @obj.extend(Carnivore::Utils::Params)
34
- end
35
-
36
- it 'adds `symbolize_hash` method' do
37
- @obj.must_respond_to :symbolize_hash
38
- end
39
-
40
- it 'converts hash keys to symbols' do
41
- h = {'string' => {'another' => {'OneHere' => 'more'}}}
42
- converted = @obj.symbolize_hash(h)
43
- converted.keys.first.must_equal :string
44
- converted[:string][:another].keys.first.must_equal :one_here
45
- end
46
- end
47
- end