Commit 3b690084 authored by Markus Koller's avatar Markus Koller

Merge branch 'backup_incremental' into 'master'

Unpack a previous backup for incremental backup

See merge request gitlab-org/gitlab!83566
parents 348a177d a7503222
...@@ -375,6 +375,30 @@ For example, for installations from source: ...@@ -375,6 +375,30 @@ For example, for installations from source:
sudo -u git -H bundle exec rake gitlab:backup:create GITLAB_BACKUP_MAX_CONCURRENCY=4 GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY=1 sudo -u git -H bundle exec rake gitlab:backup:create GITLAB_BACKUP_MAX_CONCURRENCY=4 GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY=1
``` ```
#### Incremental repository backups
> Introduced in GitLab 14.9 [with a flag](../administration/feature_flags.md) named `incremental_repository_backup`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `incremental_repository_backup`.
On GitLab.com, this feature is not available.
This feature is not ready for production use.
Incremental backups can be faster than full backups because they only pack changes since the last backup into the backup
bundle for each repository. There must be an existing backup to create an incremental backup from and this backup will be overwritten. You can use the `BACKUP=timestamp_of_backup` option to choose which backup will be used.
To create an incremental backup, run:
```shell
sudo gitlab-backup create INCREMENTAL=yes
```
Incremental backups can also be created from [an untarred backup](#skipping-tar-creation) by using `SKIP=tar`:
```shell
sudo gitlab-backup create INCREMENTAL=yes SKIP=tar
```
#### Uploading backups to a remote (cloud) storage #### Uploading backups to a remote (cloud) storage
You can let the backup script upload (using the [Fog library](http://fog.io/)) You can let the backup script upload (using the [Fog library](http://fog.io/))
...@@ -1830,22 +1854,3 @@ If you have a specific reason to change the path, it can be configured in Omnibu ...@@ -1830,22 +1854,3 @@ If you have a specific reason to change the path, it can be configured in Omnibu
1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) 1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
for the changes to take effect for the changes to take effect
### Incremental repository backups
> Introduced in GitLab 14.9 [with a flag](../administration/feature_flags.md) named `incremental_repository_backup`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `incremental_repository_backup`.
On GitLab.com, this feature is not available.
This feature is not ready for production use.
Incremental backups can be faster than full backups because they only pack changes since the last backup into the backup
bundle for each repository. Because incremental backups require access to the previous backup, you can't use incremental
backups with tar files.
To create an incremental backup, run:
```shell
sudo gitlab-backup create SKIP=tar INCREMENTAL=yes
```
...@@ -19,7 +19,9 @@ module Backup ...@@ -19,7 +19,9 @@ module Backup
@progress = progress @progress = progress
force = ENV['force'] == 'yes' force = ENV['force'] == 'yes'
incremental = Gitlab::Utils.to_boolean(ENV['INCREMENTAL'], default: false) @incremental = Feature.feature_flags_available? &&
Feature.enabled?(:incremental_repository_backup, default_enabled: :yaml) &&
Gitlab::Utils.to_boolean(ENV['INCREMENTAL'], default: false)
@definitions = definitions || { @definitions = definitions || {
'db' => TaskDefinition.new( 'db' => TaskDefinition.new(
...@@ -30,8 +32,7 @@ module Backup ...@@ -30,8 +32,7 @@ module Backup
'repositories' => TaskDefinition.new( 'repositories' => TaskDefinition.new(
destination_path: 'repositories', destination_path: 'repositories',
destination_optional: true, destination_optional: true,
task: Repositories.new(progress, task: Repositories.new(progress, strategy: repository_backup_strategy)
strategy: repository_backup_strategy(incremental))
), ),
'uploads' => TaskDefinition.new( 'uploads' => TaskDefinition.new(
destination_path: 'uploads.tar.gz', destination_path: 'uploads.tar.gz',
...@@ -69,6 +70,12 @@ module Backup ...@@ -69,6 +70,12 @@ module Backup
end end
def create def create
if incremental?
unpack
read_backup_information
verify_backup_version
end
@definitions.keys.each do |task_name| @definitions.keys.each do |task_name|
run_create_task(task_name) run_create_task(task_name)
end end
...@@ -87,7 +94,7 @@ module Backup ...@@ -87,7 +94,7 @@ module Backup
puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \ puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
"and are not included in this backup. You will need these files to restore a backup.\n" \ "and are not included in this backup. You will need these files to restore a backup.\n" \
"Please back them up manually.".color(:red) "Please back them up manually.".color(:red)
puts_time "Backup task is done." puts_time "Backup #{backup_id} is done."
end end
def run_create_task(task_name) def run_create_task(task_name)
...@@ -169,6 +176,10 @@ module Backup ...@@ -169,6 +176,10 @@ module Backup
private private
def incremental?
@incremental
end
def read_backup_information def read_backup_information
@backup_information ||= YAML.load_file(File.join(backup_path, MANIFEST_NAME)) @backup_information ||= YAML.load_file(File.join(backup_path, MANIFEST_NAME))
end end
...@@ -338,8 +349,15 @@ module Backup ...@@ -338,8 +349,15 @@ module Backup
puts_time 'Found more than one backup:' puts_time 'Found more than one backup:'
# print list of available backups # print list of available backups
puts_time " " + available_timestamps.join("\n ") puts_time " " + available_timestamps.join("\n ")
puts_time 'Please specify which one you want to restore:'
puts_time 'rake gitlab:backup:restore BACKUP=timestamp_of_backup' if incremental?
puts_time 'Please specify which one you want to create an incremental backup for:'
puts_time 'rake gitlab:backup:create INCREMENTAL=true BACKUP=timestamp_of_backup'
else
puts_time 'Please specify which one you want to restore:'
puts_time 'rake gitlab:backup:restore BACKUP=timestamp_of_backup'
end
exit 1 exit 1
end end
...@@ -437,11 +455,15 @@ module Backup ...@@ -437,11 +455,15 @@ module Backup
end end
def tar_file def tar_file
@tar_file ||= if ENV['BACKUP'].present? @tar_file ||= "#{backup_id}#{FILE_NAME_SUFFIX}"
File.basename(ENV['BACKUP']) + FILE_NAME_SUFFIX end
else
"#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}#{FILE_NAME_SUFFIX}" def backup_id
end @backup_id ||= if ENV['BACKUP'].present?
File.basename(ENV['BACKUP'])
else
"#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}"
end
end end
def create_attributes def create_attributes
...@@ -477,10 +499,10 @@ module Backup ...@@ -477,10 +499,10 @@ module Backup
Gitlab.config.backup.upload.connection&.provider&.downcase == 'google' Gitlab.config.backup.upload.connection&.provider&.downcase == 'google'
end end
def repository_backup_strategy(incremental) def repository_backup_strategy
max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence
max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence
Backup::GitalyBackup.new(progress, incremental: incremental, max_parallelism: max_concurrency, storage_parallelism: max_storage_concurrency) Backup::GitalyBackup.new(progress, incremental: incremental?, max_parallelism: max_concurrency, storage_parallelism: max_storage_concurrency)
end end
def puts_time(msg) def puts_time(msg)
......
...@@ -145,6 +145,7 @@ RSpec.describe Backup::Manager do ...@@ -145,6 +145,7 @@ RSpec.describe Backup::Manager do
end end
describe '#create' do describe '#create' do
let(:incremental_env) { 'false' }
let(:expected_backup_contents) { %w{backup_information.yml task1.tar.gz task2.tar.gz} } let(:expected_backup_contents) { %w{backup_information.yml task1.tar.gz task2.tar.gz} }
let(:tar_file) { '1546300800_2019_01_01_12.3_gitlab_backup.tar' } let(:tar_file) { '1546300800_2019_01_01_12.3_gitlab_backup.tar' }
let(:tar_system_options) { { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } } let(:tar_system_options) { { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } }
...@@ -166,6 +167,7 @@ RSpec.describe Backup::Manager do ...@@ -166,6 +167,7 @@ RSpec.describe Backup::Manager do
end end
before do before do
stub_env('INCREMENTAL', incremental_env)
allow(ActiveRecord::Base.connection).to receive(:reconnect!) allow(ActiveRecord::Base.connection).to receive(:reconnect!)
allow(Gitlab::BackupLogger).to receive(:info) allow(Gitlab::BackupLogger).to receive(:info)
allow(Kernel).to receive(:system).and_return(true) allow(Kernel).to receive(:system).and_return(true)
...@@ -561,6 +563,158 @@ RSpec.describe Backup::Manager do ...@@ -561,6 +563,158 @@ RSpec.describe Backup::Manager do
end end
end end
end end
context 'incremental' do
let(:incremental_env) { 'true' }
let(:gitlab_version) { Gitlab::VERSION }
let(:tar_file) { "1546300800_2019_01_01_#{gitlab_version}_gitlab_backup.tar" }
let(:backup_information) do
{
backup_created_at: Time.zone.parse('2019-01-01'),
gitlab_version: gitlab_version
}
end
context 'when there are no backup files in the directory' do
before do
allow(Dir).to receive(:glob).and_return([])
end
it 'fails the operation and prints an error' do
expect { subject.create }.to raise_error SystemExit # rubocop:disable Rails/SaveBang
expect(progress).to have_received(:puts)
.with(a_string_matching('No backups found'))
end
end
context 'when there are two backup files in the directory and BACKUP variable is not set' do
before do
allow(Dir).to receive(:glob).and_return(
[
'1451606400_2016_01_01_1.2.3_gitlab_backup.tar',
'1451520000_2015_12_31_gitlab_backup.tar'
]
)
end
it 'prints the list of available backups' do
expect { subject.create }.to raise_error SystemExit # rubocop:disable Rails/SaveBang
expect(progress).to have_received(:puts)
.with(a_string_matching('1451606400_2016_01_01_1.2.3\n 1451520000_2015_12_31'))
end
it 'fails the operation and prints an error' do
expect { subject.create }.to raise_error SystemExit # rubocop:disable Rails/SaveBang
expect(progress).to have_received(:puts)
.with(a_string_matching('Found more than one backup'))
end
end
context 'when BACKUP variable is set to a non-existing file' do
before do
allow(Dir).to receive(:glob).and_return(
[
'1451606400_2016_01_01_gitlab_backup.tar'
]
)
allow(File).to receive(:exist?).and_return(false)
stub_env('BACKUP', 'wrong')
end
it 'fails the operation and prints an error' do
expect { subject.create }.to raise_error SystemExit # rubocop:disable Rails/SaveBang
expect(File).to have_received(:exist?).with('wrong_gitlab_backup.tar')
expect(progress).to have_received(:puts)
.with(a_string_matching('The backup file wrong_gitlab_backup.tar does not exist'))
end
end
context 'when BACKUP variable is set to a correct file' do
let(:tar_cmdline) { %w{tar -xf 1451606400_2016_01_01_1.2.3_gitlab_backup.tar} }
before do
allow(Gitlab::BackupLogger).to receive(:info)
allow(Dir).to receive(:glob).and_return(
[
'1451606400_2016_01_01_1.2.3_gitlab_backup.tar'
]
)
allow(File).to receive(:exist?).and_return(true)
allow(Kernel).to receive(:system).and_return(true)
stub_env('BACKUP', '/ignored/path/1451606400_2016_01_01_1.2.3')
end
it 'unpacks the file' do
subject.create # rubocop:disable Rails/SaveBang
expect(Kernel).to have_received(:system).with(*tar_cmdline)
end
context 'tar fails' do
before do
expect(Kernel).to receive(:system).with(*tar_cmdline).and_return(false)
end
it 'logs a failure' do
expect do
subject.create # rubocop:disable Rails/SaveBang
end.to raise_error(SystemExit)
expect(Gitlab::BackupLogger).to have_received(:info).with(message: 'Unpacking backup failed')
end
end
context 'on version mismatch' do
let(:backup_information) do
{
backup_created_at: Time.zone.parse('2019-01-01'),
gitlab_version: "not #{gitlab_version}"
}
end
it 'stops the process' do
expect { subject.create }.to raise_error SystemExit # rubocop:disable Rails/SaveBang
expect(progress).to have_received(:puts)
.with(a_string_matching('GitLab version mismatch'))
end
end
end
context 'when there is a non-tarred backup in the directory' do
before do
allow(Dir).to receive(:glob).and_return(
[
'backup_information.yml'
]
)
allow(File).to receive(:exist?).and_return(true)
end
it 'selects the non-tarred backup to restore from' do
subject.create # rubocop:disable Rails/SaveBang
expect(progress).to have_received(:puts)
.with(a_string_matching('Non tarred backup found '))
end
context 'on version mismatch' do
let(:backup_information) do
{
backup_created_at: Time.zone.parse('2019-01-01'),
gitlab_version: "not #{gitlab_version}"
}
end
it 'stops the process' do
expect { subject.create }.to raise_error SystemExit # rubocop:disable Rails/SaveBang
expect(progress).to have_received(:puts)
.with(a_string_matching('GitLab version mismatch'))
end
end
end
end
end end
describe '#restore' do describe '#restore' do
......
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