Commit 0625d469 authored by Alejandro Rodríguez's avatar Alejandro Rodríguez

Track ongoing pushes and reject mv-storage commands if there are push running...

Track ongoing pushes and reject mv-storage commands if there are push running (after waiting some time)
parent 522567af
v3.3.0 v3.3.0
- Track ongoing push commands
- Add command to move repositories between repository storages - Add command to move repositories between repository storages
v3.2.1 v3.2.1
......
...@@ -9,10 +9,16 @@ protocol = ENV.delete('GL_PROTOCOL') ...@@ -9,10 +9,16 @@ protocol = ENV.delete('GL_PROTOCOL')
repo_path = Dir.pwd repo_path = Dir.pwd
require_relative '../lib/gitlab_custom_hook' require_relative '../lib/gitlab_custom_hook'
require_relative '../lib/gitlab_reference_counter'
require_relative '../lib/gitlab_access' require_relative '../lib/gitlab_access'
# It's important that on pre-receive `increase_reference_counter` gets executed
# last so that it only runs if everything else succeeded. On post-receive on the
# other hand, we run GitlabPostReceive first because the push is already done
# and we don't want to skip it if the custom hook fails.
if GitlabAccess.new(repo_path, key_id, refs, protocol).exec && if GitlabAccess.new(repo_path, key_id, refs, protocol).exec &&
GitlabCustomHook.new.pre_receive(refs, repo_path) GitlabCustomHook.new.pre_receive(refs, repo_path) &&
GitlabReferenceCounter.new(repo_path).increase
exit 0 exit 0
else else
exit 1 exit 1
......
require_relative 'gitlab_init' require_relative 'gitlab_init'
require_relative 'gitlab_net' require_relative 'gitlab_net'
require_relative 'gitlab_reference_counter'
require 'json' require 'json'
require 'base64' require 'base64'
require 'securerandom' require 'securerandom'
...@@ -28,7 +29,7 @@ class GitlabPostReceive ...@@ -28,7 +29,7 @@ class GitlabPostReceive
nil nil
end end
result result && GitlabReferenceCounter.new(repo_path).decrease
end end
protected protected
......
...@@ -4,6 +4,7 @@ require 'open3' ...@@ -4,6 +4,7 @@ require 'open3'
require_relative 'gitlab_config' require_relative 'gitlab_config'
require_relative 'gitlab_logger' require_relative 'gitlab_logger'
require_relative 'gitlab_reference_counter'
class GitlabProjects class GitlabProjects
GLOBAL_HOOKS_DIRECTORY = File.join(ROOT_PATH, 'hooks') GLOBAL_HOOKS_DIRECTORY = File.join(ROOT_PATH, 'hooks')
...@@ -313,8 +314,13 @@ class GitlabProjects ...@@ -313,8 +314,13 @@ class GitlabProjects
# contents of the directory, as opposed to copying the directory by name # contents of the directory, as opposed to copying the directory by name
source_path = File.join(full_path, '') source_path = File.join(full_path, '')
if wait_for_pushes
$logger.info "Syncing project #{@project_name} from <#{full_path}> to <#{new_full_path}>." $logger.info "Syncing project #{@project_name} from <#{full_path}> to <#{new_full_path}>."
system(*%W(rsync -a #{source_path} #{new_full_path})) system(*%W(rsync -a --delete #{source_path} #{new_full_path}))
else
$logger.error "mv-storage failed: source path <#{full_path}> is waiting for pushes to finish."
false
end
end end
def fork_project def fork_project
...@@ -361,4 +367,18 @@ class GitlabProjects ...@@ -361,4 +367,18 @@ class GitlabProjects
cmd = %W(git --git-dir=#{full_path} gc) cmd = %W(git --git-dir=#{full_path} gc)
system(*cmd) system(*cmd)
end end
def wait_for_pushes
# Try for 30 seconds, polling every 10
3.times do
return true if gitlab_reference_counter.value == 0
sleep 10
end
false
end
def gitlab_reference_counter
@gitlab_reference_counter ||= GitlabReferenceCounter.new(full_path)
end
end end
require_relative 'gitlab_init'
require_relative 'gitlab_net'
class GitlabReferenceCounter
REFERENCE_EXPIRE_TIME = 600
attr_reader :path, :key
def initialize(path)
@path = path
@key = "git-receive-pack-reference-counter:#{path}"
end
def value
(redis_client.get(key) || 0).to_i
end
def increase
redis_cmd do
redis_client.incr(key)
redis_client.expire(key, REFERENCE_EXPIRE_TIME)
end
end
def decrease
redis_cmd do
current_value = redis_client.decr(key)
if current_value < 0
$logger.warn "Reference counter for #{path} decreased when its value was less than 1. Reseting the counter."
redis_client.del(key)
end
end
end
private
def redis_client
@redis_client ||= GitlabNet.new.redis_client
end
def redis_cmd
begin
yield
true
rescue => e
message = "GitLab: An unexpected error occurred in writing to Redis: #{e}"
$stderr.puts message
$logger.error message
false
end
end
end
...@@ -25,6 +25,11 @@ describe GitlabPostReceive do ...@@ -25,6 +25,11 @@ describe GitlabPostReceive do
before do before do
allow_any_instance_of(GitlabNet).to receive(:redis_client).and_return(redis_client) allow_any_instance_of(GitlabNet).to receive(:redis_client).and_return(redis_client)
allow_any_instance_of(GitlabReferenceCounter).to receive(:redis_client).and_return(redis_client)
allow(redis_client).to receive(:get).and_return(1)
allow(redis_client).to receive(:incr).and_return(true)
allow(redis_client).to receive(:decr).and_return(0)
allow(redis_client).to receive(:rpush).and_return(true)
end end
it "prints the broadcast message" do it "prints the broadcast message" do
...@@ -59,6 +64,34 @@ describe GitlabPostReceive do ...@@ -59,6 +64,34 @@ describe GitlabPostReceive do
gitlab_post_receive.exec gitlab_post_receive.exec
end end
context 'reference counter' do
it 'decreases the reference counter for the project' do
expect_any_instance_of(GitlabReferenceCounter).to receive(:decrease).and_return(true)
gitlab_post_receive.exec
end
context "when the redis command succeeds" do
before do
allow(redis_client).to receive(:decr).and_return(0)
end
it "returns true" do
expect(gitlab_post_receive.exec).to eq(true)
end
end
context "when the redis command fails" do
before do
allow(redis_client).to receive(:decr).and_raise('Fail')
end
it "returns false" do
expect(gitlab_post_receive.exec).to eq(false)
end
end
end
context "when the redis command succeeds" do context "when the redis command succeeds" do
before do before do
......
...@@ -213,6 +213,7 @@ describe GitlabProjects do ...@@ -213,6 +213,7 @@ describe GitlabProjects do
before do before do
FileUtils.mkdir_p(tmp_repo_path) FileUtils.mkdir_p(tmp_repo_path)
FileUtils.mkdir_p(alternative_storage_path) FileUtils.mkdir_p(alternative_storage_path)
allow_any_instance_of(GitlabReferenceCounter).to receive(:value).and_return(0)
end end
after { FileUtils.rm_rf(alternative_storage_path) } after { FileUtils.rm_rf(alternative_storage_path) }
...@@ -235,6 +236,12 @@ describe GitlabProjects do ...@@ -235,6 +236,12 @@ describe GitlabProjects do
bad_source.exec.should be_false bad_source.exec.should be_false
end end
it "should fail if there are pushes ongoing" do
allow_any_instance_of(GitlabReferenceCounter).to receive(:value).and_return(1)
$logger.should_receive(:error).with("mv-storage failed: source path <#{tmp_repo_path}> is waiting for pushes to finish.")
gl_projects.exec.should be_false
end
it "should log an mv-storage event" do it "should log an mv-storage event" do
message = "Syncing project #{repo_name} from <#{tmp_repo_path}> to <#{new_repo_path}>." message = "Syncing project #{repo_name} from <#{tmp_repo_path}> to <#{new_repo_path}>."
$logger.should_receive(:info).with(message) $logger.should_receive(:info).with(message)
......
# coding: utf-8
require 'spec_helper'
require 'gitlab_reference_counter'
describe GitlabReferenceCounter do
let(:redis_client) { double('redis_client') }
let(:reference_counter_key) { "git-receive-pack-reference-counter:/test/path" }
let(:gitlab_reference_counter) { GitlabReferenceCounter.new('/test/path') }
before do
allow(gitlab_reference_counter).to receive(:redis_client).and_return(redis_client)
$logger = double('logger').as_null_object
end
it 'increases and set the expire time of a reference count for a path' do
expect(redis_client).to receive(:incr).with(reference_counter_key)
expect(redis_client).to receive(:expire).with(reference_counter_key, GitlabReferenceCounter::REFERENCE_EXPIRE_TIME)
expect(gitlab_reference_counter.increase).to be(true)
end
it 'decreases the reference count for a path' do
allow(redis_client).to receive(:decr).and_return(0)
expect(redis_client).to receive(:decr).with(reference_counter_key)
expect(gitlab_reference_counter.decrease).to be(true)
end
it 'warns if attempting to decrease a counter with a value of one or less, and resets the counter' do
expect(redis_client).to receive(:decr).and_return(-1)
expect(redis_client).to receive(:del)
expect($logger).to receive(:warn).with("Reference counter for /test/path decreased when its value was less than 1. Reseting the counter.")
expect(gitlab_reference_counter.decrease).to be(true)
end
it 'get the reference count for a path' do
allow(redis_client).to receive(:get).and_return(1)
expect(gitlab_reference_counter.value).to be(1)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment