ruote-nats 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 +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
|
+

|
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:
|