daemon_objects 0.1.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/.gitignore +20 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +115 -0
- data/Rakefile +7 -0
- data/daemon_objects.gemspec +29 -0
- data/lib/daemon_objects/amqp_support.rb +34 -0
- data/lib/daemon_objects/base.rb +75 -0
- data/lib/daemon_objects/consumer_base.rb +35 -0
- data/lib/daemon_objects/loader.rb +31 -0
- data/lib/daemon_objects/logging.rb +31 -0
- data/lib/daemon_objects/railtie.rb +6 -0
- data/lib/daemon_objects/tasks/daemon_objects.rake +41 -0
- data/lib/daemon_objects/version.rb +3 -0
- data/lib/daemon_objects.rb +14 -0
- data/spec/fixtures/daemon_path_spec/daemon_one_daemon.rb +0 -0
- data/spec/fixtures/daemon_path_spec/daemon_two_daemon.rb +0 -0
- data/spec/lib/daemon_objects/base_spec.rb +173 -0
- data/spec/lib/daemon_objects/consumer_base_spec.rb +26 -0
- data/spec/lib/daemon_objects/logging_spec.rb +75 -0
- data/spec/lib/daemon_objects_spec.rb +17 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/stub_logger.rb +23 -0
- metadata +191 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Craig Israel
|
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,115 @@
|
|
1
|
+
# DaemonObjects
|
2
|
+
[](https://travis-ci.org/craigisrael/daemon_objects)
|
3
|
+
[](https://codeclimate.com/github/craigisrael/daemon_objects)
|
4
|
+
|
5
|
+
Daemon Objects is designed to simplify using daemons in your ruby applications. Under the hood, it uses the
|
6
|
+
[daemons](http://daemons.rubyforge.org) gem, which is a mature and tested solution. But, it adds support for managing via rake tasks,
|
7
|
+
error handling and instrumentation.
|
8
|
+
|
9
|
+
The [daemons](http://daemons.rubyforge.org) gem also is intended to be used to daemonize a ruby script. DaemonObjects provides an
|
10
|
+
object-oriented framework for developing daemons. This allows the application developer to focus on the specific behavior of the daemon
|
11
|
+
instead of the infrastructure of daemon management.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'daemon_objects'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install daemon_objects
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
DaemonObjects will create daemons based on a simple convention. It will search a directory for files name \*Daemon.rb. These typically
|
30
|
+
will just inherit from the base Daemon class.
|
31
|
+
|
32
|
+
class MyDaemon < DaemonObjects::Base; end
|
33
|
+
|
34
|
+
This provides the basic daemon control methods (start, stop, run and restart) to your daemon.
|
35
|
+
|
36
|
+
To add behavior to your daemon, you will need a consumer. DaemonObjects will load the consumer using the name of the daemon and
|
37
|
+
will search in the same directory for it. For example, if your daemon is name MyDaemon, the consumer should be named MyConsumer.
|
38
|
+
|
39
|
+
A consumer needs to inherit from the consumer base and implement run. For example,
|
40
|
+
|
41
|
+
class MyConsumer < DaemonObjects::ConsumerBase
|
42
|
+
|
43
|
+
def run
|
44
|
+
loop do
|
45
|
+
"I'm looping"
|
46
|
+
sleep 5
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
### Rake tasks
|
53
|
+
|
54
|
+
Once you have defined the daemon, you can control it with rake tasks. To access the rake tasks,
|
55
|
+
you will need to include the daemon\_objects railtie in config/application.rb.
|
56
|
+
|
57
|
+
require 'daemon_objects/railtie'
|
58
|
+
|
59
|
+
Rake tasks are created using the daemon file name. The rake syntax is:
|
60
|
+
|
61
|
+
rake daemon:<daemon_file_name>:<command>
|
62
|
+
|
63
|
+
For example, to start the MyDaemon daemon:
|
64
|
+
|
65
|
+
rake daemon:my_daemon:start
|
66
|
+
|
67
|
+
Four commands are supported
|
68
|
+
|
69
|
+
* start - Starts the daemon
|
70
|
+
* stop - Stops the daemon
|
71
|
+
* restart - Stops and then starts the daemon
|
72
|
+
* run - Runs the daemon synchronously
|
73
|
+
|
74
|
+
### Amqp Support
|
75
|
+
|
76
|
+
_in beta_
|
77
|
+
|
78
|
+
DaemonObjects also has support for monitoring an amqp queue. This is done with the amqp gem. To support this
|
79
|
+
with your daemon, add `supports_amqp` to your daemon class.
|
80
|
+
|
81
|
+
class MyQueueProcessingDaemon < Daemon::Base
|
82
|
+
supports_amqp :endpoint => "http://localhost:5672",
|
83
|
+
:queue_name => "my_awesome_queue",
|
84
|
+
:worker_class => MyAmqpWorker
|
85
|
+
end
|
86
|
+
|
87
|
+
This will add the code to monitor the queue, so all you need now is code to handle the messages.
|
88
|
+
|
89
|
+
class MyQueueProcessingConsumer < Daemon::ConsumerBase
|
90
|
+
|
91
|
+
handle_messages_with do |payload|
|
92
|
+
puts "payload"
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
### Logging
|
98
|
+
|
99
|
+
DaemonObjects will create a new log file for your daemon using the pattern _daemon\_file\_name_\_daemon.log. In a rails project,
|
100
|
+
this will be created in the log directory of your application.
|
101
|
+
|
102
|
+
### Support for third-party libraries
|
103
|
+
|
104
|
+
DaemonObjects supports the following third-party libraries. If they are required in your application, your daemon will use them.
|
105
|
+
|
106
|
+
* [Airbrake](http://airbrake.io) - any errors that occur in the daemon will be reported to Airbrake.
|
107
|
+
* [NewRelic](http://newrelic.com) - amqp daemons will have instrument the handle\_message method and report to New Relic.
|
108
|
+
|
109
|
+
## Contributing
|
110
|
+
|
111
|
+
1. Fork it
|
112
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
113
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
114
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
115
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'daemon_objects/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "daemon_objects"
|
8
|
+
spec.version = DaemonObjects::VERSION
|
9
|
+
spec.authors = ["Craig Israel"]
|
10
|
+
spec.email = ["craig@theisraels.net"]
|
11
|
+
spec.description = %q{ Object-oriented daemons}
|
12
|
+
spec.summary = %q{ Daemon objects provides an object-based interface to daemons}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "daemons", "~> 1.1"
|
22
|
+
spec.add_dependency "activesupport", "~> 3.2"
|
23
|
+
spec.add_dependency "amqp", "~> 0.9"
|
24
|
+
spec.add_dependency "rake"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "pry-nav"
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module DaemonObjects::AmqpSupport
|
2
|
+
attr_accessor :endpoint, :queue, :prefetch, :worker_class
|
3
|
+
|
4
|
+
def arguments
|
5
|
+
@arguments ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
logger.info "Preparing to start the AMQP watcher."
|
10
|
+
|
11
|
+
AMQP.start(endpoint) do |connection, open_ok|
|
12
|
+
logger.info "Starting up the AMQP watcher."
|
13
|
+
|
14
|
+
channel = AMQP::Channel.new(connection)
|
15
|
+
channel.prefetch(1) if prefetch
|
16
|
+
|
17
|
+
worker = worker_class.new(
|
18
|
+
channel,
|
19
|
+
get_consumer,
|
20
|
+
{
|
21
|
+
:queue_name => queue,
|
22
|
+
:arguments => arguments
|
23
|
+
})
|
24
|
+
|
25
|
+
worker.start
|
26
|
+
|
27
|
+
Signal.trap("INT") do
|
28
|
+
logger.info "Exiting process"
|
29
|
+
connection.close { EventMachine.stop }
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'daemon_objects/logging'
|
2
|
+
|
3
|
+
class DaemonObjects::Base
|
4
|
+
extend DaemonObjects::Logging
|
5
|
+
|
6
|
+
def self.consumes_amqp(opts={})
|
7
|
+
extend DaemonObjects::AmqpSupport
|
8
|
+
self.endpoint = opts.delete(:endpoint)
|
9
|
+
self.queue = opts.delete(:queue_name)
|
10
|
+
self.arguments["x-message-ttl"] = opts.delete(:ttl) if opts[:ttl]
|
11
|
+
self.prefetch = opts.delete(:prefetch)
|
12
|
+
self.worker_class = opts.delete(:worker_class)
|
13
|
+
|
14
|
+
logger.info "Configured to consume queue [#{queue}] at endpoint [#{endpoint}]"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.pid_directory
|
18
|
+
(defined? Rails) ? File.join(Rails.root, "tmp/pids") : "."
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.consumer_class
|
22
|
+
@consumer_class ||= "#{self.to_s.gsub("Daemon", "")}Consumer".constantize
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.proc_name
|
26
|
+
@proc_name ||= self.to_s.underscore
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get_consumer
|
30
|
+
consumer_class.new(logger)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.run
|
34
|
+
get_consumer.run
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.after_fork
|
38
|
+
# daemonizing closes all file handles, so this will reopen the log
|
39
|
+
force_logger_reset
|
40
|
+
# this seems to be enough to initialize NewRelic if it's defined
|
41
|
+
defined?(NewRelic)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.start
|
45
|
+
# connection will get severed on fork, so disconnect first
|
46
|
+
ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
|
47
|
+
|
48
|
+
FileUtils.mkdir_p(pid_directory)
|
49
|
+
|
50
|
+
Daemons.run_proc(proc_name,
|
51
|
+
{ :ARGV => ["start", "-f"],
|
52
|
+
:log_dir => "/tmp",
|
53
|
+
:dir => pid_directory,
|
54
|
+
:log_output => true}) do
|
55
|
+
|
56
|
+
after_fork
|
57
|
+
run
|
58
|
+
end
|
59
|
+
|
60
|
+
rescue StandardError => e
|
61
|
+
logger.error(e.message)
|
62
|
+
logger.error(e.backtrace.join("\n"))
|
63
|
+
Airbrake.notify(e) if defined?(Airbrake)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.stop
|
67
|
+
Daemons.run_proc(proc_name, { :ARGV => [ "stop", "-f" ], :dir => pid_directory})
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.restart
|
71
|
+
start
|
72
|
+
stop
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class DaemonObjects::ConsumerBase
|
2
|
+
|
3
|
+
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation if defined?(::NewRelic)
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :message_handler
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :logger
|
10
|
+
|
11
|
+
def initialize(logger)
|
12
|
+
@logger = logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
logger.info("Starting consumer")
|
17
|
+
end
|
18
|
+
|
19
|
+
def handle_message(payload)
|
20
|
+
logger.info("Handling message #{payload}")
|
21
|
+
handle_message_impl(payload)
|
22
|
+
logger.info("Completed handling message")
|
23
|
+
rescue StandardError => e
|
24
|
+
logger.error("#{e.class}: #{e.message}")
|
25
|
+
logger.error(e.backtrace.join("\n"))
|
26
|
+
Airbrake.notify(e) if defined?(Airbrake)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.handle_messages_with(&block)
|
30
|
+
raise StandardError.new("Provided block must take at least one argument - 'payload'") if block.arity < 1
|
31
|
+
define_method(:handle_message_impl, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
add_transaction_tracer :handle_message, :category => :task if defined?(::NewRelic)
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DaemonObjects
|
2
|
+
ROOT = File.join(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
DAEMON_FILE_ENDING = "_daemon.rb"
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :daemon_path
|
8
|
+
|
9
|
+
def daemon_path
|
10
|
+
@daemon_path ||= File.join(Rake.application.original_dir, "lib/daemons")
|
11
|
+
end
|
12
|
+
|
13
|
+
def daemons
|
14
|
+
@daemons ||= get_daemons
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_daemon_name(path)
|
18
|
+
file = Pathname(path).basename.to_s
|
19
|
+
file.gsub!(/#{DAEMON_FILE_ENDING}$/, "")
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def get_daemons
|
25
|
+
paths = Dir["#{daemon_path}/*#{DAEMON_FILE_ENDING}"]
|
26
|
+
warn "No daemons found at #{daemon_path}" if paths.empty?
|
27
|
+
paths.map{|p| get_daemon_name(p) }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DaemonObjects::Logging
|
2
|
+
|
3
|
+
def log_filename
|
4
|
+
"#{to_s.underscore}.log"
|
5
|
+
end
|
6
|
+
|
7
|
+
def log_directory
|
8
|
+
(defined? Rails) ? File.join(Rails.root, "log") : "log"
|
9
|
+
end
|
10
|
+
|
11
|
+
def logger
|
12
|
+
@logger ||= create_logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_logger
|
16
|
+
FileUtils.mkdir_p log_directory
|
17
|
+
logger = ::Logger.new(File.join(log_directory, log_filename))
|
18
|
+
logger.formatter = ::Logger::Formatter.new
|
19
|
+
logger
|
20
|
+
end
|
21
|
+
|
22
|
+
def logger=(value)
|
23
|
+
@logger = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def force_logger_reset
|
27
|
+
@logger = nil
|
28
|
+
Rails.logger = logger if defined?(Rails)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
namespace :daemon do
|
2
|
+
|
3
|
+
# create tasks for each daemon to start/stop/restart/run
|
4
|
+
DaemonObjects.daemons.each do |daemon|
|
5
|
+
|
6
|
+
namespace daemon do
|
7
|
+
description = "#{daemon.to_s.gsub("_", " ")} daemon"
|
8
|
+
desc "starts the #{description}; can substitute stop or restart for start, " +
|
9
|
+
"or use run to run the daemon in the foreground"
|
10
|
+
|
11
|
+
[:start, :stop, :run].each do |action|
|
12
|
+
task action => :environment do
|
13
|
+
|
14
|
+
require "daemon_objects"
|
15
|
+
require "#{DaemonObjects.daemon_path}/#{daemon}_daemon.rb"
|
16
|
+
require "#{DaemonObjects.daemon_path}/#{daemon}_consumer.rb"
|
17
|
+
|
18
|
+
|
19
|
+
puts "#{description} #{action}"
|
20
|
+
daemon_class = "#{daemon}_daemon".camelcase.constantize
|
21
|
+
daemon_class.send(action)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
task :restart => [:environment, :stop, :start]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
namespace :all do
|
30
|
+
|
31
|
+
desc 'start all daemons'
|
32
|
+
task :start => DaemonObjects.daemons.map{|d| "daemon:#{d}:start"}
|
33
|
+
|
34
|
+
desc 'stop all daemons'
|
35
|
+
task :stop => DaemonObjects.daemons.map{|d| "daemon:#{d}:stop"}
|
36
|
+
|
37
|
+
desc 'restart all daemons'
|
38
|
+
task :restart => [:stop, :start]
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "daemon_objects/version"
|
2
|
+
require 'active_support/core_ext/string'
|
3
|
+
require 'daemons'
|
4
|
+
require 'amqp'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
module DaemonObjects; end
|
8
|
+
|
9
|
+
require 'daemon_objects/loader'
|
10
|
+
require 'daemon_objects/base'
|
11
|
+
require 'daemon_objects/consumer_base'
|
12
|
+
require 'daemon_objects/amqp_support'
|
13
|
+
require 'daemon_objects/railtie'
|
14
|
+
|
File without changes
|
File without changes
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DaemonObjects::Base do
|
4
|
+
after :each do
|
5
|
+
[ :MyDaemon, :ThreePartNameDaemon, :MyConsumer].each do |sym|
|
6
|
+
Object.send(:remove_const, sym) if Object.const_defined?(sym)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#extends' do
|
11
|
+
it 'should extend logging' do
|
12
|
+
MyDaemon = Class.new(DaemonObjects::Base)
|
13
|
+
MyDaemon.singleton_class.included_modules.should include(DaemonObjects::Logging)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#run' do
|
18
|
+
it 'should create new consumer' do
|
19
|
+
MyConsumer = Class.new(DaemonObjects::ConsumerBase)
|
20
|
+
|
21
|
+
MyDaemon = Class.new(DaemonObjects::Base) do
|
22
|
+
self.logger = StubLogger.new
|
23
|
+
end
|
24
|
+
|
25
|
+
MyDaemon.run
|
26
|
+
MyDaemon.logger.logged_output.should =~ /Starting consumer\n/
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#start' do
|
32
|
+
it 'should call daemon run_proc' do
|
33
|
+
MyDaemon = Class.new(DaemonObjects::Base)
|
34
|
+
Daemons.should_receive(:run_proc).
|
35
|
+
with(MyDaemon.proc_name,
|
36
|
+
{ :ARGV => ['start', '-f'],
|
37
|
+
:log_dir => "/tmp",
|
38
|
+
:dir => MyDaemon.pid_directory,
|
39
|
+
:log_output => true
|
40
|
+
} )
|
41
|
+
MyDaemon.start
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#stop' do
|
46
|
+
it 'should call daemon stop_proc' do
|
47
|
+
MyDaemon = Class.new(DaemonObjects::Base)
|
48
|
+
Daemons.should_receive(:run_proc).
|
49
|
+
with(MyDaemon.proc_name,
|
50
|
+
{ :ARGV => ['stop', '-f'],
|
51
|
+
:dir => MyDaemon.pid_directory})
|
52
|
+
MyDaemon.stop
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '##consumer_class' do
|
57
|
+
it 'should constantize a file with multiple part name' do
|
58
|
+
ThreePartNameConsumer = Class.new
|
59
|
+
ThreePartNameDaemon = Class.new(DaemonObjects::Base)
|
60
|
+
ThreePartNameDaemon.consumer_class.should == ThreePartNameConsumer
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '##proc_name' do
|
65
|
+
it 'should underscore class to get daemon name' do
|
66
|
+
ThreePartNameDaemon = Class.new(DaemonObjects::Base)
|
67
|
+
ThreePartNameDaemon.proc_name.should == "three_part_name_daemon"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'AMQP support' do
|
72
|
+
let(:endpoint){ }
|
73
|
+
|
74
|
+
before :each do
|
75
|
+
MyWorker = Class.new do
|
76
|
+
def initialize(*args); end
|
77
|
+
def run; end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
after :each do
|
82
|
+
Object.send( :remove_const, :MyWorker) if defined?(MyWorker)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should start AMQP if daemon is an amqp consumer' do
|
86
|
+
MyConsumer = Class.new(DaemonObjects::ConsumerBase)
|
87
|
+
MyDaemon = Class.new(DaemonObjects::Base) do
|
88
|
+
consumes_amqp :endpoint => 'amqp://localhost:4567',
|
89
|
+
:queue_name => 'queue',
|
90
|
+
:worker_class => MyWorker
|
91
|
+
end
|
92
|
+
|
93
|
+
AMQP.should_receive(:start).with('amqp://localhost:4567').and_return(true)
|
94
|
+
MyDaemon.run
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should not start AMQP if daemon is not an amqp consumer' do
|
98
|
+
MyConsumer = Class.new(DaemonObjects::ConsumerBase)
|
99
|
+
MyDaemon = Class.new(DaemonObjects::Base)
|
100
|
+
|
101
|
+
AMQP.should_not_receive(:start)
|
102
|
+
MyDaemon.run
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should start a worker' do
|
107
|
+
MyConsumer = Class.new(DaemonObjects::ConsumerBase)
|
108
|
+
MyDaemon = Class.new(DaemonObjects::Base) do
|
109
|
+
consumes_amqp :endpoint => 'amqp://localhost:4567',
|
110
|
+
:queue_name => 'queue',
|
111
|
+
:worker_class => MyWorker
|
112
|
+
end
|
113
|
+
|
114
|
+
def AMQP.start(endpoint)
|
115
|
+
yield "connection", "open"
|
116
|
+
end
|
117
|
+
|
118
|
+
channel = double(AMQP::Channel)
|
119
|
+
AMQP::Channel.stub(:new).and_return(channel)
|
120
|
+
channel.should_not_receive(:prefetch)
|
121
|
+
|
122
|
+
worker = MyWorker.new
|
123
|
+
consumer = MyDaemon.get_consumer
|
124
|
+
MyDaemon.stub(:get_consumer).and_return(consumer)
|
125
|
+
|
126
|
+
MyWorker.should_receive(:new).
|
127
|
+
with(channel, consumer, {
|
128
|
+
:queue_name => 'queue',
|
129
|
+
:arguments => {}
|
130
|
+
}).
|
131
|
+
and_return(worker)
|
132
|
+
worker.should_receive(:start)
|
133
|
+
|
134
|
+
MyDaemon.run
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should use prefetch value when available' do
|
138
|
+
MyConsumer = Class.new(DaemonObjects::ConsumerBase)
|
139
|
+
MyDaemon = Class.new(DaemonObjects::Base) do
|
140
|
+
consumes_amqp :endpoint => 'amqp://localhost:4567',
|
141
|
+
:queue_name => 'queue',
|
142
|
+
:prefetch => 1,
|
143
|
+
:worker_class => MyWorker
|
144
|
+
end
|
145
|
+
|
146
|
+
def AMQP.start(endpoint)
|
147
|
+
yield "connection", "open"
|
148
|
+
end
|
149
|
+
|
150
|
+
channel = double(AMQP::Channel)
|
151
|
+
channel.should_receive(:prefetch).with(1)
|
152
|
+
|
153
|
+
AMQP::Channel.stub(:new).and_return(channel)
|
154
|
+
|
155
|
+
worker = MyWorker.new
|
156
|
+
consumer = MyDaemon.get_consumer
|
157
|
+
MyDaemon.stub(:get_consumer).and_return(consumer)
|
158
|
+
|
159
|
+
MyWorker.should_receive(:new).
|
160
|
+
with(channel, consumer, {
|
161
|
+
:queue_name => 'queue',
|
162
|
+
:arguments => {}
|
163
|
+
}).
|
164
|
+
and_return(worker)
|
165
|
+
worker.should_receive(:start)
|
166
|
+
|
167
|
+
MyDaemon.run
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DaemonObjects::ConsumerBase do
|
4
|
+
describe '#handle_message' do
|
5
|
+
after :each do
|
6
|
+
Object.send(:remove_const, :Harness)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should call the configured message handler' do
|
10
|
+
|
11
|
+
Harness = Class.new(DaemonObjects::ConsumerBase) do
|
12
|
+
def payloads_received
|
13
|
+
@payloads_received ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
handle_messages_with{|p| payloads_received << p }
|
17
|
+
end
|
18
|
+
|
19
|
+
h = Harness.new(StubLogger.new)
|
20
|
+
h.handle_message({:x => 1})
|
21
|
+
|
22
|
+
h.payloads_received.should == [{:x => 1}]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DaemonObjects::Logging do
|
4
|
+
let(:harness) do
|
5
|
+
Class.new do
|
6
|
+
extend DaemonObjects::Logging
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#logger' do
|
11
|
+
it 'should create a logger at log/log_filename path' do
|
12
|
+
logger = StubLogger.new
|
13
|
+
|
14
|
+
Logger.stub(:new).
|
15
|
+
with("#{harness.log_directory}/#{harness.log_filename}").
|
16
|
+
and_return(logger)
|
17
|
+
|
18
|
+
harness.logger.info("starting consumer")
|
19
|
+
|
20
|
+
logger.logged_output.should =~ /starting consumer\n$/
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#create_logger' do
|
25
|
+
it 'should create a logger with timestamp formatting' do
|
26
|
+
logger = harness.logger
|
27
|
+
logger.formatter.class.should == ::Logger::Formatter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#log_filename' do
|
32
|
+
before :each do
|
33
|
+
MyDaemon = Class.new do
|
34
|
+
extend DaemonObjects::Logging
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
after :each do
|
39
|
+
Object.send(:remove_const, :MyDaemon)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should underscore name for log file' do
|
43
|
+
MyDaemon.log_filename.should == "my_daemon.log"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#log_directory' do
|
48
|
+
it "should use 'log' for default log path" do
|
49
|
+
harness.log_directory.to_s.should == "log"
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with rails' do
|
53
|
+
before :each do
|
54
|
+
unless defined?(Rails)
|
55
|
+
module Rails
|
56
|
+
def self.root
|
57
|
+
"/root"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
after :each do
|
64
|
+
Object.send(:remove_const, :Rails)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should use Rails log path when Rails is defined' do
|
68
|
+
harness.log_directory.to_s.should == File.join(Rails.root, "log")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DaemonObjects do
|
4
|
+
describe '#daemons' do
|
5
|
+
it 'should get daemons from daemon_path' do
|
6
|
+
DaemonObjects.daemon_path = File.join(FIXTURES_PATH, "daemon_path_spec")
|
7
|
+
DaemonObjects.daemons.sort.should == ["daemon_one", "daemon_two"]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#get_daemon_name' do
|
12
|
+
it 'should parse out path and Daemon.rb' do
|
13
|
+
path = File.join(FIXTURES_PATH, "daemon_path_spec/daemon_one_daemon.rb")
|
14
|
+
DaemonObjects.get_daemon_name(path).should == "daemon_one"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
require 'pry'
|
8
|
+
SPEC_PATH = File.dirname(__FILE__)
|
9
|
+
Dir[File.join(SPEC_PATH, "support/**/*.rb")].each{|f| require f}
|
10
|
+
|
11
|
+
FIXTURES_PATH = File.join(SPEC_PATH, "fixtures")
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
config.filter_run :focus
|
17
|
+
|
18
|
+
# Run specs in random order to surface order dependencies. If you find an
|
19
|
+
# order dependency and want to debug it, you can fix the order by providing
|
20
|
+
# the seed, which is printed after each run.
|
21
|
+
# --seed 1234
|
22
|
+
config.order = 'random'
|
23
|
+
end
|
24
|
+
|
25
|
+
require File.join(File.dirname(__FILE__), "../lib/daemon_objects.rb")
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class StubLogger
|
2
|
+
|
3
|
+
attr_accessor :logger
|
4
|
+
|
5
|
+
def method_missing(sym, *args, &block)
|
6
|
+
logger.send(sym, *args, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@io = StringIO.new
|
11
|
+
|
12
|
+
require 'logger'
|
13
|
+
@logger = Logger.new(@io)
|
14
|
+
end
|
15
|
+
|
16
|
+
def logged_output
|
17
|
+
@io.string
|
18
|
+
end
|
19
|
+
|
20
|
+
def messages
|
21
|
+
logged_output.split("\n")
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: daemon_objects
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Craig Israel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: daemons
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.1'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.2'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.2'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: amqp
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.9'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.9'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.3'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.3'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: pry-nav
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: ! ' Object-oriented daemons'
|
127
|
+
email:
|
128
|
+
- craig@theisraels.net
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .rspec
|
135
|
+
- .travis.yml
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- daemon_objects.gemspec
|
141
|
+
- lib/daemon_objects.rb
|
142
|
+
- lib/daemon_objects/amqp_support.rb
|
143
|
+
- lib/daemon_objects/base.rb
|
144
|
+
- lib/daemon_objects/consumer_base.rb
|
145
|
+
- lib/daemon_objects/loader.rb
|
146
|
+
- lib/daemon_objects/logging.rb
|
147
|
+
- lib/daemon_objects/railtie.rb
|
148
|
+
- lib/daemon_objects/tasks/daemon_objects.rake
|
149
|
+
- lib/daemon_objects/version.rb
|
150
|
+
- spec/fixtures/daemon_path_spec/daemon_one_daemon.rb
|
151
|
+
- spec/fixtures/daemon_path_spec/daemon_two_daemon.rb
|
152
|
+
- spec/lib/daemon_objects/base_spec.rb
|
153
|
+
- spec/lib/daemon_objects/consumer_base_spec.rb
|
154
|
+
- spec/lib/daemon_objects/logging_spec.rb
|
155
|
+
- spec/lib/daemon_objects_spec.rb
|
156
|
+
- spec/spec_helper.rb
|
157
|
+
- spec/support/stub_logger.rb
|
158
|
+
homepage: ''
|
159
|
+
licenses:
|
160
|
+
- MIT
|
161
|
+
post_install_message:
|
162
|
+
rdoc_options: []
|
163
|
+
require_paths:
|
164
|
+
- lib
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
166
|
+
none: false
|
167
|
+
requirements:
|
168
|
+
- - ! '>='
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ! '>='
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
177
|
+
requirements: []
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 1.8.23
|
180
|
+
signing_key:
|
181
|
+
specification_version: 3
|
182
|
+
summary: Daemon objects provides an object-based interface to daemons
|
183
|
+
test_files:
|
184
|
+
- spec/fixtures/daemon_path_spec/daemon_one_daemon.rb
|
185
|
+
- spec/fixtures/daemon_path_spec/daemon_two_daemon.rb
|
186
|
+
- spec/lib/daemon_objects/base_spec.rb
|
187
|
+
- spec/lib/daemon_objects/consumer_base_spec.rb
|
188
|
+
- spec/lib/daemon_objects/logging_spec.rb
|
189
|
+
- spec/lib/daemon_objects_spec.rb
|
190
|
+
- spec/spec_helper.rb
|
191
|
+
- spec/support/stub_logger.rb
|