ruote-nats 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.yardopts +8 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +62 -0
- data/Rakefile +8 -0
- data/example/single_file.rb +30 -0
- data/image/system-diagram.png +0 -0
- data/lib/ruote-nats.rb +29 -0
- data/lib/ruote-nats/command_receiver.rb +69 -0
- data/lib/ruote-nats/participant.rb +50 -0
- data/lib/ruote-nats/reply_receiver.rb +44 -0
- data/lib/ruote-nats/shell_executor.rb +55 -0
- data/lib/ruote-nats/version.rb +3 -0
- data/ruote-nats.gemspec +32 -0
- data/spec/command_receiver_spec.rb +57 -0
- data/spec/participant_spec.rb +57 -0
- data/spec/reply_receiver_spec.rb +70 -0
- data/spec/shell_executor_spec.rb +44 -0
- data/spec/spec_helper.rb +46 -0
- metadata +248 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (C) 2012 Naoto Takai. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions
|
5
|
+
are met:
|
6
|
+
1. Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
13
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
14
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
15
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
16
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
17
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
18
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
19
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
20
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
21
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
22
|
+
SUCH DAMAGE.RE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# ruote-nats
|
2
|
+
|
3
|
+
ruote-nats is an implementation of the ruote participant and receivers to process workitem on remote host using NATS.
|
4
|
+
|
5
|
+
![System Diagram](https://github.com/takai/ruote-nats/raw/master/image/system-diagram.png)
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Participant registration:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
engine.register_participant :remote_shell, RuoteNATS::Participant
|
13
|
+
```
|
14
|
+
|
15
|
+
Process definition:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
remote_shell :command => '/bin/date', :env => { 'LANG' => 'C' }
|
19
|
+
```
|
20
|
+
|
21
|
+
### Options
|
22
|
+
|
23
|
+
* ```queue```: subject to publish command name, the default value is "remote.command".
|
24
|
+
* ```executor```: executor to dispatch on remote host, the default value is "RuoteNATS::ShellExecutor".
|
25
|
+
|
26
|
+
### Options for ShellExecutor
|
27
|
+
|
28
|
+
* ```command```: (required) shell script.
|
29
|
+
* ```env```: environments variables for command.
|
30
|
+
|
31
|
+
## Single File Example
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'bundler/setup'
|
35
|
+
|
36
|
+
require 'ruote'
|
37
|
+
require 'ruote-nats'
|
38
|
+
|
39
|
+
RuoteNATS.logger.level = Logger::DEBUG
|
40
|
+
|
41
|
+
NATS.start do
|
42
|
+
begin
|
43
|
+
pdef = Ruote.define do
|
44
|
+
remote_shell :command => '/bin/date', :env => { 'LANG' => 'C' }
|
45
|
+
end
|
46
|
+
|
47
|
+
engine = Ruote::Engine.new(Ruote::Worker.new(Ruote::HashStorage.new))
|
48
|
+
engine.register_participant :remote_shell, RuoteNATS::Participant
|
49
|
+
|
50
|
+
RuoteNATS::CommandReceiver.new.start
|
51
|
+
RuoteNATS::ReplyReceiver.new(engine).start
|
52
|
+
|
53
|
+
engine.launch(pdef)
|
54
|
+
|
55
|
+
EM.add_timer(1) do
|
56
|
+
NATS.stop
|
57
|
+
end
|
58
|
+
rescue
|
59
|
+
Logger.new(STDOUT).error($!.message)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
require 'ruote'
|
6
|
+
require 'ruote-nats'
|
7
|
+
|
8
|
+
RuoteNATS.logger.level = Logger::DEBUG
|
9
|
+
|
10
|
+
NATS.start do
|
11
|
+
begin
|
12
|
+
pdef = Ruote.define do
|
13
|
+
remote_shell :command => '/bin/date', :env => { 'LANG' => 'C' }
|
14
|
+
end
|
15
|
+
|
16
|
+
engine = Ruote::Engine.new(Ruote::Worker.new(Ruote::HashStorage.new))
|
17
|
+
engine.register_participant :remote_shell, RuoteNATS::Participant
|
18
|
+
|
19
|
+
RuoteNATS::CommandReceiver.new.start
|
20
|
+
RuoteNATS::ReplyReceiver.new(engine).start
|
21
|
+
|
22
|
+
engine.launch(pdef)
|
23
|
+
|
24
|
+
EM.add_timer(1) do
|
25
|
+
NATS.stop
|
26
|
+
end
|
27
|
+
rescue
|
28
|
+
Logger.new(STDOUT).error($!.message)
|
29
|
+
end
|
30
|
+
end
|
Binary file
|
data/lib/ruote-nats.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'msgpack'
|
3
|
+
require 'nats/client'
|
4
|
+
require 'ruote'
|
5
|
+
require 'open3'
|
6
|
+
|
7
|
+
require 'ruote-nats/command_receiver'
|
8
|
+
require 'ruote-nats/participant'
|
9
|
+
require 'ruote-nats/reply_receiver'
|
10
|
+
require 'ruote-nats/shell_executor'
|
11
|
+
require 'ruote-nats/version'
|
12
|
+
|
13
|
+
module RuoteNATS
|
14
|
+
class << self
|
15
|
+
# Get the logger.
|
16
|
+
#
|
17
|
+
# @return [Logger] logger
|
18
|
+
def logger
|
19
|
+
@logger ||= Logger.new(STDOUT).tap { |log| log.level = Logger::INFO }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the logger.
|
23
|
+
#
|
24
|
+
# @param [Logger] logger
|
25
|
+
def logger=(logger)
|
26
|
+
@logger = logger
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RuoteNATS
|
2
|
+
class CommandReceiver
|
3
|
+
|
4
|
+
# Starts to subscribe command queue.
|
5
|
+
#
|
6
|
+
# @param [String] queue_name
|
7
|
+
def start(queue_name = 'remote.command')
|
8
|
+
NATS.subscribe(queue_name, queue: queue_name, max: 1) do |message, reply|
|
9
|
+
NATS.publish(reply, 'ACCEPT') do
|
10
|
+
unpacked = MessagePack.unpack(message)
|
11
|
+
workitem = Ruote::Workitem.new(unpacked)
|
12
|
+
|
13
|
+
RuoteNATS.logger.info do
|
14
|
+
"(#{workitem.sid}) receive command: #{lookup_executor(workitem)} (#{workitem.lookup('params')})"
|
15
|
+
end
|
16
|
+
|
17
|
+
dispatch(workitem)
|
18
|
+
publish_reply(workitem)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def dispatch(workitem)
|
25
|
+
executor = lookup_executor(workitem)
|
26
|
+
results = constantize(executor).new.execute(workitem)
|
27
|
+
rescue
|
28
|
+
workitem.result = 'error'
|
29
|
+
results = { message: $!.message, backtrace: $!.backtrace }
|
30
|
+
ensure
|
31
|
+
store_results(workitem, results)
|
32
|
+
end
|
33
|
+
|
34
|
+
def lookup_executor(workitem)
|
35
|
+
workitem.lookup('params.executor') || 'RuoteNATS::ShellExecutor'
|
36
|
+
end
|
37
|
+
|
38
|
+
def constantize(executor_name)
|
39
|
+
names = executor_name.split('::')
|
40
|
+
names.shift if names.empty? || names.first.empty?
|
41
|
+
|
42
|
+
constant = Object
|
43
|
+
names.each do |name|
|
44
|
+
constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
|
45
|
+
end
|
46
|
+
constant
|
47
|
+
end
|
48
|
+
|
49
|
+
def store_results(workitem, results)
|
50
|
+
params = workitem.lookup('params')
|
51
|
+
|
52
|
+
workitem.set_field('results', Hash.new) unless workitem.lookup('results')
|
53
|
+
workitem.set_field("results.#{workitem.sid}", params.merge(results))
|
54
|
+
end
|
55
|
+
|
56
|
+
def publish_reply(workitem)
|
57
|
+
queue_name = workitem.lookup('reply_to') || 'remote.command.reply'
|
58
|
+
|
59
|
+
packed = MessagePack.pack(workitem.to_h)
|
60
|
+
NATS.publish(queue_name, packed) do
|
61
|
+
RuoteNATS.logger.info do
|
62
|
+
results = workitem.lookup("results.#{workitem.sid}")
|
63
|
+
"(#{workitem.sid}) reply: #{lookup_executor(workitem)} (#{results})"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RuoteNATS
|
2
|
+
|
3
|
+
class TimeoutError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# # RuoteNATS::Participant
|
7
|
+
#
|
8
|
+
class Participant
|
9
|
+
include Ruote::LocalParticipant
|
10
|
+
|
11
|
+
DEFALUT_TIMEOUT = 1
|
12
|
+
|
13
|
+
# @param [Ruote::Workitem] workitem
|
14
|
+
def consume(workitem)
|
15
|
+
queue_name = workitem.lookup('params.queue') || 'remote.command'
|
16
|
+
message = MessagePack.pack(workitem.to_h)
|
17
|
+
|
18
|
+
sid = NATS.request(queue_name, message) do |reply|
|
19
|
+
RuoteNATS.logger.info do
|
20
|
+
executor = workitem.lookup('params.executor') || 'RuoteNATS::ShellExecutor'
|
21
|
+
"(#{workitem.sid}) request: #{executor} (#{workitem.lookup('params')})"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
timeout = (workitem.lookup('params.timeout') || DEFALUT_TIMEOUT).to_i
|
26
|
+
NATS.timeout(sid, timeout) do
|
27
|
+
handle_error(workitem)
|
28
|
+
end
|
29
|
+
rescue
|
30
|
+
RuoteNATS.logger.error($!.message)
|
31
|
+
raise $!
|
32
|
+
end
|
33
|
+
|
34
|
+
def cancel
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def handle_error(workitem)
|
39
|
+
executor = workitem.lookup('params.executor') || 'RuoteNATS::ShellExecutor'
|
40
|
+
RuoteNATS.logger.error do
|
41
|
+
"(#{workitem.sid}) timeout: #{executor} (#{workitem.lookup('params')})"
|
42
|
+
end
|
43
|
+
|
44
|
+
error = TimeoutError.new("Request timeout: workitem could not be processed.")
|
45
|
+
|
46
|
+
error_handler = context.error_handler
|
47
|
+
error_handler.action_handle('error', workitem.to_h['fei'], error)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RuoteNATS
|
2
|
+
class ReplyReceiver
|
3
|
+
|
4
|
+
# @param [Ruote::Engine] engine
|
5
|
+
def initialize(engine)
|
6
|
+
@engine = engine
|
7
|
+
end
|
8
|
+
|
9
|
+
# Start to subscribe reply queue.
|
10
|
+
#
|
11
|
+
# @param [String] queue name
|
12
|
+
def start(queue_name = 'remote.command.reply')
|
13
|
+
NATS.subscribe(queue_name) do |message, reply|
|
14
|
+
unpacked = MessagePack.unpack(message)
|
15
|
+
workitem = Ruote::Workitem.new(unpacked)
|
16
|
+
|
17
|
+
RuoteNATS.logger.info do
|
18
|
+
executor = workitem.lookup('params.executor') || 'RuoteNATS::ShellExecutor'
|
19
|
+
result = workitem.lookup("results.#{workitem.sid}")
|
20
|
+
|
21
|
+
"(#{workitem.sid}) receive reply: #{executor} #{workitem.result} (#{result})"
|
22
|
+
end
|
23
|
+
|
24
|
+
if workitem.result == 'success'
|
25
|
+
@engine.reply_to_engine(workitem)
|
26
|
+
else
|
27
|
+
handle_error(workitem)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def handle_error(workitem)
|
34
|
+
message = workitem.lookup("results.#{workitem.sid}.message")
|
35
|
+
backtrace = workitem.lookup("results.#{workitem.sid}.backtrace")
|
36
|
+
|
37
|
+
error = RuntimeError.new(message)
|
38
|
+
error.set_backtrace(backtrace)
|
39
|
+
|
40
|
+
error_handler = @engine.context.error_handler
|
41
|
+
error_handler.action_handle('error', workitem.to_h['fei'], error)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RuoteNATS
|
2
|
+
class ShellExecutor
|
3
|
+
|
4
|
+
# Execute shell command
|
5
|
+
#
|
6
|
+
# @param [Ruote::Workitem] workitem
|
7
|
+
# @return [Hash] the result of command execution
|
8
|
+
def execute(workitem)
|
9
|
+
if workitem.lookup('params.command')
|
10
|
+
out, status = invoke(workitem)
|
11
|
+
|
12
|
+
if status.success?
|
13
|
+
{ out: out, status: status.exitstatus, finished_at: Ruote.now_to_utc_s }
|
14
|
+
else
|
15
|
+
raise "out: #{out}, status: #{status.exitstatus}, finished_at: #{Ruote.now_to_utc_s}"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
workitem.result = 'failure'
|
19
|
+
|
20
|
+
message = 'command is not specified, check your process definition'
|
21
|
+
RuoteNATS.logger.error do
|
22
|
+
"(#{workitem.sid}) shell: #{message}"
|
23
|
+
end
|
24
|
+
raise message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def invoke(workitem)
|
30
|
+
params = workitem.lookup('params')
|
31
|
+
env = params['env'] || { }
|
32
|
+
command = params['command']
|
33
|
+
|
34
|
+
RuoteNATS.logger.info do
|
35
|
+
message = "(#{workitem.sid}) shell: `#{command}`"
|
36
|
+
message << " with env #{env.inspect}" if env
|
37
|
+
message
|
38
|
+
end
|
39
|
+
|
40
|
+
out, status = Open3.capture2e(env, command)
|
41
|
+
|
42
|
+
RuoteNATS.logger.info do
|
43
|
+
"(#{workitem.sid}) shell: `#{command}` returns #{status.exitstatus}"
|
44
|
+
end
|
45
|
+
if RuoteNATS.logger.debug?
|
46
|
+
out.each_line do |line|
|
47
|
+
RuoteNATS.logger.debug "(#{workitem.sid}) shell: #{line.chomp}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
return out, status
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/ruote-nats.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/ruote-nats/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ['Naoto Takai']
|
7
|
+
gem.email = ['takai@recompile.net']
|
8
|
+
gem.description = 'NATS participant and receivers for ruote'
|
9
|
+
gem.summary = 'ruote-nats is an implementation of the ruote participant and receivers ' \
|
10
|
+
'to process workitem on remote host using NATS'
|
11
|
+
gem.homepage = 'https://github.com/takai/ruote-nats'
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.name = 'ruote-nats'
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
gem.version = RuoteNATS::VERSION
|
19
|
+
|
20
|
+
gem.add_dependency('ruote', '= 2.2.0')
|
21
|
+
gem.add_dependency('nats', '>= 0.4.22')
|
22
|
+
gem.add_dependency('msgpack', '>= 0.4.6')
|
23
|
+
|
24
|
+
gem.add_development_dependency('rspec')
|
25
|
+
gem.add_development_dependency('rake')
|
26
|
+
gem.add_development_dependency('simplecov')
|
27
|
+
gem.add_development_dependency('pry')
|
28
|
+
gem.add_development_dependency('pry-nav')
|
29
|
+
gem.add_development_dependency('yard')
|
30
|
+
gem.add_development_dependency('redcarpet')
|
31
|
+
gem.add_development_dependency('github-markup')
|
32
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RuoteNATS
|
4
|
+
|
5
|
+
class NoopExecutor
|
6
|
+
def execute(workitem)
|
7
|
+
{ noop: true }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe CommandReceiver do
|
12
|
+
let(:dispatcher) { CommandReceiver.new }
|
13
|
+
let(:workitem) do
|
14
|
+
Ruote::Workitem.new("fields" =>
|
15
|
+
{ "params" =>
|
16
|
+
{ "command" => "/bin/date",
|
17
|
+
"env" => {
|
18
|
+
"LANG" => "C" },
|
19
|
+
"ref" => "noop",
|
20
|
+
"executor" => "RuoteNATS::NoopExecutor" },
|
21
|
+
"dispatched_at" => "2000-01-01 11:11:11.111111 UTC" },
|
22
|
+
"fei" =>
|
23
|
+
{ "engine_id" => "engine",
|
24
|
+
"wfid" => "20000101-abcdefg",
|
25
|
+
"subid" => "abcdefghijklmnopqrstu",
|
26
|
+
"expid" => "0_0" },
|
27
|
+
"participant_name" => "noop")
|
28
|
+
end
|
29
|
+
|
30
|
+
around :each do |example|
|
31
|
+
NATS.start(autostart: true) { example.run }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#start' do
|
35
|
+
context do
|
36
|
+
it 'starts to subscribe queue' do
|
37
|
+
sid = NATS.subscribe('remote.command.reply') do |message|
|
38
|
+
unpacked = MessagePack.unpack(message)
|
39
|
+
workitem = Ruote::Workitem.new(unpacked)
|
40
|
+
workitem.lookup("results.#{workitem.sid}").should include("noop" => true)
|
41
|
+
|
42
|
+
NATS.stop
|
43
|
+
end
|
44
|
+
NATS.timeout(sid, 1, expected: 1) do
|
45
|
+
NATS.stop
|
46
|
+
fail "reply message is not sent"
|
47
|
+
end
|
48
|
+
|
49
|
+
subject.start
|
50
|
+
message = MessagePack.pack(workitem.to_h)
|
51
|
+
NATS.request('remote.command', message) do |reply|
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RuoteNATS
|
4
|
+
describe Participant do
|
5
|
+
around :each do |example|
|
6
|
+
NATS.start(autostart: true) { example.run }
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:workitem) do
|
10
|
+
Ruote::Workitem.new("fields" =>
|
11
|
+
{ "params" =>
|
12
|
+
{ "command" => "/bin/date",
|
13
|
+
"env" => {
|
14
|
+
"LANG" => "C" },
|
15
|
+
"ref" => "shell" },
|
16
|
+
"dispatched_at" => "2000-01-01 11:11:11.111111 UTC" },
|
17
|
+
"fei" =>
|
18
|
+
{ "engine_id" => "engine",
|
19
|
+
"wfid" => "20000101-abcdefg",
|
20
|
+
"subid" => "abcdefghijklmnopqrstu",
|
21
|
+
"expid" => "0_0" },
|
22
|
+
"participant_name" => "shell")
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#consume' do
|
26
|
+
context 'send successfully' do
|
27
|
+
it 'sends command message' do
|
28
|
+
sid = NATS.subscribe('remote.command', queue: 'remote.command', max: 1) do |message, reply|
|
29
|
+
unpacked = MessagePack.unpack(message)
|
30
|
+
unpacked.should eq workitem.to_h
|
31
|
+
|
32
|
+
NATS.publish(reply, 'ACCEPT') do
|
33
|
+
NATS.stop
|
34
|
+
end
|
35
|
+
end
|
36
|
+
NATS.timeout(sid, 1, expected: 1) do
|
37
|
+
NATS.stop
|
38
|
+
fail "command message is not sent"
|
39
|
+
end
|
40
|
+
|
41
|
+
subject.consume(workitem)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
context 'send successfully' do
|
46
|
+
before { subject.context = MockContext.new }
|
47
|
+
|
48
|
+
it 'sends command message' do
|
49
|
+
EM.add_timer(2) do
|
50
|
+
NATS.stop
|
51
|
+
fail "#handle_error must be called"
|
52
|
+
end
|
53
|
+
subject.consume(workitem)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RuoteNATS
|
4
|
+
|
5
|
+
describe ReplyReceiver do
|
6
|
+
around :each do |example|
|
7
|
+
NATS.start(autostart: true) { example.run }
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:engine) { MockEngine.new }
|
11
|
+
let(:receiver) { ReplyReceiver.new(engine) }
|
12
|
+
let(:message) { MessagePack.pack(workitem.to_h) }
|
13
|
+
|
14
|
+
describe '#start' do
|
15
|
+
context 'success' do
|
16
|
+
let(:workitem) do
|
17
|
+
Ruote::Workitem.new("fields" =>
|
18
|
+
{ "params" =>
|
19
|
+
{ "executor" => "ReplyReceiverSpec" },
|
20
|
+
"dispatched_at" => "2000-01-01 11:11:11.111111 UTC",
|
21
|
+
"__result__" => "success" },
|
22
|
+
"fei" =>
|
23
|
+
{ "engine_id" => "engine",
|
24
|
+
"wfid" => "20000101-abcdefg",
|
25
|
+
"subid" => "abcdefghijklmnopqrstu",
|
26
|
+
"expid" => "0_0" },
|
27
|
+
"participant_name" => "shell")
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'replies to engine' do
|
31
|
+
receiver.start
|
32
|
+
NATS.publish('remote.command.reply', message)
|
33
|
+
|
34
|
+
EM.add_timer(1) do
|
35
|
+
fail "#reply_to_engine must be called"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'failure' do
|
41
|
+
let(:workitem) do
|
42
|
+
Ruote::Workitem.new("fields" =>
|
43
|
+
{ "params" =>
|
44
|
+
{ "executor" => "ReplyReceiverSpec" },
|
45
|
+
"dispatched_at" => "2000-01-01 11:11:11.111111 UTC",
|
46
|
+
"__result__" => "failure" },
|
47
|
+
"fei" =>
|
48
|
+
{ "engine_id" => "engine",
|
49
|
+
"wfid" => "20000101-abcdefg",
|
50
|
+
"subid" => "abcdefghijklmnopqrstu",
|
51
|
+
"expid" => "0_0" },
|
52
|
+
"participant_name" => "shell")
|
53
|
+
end
|
54
|
+
it 'replies to engine' do
|
55
|
+
handler = double.as_null_object
|
56
|
+
engine.stub_chain(:context, :error_handler => handler)
|
57
|
+
|
58
|
+
receiver.start
|
59
|
+
NATS.publish('remote.command.reply', message)
|
60
|
+
|
61
|
+
EM.add_timer(1) do
|
62
|
+
fail "#handle_error must be called"
|
63
|
+
NATS.stop
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RuoteNATS
|
4
|
+
describe ShellExecutor do
|
5
|
+
describe '#execute' do
|
6
|
+
subject { ShellExecutor.new.execute(workitem) }
|
7
|
+
|
8
|
+
context 'with command and env fields' do
|
9
|
+
let(:workitem) do
|
10
|
+
Ruote::Workitem.new("fields" =>
|
11
|
+
{ "params" =>
|
12
|
+
{ "command" => "ruby -e 'print ENV[\"LANG\"]'",
|
13
|
+
"env" => {
|
14
|
+
"LANG" => "ja_JP.UTF-8" },
|
15
|
+
"ref" => "shell" },
|
16
|
+
"dispatched_at" => "2000-01-01 11:11:11.111111 UTC" },
|
17
|
+
"fei" =>
|
18
|
+
{ "engine_id" => "engine",
|
19
|
+
"wfid" => "20000101-abcdefg",
|
20
|
+
"subid" => "abcdefghijklmnopqrstu",
|
21
|
+
"expid" => "0_0" },
|
22
|
+
"participant_name" => "shell")
|
23
|
+
end
|
24
|
+
it { should include(out: "ja_JP.UTF-8") }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'without command fields' do
|
28
|
+
let(:workitem) do
|
29
|
+
Ruote::Workitem.new("fields" =>
|
30
|
+
{ "params" =>
|
31
|
+
{ "ref" => "shell" },
|
32
|
+
"dispatched_at" => "2000-01-01 11:11:11.111111 UTC" },
|
33
|
+
"fei" =>
|
34
|
+
{ "engine_id" => "engine",
|
35
|
+
"wfid" => "20000101-abcdefg",
|
36
|
+
"subid" => "abcdefghijklmnopqrstu",
|
37
|
+
"expid" => "0_0" },
|
38
|
+
"participant_name" => "send_command")
|
39
|
+
end
|
40
|
+
it { expect{ subject }.to raise_error(RuntimeError, 'command is not specified, check your process definition') }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- mode:ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'simplecov'
|
5
|
+
SimpleCov.start { add_filter 'spec' }
|
6
|
+
|
7
|
+
require 'ruote-nats'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.before(:all) do
|
11
|
+
RuoteNATS.logger.level = Logger::DEBUG
|
12
|
+
end
|
13
|
+
|
14
|
+
config.after(:all) do
|
15
|
+
begin
|
16
|
+
pid = IO.read('/tmp/nats-server.pid')
|
17
|
+
`kill -TERM #{pid}`
|
18
|
+
rescue Errno::ENOENT
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
module RuoteNATS
|
25
|
+
class MockErrorHandler
|
26
|
+
def action_handle(action, fei, exception)
|
27
|
+
NATS.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MockContext
|
32
|
+
def error_handler
|
33
|
+
MockErrorHandler.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class MockEngine
|
38
|
+
def reply_to_engine(workitem)
|
39
|
+
NATS.stop
|
40
|
+
end
|
41
|
+
|
42
|
+
def context
|
43
|
+
MockContext.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruote-nats
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Naoto Takai
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ruote
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.2.0
|
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: 2.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: nats
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.4.22
|
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: 0.4.22
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: msgpack
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.4.6
|
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.4.6
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
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: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
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: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: simplecov
|
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
|
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
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: pry-nav
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: yard
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: redcarpet
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: github-markup
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
description: NATS participant and receivers for ruote
|
191
|
+
email:
|
192
|
+
- takai@recompile.net
|
193
|
+
executables: []
|
194
|
+
extensions: []
|
195
|
+
extra_rdoc_files: []
|
196
|
+
files:
|
197
|
+
- .gitignore
|
198
|
+
- .yardopts
|
199
|
+
- Gemfile
|
200
|
+
- LICENSE
|
201
|
+
- README.md
|
202
|
+
- Rakefile
|
203
|
+
- example/single_file.rb
|
204
|
+
- image/system-diagram.png
|
205
|
+
- lib/ruote-nats.rb
|
206
|
+
- lib/ruote-nats/command_receiver.rb
|
207
|
+
- lib/ruote-nats/participant.rb
|
208
|
+
- lib/ruote-nats/reply_receiver.rb
|
209
|
+
- lib/ruote-nats/shell_executor.rb
|
210
|
+
- lib/ruote-nats/version.rb
|
211
|
+
- ruote-nats.gemspec
|
212
|
+
- spec/command_receiver_spec.rb
|
213
|
+
- spec/participant_spec.rb
|
214
|
+
- spec/reply_receiver_spec.rb
|
215
|
+
- spec/shell_executor_spec.rb
|
216
|
+
- spec/spec_helper.rb
|
217
|
+
homepage: https://github.com/takai/ruote-nats
|
218
|
+
licenses: []
|
219
|
+
post_install_message:
|
220
|
+
rdoc_options: []
|
221
|
+
require_paths:
|
222
|
+
- lib
|
223
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
224
|
+
none: false
|
225
|
+
requirements:
|
226
|
+
- - ! '>='
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '0'
|
229
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
230
|
+
none: false
|
231
|
+
requirements:
|
232
|
+
- - ! '>='
|
233
|
+
- !ruby/object:Gem::Version
|
234
|
+
version: '0'
|
235
|
+
requirements: []
|
236
|
+
rubyforge_project:
|
237
|
+
rubygems_version: 1.8.23
|
238
|
+
signing_key:
|
239
|
+
specification_version: 3
|
240
|
+
summary: ruote-nats is an implementation of the ruote participant and receivers to
|
241
|
+
process workitem on remote host using NATS
|
242
|
+
test_files:
|
243
|
+
- spec/command_receiver_spec.rb
|
244
|
+
- spec/participant_spec.rb
|
245
|
+
- spec/reply_receiver_spec.rb
|
246
|
+
- spec/shell_executor_spec.rb
|
247
|
+
- spec/spec_helper.rb
|
248
|
+
has_rdoc:
|