2014-10-10 20:04:07 +02:00
require " backup_restore/backuper "
require " backup_restore/restorer "
2014-02-12 20:32:58 -08:00
module BackupRestore
class OperationRunningError < RuntimeError ; end
DUMP_FILE = " dump.sql "
METADATA_FILE = " meta.json "
2014-02-13 10:41:46 -08:00
LOGS_CHANNEL = " /admin/backups/logs "
2014-02-12 20:32:58 -08:00
2014-08-20 18:48:56 +02:00
def self . backup! ( user_id , opts = { } )
2014-10-10 20:04:07 +02:00
start! BackupRestore :: Backuper . new ( user_id , opts )
2014-02-12 20:32:58 -08:00
end
2015-08-27 20:02:13 +02:00
def self . restore! ( user_id , opts = { } )
start! BackupRestore :: Restorer . new ( user_id , opts )
2014-02-12 20:32:58 -08:00
end
def self . rollback!
raise BackupRestore :: OperationRunningError if BackupRestore . is_operation_running?
if can_rollback?
2014-02-19 15:25:31 +01:00
move_tables_between_schemas ( " backup " , " public " )
2014-02-13 09:11:44 -08:00
after_fork
2014-02-12 20:32:58 -08:00
end
end
def self . cancel!
set_shutdown_signal!
true
end
def self . mark_as_running!
2014-08-19 16:25:57 +10:00
$redis . setex ( running_key , 60 , " 1 " )
2014-02-13 10:41:46 -08:00
save_start_logs_message_id
2014-05-13 16:18:08 +02:00
keep_it_running
2014-02-12 20:32:58 -08:00
end
def self . is_operation_running?
! ! $redis . get ( running_key )
end
def self . mark_as_not_running!
$redis . del ( running_key )
end
def self . should_shutdown?
! ! $redis . get ( shutdown_signal_key )
end
def self . can_rollback?
backup_tables_count > 0
end
def self . operations_status
{
is_operation_running : is_operation_running? ,
can_rollback : can_rollback? ,
2014-08-28 17:02:26 -04:00
allow_restore : Rails . env . development? || SiteSetting . allow_restore
2014-02-12 20:32:58 -08:00
}
end
2014-02-13 10:41:46 -08:00
def self . logs
id = start_logs_message_id
2015-05-04 12:21:00 +10:00
MessageBus . backlog ( LOGS_CHANNEL , id ) . map { | m | m . data }
2014-02-13 10:41:46 -08:00
end
2014-02-12 20:32:58 -08:00
def self . current_version
ActiveRecord :: Migrator . current_version
end
2014-02-19 15:25:31 +01:00
def self . move_tables_between_schemas ( source , destination )
User . exec_sql ( move_tables_between_schemas_sql ( source , destination ) )
end
def self . move_tables_between_schemas_sql ( source , destination )
<<-SQL
DO $$ DECLARE row record ;
BEGIN
2014-05-13 16:18:08 +02:00
- - create < destination > schema if it does not exists already
- - NOTE : DROP & CREATE SCHEMA is easier , but we don ' t want to drop the public schema
- - ortherwise extensions ( like hstore & pg_trgm ) won ' t work anymore ...
2014-07-11 18:29:24 +02:00
CREATE SCHEMA IF NOT EXISTS #{destination};
2014-05-13 16:18:08 +02:00
- - move all < source > tables to < destination > schema
2014-02-19 15:25:31 +01:00
FOR row IN SELECT tablename FROM pg_tables WHERE schemaname = '#{source}'
LOOP
2014-02-20 18:42:17 +01:00
EXECUTE 'DROP TABLE IF EXISTS #{destination}.' || quote_ident ( row . tablename ) || ' CASCADE;' ;
2014-02-19 15:25:31 +01:00
EXECUTE 'ALTER TABLE #{source}.' || quote_ident ( row . tablename ) || ' SET SCHEMA #{destination};' ;
END LOOP ;
2014-07-11 18:29:24 +02:00
- - move all < source > views to < destination > schema
FOR row IN SELECT viewname FROM pg_views WHERE schemaname = '#{source}'
LOOP
EXECUTE 'DROP VIEW IF EXISTS #{destination}.' || quote_ident ( row . viewname ) || ' CASCADE;' ;
EXECUTE 'ALTER VIEW #{source}.' || quote_ident ( row . viewname ) || ' SET SCHEMA #{destination};' ;
END LOOP ;
2014-02-19 15:25:31 +01:00
END $$ ;
2014-02-12 20:32:58 -08:00
SQL
2014-02-19 15:25:31 +01:00
end
2014-07-30 17:20:25 +02:00
DatabaseConfiguration = Struct . new ( :host , :port , :username , :password , :database )
2014-02-12 20:32:58 -08:00
2014-02-19 15:25:31 +01:00
def self . database_configuration
2016-02-12 17:20:38 -05:00
config = ActiveRecord :: Base . connection_pool . spec . config
2014-02-20 19:11:43 +01:00
config = config . with_indifferent_access
DatabaseConfiguration . new (
config [ " host " ] ,
2014-07-30 17:20:25 +02:00
config [ " port " ] ,
2014-02-20 19:11:43 +01:00
config [ " username " ] || ENV [ " USER " ] || " postgres " ,
config [ " password " ] ,
config [ " database " ]
)
2014-02-12 20:32:58 -08:00
end
private
def self . running_key
" backup_restore_operation_is_running "
end
2014-05-13 16:18:08 +02:00
def self . keep_it_running
# extend the expiry by 1 minute every 30 seconds
Thread . new do
# this thread will be killed when the fork dies
while true
$redis . expire ( running_key , 1 . minute )
sleep 30 . seconds
end
end
end
2014-02-12 20:32:58 -08:00
def self . shutdown_signal_key
" backup_restore_operation_should_shutdown "
end
def self . set_shutdown_signal!
$redis . set ( shutdown_signal_key , " 1 " )
end
def self . clear_shutdown_signal!
$redis . del ( shutdown_signal_key )
end
2014-02-13 10:41:46 -08:00
def self . save_start_logs_message_id
2015-05-04 12:21:00 +10:00
id = MessageBus . last_id ( LOGS_CHANNEL )
2014-02-13 10:41:46 -08:00
$redis . set ( start_logs_message_id_key , id )
end
def self . start_logs_message_id
$redis . get ( start_logs_message_id_key ) . to_i
end
def self . start_logs_message_id_key
" start_logs_message_id "
end
2014-02-12 20:32:58 -08:00
def self . start! ( runner )
child = fork do
begin
after_fork
runner . run
rescue Exception = > e
puts " -------------------------------------------- "
puts " ---------------- EXCEPTION ----------------- "
puts e . message
puts e . backtrace . join ( " \n " )
puts " -------------------------------------------- "
ensure
begin
clear_shutdown_signal!
rescue Exception = > e
puts " ============================================ "
puts " ================ EXCEPTION ================= "
puts e . message
puts e . backtrace . join ( " \n " )
puts " ============================================ "
ensure
exit! ( 0 )
end
end
end
Process . detach ( child )
true
end
def self . after_fork
2014-03-28 13:48:14 +11:00
Discourse . after_fork
2014-02-12 20:32:58 -08:00
end
def self . backup_tables_count
User . exec_sql ( " SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = 'backup' " ) [ 0 ] [ 'count' ] . to_i
end
end