tengine_job_agent 0.3.17
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +67 -0
- data/README.rdoc +20 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/bin/tengine_job_agent_kill +23 -0
- data/bin/tengine_job_agent_run +8 -0
- data/bin/tengine_job_agent_watchdog +15 -0
- data/lib/tengine_job_agent.rb +10 -0
- data/lib/tengine_job_agent/command_utils.rb +38 -0
- data/lib/tengine_job_agent/run.rb +74 -0
- data/lib/tengine_job_agent/watchdog.rb +162 -0
- data/spec/.gitignore +1 -0
- data/spec/config/tengine_job_agent.yml.erb +17 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/tengine_job_agent/command_utils_spec.rb +124 -0
- data/spec/tengine_job_agent/run_spec.rb +107 -0
- data/spec/tengine_job_agent/scripts/echo_foo.sh +4 -0
- data/spec/tengine_job_agent/scripts/sleep.sh +6 -0
- data/spec/tengine_job_agent/watchdog_spec.rb +307 -0
- data/tengine_job_agent.gemspec +87 -0
- metadata +180 -0
data/spec/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/pid_for_*
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
timeout: 1
|
3
|
+
# log_dir: "log"
|
4
|
+
logfile: "<%= %!log/#{File.basename($PROGRAM_NAME)}-#{`hostname`.strip}-#{Process.pid}.log! %>"
|
5
|
+
connection:
|
6
|
+
host: 'localhost'
|
7
|
+
port: 5672
|
8
|
+
# vhost:
|
9
|
+
# user:
|
10
|
+
# pass:
|
11
|
+
exchange:
|
12
|
+
name: 'tengine_event_exchange'
|
13
|
+
type: 'direct'
|
14
|
+
durable: true
|
15
|
+
heartbeat:
|
16
|
+
job:
|
17
|
+
interval: 1
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start if ENV["COVERAGE"]
|
5
|
+
require 'rspec'
|
6
|
+
require 'tengine_job_agent'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
describe TengineJobAgent::CommandUtils do
|
7
|
+
describe ".included" do
|
8
|
+
it "ClassMethodsを追加" do
|
9
|
+
foo = Class.new do
|
10
|
+
include TengineJobAgent::CommandUtils
|
11
|
+
end
|
12
|
+
foo.singleton_class.ancestors.should include(TengineJobAgent::CommandUtils::ClassMethods)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "::ClassMethods" do
|
17
|
+
describe "#load_config" do
|
18
|
+
subject { Class.new { include TengineJobAgent::CommandUtils::ClassMethods }.new }
|
19
|
+
|
20
|
+
it "Hashをかえす" do
|
21
|
+
Dir.chdir File.expand_path("../..", __FILE__) do
|
22
|
+
subject.load_config.should be_kind_of(Hash)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "./tengine_job_agent.ymlを読む" do
|
27
|
+
Dir.mktmpdir do |nam|
|
28
|
+
Dir.chdir nam do
|
29
|
+
File.open("tengine_job_agent.yml", "wb") {|f| f.puts "foo: bar\n" }
|
30
|
+
subject.load_config.should == { "foo" => "bar" }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "./config/tengine_job_agent.ymlを読む" do
|
36
|
+
Dir.mktmpdir do |nam|
|
37
|
+
Dir.chdir nam do
|
38
|
+
Dir.mkdir "config"
|
39
|
+
File.open("config/tengine_job_agent.yml", "wb") {|f| f.puts "foo: bar\n" }
|
40
|
+
subject.load_config.should == { "foo" => "bar" }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "/etc/tengine_job_agent.ymlを読む" do
|
46
|
+
begin
|
47
|
+
if File.exist? "/etc/tengine_job_agent.yml"
|
48
|
+
obj = YAML.load_file "/etc/tengine_job_agent.yml"
|
49
|
+
subject.load_config.should == obj
|
50
|
+
else
|
51
|
+
File.open("/etc/tengine_job_agent.yml", "wb") {|f| f.puts "foo: bar\n" }
|
52
|
+
subject.load_config.should == { "foo" => "bar" }
|
53
|
+
end
|
54
|
+
rescue Errno::EACCES
|
55
|
+
pending $!.message
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#new_logger" do
|
61
|
+
subject { Class.new { include TengineJobAgent::CommandUtils::ClassMethods }.new }
|
62
|
+
before { subject.stub(:name).and_return("foobar") }
|
63
|
+
|
64
|
+
it "logfileを指定する場合" do
|
65
|
+
Dir.mktmpdir do |nam|
|
66
|
+
Logger.should_receive(:new).with("foo/bar/baz.log")
|
67
|
+
subject.new_logger('logfile' => "foo/bar/baz.log")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "logfileもlog_dirも指定する場合" do
|
72
|
+
Dir.mktmpdir do |nam|
|
73
|
+
Logger.should_receive(:new).with(/\/foobar-\d+?.log$/)
|
74
|
+
subject.new_logger({})
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "Loggerを返す" do
|
79
|
+
Dir.mktmpdir do |nam|
|
80
|
+
subject.new_logger('log_dir' => nam).should be_kind_of(Logger)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "引数はディレクトリである" do
|
85
|
+
Tempfile.new("") do |f|
|
86
|
+
subject.new_logger('log_dir' => "nonexistent").should raise_exception(Errno::ENOENT)
|
87
|
+
subject.new_logger('log_dir' => f.path).should raise_exception(Errno::ENOENT)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "ログファイルは引数のディレクトリの中にできる" do
|
92
|
+
Dir.mktmpdir do |nam|
|
93
|
+
subject.new_logger('log_dir' => nam)
|
94
|
+
nam.should have_at_least(3).files
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#process" do
|
101
|
+
subject { Class.new { include TengineJobAgent::CommandUtils } }
|
102
|
+
let(:instance) { mock(subject.new) }
|
103
|
+
before do
|
104
|
+
instance
|
105
|
+
subject.stub(:new).with(anything, anything, anything).and_return(instance)
|
106
|
+
subject.stub(:name).and_return(File.basename(__FILE__, ".rb"))
|
107
|
+
end
|
108
|
+
|
109
|
+
it "インスタンスを生成してprocessを呼ぶ" do
|
110
|
+
instance.should_receive(:process)
|
111
|
+
Dir.chdir File.expand_path("../..", __FILE__) do
|
112
|
+
subject.process
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "失敗するとfalseを返す" do
|
117
|
+
instance.should_receive(:process).and_raise(RuntimeError)
|
118
|
+
Dir.chdir File.expand_path("../..", __FILE__) do
|
119
|
+
subject.process.should == false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'tengine/support/yaml_with_erb'
|
6
|
+
require 'rbconfig'
|
7
|
+
|
8
|
+
describe TengineJobAgent::Run do
|
9
|
+
|
10
|
+
before do
|
11
|
+
@log_buffer = StringIO.new
|
12
|
+
@logger = Logger.new(@log_buffer)
|
13
|
+
config = YAML.load_file(File.expand_path("../config/tengine_job_agent.yml.erb",
|
14
|
+
File.dirname(__FILE__)))
|
15
|
+
@config = config.inject({}) {|r, (k, v)| r.update k.intern => v }
|
16
|
+
end
|
17
|
+
|
18
|
+
subject do
|
19
|
+
Dir.chdir File.expand_path("../..", __FILE__) do
|
20
|
+
TengineJobAgent::Run.new(@logger, %w"scripts/echo_foo.sh", @config)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it { should_not be_nil }
|
25
|
+
|
26
|
+
describe "#process" do
|
27
|
+
|
28
|
+
let(:f) { mock(File.open("/dev/null")) }
|
29
|
+
|
30
|
+
before do
|
31
|
+
subject.stub(:spawn_watchdog).and_return mock(Numeric.new)
|
32
|
+
f
|
33
|
+
File.stub(:open).with(an_instance_of(String), "r").and_yield(f)
|
34
|
+
File.stub(:open).with(an_instance_of(String), "w")
|
35
|
+
end
|
36
|
+
|
37
|
+
context "正常起動" do
|
38
|
+
it "EXIT_SUCCESS" do
|
39
|
+
STDOUT.stub(:puts).with(an_instance_of(String))
|
40
|
+
f.stub(:gets).and_return("0\n")
|
41
|
+
subject.process.should == true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "spawnできない" do
|
46
|
+
it "EXIT_FAILURE" do
|
47
|
+
STDERR.stub(:puts) do |arg|
|
48
|
+
arg.should =~ /foo bar/
|
49
|
+
end
|
50
|
+
f.stub(:read).and_return("foo bar")
|
51
|
+
f.stub(:gets).and_return("foo bar")
|
52
|
+
f.stub(:rewind)
|
53
|
+
subject.process.should == false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "timeout" do
|
58
|
+
it "EXIT_FAILURE" do
|
59
|
+
f.stub(:gets)
|
60
|
+
lambda { subject.process }.should raise_exception(Timeout::Error)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#spawn_watchdog" do
|
66
|
+
it "tengine_job_agent_watchdogを起動する" do
|
67
|
+
watchdog = File.expand_path("../../bin/tengine_job_agent_watchdog", File.dirname(__FILE__))
|
68
|
+
Process.should_receive(:spawn).with(RbConfig.ruby, watchdog, an_instance_of(String), anything)
|
69
|
+
subject.spawn_watchdog
|
70
|
+
end
|
71
|
+
|
72
|
+
it "pidを返す" do
|
73
|
+
pid = mock(Numeric.new)
|
74
|
+
Process.stub(:spawn).with(RbConfig.ruby, anything, anything, anything).and_return(pid)
|
75
|
+
subject.spawn_watchdog.should == pid
|
76
|
+
end
|
77
|
+
|
78
|
+
it "終了を待たない" do
|
79
|
+
Process.stub(:spawn).with(RbConfig.ruby, anything, anything, anything)
|
80
|
+
Process.should_not_receive :wait
|
81
|
+
Process.should_not_receive :waitpid
|
82
|
+
Process.should_not_receive :waitpid2
|
83
|
+
subject.spawn_watchdog
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#initialize" do
|
88
|
+
it "第一引数にlogger" do
|
89
|
+
watchdog = File.expand_path("../../bin/tengine_job_agent_watchdog", File.dirname(__FILE__))
|
90
|
+
Process.should_receive(:spawn).with(RbConfig.ruby, watchdog, an_instance_of(String), anything)
|
91
|
+
subject.spawn_watchdog
|
92
|
+
@log_buffer.string.should_not be_empty
|
93
|
+
end
|
94
|
+
|
95
|
+
it "第二引数は起動するプロセスへの引数の配列" do
|
96
|
+
watchdog = File.expand_path("../../bin/tengine_job_agent_watchdog", File.dirname(__FILE__))
|
97
|
+
Process.should_receive(:spawn).with(RbConfig.ruby, watchdog, an_instance_of(String), "scripts/echo_foo.sh")
|
98
|
+
subject.spawn_watchdog
|
99
|
+
end
|
100
|
+
|
101
|
+
it "第三引数はconfig" do
|
102
|
+
subject.stub(:timeout) do |tim|
|
103
|
+
tim.should == @config[:timeout]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'amqp'
|
4
|
+
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
require 'yaml'
|
8
|
+
require 'tengine/support/yaml_with_erb'
|
9
|
+
|
10
|
+
describe TengineJobAgent::Watchdog do
|
11
|
+
|
12
|
+
before do
|
13
|
+
@log_buffer = StringIO.new
|
14
|
+
@logger = Logger.new(@log_buffer)
|
15
|
+
@config = YAML.load_file(File.expand_path("../config/tengine_job_agent.yml.erb",
|
16
|
+
File.dirname(__FILE__)))
|
17
|
+
end
|
18
|
+
|
19
|
+
subject do
|
20
|
+
@pid_path = "/dev/null"
|
21
|
+
echo_foo = File.expand_path "../scripts/echo_foo.sh", __FILE__
|
22
|
+
TengineJobAgent::Watchdog.new(@logger, [@pid_path, echo_foo], @config)
|
23
|
+
end
|
24
|
+
|
25
|
+
it { should_not be_nil }
|
26
|
+
|
27
|
+
describe "#process" do
|
28
|
+
let(:pid) { mock(Numeric.new) }
|
29
|
+
let(:stat) { mock($?) }
|
30
|
+
before do
|
31
|
+
bigzero = (1 << 1024).coerce(0)[0]
|
32
|
+
pid.stub(:to_int).and_return(bigzero)
|
33
|
+
stat.stub(:exitstatus).and_return(bigzero)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "spawnする" do
|
37
|
+
subject.should_receive(:spawn_process).and_return(pid)
|
38
|
+
subject.stub(:detach_and_wait_process).with(pid).and_return(stat)
|
39
|
+
subject.stub(:fire_finished).with(pid, stat)
|
40
|
+
sender = mock(:sender)
|
41
|
+
sender.stub_chain(:mq_suite, :ensures).with(:connection).and_yield
|
42
|
+
subject.stub(:sender).and_return(sender)
|
43
|
+
sender.stub(:wait_for_connection).and_yield
|
44
|
+
EM.run do
|
45
|
+
EM.add_timer(0.1) { EM.stop }
|
46
|
+
subject.process
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "子プロセスを待つ" do
|
51
|
+
subject.stub(:spawn_process).and_return(pid)
|
52
|
+
subject.should_receive(:detach_and_wait_process).and_return(stat)
|
53
|
+
subject.stub(:fire_finished).with(pid, stat)
|
54
|
+
sender = mock(:sender)
|
55
|
+
sender.stub_chain(:mq_suite, :ensures).with(:connection).and_yield
|
56
|
+
subject.stub(:sender).and_return(sender)
|
57
|
+
sender.stub(:wait_for_connection).and_yield
|
58
|
+
EM.run do
|
59
|
+
EM.add_timer(0.1) { EM.stop }
|
60
|
+
subject.process
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "ファイルへの出力" do
|
65
|
+
before do
|
66
|
+
@echo_foo = File.expand_path "../scripts/echo_foo.sh", __FILE__
|
67
|
+
mock_stdout = mock(:stdout, :path => "/tmp/stdout")
|
68
|
+
mock_stderr = mock(:stderr, :path => "/tmp/stderr")
|
69
|
+
subject.should_receive(:with_tmp_outs).and_yield(mock_stdout, mock_stderr)
|
70
|
+
sender = mock(:sender)
|
71
|
+
sender.stub_chain(:mq_suite, :ensures).with(:connection).and_yield
|
72
|
+
subject.stub(:sender).and_return(sender)
|
73
|
+
sender.stub(:wait_for_connection).and_yield
|
74
|
+
end
|
75
|
+
|
76
|
+
it "実行に成功した場合はPIDが出力される" do
|
77
|
+
subject.should_receive(:spawn_process).and_return(pid)
|
78
|
+
mock_pid_file = mock(:pid_file)
|
79
|
+
File.should_receive(:open).with(@pid_path, "a").and_yield(mock_pid_file)
|
80
|
+
mock_pid_file.should_receive(:puts).with(pid)
|
81
|
+
subject.should_receive(:detach_and_wait_process).with(pid)
|
82
|
+
EM.run do
|
83
|
+
EM.add_timer(0.1) { EM.stop }
|
84
|
+
subject.process
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "ファイルが存在せずspawnに失敗した場合、エラーを出力する" do
|
89
|
+
subject.should_receive(:spawn_process).and_raise(Errno::ENOENT.new(@echo_foo))
|
90
|
+
mock_pid_file = mock(:pid_file)
|
91
|
+
File.should_receive(:open).with(@pid_path, "a").and_yield(mock_pid_file)
|
92
|
+
mock_pid_file.should_receive(:puts).with("[Errno::ENOENT] No such file or directory - #{@echo_foo}")
|
93
|
+
timeout(0.5) do
|
94
|
+
EM.run do
|
95
|
+
# EM.add_timer(0.1) { EM.stop } # 起動に失敗したらEMは自動でstopされる
|
96
|
+
subject.process
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "#spawn_process" do
|
105
|
+
let(:pid) { mock(Numeric.new) }
|
106
|
+
let(:thr) { mock(Thread.start do Thread.stop end) }
|
107
|
+
let(:stat) { mock($?) }
|
108
|
+
before do
|
109
|
+
o = mock(STDOUT)
|
110
|
+
e = mock(STDERR)
|
111
|
+
o.stub(:path).and_return(String.new)
|
112
|
+
e.stub(:path).and_return(String.new)
|
113
|
+
subject.instance_eval do
|
114
|
+
@stdout = o
|
115
|
+
@stderr = e
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "spawnする" do
|
120
|
+
echo_foo = File.expand_path "../scripts/echo_foo.sh", __FILE__
|
121
|
+
Process.should_receive(:spawn).with(echo_foo, an_instance_of(Hash)).and_return(pid)
|
122
|
+
subject.spawn_process.should == pid
|
123
|
+
end
|
124
|
+
|
125
|
+
it "No such file or directoryで失敗する" do
|
126
|
+
echo_foo = File.expand_path "../scripts/echo_foo.sh", __FILE__
|
127
|
+
Process.should_receive(:spawn).with(echo_foo, an_instance_of(Hash)).
|
128
|
+
and_raise(Errno::ENOENT.new(echo_foo))
|
129
|
+
expect {
|
130
|
+
subject.spawn_process
|
131
|
+
}.to raise_error(Errno::ENOENT)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#detach_and_wait_process" do
|
136
|
+
let(:pid) { mock(Numeric.new) }
|
137
|
+
let(:stat) { mock($?) }
|
138
|
+
before do
|
139
|
+
bigzero = (1 << 1024).coerce(0).first
|
140
|
+
stat.stub(:exitstatus).and_return(bigzero)
|
141
|
+
pid.stub(:to_int).and_return(bigzero)
|
142
|
+
Process.stub(:waitpid2).with(pid) do
|
143
|
+
sleep 3
|
144
|
+
[pid, stat]
|
145
|
+
end
|
146
|
+
subject.stub(:fire_finished) do EM.stop end
|
147
|
+
subject.stub(:fire_heartbeat).with(pid).and_yield
|
148
|
+
end
|
149
|
+
|
150
|
+
it "pidを待つ" do
|
151
|
+
EM.run do
|
152
|
+
subject.should_receive(:fire_finished) do EM.stop end
|
153
|
+
subject.detach_and_wait_process(pid)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it "heartbeatをfireしつづける" do
|
158
|
+
EM.run do
|
159
|
+
subject.should_receive(:fire_heartbeat).at_least(2).times.and_yield
|
160
|
+
subject.detach_and_wait_process(pid)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
it "https://www.pivotaltracker.com/story/show/21515847" do
|
165
|
+
EM.run do
|
166
|
+
subject.instance_eval { @config["heartbeat"]["job"]["interval"] = 0 }
|
167
|
+
subject.unstub(:fire_heartbeat)
|
168
|
+
subject.should_receive(:fire_heartbeat).at_least(1).times.and_yield
|
169
|
+
subject.detach_and_wait_process(pid)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "プロセスは正常に動き続けているがfireに失敗した場合" do
|
174
|
+
it "その回のfireはあきらめる。例外などで死なない" do
|
175
|
+
EM.run do
|
176
|
+
subject.unstub(:fire_heartbeat)
|
177
|
+
s = mock(Tengine::Event::Sender.new)
|
178
|
+
subject.stub(:sender).and_return(s)
|
179
|
+
def s.fire e, h, &b
|
180
|
+
h[:retry_count].should_not == nil
|
181
|
+
h[:retry_count].should == 0
|
182
|
+
b.yield if b
|
183
|
+
end
|
184
|
+
expect {
|
185
|
+
subject.detach_and_wait_process(pid)
|
186
|
+
}.to_not raise_exception(Tengine::Event::Sender::RetryError)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "finishしたときのtimerの挙動" do
|
192
|
+
def live_timers_count
|
193
|
+
# これはひどい...
|
194
|
+
ObjectSpace.each_object(EM::PeriodicTimer).reject do |i|
|
195
|
+
i.instance_eval do
|
196
|
+
@cancelled
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
it "https://www.pivotaltracker.com/story/show/21466285" do
|
201
|
+
n = live_timers_count
|
202
|
+
EM.run do
|
203
|
+
subject.detach_and_wait_process(pid)
|
204
|
+
end
|
205
|
+
live_timers_count.should == n
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "#fire_finished" do
|
211
|
+
let(:pid) { mock(Numeric.new) }
|
212
|
+
let(:stat) { mock($?) }
|
213
|
+
before do
|
214
|
+
pid.stub(:to_int).and_return(-0.0/1.0)
|
215
|
+
conn = mock(:connection)
|
216
|
+
ch = Object.new
|
217
|
+
|
218
|
+
AMQP.stub(:connect).with(an_instance_of(Hash)).and_return(conn)
|
219
|
+
AMQP::Channel.stub(:new).with(conn, :prefetch => 1, :auto_recovery => true).and_return(ch)
|
220
|
+
AMQP::Exchange.stub(:new).with(ch, "direct", "exchange1",
|
221
|
+
:passive=>false, :durable=>true, :auto_delete=>false, :internal=>false, :nowait=>true)
|
222
|
+
conn.stub(:on_tcp_connection_loss)
|
223
|
+
conn.stub(:after_recovery)
|
224
|
+
conn.stub(:on_closed)
|
225
|
+
sender = mock(:sender)
|
226
|
+
subject.stub(:sender).and_return(sender)
|
227
|
+
sender.stub(:wait_for_connection).and_yield
|
228
|
+
|
229
|
+
o = mock(STDOUT)
|
230
|
+
e = mock(STDERR)
|
231
|
+
o.stub(:path).and_return(String.new)
|
232
|
+
e.stub(:path).and_return(String.new)
|
233
|
+
subject.instance_eval do
|
234
|
+
@stdout = o
|
235
|
+
@stderr = e
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
it "finished.process.job.tengineをfire" do
|
240
|
+
EM.run do
|
241
|
+
FileUtils.stub(:cp).with(an_instance_of(String), an_instance_of(String))
|
242
|
+
stat.stub(:exitstatus).and_return(0)
|
243
|
+
s = mock(Tengine::Event::Sender.new)
|
244
|
+
subject.stub(:sender).and_return s
|
245
|
+
s.stub(:stop)
|
246
|
+
s.should_receive(:fire) do |k, v|
|
247
|
+
k.should == "finished.process.job.tengine"
|
248
|
+
v[:level_key].should == :info
|
249
|
+
v[:properties]["pid"].should == pid
|
250
|
+
v[:properties]["exit_status"].should == stat.exitstatus
|
251
|
+
end
|
252
|
+
subject.fire_finished(pid, stat)
|
253
|
+
EM.add_timer(0.1) { EM.stop }
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
it "プロセスが失敗していた場合" do
|
258
|
+
EM.run do
|
259
|
+
FileUtils.stub(:cp).with(an_instance_of(String), an_instance_of(String))
|
260
|
+
stat.stub(:exitstatus).and_return(256)
|
261
|
+
s = mock(Tengine::Event::Sender.new)
|
262
|
+
subject.stub(:sender).and_return s
|
263
|
+
s.stub(:stop)
|
264
|
+
s.should_receive(:fire) do |k, v|
|
265
|
+
k.should == "finished.process.job.tengine"
|
266
|
+
v[:level_key].should == :error
|
267
|
+
v[:properties][:message].should =~ /^Job process failed./
|
268
|
+
end
|
269
|
+
subject.fire_finished(pid, stat)
|
270
|
+
EM.add_timer(0.1) { EM.stop }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context "プロセスは正常に終了したがfireに失敗した場合" do
|
275
|
+
it "fireできるようになるまでリトライを続ける" do
|
276
|
+
EM.run do
|
277
|
+
stat.stub(:exitstatus).and_return(0)
|
278
|
+
s = mock(Tengine::Event::Sender.new)
|
279
|
+
n = 0
|
280
|
+
subject.stub(:sender).and_return(s)
|
281
|
+
s.stub(:stop)
|
282
|
+
s.stub(:fire).with("finished.process.job.tengine", an_instance_of(Hash)) do |e, h|
|
283
|
+
if h[:retry_count]
|
284
|
+
h[:retry_count].should > 0
|
285
|
+
end
|
286
|
+
end
|
287
|
+
expect {
|
288
|
+
subject.fire_finished(pid, stat)
|
289
|
+
}.to_not raise_exception(Tengine::Event::Sender::RetryError)
|
290
|
+
EM.add_timer(0.1) { EM.stop }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe "#sender" do
|
297
|
+
before do
|
298
|
+
conn = mock(:connection)
|
299
|
+
conn.stub(:on_tcp_connection_loss)
|
300
|
+
conn.stub(:after_recovery)
|
301
|
+
conn.stub(:on_closed)
|
302
|
+
AMQP.stub(:connect).with(an_instance_of(Hash)).and_return(conn)
|
303
|
+
end
|
304
|
+
subject { TengineJobAgent::Watchdog.new(@logger, %w"", @config).sender }
|
305
|
+
it { should be_kind_of(Tengine::Event::Sender) }
|
306
|
+
end
|
307
|
+
end
|