kinetic 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 67a4ff614b5f65c94ff43ae5e18e7a73944c13e3
4
- data.tar.gz: 41f13ae0b524573c98a63f853d7be3c458c12a96
3
+ metadata.gz: b7a08f594cae0d5c01c00f340386df7c7788a745
4
+ data.tar.gz: 280fe43699041a8a233b455887d795b6c47e42f8
5
5
  SHA512:
6
- metadata.gz: 72ce1e7b862c33785117568f17944f7d40401c158e78e80a3a44e7b865d299ef1c521c24104a81b666397689e069273434be0cbaa553f5fb6f83b7a6411c4625
7
- data.tar.gz: 575a3a38d19d22d92125da043f9a065cd18f53d348003f3eda1ac9ef0679476ed636f923bc0a2d722d6c16e3dd08d87df3f18a87f532869463f1f3f50cb8f1be
6
+ metadata.gz: 65871da55313e959082177229600037b1ffdf5e94b1886bebb525e9099effe4b249fb7b581f54d45ed908fa68eac60e0ce7e44841e96f0738d7ae1fc731a8c3f
7
+ data.tar.gz: e8452cb376cab92b21796269deff0ac5ab24306099675eca52eeb9d64071cced80ba649e0b6399748f77d113b13e6bdd7f7aecbcf1a4bc1cbf386faa30be73ff
data/.gitignore CHANGED
@@ -17,3 +17,5 @@ test/version_tmp
17
17
  tmp
18
18
  .idea/**
19
19
  *.iml
20
+ doc/**
21
+ *.pid
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ script: rspec spec
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Kinetic
2
2
 
3
+ [<img src="https://secure.travis-ci.org/fugufish/kinetic.png">](http://travis-ci.org/fugufish/kinetic)
4
+ [![Code Climate](https://codeclimate.com/repos/52b06fa413d637197401ebb7/badges/c71d7f47797b4caec5ae/gpa.png)](https://codeclimate.com/repos/52b06fa413d637197401ebb7/feed)
5
+
6
+
3
7
  Kinetic is an AMQP worker framework designed in vein of microframeworks such as sinatra. Its goal is to provide an
4
8
  easy way to configure and run AMQP consumers in order to reduce developer overhead when working with AMQP. Kinetic
5
9
  follows the master/worker convention used in Unicorn, and its prefork code is mostly based on that of Unicorn's.
data/kinetic.gemspec CHANGED
@@ -28,7 +28,8 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_development_dependency 'bundler', '~> 1.3'
30
30
  spec.add_development_dependency 'rake'
31
-
31
+ spec.add_development_dependency 'rspec'
32
+ spec.add_development_dependency 'yard'
32
33
 
33
34
 
34
35
  end
data/lib/kinetic/base.rb CHANGED
@@ -17,6 +17,8 @@ require_relative '../kinetic/version'
17
17
  require_relative '../kinetic/configuration'
18
18
  require_relative '../kinetic/master'
19
19
  require_relative '../kinetic/worker'
20
+ require_relative '../kinetic/dsl'
21
+ require_relative '../kinetic/errors'
20
22
 
21
23
  module Kinetic
22
24
 
@@ -34,28 +36,10 @@ module Kinetic
34
36
  class Base
35
37
 
36
38
  class << self
39
+ include Kinetic::DSL
37
40
 
38
- attr_reader :exchanges
39
-
40
- def set(key, value)
41
- config[key] = value
42
- end
43
-
44
- def config
45
- @config ||= Kinetic::Configuration.new
46
- end
47
-
48
- def logger
49
- @logger ||= reopen_logger
50
- end
51
-
52
- def reopen_logger
53
- @logger = Logger.new(STDOUT)
54
- end
55
-
56
- def on(key, &block)
57
- logger.debug "Setting up '#{key}' on 'direct'"
58
- direct[key] = block
41
+ def exchanges
42
+ @exchanges ||= {}
59
43
  end
60
44
 
61
45
  private
@@ -65,22 +49,11 @@ module Kinetic
65
49
  end
66
50
 
67
51
 
68
- def exchanges
69
- @exchanges ||= {}
70
- end
71
-
72
- end
73
52
 
74
- delegate :logger, to: :class
75
- delegate :config, to: :class
76
53
 
77
- def reopen_logger
78
- self.class.reopen_logger
79
54
  end
80
55
 
81
- def exchanges
82
- self.class.send(:exchanges)
83
- end
56
+ delegate :logger, :set, :get, :get!, :exchanges, to: :class
84
57
 
85
58
  end
86
59
 
@@ -6,6 +6,7 @@ module Kinetic
6
6
  root: File.dirname($0),
7
7
  app_file: $0,
8
8
  workers: 1,
9
+ log_file: STDOUT,
9
10
  }
10
11
 
11
12
  def initialize
@@ -0,0 +1,74 @@
1
+ module Kinetic
2
+
3
+ module DSL
4
+
5
+ # Sets a configuration value
6
+ #
7
+ # @param [Symbol] key the configuration key to set
8
+ # @param [Symbol] value the configuration value
9
+ #
10
+ # @return [Boolean] returns true
11
+ def set(key, value)
12
+ config[key.to_sym] = value
13
+ true
14
+ end
15
+
16
+ # Gets a configuration value
17
+ #
18
+ # @param [Symbol] key the key of the value to get
19
+ #
20
+ # @return [Object, nil] returns the requested configuration value or nil if the value does not exist
21
+ def get(key)
22
+ config[key.to_sym]
23
+ end
24
+
25
+ # Gets a configuration value and returns an exception if the value is not present
26
+ #
27
+ # @param [Symbol] key the key of the value to get
28
+ #
29
+ # @return [Object]
30
+ #
31
+ # @raise [Kinetic::Errors::MissingConfigurationValue] if the value does not exist
32
+ def get!(key)
33
+ raise Kinetic::Errors::MissingConfigurationValue.new(key) unless (value = get(key))
34
+ value
35
+ end
36
+
37
+ # Defines a direct queue subscription. Direct queues do not allow fuzzy matching so all messages sent to this queue
38
+ # must exactly match the key.
39
+ #
40
+ # @param [String] key the key of the queue to which to subscribe.
41
+ #
42
+ # @yield [message] yields the message passed to the queue to the block
43
+ #
44
+ # @raise [Kinetic::Errors::NoSubcriberBlock] if a block is not passed
45
+ # @raise [Kinetic::Errors::KeyMustBeString] if the passed key is not a string
46
+ def on_direct(key, &block)
47
+ raise Kinetic::Errors::NoSubscriberBlock unless block_given?
48
+ raise Kinetic::Errors::KeyMustBeString unless key.is_a? String
49
+ logger.debug "Setting up '#{key}' on 'direct'"
50
+ direct[key] = block
51
+ end
52
+
53
+ alias :on :on_direct
54
+
55
+ # @return [Logger] returns the application logger instance
56
+ def logger
57
+ @logger ||= reopen_logger
58
+ end
59
+
60
+ private
61
+
62
+ def config
63
+ @config ||= Kinetic::Configuration.new
64
+ end
65
+
66
+ def reopen_logger
67
+ @logger = Logger.new(config.log_file)
68
+ end
69
+
70
+
71
+
72
+ end
73
+
74
+ end
@@ -3,16 +3,17 @@ module Kinetic
3
3
 
4
4
  class KineticError < StandardError; end
5
5
 
6
- class ConnectionError < KineticError
6
+ class NoSubscriberBlock < KineticError; end
7
+ class KeyMustBeString < KineticError; end
7
8
 
8
- def initialize(error)
9
- message = error.message
10
- backtrace = error.backtrace
11
- super message
12
- self.set_backtrace(backtrace)
9
+ class MissingConfigurationValue < KineticError
10
+
11
+ def initialize(key)
12
+ super "Expected configuration for '#{key}' but the configuration was not found."
13
13
  end
14
14
 
15
15
  end
16
16
 
17
+
17
18
  end
18
19
  end
@@ -7,8 +7,8 @@ module Kinetic
7
7
  WORKERS = {}
8
8
  SIG_QUEUE = []
9
9
 
10
- delegate :logger, to: :app
11
- delegate :config, to: :app
10
+ delegate :logger, :get, :get!, :set, to: :app
11
+
12
12
 
13
13
  attr_reader :app
14
14
 
@@ -23,12 +23,15 @@ module Kinetic
23
23
  begin
24
24
  logger.info "Starting Kinetic #{Kinetic::VERSION} with PID #{Process.pid}"
25
25
  logger.debug 'Configuration:'
26
- logger.ap config.to_hash
26
+ logger.ap app.class.send(:config).to_hash
27
27
  write_pidfile!
28
28
  initialize_self_pipe!
29
29
  initialize_signal_traps!
30
30
  spawn_missing_workers
31
31
  join
32
+ rescue => e
33
+ logger.fatal e
34
+ logger.fatal 'Unable to start worker!'
32
35
  ensure
33
36
  call_on_exit_callbacks
34
37
  exit!
@@ -53,11 +56,24 @@ module Kinetic
53
56
 
54
57
  private
55
58
 
59
+ def master_sleep(time)
60
+ IO.select([ SELF_PIPE[0] ], nil, nil, time) or return
61
+ SELF_PIPE[0].kgio_tryread(11)
62
+ end
63
+
56
64
  def join
57
- logger.debug "joining thread"
58
- loop do
59
- sleep 1
60
- end
65
+ logger.debug 'Joining thread'
66
+ case SIG_QUEUE.shift
67
+ when :QUIT
68
+ break
69
+ when :TERM, :INT
70
+ logger.warn "#{get(:name)} is shutting down immediately!"
71
+ exit!(1)
72
+ break
73
+ else
74
+ master_sleep(0.5)
75
+ end while true
76
+ logger.warn "#{get(:name)} is going down!"
61
77
  end
62
78
 
63
79
  # Based on Unicorn's self-pipe
@@ -73,33 +89,40 @@ module Kinetic
73
89
  end
74
90
 
75
91
  def awaken_master
76
- logger.debug 'Awakening master'
77
92
  SELF_PIPE[1].kgio_trywrite('.')
78
93
  end
79
94
 
80
95
  def write_pidfile!
81
- config.pid ||= File.join(config.root, "#{config.name}.pid")
82
- logger.info "Writing PID file to #{config.pid}"
83
- File.open(config.pid, 'w') { |f| f.write(Process.pid) }
96
+ set(:pid, File.join(get!(:root), "#{get!(:name)}.pid"))
97
+ logger.info "Writing PID file to #{get!(:pid)}"
98
+ File.open(get!(:pid), 'w') { |f| f.write(Process.pid) }
84
99
  end
85
100
 
86
101
  def call_on_exit_callbacks
87
102
  ensure
88
- FileUtils.rm(config.pid)
103
+ FileUtils.rm(get(:pid)) if get(:pid)
89
104
  end
90
105
 
91
106
  def spawn_missing_workers
92
107
  logger.debug 'Spawning missing workers'
93
108
  worker_nr = -1
94
- until (worker_nr += 1) == config.workers
109
+ until (worker_nr += 1) == get!(:workers)
95
110
  WORKERS.value?(worker_nr) and next
111
+ #noinspection RubyArgCount
96
112
  worker = Worker.new(worker_nr, @app)
97
113
  logger.debug 'Calling before_fork block'
98
114
  before_fork.call(self, worker)
99
115
  logger.debug 'Forking worker'
116
+ parent = Process.pid
100
117
  pid = fork do
101
- app.reopen_logger
102
- worker.run
118
+ begin
119
+ #noinspection RubyArgCount
120
+ worker.run
121
+ rescue => e
122
+ logger.fatal e
123
+ logger.fatal 'Unable to start workers'
124
+ Process.kill(:TERM, parent)
125
+ end
103
126
  end
104
127
  logger.debug "Worker #{worker_nr} started on #{pid}"
105
128
  end
@@ -1,3 +1,3 @@
1
1
  module Kinetic
2
- VERSION = "0.0.1"
2
+ VERSION = '0.0.2'
3
3
  end
@@ -1,8 +1,7 @@
1
1
  module Kinetic
2
2
  class Worker
3
3
 
4
- delegate :logger, to: :app
5
- delegate :config, to: :app
4
+ delegate :logger, :get, :get!, :set, to: :app
6
5
 
7
6
  attr_reader :id, :app, :channel, :exchanges
8
7
 
@@ -24,9 +23,8 @@ module Kinetic
24
23
  end
25
24
 
26
25
  def run
27
- logger.debug "Establishing connection host: '#{config.host}', port: '#{config.port}'"
28
- AMQP.start(host: config.host, port: config.port) do |connection|
29
- app.reopen_logger
26
+ logger.debug "Establishing connection host: '#{get!(:host)}', port: '#{get!(:port)}'"
27
+ AMQP.start(host: get!(:host), port: get!(:port)) do |connection|
30
28
  logger.debug "AMQP started with conneciton #{connection}"
31
29
  initialize_channel!(connection)
32
30
  initialize_exchanges!
@@ -45,7 +43,7 @@ module Kinetic
45
43
  def initialize_exchanges!
46
44
  logger.debug 'Initializing exchanges'
47
45
  @exchanges = {}
48
- prefix = config.name
46
+ prefix = get!(:name)
49
47
  app.exchanges.each_key do |name|
50
48
  logger.debug " Initializing #{name} exchange"
51
49
  @exchanges[name] = channel.send(name, "#{prefix}.#{name}")
data/spec/base_spec.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/kinetic/base'
3
+
4
+ describe Kinetic::Base do
5
+
6
+
7
+
8
+ end
data/spec/dsl_spec.rb ADDED
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/kinetic/dsl'
3
+ require_relative '../lib/kinetic/errors'
4
+ class TestClass
5
+ extend Kinetic::DSL
6
+ end
7
+
8
+ describe Kinetic::DSL do
9
+ # setting this to open brackes helps us test the returns true case
10
+
11
+ let(:config) { { key: 'value' } }
12
+
13
+ before :each do
14
+ stub_const("Kinetic::Configuration", config)
15
+ end
16
+
17
+ subject do
18
+ TestClass
19
+ end
20
+
21
+ before :each do
22
+ subject.stub(config: config)
23
+ end
24
+
25
+ describe '::set' do
26
+
27
+ it 'returns true' do
28
+ subject.set('foo', 'baz').should == true
29
+ end
30
+
31
+ it 'sets the configuration value' do
32
+ subject.set(:baz, 'bar')
33
+ subject.send(:config)[:baz].should == 'bar'
34
+ end
35
+
36
+ it 'symbolizes strings into keys' do
37
+ subject.set('foo', 'bar')
38
+ subject.send(:config)[:foo].should == 'bar'
39
+ end
40
+
41
+ end
42
+
43
+ describe '::get' do
44
+
45
+ it 'returns the value of the config key' do
46
+ config[:foo] = 'bar'
47
+ subject.get(:foo).should == 'bar'
48
+ end
49
+
50
+ it 'symbolizes the key' do
51
+ config[:baz] = 'bar'
52
+ subject.get('baz').should == 'bar'
53
+ end
54
+
55
+ end
56
+
57
+ describe '::get!' do
58
+
59
+ it 'raises Kinetic::Errors::MissingConfigurationValue if the value is not present' do
60
+ expect do
61
+ subject.send(:get!, :nonexistentkey)
62
+ end.to raise_error(Kinetic::Errors::MissingConfigurationValue)
63
+ end
64
+
65
+ it 'returns the value if the value is present and does not raise an exception' do
66
+ config[:existentkey] = 'foo'
67
+ expect do
68
+ subject.get!(:existentkey).should == 'foo'
69
+ end.not_to raise_error
70
+ end
71
+
72
+ it 'symbolizes the key' do
73
+ config[:akey] = 'foo'
74
+ expect do
75
+ subject.get!(:akey)
76
+ end.not_to raise_error
77
+ end
78
+
79
+ end
80
+
81
+ describe '::logger' do
82
+
83
+ let(:logger) { double('Logger') }
84
+
85
+ after :each do
86
+ subject.instance_variable_set(:@logger, nil)
87
+ end
88
+
89
+ it 'returns calls reopen_logger and returns the result if @logger is nil' do
90
+ subject.instance_variable_set(:@logger, nil)
91
+ subject.should_receive(:reopen_logger).and_return(logger)
92
+ subject.logger.should == logger
93
+ end
94
+
95
+ it 'returns the existing logger if already set' do
96
+ subject.instance_variable_set(:@logger, logger)
97
+ subject.should_not_receive(:reopen_logger)
98
+ subject.logger.should == logger
99
+ end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1 @@
1
+ require 'rspec'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kinetic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jarod Reid
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-16 00:00:00.000000000 Z
11
+ date: 2013-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: confstruct
@@ -136,6 +136,34 @@ dependencies:
136
136
  - - '>='
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: yard
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
139
167
  description: A powerful yet simple AMQP worker framework
140
168
  email:
141
169
  - jreid@voyst.com
@@ -144,6 +172,7 @@ extensions: []
144
172
  extra_rdoc_files: []
145
173
  files:
146
174
  - .gitignore
175
+ - .travis.yml
147
176
  - Gemfile
148
177
  - LICENSE.txt
149
178
  - README.md
@@ -154,11 +183,15 @@ files:
154
183
  - lib/kinetic/base.rb
155
184
  - lib/kinetic/cli.rb
156
185
  - lib/kinetic/configuration.rb
186
+ - lib/kinetic/dsl.rb
157
187
  - lib/kinetic/errors.rb
158
188
  - lib/kinetic/master.rb
159
189
  - lib/kinetic/publisher.rb
160
190
  - lib/kinetic/version.rb
161
191
  - lib/kinetic/worker.rb
192
+ - spec/base_spec.rb
193
+ - spec/dsl_spec.rb
194
+ - spec/spec_helper.rb
162
195
  homepage: ''
163
196
  licenses:
164
197
  - MIT
@@ -183,5 +216,8 @@ rubygems_version: 2.0.3
183
216
  signing_key:
184
217
  specification_version: 4
185
218
  summary: A powerful yet simple AMQP worker framework
186
- test_files: []
219
+ test_files:
220
+ - spec/base_spec.rb
221
+ - spec/dsl_spec.rb
222
+ - spec/spec_helper.rb
187
223
  has_rdoc: