roundhousekiq 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c3d38ff3867de7878b149a1d42eec8cb890a8e56
4
+ data.tar.gz: b2b98a7563c1a9b1e5bf6710108f3fcd9f9f39ef
5
+ SHA512:
6
+ metadata.gz: b3a50b8d5dbe7c744d4bcea0cf5a00bc7baf97a70c943dab37ff4b3876fd2ec4e3d7ce372a799f0235411e4494f0d3532445d3ee88c318474983e59fd539241d
7
+ data.tar.gz: e97745b71d28ded61c9197fefe0a6e93227413c0d82579ae36d0a025a2fe3e624d33a09a6a5bc2a29edaa3189e17434a47aee3f778b29efa4acc77ba271a6e42
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format documentation
3
+ --format Nc
4
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ roundhousekiq
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.2
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 2.1
5
+ - 2.2
6
+ addons:
7
+ code_climate:
8
+ repo_token: f2f7c01bd01cde392292bbd6fea2e9aa64f6fc2dd6d599ff4f01123985d489cd
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in roundhousekiq.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,44 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # Feel free to open issues for suggestions and improvements
6
+
7
+ # RSpec files
8
+ rspec = dsl.rspec
9
+ watch(rspec.spec_helper) { rspec.spec_dir }
10
+ watch(rspec.spec_support) { rspec.spec_dir }
11
+ watch(rspec.spec_files)
12
+
13
+ # Ruby files
14
+ ruby = dsl.ruby
15
+ dsl.watch_spec_files_for(ruby.lib_files)
16
+
17
+ # Rails files
18
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
19
+ dsl.watch_spec_files_for(rails.app_files)
20
+ dsl.watch_spec_files_for(rails.views)
21
+
22
+ watch(rails.controllers) do |m|
23
+ [
24
+ rspec.spec.("routing/#{m[1]}_routing"),
25
+ rspec.spec.("controllers/#{m[1]}_controller"),
26
+ rspec.spec.("acceptance/#{m[1]}")
27
+ ]
28
+ end
29
+
30
+ # Rails config changes
31
+ watch(rails.spec_helper) { rspec.spec_dir }
32
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
33
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
34
+
35
+ # Capybara features specs
36
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
37
+ watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
38
+
39
+ # Turnip features and steps
40
+ watch(%r{^spec/acceptance/(.+)\.feature$})
41
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
42
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
43
+ end
44
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Moritz Lawitschka
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ ![Chuck Norris](chuck-norris.png)
2
+
3
+ # Roundhousekiq
4
+
5
+ ![Gem version](https://img.shields.io/gem/v/roundhousekiq.svg?style=flat)
6
+ ![Build Status](https://img.shields.io/travis/suitepad-gmbh/roundhousekiq.svg?style=flat)
7
+ ![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)
8
+ ![CC Score](https://img.shields.io/codeclimate/github/suitepad-gmbh/roundhousekiq.svg?style=flat)
9
+ ![Coverage](https://img.shields.io/codeclimate/coverage/github/suitepad-gmbh/roundhousekiq.svg?style=flat)
10
+
11
+ Small AMQP to Sidekiq bridge, allowing Sidekiq jobs to be triggered via AMQP.
12
+ You define your Sidekiq jobs as usual, but instead of manually invoking the
13
+ jobs, you define to which AMQP event the worker should listen on.
14
+
15
+ Take for example a fleet of services all reporting their current status every
16
+ once in a while to your central monitoring service. Because these services do
17
+ not care about when their status report is being processed and by whom, they
18
+ simple send it via your AMQP server's `status` exchange and let others handle
19
+ the rest.
20
+
21
+ The monitoring service now uses Roundhousekiq to asynchronously process these
22
+ status reports and to keep the load from the main server process, it does the
23
+ processing in background using Sidekiq. Simply set up a new Sidekiq worker,
24
+ specify the AMQP exchange and routing key to listen on, and let Roundhousekiq
25
+ handle the AMQP bindings and finding the right worker for each message:
26
+
27
+ ```ruby
28
+ class StatusWorker
29
+ include Sidekiq::Worker
30
+ include Roundhousekiq::Worker
31
+
32
+ # AMQP configuration
33
+ exchange_name 'status'
34
+ exchange_type :topic
35
+ queue_name 'roundhousekiq_status_worker'
36
+ routing_key 'status.*'
37
+
38
+ # Attributes:
39
+ # payload: Parsed JSON payload directly from AMQP
40
+ def perform(payload)
41
+ # Heavy computing action...
42
+ end
43
+
44
+ end
45
+ ```
46
+
47
+ ## Installation
48
+
49
+ Add this line to your application's Gemfile:
50
+
51
+ ```ruby
52
+ gem 'roundhousekiq'
53
+ ```
54
+
55
+ And then execute:
56
+
57
+ $ bundle
58
+
59
+ Or install it yourself as:
60
+
61
+ $ gem install roundhousekiq
62
+
63
+ ## Usage
64
+
65
+ 1. Create an initializer in _config/initializers/roundhousekiq.rb_ and specify
66
+ your AMQP host.
67
+
68
+ ```ruby
69
+ Roundhousekiq.configure do |config|
70
+ # AMQP host address
71
+ # config.host = '127.0.0.1'
72
+
73
+ # AMQP host port
74
+ # config.port = '5672'
75
+
76
+ # AMQP vhost to be connected to
77
+ # config.vhost = '/'
78
+
79
+ # User credentials
80
+ # config.username = 'guest'
81
+ # config.password = 'guest'
82
+
83
+ # Prefetch count on all queues Roundhousekiq will subscribe to
84
+ # config.prefetch = 256
85
+ end
86
+ ```
87
+
88
+ 2. Create your first worker. This worker does only differ from a normal Sidekiq
89
+ worker in the `Roundhousekiq::Worker` module being included and specifying which
90
+ exchange and routing key to listen on:
91
+
92
+ ```ruby
93
+ class Worker
94
+ include Sidekiq::Worker
95
+ include Roundhousekiq::Worker
96
+
97
+ exchange_name 'amq.topic'
98
+ exchange_type :topic
99
+ queue_name 'worker'
100
+ routing_key 'work'
101
+
102
+ def perform(payload)
103
+ # ...
104
+ end
105
+ end
106
+ ```
107
+
108
+ A persistent queue named _worker_ bound to the _amq.topic_ exchange with the
109
+ routing key _work_ will be created. Each time a message arrives in that
110
+ queue, this worker will be triggered.
111
+
112
+ You do not have to specify a queue name, if you do not want to have a
113
+ persistent queue. AMQP will automatically create a queue for that worker,
114
+ which is being deleted once the Roundhousekiq daemon shuts down.
115
+
116
+ 3. Run the Roundhousekiq daemon from the root of your Rails project:
117
+
118
+ ```shell
119
+ $ bundle exec roundhousekiq
120
+ ```
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ # Default directory to look in is `/spec`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color', '--format', 'documentation']
8
+ end
9
+
10
+ task :default => :spec
data/bin/roundhousekiq ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Load Rails environment
4
+ ENV['RAILS_ENV'] ||= 'development'
5
+ require File.expand_path('./config/boot')
6
+ require File.expand_path('./config/environment')
7
+
8
+ # Always pre-load Rails application
9
+ ::Rails.application.eager_load!
10
+
11
+ runner = Roundhousekiq::Runner.new
12
+ runner.run
13
+
14
+ # Trap `Kill `
15
+ Signal.trap("TERM") do
16
+ runner.shutdown
17
+ end
data/chuck-norris.png ADDED
Binary file
@@ -0,0 +1,15 @@
1
+ module Roundhousekiq
2
+ class Configuration < Struct.new(:host, :port, :vhost, :username, :password, :prefetch)
3
+ def initialize
4
+ # AMQP connection
5
+ self.host = '127.0.0.1'
6
+ self.port = '5672'
7
+ self.vhost = '/'
8
+ self.prefetch = 256
9
+
10
+ # AMQP auth
11
+ self.username = 'guest'
12
+ self.password = 'guest'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,103 @@
1
+ module Roundhousekiq
2
+ class Runner
3
+
4
+ attr_accessor :connection, :channel, :queue, :consumer, :shutdown_runner,
5
+ :exchange, :error_exchange, :queues, :queue_worker_map
6
+
7
+ ##################################
8
+ # Public API
9
+ ##################################
10
+
11
+ def initialize
12
+ self.queues = []
13
+ self.queue_worker_map = {}
14
+ end
15
+
16
+ def run
17
+ establish_connection
18
+ create_channel
19
+ create_exchanges_and_queues
20
+ setup_subscribers
21
+ end
22
+
23
+ def shutdown
24
+ # Give runner time to finish its work
25
+ self.shutdown_runner = true
26
+ sleep 10
27
+
28
+ # Spawn new thread for closing the connection. Connection cannot be closed
29
+ # from current thread (being in TRAP context).
30
+ Thread.new { self.connection.try :close }
31
+
32
+ # Sleep again to wait for connection close
33
+ sleep 10
34
+ end
35
+
36
+ def shutdown?
37
+ self.shutdown_runner
38
+ end
39
+
40
+
41
+ ##################################
42
+ # Connection
43
+ ##################################
44
+
45
+ def establish_connection
46
+ options = { properties: self.class.client_settings }
47
+
48
+ self.connection = Bunny.new self.class.connection_settings, options
49
+ self.connection.start
50
+ end
51
+
52
+ def self.connection_settings
53
+ config = Roundhousekiq.config.to_h
54
+ config.select { |k, v| %i(host port vhost username password).include? k }
55
+ end
56
+
57
+ def self.client_settings
58
+ Bunny::Session::DEFAULT_CLIENT_PROPERTIES.merge product: 'Roundhousekiq'
59
+ end
60
+
61
+ def create_channel
62
+ self.channel = self.connection.create_channel
63
+ self.channel.prefetch Roundhousekiq.config.prefetch
64
+ self.channel
65
+ end
66
+
67
+ def create_exchanges_and_queues
68
+ Workers.definitions.each do |worker, definition|
69
+ exchange = self.channel.exchange(
70
+ definition.exchange[:name],
71
+ type: definition.exchange[:type],
72
+ durable: true
73
+ )
74
+
75
+ queue = self.channel.queue(
76
+ definition.queue[:name],
77
+ auto_delete: definition.queue[:auto_delete],
78
+ durable: definition.queue[:durable]
79
+ ).bind(exchange, routing_key: definition.queue[:routing_key])
80
+
81
+ self.queues << queue
82
+ self.queue_worker_map[queue] = worker
83
+ end
84
+ end
85
+
86
+ def setup_subscribers
87
+ self.queues.each do |queue|
88
+ queue.subscribe(manual_ack: true) do |delivery_info, metadata, payload|
89
+ self.channel.ack delivery_info.delivery_tag
90
+ process_message queue, payload
91
+ end
92
+ end
93
+
94
+ while not self.shutdown?
95
+ sleep 5
96
+ end
97
+ end
98
+
99
+ def process_message(queue, payload)
100
+ queue_worker_map[queue].perform_async JSON.parse(payload)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ module Roundhousekiq
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ module Roundhousekiq
2
+ module Worker
3
+ def self.included(base)
4
+ Workers.register base
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def exchange_name(name)
10
+ Workers.exchange_name_for self, name
11
+ end
12
+
13
+ def exchange_type(type)
14
+ Workers.exchange_type_for self, type
15
+ end
16
+
17
+ def queue_name(name)
18
+ Workers.queue_name_for self, name
19
+ end
20
+
21
+ def routing_key(key)
22
+ Workers.routing_key_for self, key
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ module Roundhousekiq
2
+ class WorkerDefinition
3
+
4
+ attr_reader :exchange, :queue
5
+
6
+ def initialize
7
+ @exchange = {}
8
+ @queue = {}
9
+ end
10
+
11
+ def exchange_name=(name)
12
+ exchange[:name] = name
13
+ end
14
+
15
+ def exchange_type=(type)
16
+ exchange[:type] = type
17
+ end
18
+
19
+ def queue_name=(name)
20
+ name ||= '' # Default name to empty string
21
+
22
+ queue[:name] = name
23
+ queue[:durable] = name != ''
24
+ queue[:auto_delete] = name == ''
25
+ end
26
+
27
+ def routing_key=(key)
28
+ queue[:routing_key] = key
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ module Roundhousekiq
2
+ class Workers
3
+ class << self
4
+ def register(worker)
5
+ if definitions.key? worker
6
+ warn "Worker class #{worker.to_s} already registered"
7
+ end
8
+
9
+ definitions[worker] = WorkerDefinition.new
10
+ end
11
+
12
+ def definitions
13
+ @@definitions ||= {}
14
+ end
15
+
16
+ def exchange_name_for(worker, name)
17
+ definition = definitions[worker]
18
+ fail "Unknown worker class passed: #{worker}" unless definition
19
+ definition.exchange_name = name
20
+ end
21
+
22
+ def exchange_type_for(worker, type)
23
+ definition = definitions[worker]
24
+ fail "Unknown worker class passed: #{worker}" unless definition
25
+ definition.exchange_type = type
26
+ end
27
+
28
+ def queue_name_for(worker, name)
29
+ definition = definitions[worker]
30
+ fail "Unknown worker class passed: #{worker}" unless definition
31
+ definition.queue_name = name
32
+ end
33
+
34
+ def routing_key_for(worker, key)
35
+ definition = definitions[worker]
36
+ fail "Unknown worker class passed: #{worker}" unless definition
37
+ definition.routing_key = key
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ require 'bunny'
2
+ require 'roundhousekiq/configuration'
3
+ require 'roundhousekiq/runner'
4
+ require 'roundhousekiq/version'
5
+ require 'roundhousekiq/worker'
6
+ require 'roundhousekiq/workers'
7
+ require 'roundhousekiq/worker_definition'
8
+
9
+ module Roundhousekiq
10
+ def self.configure
11
+ @config = Configuration.new
12
+ yield(@config) if block_given?
13
+ @config
14
+ end
15
+
16
+ def self.config
17
+ @config || configure
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'roundhousekiq/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "roundhousekiq"
8
+ spec.version = Roundhousekiq::VERSION
9
+ spec.authors = ["Moritz Lawitschka"]
10
+ spec.email = ["moritz.lawitschka@suitepad.de"]
11
+ spec.summary = %q{AMQP to Sidekiq bridge}
12
+ spec.description = %q{Trigger Sidekiq jobs asynchronously over AMQP}
13
+ spec.homepage = "https://github.com/suitepad-gmbh/roundhousekiq"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = ['roundhousekiq']
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "bunny", "~> 1.7"
22
+ spec.add_dependency "sidekiq", "~> 3.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.3.0"
27
+ spec.add_development_dependency "rspec-nc", "~> 0.2.0"
28
+ spec.add_development_dependency "guard", "~> 2.13.0"
29
+ spec.add_development_dependency "guard-rspec", "~> 4.6.4"
30
+ spec.add_development_dependency "codeclimate-test-reporter"
31
+ end
@@ -0,0 +1,6 @@
1
+ require 'sidekiq'
2
+
3
+ class DummyWorker
4
+ include Sidekiq::Worker
5
+ include Roundhousekiq::Worker
6
+ end
@@ -0,0 +1,17 @@
1
+ describe Roundhousekiq::Configuration do
2
+
3
+ # Method existence
4
+ it { should respond_to :host }
5
+ it { should respond_to :host= }
6
+ it { should respond_to :port }
7
+ it { should respond_to :port= }
8
+ it { should respond_to :vhost }
9
+ it { should respond_to :vhost= }
10
+ it { should respond_to :username }
11
+ it { should respond_to :username= }
12
+ it { should respond_to :password }
13
+ it { should respond_to :password= }
14
+ it { should respond_to :prefetch }
15
+ it { should respond_to :prefetch= }
16
+
17
+ end