vx-worker 0.2.0.pre28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +276 -0
  6. data/Rakefile +27 -0
  7. data/bin/vx-worker +6 -0
  8. data/docker/Dockerfile +15 -0
  9. data/docker/bootstrap.sh +106 -0
  10. data/docker/sv-enable +26 -0
  11. data/docker/sv-gen +50 -0
  12. data/lib/vx/worker/cli.rb +55 -0
  13. data/lib/vx/worker/configuration.rb +62 -0
  14. data/lib/vx/worker/consumers/job_logs_consumer.rb +13 -0
  15. data/lib/vx/worker/consumers/job_status_consumer.rb +13 -0
  16. data/lib/vx/worker/consumers/jobs_consumer.rb +28 -0
  17. data/lib/vx/worker/docker.rb +32 -0
  18. data/lib/vx/worker/ext/string.rb +10 -0
  19. data/lib/vx/worker/helper/config.rb +11 -0
  20. data/lib/vx/worker/helper/logger.rb +11 -0
  21. data/lib/vx/worker/initializers/amqp.rb +3 -0
  22. data/lib/vx/worker/job.rb +49 -0
  23. data/lib/vx/worker/local.rb +32 -0
  24. data/lib/vx/worker/middlewares/log_job.rb +19 -0
  25. data/lib/vx/worker/middlewares/run_script.rb +84 -0
  26. data/lib/vx/worker/middlewares/start_connector.rb +32 -0
  27. data/lib/vx/worker/middlewares/timeout.rb +22 -0
  28. data/lib/vx/worker/middlewares/update_job_status.rb +69 -0
  29. data/lib/vx/worker/version.rb +5 -0
  30. data/lib/vx/worker.rb +83 -0
  31. data/spec/lib/worker/configuration_spec.rb +40 -0
  32. data/spec/lib/worker/docker_spec.rb +28 -0
  33. data/spec/lib/worker/job_spec.rb +104 -0
  34. data/spec/lib/worker/local_spec.rb +29 -0
  35. data/spec/lib/worker/middlewares/log_job_spec.rb +15 -0
  36. data/spec/lib/worker/middlewares/run_script_spec.rb +63 -0
  37. data/spec/lib/worker/middlewares/start_connector_spec.rb +34 -0
  38. data/spec/lib/worker/middlewares/timeout_spec.rb +28 -0
  39. data/spec/lib/worker/middlewares/update_job_status_spec.rb +75 -0
  40. data/spec/lib/worker_spec.rb +39 -0
  41. data/spec/spec_helper.rb +27 -0
  42. data/spec/support/all_job_log_output.rb +3 -0
  43. data/spec/support/create.rb +19 -0
  44. data/spec/support/last_job_logs_message.rb +3 -0
  45. data/spec/support/shared_examples/update_job_status_message_spec.rb +6 -0
  46. data/vx-worker.gemspec +31 -0
  47. metadata +231 -0
@@ -0,0 +1,13 @@
1
+ require 'vx/common/amqp'
2
+
3
+ module Vx
4
+ module Worker
5
+ class JobLogsConsumer
6
+
7
+ include Vx::Common::AMQP::Consumer
8
+
9
+ exchange 'vx.jobs.log'
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'vx/common/amqp'
2
+
3
+ module Vx
4
+ module Worker
5
+ class JobStatusConsumer
6
+
7
+ include Vx::Common::AMQP::Consumer
8
+
9
+ exchange 'vx.jobs.status'
10
+
11
+ end
12
+ end
13
+ end
@@ -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,10 @@
1
+ class String
2
+ def compact
3
+ gsub(/\n/, ' ').gsub(/ +/, ' ').strip
4
+ end
5
+
6
+ def camelize
7
+ split("_").each {|s| s.capitalize! }.join("")
8
+ end
9
+ end
10
+
@@ -0,0 +1,11 @@
1
+ module Vx
2
+ module Worker
3
+ module Helper::Config
4
+
5
+ def config
6
+ Worker.config
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Vx
2
+ module Worker
3
+ module Helper::Logger
4
+
5
+ def logger
6
+ Worker.logger
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ require 'vx/common/amqp'
2
+
3
+ Vx::Common::AMQP.setup(Vx::Worker.logger, url: Vx::Worker.config.amqp_url)
@@ -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
@@ -0,0 +1,5 @@
1
+ module Vx
2
+ module Worker
3
+ VERSION = "0.2.0.pre28"
4
+ end
5
+ 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