summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean McGivern <sean@mcgivern.me.uk>2017-04-28 13:21:45 +0000
committerSean McGivern <sean@mcgivern.me.uk>2017-04-28 13:21:45 +0000
commit31b87b8cab2c2daebb99657046e5d121b48499ad (patch)
tree4901b71ad54a369deacbffcc43b85ea2fe6c7dd2
parent0f0517d727ff9f7fac911f88b6d6619cffe262d7 (diff)
parent3532b8db712d1d6daa0a656b0fde317af9b8ae53 (diff)
downloadgitlab-ce-31b87b8cab2c2daebb99657046e5d121b48499ad.tar.gz
Merge branch 'grpc-fork-tests' into 'master' 24339-job-page-step-2
Unicorn worker termination tests See merge request !10818
-rw-r--r--app/controllers/unicorn_test_controller.rb12
-rw-r--r--config/routes.rb2
-rw-r--r--config/routes/test.rb2
-rw-r--r--lib/tasks/brakeman.rake2
-rw-r--r--spec/unicorn/unicorn_spec.rb98
5 files changed, 115 insertions, 1 deletions
diff --git a/app/controllers/unicorn_test_controller.rb b/app/controllers/unicorn_test_controller.rb
new file mode 100644
index 00000000000..b7a1a046be0
--- /dev/null
+++ b/app/controllers/unicorn_test_controller.rb
@@ -0,0 +1,12 @@
+if Rails.env.test?
+ class UnicornTestController < ActionController::Base
+ def pid
+ render plain: Process.pid.to_s
+ end
+
+ def kill
+ Process.kill(params[:signal], Process.pid)
+ render plain: 'Bye!'
+ end
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index 1da226a3b57..2584981bb04 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -99,5 +99,7 @@ Rails.application.routes.draw do
end
end
+ draw :test if Rails.env.test?
+
get '*unmatched_route', to: 'application#route_not_found'
end
diff --git a/config/routes/test.rb b/config/routes/test.rb
new file mode 100644
index 00000000000..ac477cdbbbc
--- /dev/null
+++ b/config/routes/test.rb
@@ -0,0 +1,2 @@
+get '/unicorn_test/pid' => 'unicorn_test#pid'
+post '/unicorn_test/kill' => 'unicorn_test#kill'
diff --git a/lib/tasks/brakeman.rake b/lib/tasks/brakeman.rake
index 2301ec9b228..99b3168d9eb 100644
--- a/lib/tasks/brakeman.rake
+++ b/lib/tasks/brakeman.rake
@@ -2,7 +2,7 @@ desc 'Security check via brakeman'
task :brakeman do
# We get 0 warnings at level 'w3' but we would like to reach 'w2'. Merge
# requests are welcome!
- if system(*%w(brakeman --no-progress --skip-files lib/backup/repository.rb -w3 -z))
+ if system(*%w(brakeman --no-progress --skip-files lib/backup/repository.rb,app/controllers/unicorn_test_controller.rb -w3 -z))
puts 'Security check succeed'
else
puts 'Security check failed'
diff --git a/spec/unicorn/unicorn_spec.rb b/spec/unicorn/unicorn_spec.rb
new file mode 100644
index 00000000000..8518c047a47
--- /dev/null
+++ b/spec/unicorn/unicorn_spec.rb
@@ -0,0 +1,98 @@
+require 'fileutils'
+
+require 'excon'
+
+require 'spec_helper'
+
+describe 'Unicorn' do
+ before(:all) do
+ config_lines = File.read('config/unicorn.rb.example').split("\n")
+
+ # Remove these because they make setup harder.
+ config_lines = config_lines.reject do |line|
+ %w[
+ working_directory
+ worker_processes
+ listen
+ pid
+ stderr_path
+ stdout_path
+ ].any? { |prefix| line.start_with?(prefix) }
+ end
+
+ config_lines << "working_directory '#{Rails.root}'"
+
+ # We want to have exactly 1 worker process because that makes it
+ # predictable which process will handle our requests.
+ config_lines << 'worker_processes 1'
+
+ @socket_path = File.join(Dir.pwd, 'tmp/tests/unicorn.socket')
+ config_lines << "listen '#{@socket_path}'"
+
+ ready_file = 'tmp/tests/unicorn-worker-ready'
+ FileUtils.rm_f(ready_file)
+ after_fork_index = config_lines.index { |l| l.start_with?('after_fork') }
+ config_lines.insert(after_fork_index + 1, "File.write('#{ready_file}', Process.pid)")
+
+ config_path = 'tmp/tests/unicorn.rb'
+ File.write(config_path, config_lines.join("\n") + "\n")
+
+ cmd = %W[unicorn -E test -c #{config_path} #{Rails.root.join('config.ru')}]
+ @unicorn_master_pid = spawn(*cmd)
+ wait_unicorn_boot!(@unicorn_master_pid, ready_file)
+ WebMock.allow_net_connect!
+ end
+
+ %w[SIGQUIT SIGTERM SIGKILL].each do |signal|
+ it "has a worker that self-terminates on signal #{signal}" do
+ response = Excon.get('unix:///unicorn_test/pid', socket: @socket_path)
+ expect(response.status).to eq(200)
+
+ worker_pid = response.body.to_i
+ expect(worker_pid).to be > 0
+
+ begin
+ Excon.post('unix:///unicorn_test/kill', socket: @socket_path, body: "signal=#{signal}")
+ rescue Excon::Error::Socket
+ # The connection may be closed abruptly
+ end
+
+ expect(pid_gone?(worker_pid)).to eq(true)
+ end
+ end
+
+ after(:all) do
+ WebMock.disable_net_connect!(allow_localhost: true)
+ Process.kill('TERM', @unicorn_master_pid)
+ end
+
+ def wait_unicorn_boot!(master_pid, ready_file)
+ # Unicorn should boot in under 60 seconds so 120 seconds seems like a good timeout.
+ timeout = 120
+ timeout.times do
+ return if File.exist?(ready_file)
+ pid = Process.waitpid(master_pid, Process::WNOHANG)
+ raise "unicorn failed to boot: #{$?}" unless pid.nil?
+
+ sleep 1
+ end
+
+ raise "unicorn boot timed out after #{timeout} seconds"
+ end
+
+ def pid_gone?(pid)
+ # Worker termination should take less than a second. That makes 10
+ # seconds a generous timeout.
+ 10.times do
+ begin
+ Process.kill(0, pid)
+ rescue Errno::ESRCH
+ return true
+ end
+
+ sleep 1
+ end
+
+ false
+ end
+end