Commit 624a2abb authored by Toon Claes's avatar Toon Claes

Pick Various improvements to repository checks into EE

Pick the changes from gitlab-org/gitlab-ce!18484 into EE.
parent 7032fc65
......@@ -12,7 +12,7 @@
Enable Repository Checks
.help-block
GitLab will periodically run
%a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
%a{ href: 'https://git-scm.com/docs/git-fsck', target: 'blank' } 'git fsck'
in all project and wiki repositories to look for silent disk corruption issues.
.form-group
.col-sm-offset-2.col-sm-10
......
......@@ -3,6 +3,12 @@ class AdminEmailWorker
include CronjobQueue
def perform
send_repository_check_mail if Gitlab::CurrentSettings.repository_checks_enabled
end
private
def send_repository_check_mail
repository_check_failed_count = Project.where(last_repository_check_failed: true).count
return if repository_check_failed_count.zero?
......
......@@ -6,6 +6,8 @@ module RepositoryCheck
RUN_TIME = 3600
def perform
break unless Gitlab::CurrentSettings.repository_checks_enabled
start = Time.now
# This loop will break after a little more than one hour ('a little
......@@ -15,7 +17,6 @@ module RepositoryCheck
# check, only one (or two) will be checked at a time.
project_ids.each do |project_id|
break if Time.now - start >= RUN_TIME
break unless current_settings.repository_checks_enabled
next unless try_obtain_lease(project_id)
......@@ -47,16 +48,5 @@ module RepositoryCheck
timeout: 24.hours
).try_obtain
end
def current_settings
# No caching of the settings! If we cache them and an admin disables
# this feature, an active RepositoryCheckWorker would keep going for up
# to 1 hour after the feature was disabled.
if Rails.env.test?
Gitlab::CurrentSettings.fake_application_settings
else
ApplicationSetting.current
end
end
end
end
......@@ -5,27 +5,38 @@ module RepositoryCheck
def perform(project_id)
project = Project.find(project_id)
save_result(project, !check)
end
private
def save_result(project, failure)
project.update_columns(
last_repository_check_failed: !check(project),
last_repository_check_failed: failure,
last_repository_check_at: Time.now
)
end
private
def check(project)
if has_pushes?(project) && !git_fsck(project.repository)
false
elsif project.wiki_enabled?
check_repo(project) && check_wiki_repo(project)
end
def check_repo(project)
return true if project.empty_repo?
git_fsck(project.repository)
end
def check_wiki_repo(project)
return true unless project.wiki_enabled?
# Historically some projects never had their wiki repos initialized;
# this happens on project creation now. Let's initialize an empty repo
# if it is not already there.
project.create_wiki
git_fsck(project.wiki.repository)
else
true
end
end
def git_fsck(repository)
......
......@@ -28,16 +28,18 @@ panel.
## What to do if a check failed
If the repository check fails for some repository you should look up the error
in repocheck.log (in the admin panel or on disk; see
`/var/log/gitlab/gitlab-rails` for Omnibus installations or
`/home/git/gitlab/log` for installations from source). Once you have
resolved the issue use the admin panel to trigger a new repository check on
the project. This will clear the 'check failed' state.
in `repocheck.log`:
- in the [admin panel](logs.md#repocheck.log)
- or on disk, see:
- `/var/log/gitlab/gitlab-rails` for Omnibus installations
- `/home/git/gitlab/log` for installations from source
If for some reason the periodic repository check caused a lot of false
alarms you can choose to clear ALL repository check states from the
'Settings' page of the admin panel.
alarms you can choose to clear *all* repository check states by
clicking "Clear all repository checks" on the 'Settings' page of the
admin panel.
---
[ce-3232]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3232 "Auto git fsck"
[git-fsck]: https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html "git fsck documentation"
[git-fsck]: https://git-scm.com/docs/git-fsck "git fsck documentation"
require 'spec_helper'
describe AdminEmailWorker do
subject(:worker) { described_class.new }
describe '.perform' do
it 'does not attempt to send repository check mail when they are disabled' do
stub_application_setting(repository_checks_enabled: false)
expect(worker).not_to receive(:send_repository_check_mail)
worker.perform
end
context 'repository_checks enabled' do
before do
stub_application_setting(repository_checks_enabled: true)
end
it 'checks if repository check mail should be sent' do
expect(worker).to receive(:send_repository_check_mail)
worker.perform
end
it 'does not send mail when there are no failed repos' do
expect(RepositoryCheckMailer).not_to receive(:notify)
worker.perform
end
it 'send mail when there is a failed repo' do
create(:project, last_repository_check_failed: true, last_repository_check_at: Date.yesterday)
expect(RepositoryCheckMailer).to receive(:notify).and_return(spy)
worker.perform
end
end
end
end
......@@ -31,8 +31,8 @@ describe RepositoryCheck::BatchWorker do
it 'does nothing when repository checks are disabled' do
create(:project, created_at: 1.week.ago)
current_settings = double('settings', repository_checks_enabled: false)
expect(subject).to receive(:current_settings) { current_settings }
stub_application_setting(repository_checks_enabled: false)
expect(subject.perform).to eq(nil)
end
......
......@@ -2,44 +2,55 @@ require 'spec_helper'
require 'fileutils'
describe RepositoryCheck::SingleRepositoryWorker do
subject { described_class.new }
subject(:worker) { described_class.new }
it 'passes when the project has no push events' do
project = create(:project_empty_repo, :wiki_disabled)
project.events.destroy_all
break_repo(project)
it 'skips when the project repo is empty' do
project = create(:project, :wiki_disabled)
subject.perform(project.id)
expect(worker).not_to receive(:git_fsck)
worker.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
end
it 'fails when the project has push events and a broken repository' do
project = create(:project_empty_repo)
create_push_event(project)
break_repo(project)
it 'succeeds when the project repo is valid' do
project = create(:project, :repository, :wiki_disabled)
subject.perform(project.id)
expect(worker).to receive(:git_fsck).and_call_original
expect do
worker.perform(project.id)
end.to change { project.reload.last_repository_check_at }
expect(project.reload.last_repository_check_failed).to eq(false)
end
it 'fails when the project is not empty and a broken repository' do
project = create(:project, :repository)
break_project(project)
worker.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(true)
end
it 'fails if the wiki repository is broken' do
project = create(:project_empty_repo, :wiki_enabled)
project = create(:project, :wiki_enabled)
project.create_wiki
# Test sanity: everything should be fine before the wiki repo is broken
subject.perform(project.id)
worker.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
break_wiki(project)
subject.perform(project.id)
worker.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(true)
end
it 'skips wikis when disabled' do
project = create(:project_empty_repo, :wiki_disabled)
project = create(:project, :wiki_disabled)
# Make sure the test would fail if the wiki repo was checked
break_wiki(project)
......@@ -49,8 +60,8 @@ describe RepositoryCheck::SingleRepositoryWorker do
end
it 'creates missing wikis' do
project = create(:project_empty_repo, :wiki_enabled)
FileUtils.rm_rf(wiki_path(project))
project = create(:project, :wiki_enabled)
Gitlab::Shell.new.rm_directory(project.repository_storage_path, project.wiki.path)
subject.perform(project.id)
......@@ -58,34 +69,35 @@ describe RepositoryCheck::SingleRepositoryWorker do
end
it 'does not create a wiki if the main repo does not exist at all' do
project = create(:project_empty_repo)
create_push_event(project)
FileUtils.rm_rf(project.repository.path_to_repo)
FileUtils.rm_rf(wiki_path(project))
project = create(:project, :repository)
Gitlab::Shell.new.rm_directory(project.repository_storage_path, project.path)
Gitlab::Shell.new.rm_directory(project.repository_storage_path, project.wiki.path)
subject.perform(project.id)
expect(File.exist?(wiki_path(project))).to eq(false)
expect(Gitlab::Shell.new.exists?(project.repository_storage_path, project.wiki.path)).to eq(false)
end
def break_wiki(project)
objects_dir = wiki_path(project) + '/objects'
# Replace the /objects directory with a file so that the repo is
# invalid, _and_ 'git init' cannot fix it.
FileUtils.rm_rf(objects_dir)
FileUtils.touch(objects_dir) if File.directory?(wiki_path(project))
break_repo(wiki_path(project))
end
def wiki_path(project)
project.wiki.repository.path_to_repo
end
def create_push_event(project)
project.events.create(action: Event::PUSHED, author_id: create(:user).id)
def break_project(project)
break_repo(project.repository.path_to_repo)
end
def break_repo(project)
FileUtils.rm_rf(File.join(project.repository.path_to_repo, 'objects'))
def break_repo(repo)
# Create or replace blob ffffffffffffffffffffffffffffffffffffffff with an empty file
# This will make the repo invalid, _and_ 'git init' cannot fix it.
path = File.join(repo, 'objects', 'ff')
file = File.join(path, 'ffffffffffffffffffffffffffffffffffffff')
FileUtils.mkdir_p(path)
FileUtils.rm_f(file)
FileUtils.touch(file)
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