carnivore 0.1.10 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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