vx-worker 0.2.0.pre28
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.
- 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
|