files/gitlab-ctl-commands/pg-upgrade.rb (735 lines of code) (raw):
#
# Copyright:: Copyright (c) 2016 GitLab Inc
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'rainbow'
require "#{base_path}/embedded/service/omnibus-ctl/lib/gitlab_ctl"
require "#{base_path}/embedded/service/omnibus-ctl/lib/postgresql"
INST_DIR = "#{base_path}/embedded/postgresql".freeze
REVERT_VERSION_FILE = "#{data_path}/postgresql-version.old".freeze
add_command_under_category 'revert-pg-upgrade', 'database',
'Run this to revert to the previous version of the database',
2 do |_cmd_name|
begin
options = GitlabCtl::PgUpgrade.parse_options(ARGV)
rescue ArgumentError => e
log "Command line parameter error: #{e.message}"
Kernel.exit 64
end
revert_version = lookup_version(options[:target_version], read_revert_version || default_version)
@attributes = GitlabCtl::Util.get_node_attributes(base_path)
patroni_enabled = service_enabled?('patroni')
pg_enabled = service_enabled?('postgresql')
geo_pg_enabled = service_enabled?('geo-postgresql')
@db_service_name = patroni_enabled ? 'patroni' : 'postgresql'
db_worker = GitlabCtl::PgUpgrade.new(
base_path,
data_path,
revert_version,
options[:tmp_dir],
options[:timeout]
)
if geo_pg_enabled
geo_db_worker = GitlabCtl::PgUpgrade.new(
base_path,
data_path,
revert_version,
options[:tmp_dir],
options[:timeout]
)
geo_db_worker.data_dir = File.join(@attributes['gitlab']['geo_postgresql']['dir'], 'data')
geo_db_worker.tmp_data_dir = "#{geo_db_worker.tmp_dir}/geo-data" unless geo_db_worker.tmp_dir.nil?
geo_db_worker.psql_command = 'gitlab-geo-psql'
end
if GitlabCtl::Util.progress_message('Checking if we need to downgrade') do
(!(pg_enabled || patroni_enabled) || db_worker.fetch_data_version == revert_version.major) && \
(!geo_pg_enabled || geo_db_worker.fetch_data_version == revert_version.major) && \
db_worker.initial_version == revert_version
end
log "Already running #{revert_version}"
Kernel.exit 1
end
maintenance_mode('enable') unless patroni_enabled
unless Dir.exist?("#{db_worker.tmp_data_dir}.#{revert_version.major}")
if !geo_pg_enabled || !Dir.exist?("#{geo_db_worker.tmp_data_dir}.#{revert_version.major}")
log "#{db_worker.tmp_data_dir}.#{revert_version.major} does not exist, cannot revert data"
log "#{geo_db_worker.tmp_data_dir}.#{revert_version.major} does not exist, cannot revert data" if geo_pg_enabled
log 'Will proceed with reverting the running program version only, unless you interrupt'
end
end
if patroni_enabled
@db_worker = db_worker
patroni_preflight_check(options)
end
log "Reverting database to #{revert_version} in 5 seconds"
log '=== WARNING ==='
log 'This will revert the database to what it was before you upgraded, including the data.'
log "Please hit Ctrl-C now if this isn't what you were looking for"
log '=== WARNING ==='
begin
sleep 5
rescue Interrupt
log 'Received interrupt, not doing anything'
Kernel.exit 0
end
if patroni_enabled
log '== Reverting =='
if @instance_type == :patroni_leader
patroni_leader_downgrade(revert_version)
elsif @instance_type == :patroni_replica
patroni_replica_downgrade(revert_version)
elsif @instance_type == :patroni_standby_leader
patroni_standby_leader_downgrade(revert_version)
end
log '== Reverted =='
elsif pg_enabled
@db_worker = db_worker
revert(revert_version)
end
if geo_pg_enabled
@db_service_name = 'geo-postgresql'
@db_worker = geo_db_worker
revert(revert_version)
end
clean_revert_version
maintenance_mode('disable') unless patroni_enabled
end
add_command_under_category 'pg-upgrade', 'database',
'Upgrade the PostgreSQL DB to the latest supported version',
2 do |_cmd_name|
options = GitlabCtl::PgUpgrade.parse_options(ARGV)
patroni_enabled = service_enabled?('patroni')
pg_enabled = service_enabled?('postgresql')
geo_enabled = service_enabled?('geo-postgresql')
@db_service_name = patroni_enabled ? 'patroni' : 'postgresql'
@timeout = options[:timeout]
@db_worker = GitlabCtl::PgUpgrade.new(
base_path,
data_path,
lookup_version(options[:target_version], default_version),
options[:tmp_dir],
options[:timeout]
)
@instance_type = :single_node
@geo_pg_enabled = service_enabled?('geo-postgresql') || false
@roles = GitlabCtl::Util.roles(base_path)
@attributes = GitlabCtl::Util.get_node_attributes(base_path)
unless GitlabCtl::Util.progress_message(
'Checking for an omnibus managed postgresql') do
!@db_worker.initial_version.nil? && \
(pg_enabled || patroni_enabled || service_enabled?('geo-postgresql'))
end
$stderr.puts 'No currently installed postgresql in the omnibus instance found.'
Kernel.exit 0
end
unless GitlabCtl::Util.progress_message(
"Checking if postgresql['version'] is set"
) do
@attributes['postgresql']['version'].nil?
end
log "postgresql['version'] is set in /etc/gitlab/gitlab.rb. Not checking for a PostgreSQL upgrade"
deprecation_message if @attributes['postgresql']['version'].to_f < 16
Kernel.exit 0
end
if GitlabCtl::Util.progress_message('Checking if we already upgraded') do
@db_worker.initial_version.major.to_f >= @db_worker.target_version.major.to_f
end
$stderr.puts "The latest version #{@db_worker.initial_version} is already running, nothing to do"
Kernel.exit 0
end
unless GitlabCtl::Util.progress_message('Checking for a newer version of PostgreSQL to install') do
@db_worker.target_version && Dir.exist?("#{INST_DIR}/#{@db_worker.target_version.major}")
end
$stderr.puts 'No new version of PostgreSQL installed, nothing to upgrade to'
Kernel.exit 0
end
log "Upgrading PostgreSQL to #{@db_worker.target_version}"
if GitlabCtl::Util.progress_message('Checking for previous failed upgrade attempts') do
File.exist?("#{@db_worker.tmp_data_dir}.#{@db_worker.initial_version.major}")
end
$stderr.puts "Detected a potential failed upgrade. Directory #{@db_worker.tmp_data_dir}.#{@db_worker.initial_version.major} already exists."
Kernel.exit 1
end
deprecation_message if @db_worker.target_version.major.to_f < 16
target_data_dir = "#{@db_worker.tmp_data_dir}.#{@db_worker.target_version.major}"
if @db_worker.upgrade_artifact_exists?(target_data_dir)
$stderr.puts "Cannot upgrade, #{target_data_dir} is not empty. Move or delete this directory to proceed with upgrade"
Kernel.exit 0
end
total_space_needed = 0 # in MiB
unless options[:skip_disk_check]
check_dirs = {}
check_dirs[@db_worker.data_dir] = 0 if pg_enabled || patroni_enabled
check_dirs[File.join(@attributes['gitlab']['geo_postgresql']['dir'], 'data')] = 0 if geo_enabled
check_dirs.each_key do |dir|
check_dirs[dir] = @db_worker.space_needed(dir)
total_space_needed += check_dirs[dir]
end
# We need space for all databases if using --tmp-dir.
check_dirs = { @db_worker.tmp_dir => total_space_needed } if @db_worker.tmp_dir
check_dirs.each do |dir, space_needed|
unless GitlabCtl::Util.progress_message(
"Checking if disk for directory #{dir} has enough free space for PostgreSQL upgrade"
) do
@db_worker.enough_free_space?(dir, space_needed)
end
log "Upgrade requires #{space_needed}MB, but only #{@db_worker.space_free(dir)}MB is free."
Kernel.exit 1
end
next
end
end
unless GitlabCtl::Util.progress_message(
'Checking if PostgreSQL bin files are symlinked to the expected location'
) do
Dir.glob("#{INST_DIR}/#{@db_worker.initial_version.major}/bin/*").each do |bin_file|
link = "#{base_path}/embedded/bin/#{File.basename(bin_file)}"
File.symlink?(link) && File.readlink(link).eql?(bin_file)
end
end
log "#{link} is not linked to #{bin_file}, unable to proceed with non-standard installation"
Kernel.exit 1
end
# The current instance needs to be running, start it if it isn't
if pg_enabled && !@db_worker.running?
log 'Starting the database'
begin
@db_worker.start
rescue Mixlib::ShellOut::ShellCommandFailed => e
log "Error starting the database. Please fix the error before continuing"
log e.message
Kernel.exit 1
end
end
if service_enabled?('geo-postgresql') && !@db_worker.running?('geo-postgresql')
log 'Starting the geo database'
begin
@db_worker.start('geo-postgresql')
rescue Mixlib::ShellOut::ShellCommandFailed => e
log "Error starting the geo database. Please fix the error before continuing"
log e.message
Kernel.exit 1
end
end
patroni_preflight_check(options) if patroni_enabled
if options[:wait]
# Wait for processes to settle, and give use one last chance to change their
# mind
log "Waiting 30 seconds to ensure tasks complete before PostgreSQL upgrade."
log "See https://docs.gitlab.com/omnibus/settings/database.html#upgrade-packaged-postgresql-server for details"
log "If you do not want to upgrade the PostgreSQL server at this time, enter Ctrl-C and see the documentation for details"
status = GitlabCtl::Util.delay_for(30)
unless status
maintenance_mode('disable') unless patroni_enabled
Kernel.exit(0)
end
end
# The possible deployment types we need to handle are:
# 1. Standalone regular postgresql node
# 2. Standalone Geo primary node
# 3. Standalone Geo secondary node
# 4. Leader in Regular/Geo-Primary patroni cluster
# 5. Replica in Regular/Geo-Primary patroni cluster
# 6. Leader in Geo-Secondary patroni cluster (i.e, standby leader)
# 7. Replica in Geo-Secondary patroni cluster
if patroni_enabled
if @instance_type == :patroni_leader
patroni_leader_upgrade
elsif @instance_type == :patroni_replica
patroni_replica_upgrade
elsif @instance_type == :patroni_standby_leader
patroni_standby_leader_upgrade
end
elsif @roles.include?('geo_primary')
log 'Detected a GEO primary node'
@instance_type = :geo_primary
general_upgrade
elsif @roles.include?('geo_secondary')
log 'Detected a Geo secondary node'
@instance_type = :geo_secondary
geo_secondary_upgrade(options[:tmp_dir], options[:timeout])
elsif service_enabled?('geo-postgresql')
log 'Detected a Geo PostgreSQL node'
@instance_type = :geo_postgresql
geo_pg_upgrade
else
general_upgrade
end
end
def common_pre_upgrade(enable_maintenance = true)
maintenance_mode('enable') if enable_maintenance
locale, collate, encoding = get_locale_encoding
stop_database
create_links(@db_worker.target_version)
create_temp_data_dir
initialize_new_db(locale, collate, encoding)
end
def common_post_upgrade(disable_maintenance = true)
cleanup_data_dir
if @db_service_name == 'patroni'
copy_patroni_dynamic_config
start_database
end
configure_postgresql
log 'Running reconfigure to re-generate any dependent service configuration'
run_reconfigure
restart_patroni_node if @db_service_name == 'patroni'
log "Waiting for Database to be running."
if @db_service_name == 'geo-postgresql'
GitlabCtl::PostgreSQL.wait_for_postgresql(120, psql_command: 'gitlab-geo-psql')
else
GitlabCtl::PostgreSQL.wait_for_postgresql(120)
end
unless [:pg_secondary, :geo_secondary, :geo_postgresql, :patroni_replica, :patroni_standby_leader].include?(@instance_type)
log 'Database upgrade is complete, running vacuumdb analyze'
analyze_cluster
end
maintenance_mode('disable') if disable_maintenance
goodbye_message
end
def general_upgrade
common_pre_upgrade
begin
@db_worker.run_pg_upgrade
rescue GitlabCtl::Errors::ExecutionError
die "Error running pg_upgrade, please check logs"
end
common_post_upgrade
end
def patroni_leader_upgrade
common_pre_upgrade(false)
begin
@db_worker.run_pg_upgrade
rescue GitlabCtl::Errors::ExecutionError
die 'Error running pg_upgrade, please check logs'
end
remove_patroni_cluster_state
common_post_upgrade(false)
end
def patroni_replica_upgrade
stop_database
create_links(@db_worker.target_version)
common_post_upgrade(!@geo_pg_enabled)
cleanup_data_dir
geo_pg_upgrade
end
def patroni_standby_leader_upgrade
stop_database
create_links(@db_worker.target_version)
remove_patroni_cluster_state
common_post_upgrade(!@geo_pg_enabled)
geo_pg_upgrade
end
def patroni_leader_downgrade(revert_version)
stop_database
create_links(revert_version)
revert_data_dir(revert_version)
remove_patroni_cluster_state
start_database
configure_postgresql
restart_patroni_node
end
def patroni_replica_downgrade(revert_version)
stop_database
create_links(revert_version)
revert_data_dir(revert_version)
start_database
configure_postgresql
restart_patroni_node
end
def patroni_standby_leader_downgrade(revert_version)
stop_database
create_links(revert_version)
revert_data_dir(revert_version)
remove_patroni_cluster_state
start_database
configure_postgresql
restart_patroni_node
end
def configure_postgresql
log 'Configuring PostgreSQL'
status = GitlabCtl::Util.chef_run('solo.rb', "#{@db_service_name}-config.json", timeout: @timeout)
$stdout.puts status.stdout
if status.error?
$stderr.puts '===STDERR==='
$stderr.puts status.stderr
$stderr.puts '======'
die 'Error updating PostgreSQL configuration. Please check the output'
end
restart_database
end
def start_database
sv_progress('start', @db_service_name)
end
def stop_database
sv_progress('stop', @db_service_name)
end
def restart_database
sv_progress('restart', @db_service_name)
end
def sv_progress(action, service)
GitlabCtl::Util.progress_message("Running #{action} on #{service}") do
run_sv_command_for_service(action, service)
end
end
def promote_database
log 'Promoting the database'
@db_worker.run_pg_command(
"#{base_path}/embedded/bin/pg_ctl -D #{@db_worker.data_dir} promote"
)
end
def geo_secondary_upgrade(tmp_dir, timeout)
pg_enabled = service_enabled?('postgresql')
# Run the first time to link the primary postgresql instance
if pg_enabled
log('Upgrading the postgresql database')
begin
promote_database
rescue GitlabCtl::Errors::ExecutionError => e
log "STDOUT: #{e.stdout}"
log "STDERR: #{e.stderr}"
die "There was an error promoting the database from standby, please check the logs and output."
end
# Restart the database after promotion, and wait for it to be ready
restart_database
GitlabCtl::PostgreSQL.wait_for_postgresql(600)
common_pre_upgrade
cleanup_data_dir
end
geo_pg_upgrade
end
def geo_pg_upgrade
return unless @geo_pg_enabled
@instance_type = :geo_postgresql
# Update the location to handle the geo-postgresql instance
log('Upgrading the geo-postgresql database')
# Secondary nodes have a replica db under /var/opt/gitlab/postgresql that needs
# the bin files updated and the geo tracking db under /var/opt/gitlab/geo-postgresl that needs data updated
data_dir = File.join(@attributes['gitlab']['geo_postgresql']['dir'], 'data')
@db_service_name = 'geo-postgresql'
@db_worker.data_dir = data_dir
@db_worker.tmp_data_dir = @db_worker.tmp_dir.nil? ? data_dir : "#{@db_worker.tmp_dir}/geo-data"
@db_worker.psql_command = 'gitlab-geo-psql'
common_pre_upgrade
begin
@db_worker.run_pg_upgrade
rescue GitlabCtl::Errors::ExecutionError
die "Error running pg_upgrade on secondary, please check logs"
end
common_post_upgrade
end
def get_locale_encoding
begin
locale = @db_worker.fetch_lc_ctype
collate = @db_worker.fetch_lc_collate
encoding = @db_worker.fetch_server_encoding
rescue GitlabCtl::Errors::ExecutionError => e
log 'There was an error fetching locale and encoding information from the database'
log 'Please ensure the database is running and functional before running pg-upgrade'
log "STDOUT: #{e.stdout}"
log "STDERR: #{e.stderr}"
die 'Please check error logs'
end
[locale, collate, encoding]
end
def create_temp_data_dir
unless GitlabCtl::Util.progress_message('Creating temporary data directory') do
begin
@db_worker.run_pg_command(
"mkdir -p #{@db_worker.tmp_data_dir}.#{@db_worker.target_version.major}"
)
rescue GitlabCtl::Errors::ExecutionError => e
log "Error creating new directory: #{@db_worker.tmp_data_dir}.#{@db_worker.target_version.major}"
log "STDOUT: #{e.stdout}"
log "STDERR: #{e.stderr}"
false
else
true
end
end
die 'Please check the output'
end
end
def initialize_new_db(locale, collate, encoding)
unless GitlabCtl::Util.progress_message('Initializing the new database') do
begin
@db_worker.run_pg_command(
"#{@db_worker.target_version_path}/bin/initdb " \
"-D #{@db_worker.tmp_data_dir}.#{@db_worker.target_version.major} " \
"--locale #{locale} " \
"--encoding #{encoding} " \
" --lc-collate=#{collate} " \
"--lc-ctype=#{locale}"
)
rescue GitlabCtl::Errors::ExecutionError => e
log "Error initializing database for #{@db_worker.target_version}"
log "STDOUT: #{e.stdout}"
log "STDERR: #{e.stderr}"
die 'Please check the output and try again'
end
end
die 'Error initializing new database'
end
end
def cleanup_data_dir
unless GitlabCtl::Util.progress_message('Move the old data directory out of the way') do
run_command(
"mv #{@db_worker.data_dir} #{@db_worker.tmp_data_dir}.#{@db_worker.initial_version.major}"
)
end
die 'Error moving data for older version, '
end
if @instance_type == :patroni_replica || @instance_type == :patroni_standby_leader
unless GitlabCtl::Util.progress_message('Recreating an empty data directory') do
run_command("mkdir -p #{@db_worker.data_dir}")
end
die "Error refreshing #{@db_worker.data_dir}"
end
else
unless GitlabCtl::Util.progress_message('Rename the new data directory') do
run_command(
"mv #{@db_worker.tmp_data_dir}.#{@db_worker.target_version.major} #{@db_worker.data_dir}"
)
end
die "Error moving #{@db_worker.tmp_data_dir}.#{@db_worker.target_version.major} to #{@db_worker.data_dir}"
end
end
unless GitlabCtl::Util.progress_message('Saving the old version information') do
save_revert_version
end
die
end
end
def run_reconfigure
unless GitlabCtl::Util.progress_message('Running reconfigure') do
# Disable automatic restart as part of reconfigure to avoid other PGs than
# the one being currently upgraded getting restarted unnecessarily.
run_chef("#{base_path}/embedded/cookbooks/pg-upgrade-config.json").success?
end
die 'Something went wrong during final reconfiguration, please check the output'
end
end
def analyze_cluster
pg_username = @attributes.dig(:gitlab, :postgresql, :username) || @attributes.dig(:postgresql, :username)
pg_host = @attributes.dig(:gitlab, :postgresql, :unix_socket_directory) || @attributes.dig(:postgresql, :unix_socket_directory)
analyze_cmd = "PGOPTIONS='-c statement_timeout=0' #{@db_worker.target_version_path}/bin/vacuumdb -j2 --all --analyze-in-stages -h #{pg_host} -p #{@db_worker.port}"
begin
@db_worker.run_pg_command(analyze_cmd)
rescue GitlabCtl::Errors::ExecutionError => e
log "Error running #{analyze_cmd}"
log "STDOUT: #{e.stdout}"
log "STDERR: #{e.stderr}"
log 'Please check the output, and rerun the command as root or with sudo if needed:'
log "sudo su - #{pg_username} -c \"#{analyze_cmd}\""
log 'If the error persists, please open an issue at: '
log 'https://gitlab.com/gitlab-org/omnibus-gitlab/issues'
rescue Mixlib::ShellOut::CommandTimeout
$stderr.puts "Time out while running the analyze stage.".color(:yellow)
$stderr.puts "Please re-run the command manually as the #{pg_username} user".color(:yellow)
$stderr.puts analyze_cmd.color(:yellow)
end
end
def patroni_preflight_check(options)
log 'Detected a Patroni cluster.'
@instance_type = if options[:leader]
:patroni_leader
elsif options[:replica]
:patroni_replica
elsif options[:standby_leader]
:patroni_standby_leader
end
guess_patroni_node_role unless @instance_type
check_patroni_cluster_status
if @instance_type == :patroni_leader
log "Using #{Rainbow('leader').yellow} node upgrade procedure."
elsif @instance_type == :patroni_replica
log "Using #{Rainbow('replica').yellow} node upgrade procedure."
log Rainbow('This procedure REMOVES DATA directory.').yellow
elsif @instance_type == :patroni_standby_leader
log "Using #{Rainbow('standby-leader').yellow} node upgrade procedure."
log Rainbow('This procedure REMOVES DATA directory.').yellow
end
end
def guess_patroni_node_role
failure_cause = :none
unless GitlabCtl::Util.progress_message('Attempting to detect the role of this Patroni node') do
begin
node = Patroni::Client.new
scope = @attributes.dig(:patroni, :scope)
node_name = @attributes.dig(:patroni, :name)
consul_binary = @attributes.dig(:consul, :binary_path)
if node.up?
@instance_type = :patroni_leader if node.leader?
@instance_type = :patroni_replica if node.replica?
@instance_type = :patroni_standby_leader if node.standby_leader?
failure_cause = :patroni_running_on_replica if @instance_type == :patroni_replica
@instance_type == :patroni_leader || @instance_type == :patroni_standby_leader
else
leader_name = GitlabCtl::Util.get_command_output("#{consul_binary} kv get /service/#{scope}/leader").strip
@instance_type = node_name == leader_name ? :patroni_leader : :patroni_replica unless leader_name.nil? || leader_name.empty?
failure_cause = :patroni_stopped_on_leader if @instance_type == :patroni_leader
@instance_type == :patroni_replica
end
rescue GitlabCtl::Errors::ExecutionError => e
log 'Unable to get the role of the Patroni node from Consul'
log "STDOUT: #{e.stdout}"
log "STDERR: #{e.stderr}"
false
end
end
case failure_cause
when :patroni_running_on_replica
log 'Looks like that this is a replica node but the Patroni service is still running.'
log 'Try to stop the Patroni service before attempting the upgrade.'
die 'Patroni service is still running on the replica node.'
when :patroni_stopped_on_leader
log 'Looks like that this is the leader node but the Patroni is not running.'
log 'Try to start the Patroni service before attempting the upgrade.'
die 'Patroni service is not running on the leader node.'
else
log 'Unable to detect the role of this Patroni node.'
log 'Try to use --leader, --replica or --standby-leader switches to specify the role manually.'
log 'See: https://docs.gitlab.com/ee/administration/postgresql/replication_and_failover.html#upgrading-postgresql-major-version-in-a-patroni-cluster'
die 'Unable to detect the role of this Patroni node.'
end
end
end
def check_patroni_cluster_status
# If the client can be created then it means that the
# required node attributes are stored correctly.
node = Patroni::Client.new
return if [:patroni_leader, :patroni_standby_leader].none? { |type| @instance_type == type }
die 'Patroni service is not running on the leader node.' unless node.up?
running_replica_count = 0
node.cluster_status[:members]&.each do |member|
running_replica_count += 1 if member[:state] == 'running' && member[:role] == 'replica'
end
log Rainbow("WARNING: Looks like that at least one replica node is running.\n" \
" It is strongly recommended to shutdown all replicas\n" \
" before upgrading the cluster.").yellow if running_replica_count.positive?
end
def remove_patroni_cluster_state
scope = @attributes.dig(:patroni, :scope) || ''
consul_binary = @attributes.dig(:consul, :binary_path)
unless !scope.empty? && GitlabCtl::Util.progress_message('Wiping Patroni cluster state') do
run_command("#{consul_binary} kv delete -recurse /service/#{scope}/")
end
die 'Unable to wipe the cluster state'
end
end
def restart_patroni_node
name = @attributes.dig(:patroni, :name) || ''
scope = @attributes.dig(:patroni, :scope) || ''
unless !name.empty? && !scope.empty? && GitlabCtl::Util.progress_message("Restarting Patroni on this node\n") do
patroni_dir = @attributes.dig(:patroni, :dir) || '/var/opt/gitlab/patroni'
run_command("#{base_path}/embedded/bin/patronictl -c #{patroni_dir}/patroni.yaml restart --force #{scope} #{name}")
end
die 'Unable to wipe the cluster state'
end
end
def copy_patroni_dynamic_config
src = "#{@db_worker.data_dir}/patroni.dynamic.json"
dst = "#{@db_worker.tmp_data_dir}.#{@db_worker.target_version.major}/patroni.dynamic.json"
FileUtils.copy_file(src, dst, true) if File.exist?(src) && !File.exist?(dst)
end
def version_from_manifest(software)
@versions = JSON.load_file("#{base_path}/version-manifest.json") if @versions.nil?
return @versions['software'][software]['described_version'] if @versions['software'].key?(software)
nil
end
def old_version
PGVersion.parse(version_from_manifest('postgresql_old')) || PGVersion.parse(version_from_manifest('postgresql'))
end
def default_version
# Once the new version is the default and recommended version, modify the
# return statement to try postgresql_new first.
# PGVersion.parse(version_from_manifest('postgresql_new')) || PGVersion.parse(version_from_manifest('postgresql'))
PGVersion.parse(version_from_manifest('postgresql'))
end
def new_version
PGVersion.parse(version_from_manifest('postgresql_new')) || PGVersion.parse(version_from_manifest('postgresql'))
end
SUPPORTED_VERSIONS = [old_version, default_version, new_version].freeze
def lookup_version(major_version, fallback_version)
return fallback_version unless major_version
target_version = SUPPORTED_VERSIONS.select { |v| v.major == major_version }
if target_version.empty?
log "The specified major version #{major_version} is not supported. Choose from one of #{SUPPORTED_VERSIONS.map(&:major).uniq.join(', ')}."
Kernel.exit 1
else
target_version[0]
end
end
def create_links(version)
GitlabCtl::Util.progress_message('Symlink correct version of binaries') do
Dir.glob("#{INST_DIR}/#{version.major}/bin/*").each do |bin_file|
destination = "#{base_path}/embedded/bin/#{File.basename(bin_file)}"
GitlabCtl::Util.get_command_output("ln -sf #{bin_file} #{destination}")
end
end
end
def revert(version)
log '== Reverting =='
run_sv_command_for_service('stop', @db_service_name)
revert_data_dir(version)
create_links(version)
run_sv_command_for_service('start', @db_service_name)
log'== Reverted =='
end
def revert_data_dir(version)
if @instance_type == :patroni_replica
run_command("rm -rf #{@db_worker.data_dir}")
run_command("mkdir #{@db_worker.data_dir}")
return
end
return unless Dir.exist?("#{@db_worker.tmp_data_dir}.#{version.major}")
run_command("rm -rf #{@db_worker.data_dir}")
run_command(
"mv #{@db_worker.tmp_data_dir}.#{version.major} #{@db_worker.data_dir}"
)
end
def maintenance_mode(command)
# In order for the deploy page to work, we need nginx, Puma, redis, and
# gitlab-workhorse running
# We'll manage postgresql and patroni during the upgrade process
omit_services = %w(postgresql geo-postgresql patroni consul nginx puma redis gitlab-workhorse)
if command.eql?('enable')
dp_cmd = 'up'
sv_cmd = 'stop'
elsif command.eql?('disable')
dp_cmd = 'down'
sv_cmd = 'start'
else
raise StandardError("Cannot handle command #{command}")
end
GitlabCtl::Util.progress_message('Toggling deploy page') do
run_command("#{base_path}/bin/gitlab-ctl deploy-page #{dp_cmd}")
end
GitlabCtl::Util.progress_message('Toggling services') do
get_all_services.select { |x| !omit_services.include?(x) }.each do |svc|
run_sv_command_for_service(sv_cmd, svc)
end
end
end
def die(message)
$stderr.puts '== Fatal error =='
$stderr.puts message
revert(@db_worker.initial_version)
$stderr.puts "== Reverted to #{@db_worker.initial_version}. Please check output for what went wrong =="
maintenance_mode('disable') unless service_enabled?('patroni')
exit 1
end
def read_revert_version
File.exist?(REVERT_VERSION_FILE) ? PGVersion.parse(File.read(REVERT_VERSION_FILE)) : nil
end
def save_revert_version
File.write(REVERT_VERSION_FILE, @db_worker.initial_version)
end
def clean_revert_version
File.delete(REVERT_VERSION_FILE) if File.exist? REVERT_VERSION_FILE
end
def goodbye_message
log '==== Upgrade has completed ===='
log 'Please verify everything is working and run the following if so'
log "sudo rm -rf #{@db_worker.tmp_data_dir}.#{@db_worker.initial_version.major}"
log "sudo rm -f #{REVERT_VERSION_FILE}"
log ""
case @instance_type
when :pg_secondary
log "As part of PostgreSQL upgrade, this secondary node was removed from"
log "the HA cluster. Once the primary node is upgraded to new version of"
log "PostgreSQL, you will have to configure this secondary node to follow"
log "the primary node again."
log "Check https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-gitlab-ha-cluster for details."
when :pg_primary
log "As part of PostgreSQL upgrade, the secondary nodes were removed from"
log "the HA cluster. So right now, the cluster has only a single node in"
log "it - the primary node."
log "Now the primary node has been upgraded to new version of PostgreSQL,"
log "you may go ahead and configure the secondary nodes to follow this"
log "primary node."
log "Check https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-gitlab-ha-cluster for details."
when :geo_primary, :geo_secondary
log 'As part of the PostgreSQL upgrade, replication between primary and secondary has'
log 'been shut down. After the secondary has been upgraded, it needs to be re-initialized'
log 'Please see the instructions at https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-geo-instance'
end
end
def deprecation_message
log '=== WARNING ==='
log "Note that PostgreSQL #{default_version.major} is the minimum required PostgreSQL version in GitLab 16.0"
log 'See docs for more information: https://about.gitlab.com/handbook/engineering/development/enablement/data_stores/database/postgresql-upgrade-cadence.html'
log "PostgreSQL #{old_version.major} has been removed in GitLab 16.0."
log 'Please consider upgrading your PostgreSQL version soon.'
log 'To upgrade, please see: https://docs.gitlab.com/omnibus/settings/database.html#upgrade-packaged-postgresql-server'
log '=== WARNING ==='
end