2017-05-15 09:07:22 +02:00
package ReplicationTestEnv ;
use strict ;
use warnings ;
2020-12-02 17:57:18 +01:00
2017-05-18 07:14:42 +02:00
use Clone 'clone' ;
use File::Basename ;
2020-12-02 17:57:18 +01:00
use JSON ;
2017-05-15 09:07:22 +02:00
use lib ( '.' , '../..' ) ;
use PVE::Cluster ;
2020-12-02 17:57:18 +01:00
use PVE::INotify ;
use PVE::LXC::Config ;
use PVE::QemuConfig ;
2017-05-15 09:07:22 +02:00
use PVE::Storage ;
2020-12-02 17:57:18 +01:00
2017-06-09 09:45:44 +02:00
use PVE::API2::Replication ;
2017-05-15 09:07:22 +02:00
use PVE::Replication ;
2020-12-02 17:57:18 +01:00
use PVE::ReplicationConfig ;
use PVE::ReplicationState ;
2017-06-09 09:45:44 +02:00
2017-05-15 09:07:22 +02:00
use Test::MockModule ;
our $ mocked_nodename = 'node1' ;
our $ mocked_replication_jobs = { } ;
2017-06-12 06:59:21 +02:00
my $ pve_replication_config_module = Test::MockModule - > new ( 'PVE::ReplicationConfig' ) ;
my $ pve_replication_state_module = Test::MockModule - > new ( 'PVE::ReplicationState' ) ;
2017-05-15 09:07:22 +02:00
our $ mocked_vm_configs = { } ;
our $ mocked_ct_configs = { } ;
2017-06-27 15:47:24 +02:00
my $ mocked_get_members = sub {
return {
node1 = > { online = > 1 } ,
node2 = > { online = > 1 } ,
node3 = > { online = > 1 } ,
} ;
} ;
2017-05-15 09:07:22 +02:00
my $ mocked_vmlist = sub {
my $ res = { } ;
foreach my $ id ( keys %$ mocked_ct_configs ) {
my $ d = $ mocked_ct_configs - > { $ id } ;
$ res - > { $ id } = { 'type' = > 'lxc' , 'node' = > $ d - > { node } , 'version' = > 1 } ;
}
foreach my $ id ( keys %$ mocked_vm_configs ) {
my $ d = $ mocked_vm_configs - > { $ id } ;
$ res - > { $ id } = { 'type' = > 'qemu' , 'node' = > $ d - > { node } , 'version' = > 1 } ;
}
return { 'ids' = > $ res } ;
} ;
2017-05-22 12:39:05 +02:00
my $ mocked_get_ssh_info = sub {
my ( $ node , $ network_cidr ) = @ _ ;
return { node = > $ node } ;
} ;
my $ mocked_ssh_info_to_command = sub {
my ( $ info , @ extra_options ) = @ _ ;
return [ 'fake_ssh' , $ info - > { name } , @ extra_options ] ;
} ;
2017-05-15 09:07:22 +02:00
2020-12-02 18:00:44 +01:00
my $ statefile = ".mocked_repl_state.$$" ;
2017-05-15 09:07:22 +02:00
unlink $ statefile ;
2017-06-02 10:07:33 +02:00
$ PVE:: ReplicationState:: state_path = $ statefile ;
2020-12-02 18:00:44 +01:00
$ PVE:: ReplicationState:: state_lock = ".mocked_repl_state_lock.$$" ;
$ PVE:: API2:: Replication:: pvesr_lock_path = ".mocked_pvesr_lock.$$" ;
$ PVE:: GuestHelpers:: lockdir = ".mocked_pve-manager_lock.$$" ;
2017-06-09 09:55:57 +02:00
if ( ! mkdir ( $ PVE:: GuestHelpers:: lockdir ) && ! $! { EEXIST } ) {
# If we cannot create the guest helper lockdir we'll loop endlessly, so die
# if it fails.
die "mkdir($PVE::GuestHelpers::lockdir): $!\n" ;
}
2017-05-15 09:07:22 +02:00
2019-11-11 11:28:35 +01:00
my $ pve_sshinfo_module = Test::MockModule - > new ( 'PVE::SSHInfo' ) ;
2017-05-15 09:07:22 +02:00
my $ pve_cluster_module = Test::MockModule - > new ( 'PVE::Cluster' ) ;
my $ pve_inotify_module = Test::MockModule - > new ( 'PVE::INotify' ) ;
my $ mocked_qemu_load_conf = sub {
my ( $ class , $ vmid , $ node ) = @ _ ;
$ node = $ mocked_nodename if ! $ node ;
my $ conf = $ mocked_vm_configs - > { $ vmid } ;
die "no such vm '$vmid'" if ! defined ( $ conf ) ;
die "vm '$vmid' on wrong node" if $ conf - > { node } ne $ node ;
return $ conf ;
} ;
my $ pve_qemuserver_module = Test::MockModule - > new ( 'PVE::QemuServer' ) ;
my $ pve_qemuconfig_module = Test::MockModule - > new ( 'PVE::QemuConfig' ) ;
my $ mocked_lxc_load_conf = sub {
my ( $ class , $ vmid , $ node ) = @ _ ;
$ node = $ mocked_nodename if ! $ node ;
my $ conf = $ mocked_ct_configs - > { $ vmid } ;
die "no such ct '$vmid'" if ! defined ( $ conf ) ;
die "ct '$vmid' on wrong node" if $ conf - > { node } ne $ node ;
return $ conf ;
} ;
my $ pve_lxc_config_module = Test::MockModule - > new ( 'PVE::LXC::Config' ) ;
2017-06-12 06:59:21 +02:00
my $ mocked_replication_config_new = sub {
2017-05-15 09:07:22 +02:00
2017-05-18 07:14:42 +02:00
my $ res = clone ( $ mocked_replication_jobs ) ;
2017-05-15 09:07:22 +02:00
return bless { ids = > $ res } , 'PVE::ReplicationConfig' ;
} ;
my $ mocked_storage_config = {
ids = > {
local = > {
type = > 'dir' ,
shared = > 0 ,
content = > {
'iso' = > 1 ,
'backup' = > 1 ,
} ,
path = > "/var/lib/vz" ,
} ,
'local-zfs' = > {
type = > 'zfspool' ,
pool = > 'nonexistent-testpool' ,
shared = > 0 ,
content = > {
'images' = > 1 ,
'rootdir' = > 1
} ,
} ,
} ,
} ;
my $ pve_storage_module = Test::MockModule - > new ( 'PVE::Storage' ) ;
2017-05-18 07:14:42 +02:00
2017-05-22 08:26:40 +02:00
my $ mocked_storage_content = { } ;
2021-10-19 09:54:58 +02:00
my $ timestamp_counter = 0 ;
sub generate_snapshot_info {
$ timestamp_counter + + ;
return {
id = > $ timestamp_counter ,
timestamp = > $ timestamp_counter ,
} ;
}
2017-05-22 08:26:40 +02:00
sub register_mocked_volid {
my ( $ volid , $ snapname ) = @ _ ;
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
2017-05-22 12:39:05 +02:00
my $ scfg = $ mocked_storage_config - > { ids } - > { $ storeid } ||
2017-05-22 08:26:40 +02:00
die "no such storage '$storeid'\n" ;
my $ d = $ mocked_storage_content - > { $ storeid } - > { $ volname } // = { } ;
2021-10-19 09:54:58 +02:00
$ d - > { $ snapname } = generate_snapshot_info ( ) if $ snapname ;
2017-05-22 08:26:40 +02:00
}
my $ mocked_volume_snapshot = sub {
my ( $ cfg , $ volid , $ snap ) = @ _ ;
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
my $ d = $ mocked_storage_content - > { $ storeid } - > { $ volname } ;
die "no such volid '$volid'\n" if ! $ d ;
2021-10-19 09:54:58 +02:00
$ d - > { $ snap } = generate_snapshot_info ( ) ;
2021-10-19 09:54:57 +02:00
return ;
2017-05-22 08:26:40 +02:00
} ;
my $ mocked_volume_snapshot_delete = sub {
my ( $ cfg , $ volid , $ snap , $ running ) = @ _ ;
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
my $ d = $ mocked_storage_content - > { $ storeid } - > { $ volname } ;
die "no such volid '$volid'\n" if ! $ d ;
delete $ d - > { $ snap } || die "no such snapshot '$snap' on '$volid'\n" ;
} ;
2021-10-19 09:54:58 +02:00
my $ mocked_volume_snapshot_info = sub {
my ( $ cfg , $ volid ) = @ _ ;
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
return $ mocked_storage_content - > { $ storeid } - > { $ volname } // { } ;
} ;
2017-06-07 14:24:09 +02:00
my $ pve_replication_module = Test::MockModule - > new ( 'PVE::Replication' ) ;
my $ mocked_job_logfile_name = sub {
my ( $ jobid ) = @ _ ;
return ".mocked_replication_log_$jobid" ;
} ;
my $ mocked_log_time = 0 ;
my $ mocked_get_log_time = sub {
return $ mocked_log_time ;
} ;
2018-05-14 13:57:26 +02:00
my $ locks = { } ;
my $ mocked_cfs_lock_file = sub {
my ( $ filename , $ timeout , $ code , @ param ) = @ _ ;
die "$filename already locked\n" if ( $ locks - > { $ filename } ) ;
$ locks - > { $ filename } = 1 ;
my $ res = $ code - > ( @ param ) ;
delete $ locks - > { $ filename } ;
return $ res ;
} ;
2019-11-11 11:28:38 +01:00
my $ mocked_cfs_read_file = sub {
my ( $ filename ) = @ _ ;
return { } if $ filename eq 'datacenter.cfg' ;
return PVE::Cluster:: cfs_read_file ( $ filename ) ;
} ;
2018-05-14 13:57:26 +02:00
my $ mocked_cfs_write_file = sub {
my ( $ filename , $ cfg ) = @ _ ;
die "wrong file - $filename\n" if $ filename ne 'replication.cfg' ;
$ cfg - > write_config ( ) ; # checks but no actual write to pmxcfs
} ;
2017-05-15 09:07:22 +02:00
sub setup {
2017-06-12 06:59:21 +02:00
$ pve_replication_state_module - > mock ( job_logfile_name = > $ mocked_job_logfile_name ) ;
2017-06-07 14:24:09 +02:00
$ pve_replication_module - > mock ( get_log_time = > $ mocked_get_log_time ) ;
2017-05-15 09:07:22 +02:00
$ pve_storage_module - > mock ( config = > sub { return $ mocked_storage_config ; } ) ;
2017-05-22 08:26:40 +02:00
$ pve_storage_module - > mock ( volume_snapshot = > $ mocked_volume_snapshot ) ;
$ pve_storage_module - > mock ( volume_snapshot_delete = > $ mocked_volume_snapshot_delete ) ;
2021-10-19 09:54:58 +02:00
$ pve_storage_module - > mock ( volume_snapshot_info = > $ mocked_volume_snapshot_info ) ;
2017-05-15 09:07:22 +02:00
2018-05-14 13:57:26 +02:00
$ pve_replication_config_module - > mock (
new = > $ mocked_replication_config_new ,
lock = > sub { $ mocked_cfs_lock_file - > ( 'replication.cfg' , undef , $ _ [ 0 ] ) ; } ,
write = > sub { $ mocked_cfs_write_file - > ( 'replication.cfg' , $ _ [ 0 ] ) ; } ,
) ;
2017-05-15 09:07:22 +02:00
$ pve_qemuserver_module - > mock ( check_running = > sub { return 0 ; } ) ;
$ pve_qemuconfig_module - > mock ( load_config = > $ mocked_qemu_load_conf ) ;
$ pve_lxc_config_module - > mock ( load_config = > $ mocked_lxc_load_conf ) ;
2019-11-11 11:28:35 +01:00
$ pve_sshinfo_module - > mock (
2017-05-22 12:39:05 +02:00
get_ssh_info = > $ mocked_get_ssh_info ,
ssh_info_to_command = > $ mocked_ssh_info_to_command ,
2019-11-11 11:28:35 +01:00
) ;
$ pve_cluster_module - > mock (
2017-06-27 15:47:24 +02:00
get_vmlist = > sub { return $ mocked_vmlist - > ( ) ; } ,
2017-10-18 15:30:59 +02:00
get_members = > $ mocked_get_members ,
2018-05-14 13:57:26 +02:00
cfs_update = > sub { } ,
cfs_lock_file = > $ mocked_cfs_lock_file ,
cfs_write_file = > $ mocked_cfs_write_file ,
2019-11-11 11:28:38 +01:00
cfs_read_file = > $ mocked_cfs_read_file ,
2018-05-14 13:57:26 +02:00
) ;
2017-05-15 09:07:22 +02:00
$ pve_inotify_module - > mock ( 'nodename' = > sub { return $ mocked_nodename ; } ) ;
} ;
2017-05-18 07:14:42 +02:00
# code to generate/conpare test logs
my $ logname ;
my $ logfh ;
sub openlog {
my ( $ filename ) = @ _ ;
if ( ! $ filename ) {
# compute from $0
$ filename = basename ( $ 0 ) ;
if ( $ filename =~ m/^(\S+)\.pl$/ ) {
$ filename = "$1.log" ;
} else {
die "unable to compute log name for $0" ;
}
}
die "log already open" if defined ( $ logname ) ;
open ( my $ fh , ">" , "$filename.tmp" ) ||
die "unable to open log - $!" ;
$ logname = $ filename ;
$ logfh = $ fh ;
}
sub commit_log {
close ( $ logfh ) ;
if ( - f $ logname ) {
my $ diff = `diff -u '$logname' '$logname.tmp'` ;
if ( $ diff ) {
2020-04-10 10:34:13 +02:00
warn "got unexpected output\n" ;
2017-05-18 07:14:42 +02:00
print "# diff -u '$logname' '$logname.tmp'\n" ;
print $ diff ;
exit ( - 1 ) ;
}
} else {
rename ( "$logname.tmp" , $ logname ) || die "rename log failed - $!" ;
}
}
my $ status ;
# helper to track job status
sub track_jobs {
my ( $ ctime ) = @ _ ;
2017-06-07 14:24:09 +02:00
$ mocked_log_time = $ ctime ;
2017-06-06 06:36:03 +02:00
my $ logmsg = sub {
my ( $ msg ) = @ _ ;
2017-06-07 14:24:09 +02:00
print "$msg\n" ;
print $ logfh "$msg\n" ;
2017-06-06 06:36:03 +02:00
} ;
2017-05-18 07:14:42 +02:00
if ( ! $ status ) {
2017-06-12 06:59:21 +02:00
$ status = PVE::ReplicationState:: job_status ( ) ;
2017-05-18 07:14:42 +02:00
foreach my $ jobid ( sort keys %$ status ) {
my $ jobcfg = $ status - > { $ jobid } ;
2017-06-07 14:24:09 +02:00
$ logmsg - > ( "$ctime $jobid: new job next_sync => $jobcfg->{next_sync}" ) ;
2017-05-18 07:14:42 +02:00
}
}
2017-06-20 08:57:40 +02:00
PVE::API2::Replication:: run_jobs ( $ ctime , $ logmsg , 1 ) ;
2017-05-18 07:14:42 +02:00
2017-06-12 06:59:21 +02:00
my $ new = PVE::ReplicationState:: job_status ( ) ;
2017-05-18 07:14:42 +02:00
# detect removed jobs
foreach my $ jobid ( sort keys %$ status ) {
if ( ! $ new - > { $ jobid } ) {
2017-06-07 14:24:09 +02:00
$ logmsg - > ( "$ctime $jobid: vanished job" ) ;
2017-05-18 07:14:42 +02:00
}
}
foreach my $ jobid ( sort keys %$ new ) {
my $ jobcfg = $ new - > { $ jobid } ;
my $ oldcfg = $ status - > { $ jobid } ;
if ( ! $ oldcfg ) {
2017-06-07 14:24:09 +02:00
$ logmsg - > ( "$ctime $jobid: new job next_sync => $jobcfg->{next_sync}" ) ;
2017-05-18 07:14:42 +02:00
next ; # no old state to compare
} else {
foreach my $ k ( qw( target guest vmtype next_sync ) ) {
my $ changes = '' ;
if ( $ oldcfg - > { $ k } ne $ jobcfg - > { $ k } ) {
$ changes . = ', ' if $ changes ;
$ changes . = "$k => $jobcfg->{$k}" ;
}
2017-06-07 14:24:09 +02:00
$ logmsg - > ( "$ctime $jobid: changed config $changes" ) if $ changes ;
2017-05-18 07:14:42 +02:00
}
}
my $ oldstate = $ oldcfg - > { state } ;
2017-06-06 09:54:53 +02:00
2017-05-18 07:14:42 +02:00
my $ state = $ jobcfg - > { state } ;
my $ changes = '' ;
2017-06-06 06:49:05 +02:00
foreach my $ k ( qw( last_node last_try last_sync fail_count error ) ) {
2017-05-18 07:14:42 +02:00
if ( ( $ oldstate - > { $ k } // '' ) ne ( $ state - > { $ k } // '' ) ) {
2017-05-23 10:31:18 +02:00
my $ value = $ state - > { $ k } // '' ;
2017-05-18 07:14:42 +02:00
chomp $ value ;
$ changes . = ', ' if $ changes ;
$ changes . = "$k => $value" ;
}
}
2017-06-07 14:24:09 +02:00
$ logmsg - > ( "$ctime $jobid: changed state $changes" ) if $ changes ;
2017-05-18 07:14:42 +02:00
2017-06-06 09:54:53 +02:00
my $ old_storeid_list = $ oldstate - > { storeid_list } ;
my $ storeid_list = $ state - > { storeid_list } ;
my $ storeid_list_changes = 0 ;
foreach my $ storeid ( @$ storeid_list ) {
next if grep { $ _ eq $ storeid } @$ old_storeid_list ;
$ storeid_list_changes = 1 ;
}
foreach my $ storeid ( @$ old_storeid_list ) {
next if grep { $ _ eq $ storeid } @$ storeid_list ;
$ storeid_list_changes = 1 ;
}
2017-06-07 14:24:09 +02:00
$ logmsg - > ( "$ctime $jobid: changed storeid list " . join ( ',' , @$ storeid_list ) )
2017-06-06 09:54:53 +02:00
if $ storeid_list_changes ;
2017-05-18 07:14:42 +02:00
}
$ status = $ new ;
}
2017-05-15 09:07:22 +02:00
1 ;