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