vx-worker 0.2.0.pre28
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +276 -0
- data/Rakefile +27 -0
- data/bin/vx-worker +6 -0
- data/docker/Dockerfile +15 -0
- data/docker/bootstrap.sh +106 -0
- data/docker/sv-enable +26 -0
- data/docker/sv-gen +50 -0
- data/lib/vx/worker/cli.rb +55 -0
- data/lib/vx/worker/configuration.rb +62 -0
- data/lib/vx/worker/consumers/job_logs_consumer.rb +13 -0
- data/lib/vx/worker/consumers/job_status_consumer.rb +13 -0
- data/lib/vx/worker/consumers/jobs_consumer.rb +28 -0
- data/lib/vx/worker/docker.rb +32 -0
- data/lib/vx/worker/ext/string.rb +10 -0
- data/lib/vx/worker/helper/config.rb +11 -0
- data/lib/vx/worker/helper/logger.rb +11 -0
- data/lib/vx/worker/initializers/amqp.rb +3 -0
- data/lib/vx/worker/job.rb +49 -0
- data/lib/vx/worker/local.rb +32 -0
- data/lib/vx/worker/middlewares/log_job.rb +19 -0
- data/lib/vx/worker/middlewares/run_script.rb +84 -0
- data/lib/vx/worker/middlewares/start_connector.rb +32 -0
- data/lib/vx/worker/middlewares/timeout.rb +22 -0
- data/lib/vx/worker/middlewares/update_job_status.rb +69 -0
- data/lib/vx/worker/version.rb +5 -0
- data/lib/vx/worker.rb +83 -0
- data/spec/lib/worker/configuration_spec.rb +40 -0
- data/spec/lib/worker/docker_spec.rb +28 -0
- data/spec/lib/worker/job_spec.rb +104 -0
- data/spec/lib/worker/local_spec.rb +29 -0
- data/spec/lib/worker/middlewares/log_job_spec.rb +15 -0
- data/spec/lib/worker/middlewares/run_script_spec.rb +63 -0
- data/spec/lib/worker/middlewares/start_connector_spec.rb +34 -0
- data/spec/lib/worker/middlewares/timeout_spec.rb +28 -0
- data/spec/lib/worker/middlewares/update_job_status_spec.rb +75 -0
- data/spec/lib/worker_spec.rb +39 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/all_job_log_output.rb +3 -0
- data/spec/support/create.rb +19 -0
- data/spec/support/last_job_logs_message.rb +3 -0
- data/spec/support/shared_examples/update_job_status_message_spec.rb +6 -0
- data/vx-worker.gemspec +31 -0
- metadata +231 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'vx/common/amqp'
|
2
|
+
require 'vx/message'
|
3
|
+
|
4
|
+
module Vx
|
5
|
+
module Worker
|
6
|
+
class JobsConsumer
|
7
|
+
|
8
|
+
include Vx::Common::AMQP::Consumer
|
9
|
+
|
10
|
+
exchange 'vx.jobs'
|
11
|
+
queue 'vx.worker.jobs'
|
12
|
+
ack true
|
13
|
+
|
14
|
+
model Message::PerformJob
|
15
|
+
|
16
|
+
def perform(message)
|
17
|
+
Worker.logger.tagged self.class.consumer_id do
|
18
|
+
job = Job.new message
|
19
|
+
number = Thread.current[:consumer_id] || 0
|
20
|
+
path_prefix = "/tmp/.test/job.#{number}"
|
21
|
+
Worker.perform(job, path_prefix)
|
22
|
+
ack!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'vx/common'
|
2
|
+
|
3
|
+
module Vx
|
4
|
+
module Worker
|
5
|
+
|
6
|
+
class Docker
|
7
|
+
|
8
|
+
include Common::Helper::Middlewares
|
9
|
+
|
10
|
+
attr_reader :job
|
11
|
+
|
12
|
+
middlewares do
|
13
|
+
use LogJob
|
14
|
+
use UpdateJobStatus
|
15
|
+
use Timeout
|
16
|
+
use StartConnector
|
17
|
+
use RunScript
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(job, _)
|
21
|
+
@job = job
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform
|
25
|
+
env = OpenStruct.new job: job
|
26
|
+
run_middlewares(env){ |_| 0 }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'vx/message'
|
2
|
+
require 'vx/common'
|
3
|
+
|
4
|
+
module Vx
|
5
|
+
module Worker
|
6
|
+
class Job
|
7
|
+
|
8
|
+
include Helper::Logger
|
9
|
+
|
10
|
+
attr_reader :output, :message, :output_counter
|
11
|
+
|
12
|
+
def initialize(perform_job_message)
|
13
|
+
@output_counter = 0
|
14
|
+
@message = perform_job_message
|
15
|
+
@output = Common::OutputBuffer.new(&method(:publish_job_log_message))
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_to_output(str)
|
19
|
+
output << str
|
20
|
+
logger.debug str.strip if logger.level == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_command_to_output(cmd)
|
24
|
+
add_to_output "$ #{cmd}\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_trace_to_output(log)
|
28
|
+
add_to_output log.split(/\n/).map{|i| " ===> #{i}\n" }.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def release
|
32
|
+
output.close
|
33
|
+
end
|
34
|
+
|
35
|
+
def publish_job_log_message(str)
|
36
|
+
@output_counter += 1
|
37
|
+
log = Message::JobLog.new(
|
38
|
+
build_id: message.id,
|
39
|
+
job_id: message.job_id,
|
40
|
+
tm: output_counter,
|
41
|
+
log: str
|
42
|
+
)
|
43
|
+
JobLogsConsumer.publish log
|
44
|
+
log
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'vx/common'
|
2
|
+
|
3
|
+
module Vx
|
4
|
+
module Worker
|
5
|
+
|
6
|
+
class Local
|
7
|
+
|
8
|
+
include Common::Helper::Middlewares
|
9
|
+
|
10
|
+
attr_reader :job
|
11
|
+
|
12
|
+
middlewares do
|
13
|
+
use LogJob
|
14
|
+
use UpdateJobStatus
|
15
|
+
use Timeout
|
16
|
+
use StartConnector
|
17
|
+
use RunScript
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(job, _)
|
21
|
+
@job = job
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform
|
25
|
+
env = OpenStruct.new job: job
|
26
|
+
run_middlewares(env){ |_| 0 }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Vx
|
2
|
+
module Worker
|
3
|
+
|
4
|
+
LogJob = Struct.new(:app) do
|
5
|
+
|
6
|
+
include Helper::Logger
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
logger.tagged("job #{env.job.message.id}.#{env.job.message.job_id}") do
|
10
|
+
logger.info "starting job"
|
11
|
+
rs = app.call env
|
12
|
+
logger.info "done job"
|
13
|
+
rs
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'vx/common'
|
2
|
+
|
3
|
+
module Vx
|
4
|
+
module Worker
|
5
|
+
|
6
|
+
RunScript = Struct.new(:app) do
|
7
|
+
|
8
|
+
include Helper::Logger
|
9
|
+
include Helper::Config
|
10
|
+
include Common::Helper::UploadShCommand
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
if env.spawner
|
14
|
+
code = run_script(env)
|
15
|
+
run_after_script(env)
|
16
|
+
|
17
|
+
if code == 0
|
18
|
+
app.call env
|
19
|
+
else
|
20
|
+
case read_state(env)
|
21
|
+
when "script"
|
22
|
+
code
|
23
|
+
else
|
24
|
+
code * -1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
app.call env
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def run_script(env)
|
35
|
+
file = [env.spawner.work_dir, ".ci_build.sh"].join("/")
|
36
|
+
|
37
|
+
script = [upload_sh_command(file, script_content(env))]
|
38
|
+
script << "env - HOME=$HOME bash #{file}"
|
39
|
+
script = script.join(" && ")
|
40
|
+
|
41
|
+
env.spawner.spawn script, read_timeout: read_timeout, &env.job.method(:add_to_output)
|
42
|
+
end
|
43
|
+
|
44
|
+
def run_after_script(env)
|
45
|
+
file = [env.spawner.work_dir, ".ci_after_build.sh"].join("/")
|
46
|
+
|
47
|
+
script = [upload_sh_command(file, after_script_content(env))]
|
48
|
+
script << "env - HOME=$HOME bash #{file}"
|
49
|
+
script = script.join(" && ")
|
50
|
+
|
51
|
+
env.spawner.spawn script, read_timeout: read_timeout, &env.job.method(:add_to_output)
|
52
|
+
end
|
53
|
+
|
54
|
+
def script_content(env)
|
55
|
+
buf = ["set -e"]
|
56
|
+
buf << "echo before_script > #{env.spawner.work_dir}/.ci_state"
|
57
|
+
buf << env.job.message.before_script
|
58
|
+
buf << "echo script > #{env.spawner.work_dir}/.ci_state"
|
59
|
+
buf << env.job.message.script
|
60
|
+
buf.join("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
def after_script_content(env)
|
64
|
+
buf = ["set -e"]
|
65
|
+
buf << env.job.message.after_script
|
66
|
+
buf.join("\n")
|
67
|
+
end
|
68
|
+
|
69
|
+
def read_state(env)
|
70
|
+
buf = ""
|
71
|
+
state_file = "#{env.spawner.work_dir}/.ci_state"
|
72
|
+
env.spawner.spawn "cat #{state_file}" do |out|
|
73
|
+
buf << out
|
74
|
+
end
|
75
|
+
buf.strip
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_timeout
|
79
|
+
10 * 60
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'pathname'
|
3
|
+
require 'vx/container_connector'
|
4
|
+
|
5
|
+
module Vx
|
6
|
+
module Worker
|
7
|
+
|
8
|
+
StartConnector = Struct.new(:app) do
|
9
|
+
|
10
|
+
include Helper::Config
|
11
|
+
include Helper::Logger
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
options = config.connector_options
|
15
|
+
options.merge! logger: logger
|
16
|
+
env.connector = ContainerConnector.lookup(config.run, options)
|
17
|
+
env.connector.start do |spawner|
|
18
|
+
env.job.add_to_output "using #{Socket.gethostname}##{spawner.id}\n"
|
19
|
+
logger.tagged("#{spawner.id}") do
|
20
|
+
begin
|
21
|
+
env.spawner = spawner
|
22
|
+
app.call env
|
23
|
+
ensure
|
24
|
+
env.spawner = spawner
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Vx
|
4
|
+
module Worker
|
5
|
+
|
6
|
+
Timeout = Struct.new(:app) do
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
rs = nil
|
10
|
+
::Timeout.timeout(_timeout) do
|
11
|
+
rs = app.call env
|
12
|
+
end
|
13
|
+
rs
|
14
|
+
end
|
15
|
+
|
16
|
+
def _timeout
|
17
|
+
30 * 60
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'vx/message'
|
2
|
+
require 'vx/common/error_notifier'
|
3
|
+
|
4
|
+
module Vx
|
5
|
+
module Worker
|
6
|
+
|
7
|
+
UpdateJobStatus = Struct.new(:app) do
|
8
|
+
|
9
|
+
include Helper::Logger
|
10
|
+
|
11
|
+
STARTED = 2
|
12
|
+
FINISHED = 3
|
13
|
+
BROKEN = 4
|
14
|
+
FAILED = 5
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
|
18
|
+
update_status env.job, STARTED
|
19
|
+
rs = -1
|
20
|
+
begin
|
21
|
+
rs = app.call env
|
22
|
+
rescue ::Timeout::Error => e
|
23
|
+
env.job.add_to_output("\n\nERROR: #{e.message}\n")
|
24
|
+
rescue ::Exception => e
|
25
|
+
env.job.add_to_output("\n\nERROR: #{e.inspect}\n")
|
26
|
+
logger.error("ERROR: #{e.inspect}\n BACKTRACE:\n#{e.backtrace.map{|i| " #{i}" }.join("\n")}")
|
27
|
+
Common::ErrorNotifier.notify(e)
|
28
|
+
end
|
29
|
+
|
30
|
+
msg = "\nDone. Your build exited with %s.\n"
|
31
|
+
env.job.add_to_output(msg % rs.abs)
|
32
|
+
|
33
|
+
case
|
34
|
+
when rs == 0
|
35
|
+
update_status env.job, FINISHED
|
36
|
+
when rs > 0
|
37
|
+
update_status env.job, BROKEN
|
38
|
+
when rs < 0
|
39
|
+
update_status env.job, FAILED
|
40
|
+
end
|
41
|
+
|
42
|
+
rs
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def update_status(job, status)
|
48
|
+
publish_status create_message(job, status)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_message(job, status)
|
52
|
+
tm = Time.now
|
53
|
+
Message::JobStatus.new(
|
54
|
+
build_id: job.message.id,
|
55
|
+
job_id: job.message.job_id,
|
56
|
+
status: status,
|
57
|
+
tm: tm.to_i,
|
58
|
+
matrix: job.message.matrix_keys
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def publish_status(message)
|
63
|
+
logger.info "delivered job status #{message.inspect}"
|
64
|
+
JobStatusConsumer.publish message
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/vx/worker.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'pathname'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require File.expand_path("../worker/ext/string", __FILE__)
|
6
|
+
require File.expand_path("../worker/version", __FILE__)
|
7
|
+
|
8
|
+
module Vx
|
9
|
+
module Worker
|
10
|
+
|
11
|
+
autoload :JobsConsumer, File.expand_path("../worker/consumers/jobs_consumer", __FILE__)
|
12
|
+
autoload :JobLogsConsumer, File.expand_path("../worker/consumers/job_logs_consumer", __FILE__)
|
13
|
+
autoload :JobStatusConsumer, File.expand_path("../worker/consumers/job_status_consumer", __FILE__)
|
14
|
+
autoload :Configuration, File.expand_path("../worker/configuration", __FILE__)
|
15
|
+
autoload :Job, File.expand_path("../worker/job", __FILE__)
|
16
|
+
autoload :Local, File.expand_path("../worker/local", __FILE__)
|
17
|
+
autoload :Docker, File.expand_path("../worker/docker", __FILE__)
|
18
|
+
autoload :CLI, File.expand_path("../worker/cli", __FILE__)
|
19
|
+
autoload :OutputBuffer, File.expand_path("../worker/output_buffer", __FILE__)
|
20
|
+
|
21
|
+
autoload :LogJob, File.expand_path("../worker/middlewares/log_job", __FILE__)
|
22
|
+
autoload :UpdateJobStatus, File.expand_path("../worker/middlewares/update_job_status", __FILE__)
|
23
|
+
autoload :Timeout, File.expand_path("../worker/middlewares/timeout", __FILE__)
|
24
|
+
autoload :StartConnector, File.expand_path("../worker/middlewares/start_connector", __FILE__)
|
25
|
+
autoload :RunScript, File.expand_path("../worker/middlewares/run_script", __FILE__)
|
26
|
+
|
27
|
+
module Helper
|
28
|
+
autoload :Logger, File.expand_path("../worker/helper/logger", __FILE__)
|
29
|
+
autoload :Config, File.expand_path("../worker/helper/config", __FILE__)
|
30
|
+
end
|
31
|
+
|
32
|
+
extend self
|
33
|
+
|
34
|
+
@@root = Pathname.new File.expand_path('../../..', __FILE__)
|
35
|
+
@@config_mutex = Mutex.new
|
36
|
+
|
37
|
+
def logger
|
38
|
+
if ENV['CI_WORKER_SILENT']
|
39
|
+
config.null_logger
|
40
|
+
else
|
41
|
+
config.logger
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure
|
46
|
+
yield config
|
47
|
+
config
|
48
|
+
end
|
49
|
+
|
50
|
+
def config
|
51
|
+
@config ||= begin
|
52
|
+
@@config_mutex.synchronize do
|
53
|
+
Configuration.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def root
|
59
|
+
@@root
|
60
|
+
end
|
61
|
+
|
62
|
+
def perform(job, path_prefix)
|
63
|
+
rs = run_class.new(job, path_prefix).perform
|
64
|
+
job.release
|
65
|
+
rs
|
66
|
+
end
|
67
|
+
|
68
|
+
def run_class
|
69
|
+
self.const_get(config.run.to_s.camelize)
|
70
|
+
end
|
71
|
+
|
72
|
+
def reset_config!
|
73
|
+
@config = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize!
|
77
|
+
root.join("lib/vx/worker/initializers").children.each do |e|
|
78
|
+
require e
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Vx::Worker::Configuration do
|
4
|
+
let(:config) { Vx::Worker.config }
|
5
|
+
subject { config }
|
6
|
+
|
7
|
+
its(:run) { should eq :docker }
|
8
|
+
its(:docker) { should be }
|
9
|
+
its(:timeout) { should eq 1800 }
|
10
|
+
its(:amqp_url) { should be_nil }
|
11
|
+
its(:connector_options) { should eq config.docker }
|
12
|
+
its(:connector_remote_dir) { should eq config.docker.remote_dir }
|
13
|
+
|
14
|
+
context "docker" do
|
15
|
+
subject { config.docker }
|
16
|
+
|
17
|
+
its(:user) { should be }
|
18
|
+
its(:password) { should be }
|
19
|
+
its(:init) { should be }
|
20
|
+
its(:image) { should be }
|
21
|
+
its(:remote_dir) { should be }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "local" do
|
25
|
+
subject { config.local }
|
26
|
+
|
27
|
+
its(:remote_dir){ should be }
|
28
|
+
end
|
29
|
+
|
30
|
+
context ".configure" do
|
31
|
+
subject {
|
32
|
+
Vx::Worker.configure do |c|
|
33
|
+
c.run = "local"
|
34
|
+
c.docker.image = 'image'
|
35
|
+
end
|
36
|
+
}
|
37
|
+
its(:run) { should eq :local }
|
38
|
+
its("docker.image") { should eq 'image' }
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Vx::Worker::Docker, docker: true do
|
4
|
+
let(:options) { { } }
|
5
|
+
let(:job) { create :job, options }
|
6
|
+
let(:local) { described_class.new job, nil }
|
7
|
+
subject { local }
|
8
|
+
|
9
|
+
context "perform" do
|
10
|
+
subject { local.perform }
|
11
|
+
it { should eq 0 }
|
12
|
+
|
13
|
+
before do
|
14
|
+
Vx::Worker.config.run = "docker"
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when fail before_script" do
|
18
|
+
let(:options) { { before_script: "false" } }
|
19
|
+
it { should eq(-1) }
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when fail script" do
|
23
|
+
let(:options) { { script: "false" } }
|
24
|
+
it { should eq(1) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|