From 4518000bffe1a87b599cb8f631f659c77fb68226 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Fabian=20Gr=C3=BCnbichler?= Date: Wed, 2 Mar 2016 14:03:50 +0100 Subject: [PATCH] Rework snapshot code, has_feature Move snapshot_create, snapshot_delete and snapshot_rollback into abstract pve-common/src/PVE/AbstractConfig.pm, splitting LXC-specific parts into __snapshot_XX methods in src/PVE/LXC/Config.pm. check_freeze_needed, snapshot_prepare and snapshot_commit are downgraded to private __snapshot_XX methods (in PVE::AbstractConfig and PVE::LXC::Config). has_feature is made an implementation of the abstract has_feature, and thus moves into src/PVE/LXC/Config.pm --- src/PVE/API2/LXC.pm | 2 +- src/PVE/API2/LXC/Snapshot.pm | 6 +- src/PVE/LXC.pm | 370 ----------------------------------- src/PVE/LXC/Config.pm | 122 ++++++++++++ src/PVE/VZDump/LXC.pm | 8 +- src/test/snapshot-test.pm | 41 ++-- 6 files changed, 152 insertions(+), 397 deletions(-) diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm index 5e98792..428f2c1 100644 --- a/src/PVE/API2/LXC.pm +++ b/src/PVE/API2/LXC.pm @@ -882,7 +882,7 @@ __PACKAGE__->register_method({ my $storage_cfg = PVE::Storage::config(); #Maybe include later #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg); - my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname); + my $hasFeature = PVE::LXC::Config->has_feature($feature, $conf, $storage_cfg, $snapname); return { hasFeature => $hasFeature, diff --git a/src/PVE/API2/LXC/Snapshot.pm b/src/PVE/API2/LXC/Snapshot.pm index 90c187e..9537486 100644 --- a/src/PVE/API2/LXC/Snapshot.pm +++ b/src/PVE/API2/LXC/Snapshot.pm @@ -128,7 +128,7 @@ __PACKAGE__->register_method({ my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "snapshot container $vmid: $snapname"); - PVE::LXC::snapshot_create($vmid, $snapname, 0, $param->{description}); + PVE::LXC::Config->snapshot_create($vmid, $snapname, 0, $param->{description}); }; return $rpcenv->fork_worker('pctsnapshot', $vmid, $authuser, $realcmd); @@ -176,7 +176,7 @@ __PACKAGE__->register_method({ my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname"); - PVE::LXC::snapshot_delete($vmid, $snapname, $param->{force}); + PVE::LXC::Config->snapshot_delete($vmid, $snapname, $param->{force}); }; return $rpcenv->fork_worker('lxcdelsnapshot', $vmid, $authuser, $realcmd); @@ -254,7 +254,7 @@ __PACKAGE__->register_method({ my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "rollback snapshot LXC $vmid: $snapname"); - PVE::LXC::snapshot_rollback($vmid, $snapname); + PVE::LXC::Config->snapshot_rollback($vmid, $snapname); }; return $rpcenv->fork_worker('lxcrollback', $vmid, $authuser, $realcmd); diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index 8f90bad..0144861 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -1661,140 +1661,6 @@ sub update_ipconfig { } -# Internal snapshots - -# NOTE: Snapshot create/delete involves several non-atomic -# actions, and can take a long time. -# So we try to avoid locking the file and use the 'lock' variable -# inside the config file instead. - -my $snapshot_copy_config = sub { - my ($source, $dest) = @_; - - foreach my $k (keys %$source) { - next if $k eq 'snapshots'; - next if $k eq 'snapstate'; - next if $k eq 'snaptime'; - next if $k eq 'vmstate'; - next if $k eq 'lock'; - next if $k eq 'digest'; - next if $k eq 'description'; - next if $k =~ m/^unused\d+$/; - - $dest->{$k} = $source->{$k}; - } -}; - -my $snapshot_apply_config = sub { - my ($conf, $snap) = @_; - - # copy snapshot list - my $newconf = { - snapshots => $conf->{snapshots}, - }; - - # keep description and list of unused disks - foreach my $k (keys %$conf) { - next if !($k =~ m/^unused\d+$/ || $k eq 'description'); - $newconf->{$k} = $conf->{$k}; - } - - &$snapshot_copy_config($snap, $newconf); - - return $newconf; -}; - -sub snapshot_save_vmstate { - die "implement me - snapshot_save_vmstate\n"; -} - -sub snapshot_prepare { - my ($vmid, $snapname, $save_vmstate, $comment) = @_; - - my $snap; - - my $updatefn = sub { - - my $conf = PVE::LXC::Config->load_config($vmid); - - die "you can't take a snapshot if it's a template\n" - if PVE::LXC::Config->is_template($conf); - - PVE::LXC::Config->check_lock($conf); - - $conf->{lock} = 'snapshot'; - - die "snapshot name '$snapname' already used\n" - if defined($conf->{snapshots}->{$snapname}); - - my $storecfg = PVE::Storage::config(); - die "snapshot feature is not available\n" - if !has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump'); - - $snap = $conf->{snapshots}->{$snapname} = {}; - - if ($save_vmstate && check_running($vmid)) { - snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg); - } - - &$snapshot_copy_config($conf, $snap); - - $snap->{snapstate} = "prepare"; - $snap->{snaptime} = time(); - $snap->{description} = $comment if $comment; - - PVE::LXC::Config->write_config($vmid, $conf); - }; - - PVE::LXC::Config->lock_config($vmid, $updatefn); - - return $snap; -} - -sub snapshot_commit { - my ($vmid, $snapname) = @_; - - my $updatefn = sub { - - my $conf = PVE::LXC::Config->load_config($vmid); - - die "missing snapshot lock\n" - if !($conf->{lock} && $conf->{lock} eq 'snapshot'); - - my $snap = $conf->{snapshots}->{$snapname}; - die "snapshot '$snapname' does not exist\n" if !defined($snap); - - die "wrong snapshot state\n" - if !($snap->{snapstate} && $snap->{snapstate} eq "prepare"); - - delete $snap->{snapstate}; - delete $conf->{lock}; - - $conf->{parent} = $snapname; - - PVE::LXC::Config->write_config($vmid, $conf); - }; - - PVE::LXC::Config->lock_config($vmid, $updatefn); -} - -sub has_feature { - my ($feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_; - - my $err; - - foreach_mountpoint($conf, sub { - my ($ms, $mountpoint) = @_; - - return if $err; # skip further test - return if $backup_only && $ms ne 'rootfs' && !$mountpoint->{backup}; - - $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname, $running); - }); - - return $err ? 0 : 1; -} - my $enter_namespace = sub { my ($vmid, $pid, $which, $type) = @_; sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY @@ -1864,242 +1730,6 @@ sub sync_container_namespace { die "failed to sync container namespace\n" if $? != 0; } -sub check_freeze_needed { - my ($vmid, $config, $save_vmstate) = @_; - - my $ret = check_running($vmid); - return ($ret, $ret); -} - -sub snapshot_create { - my ($vmid, $snapname, $save_vmstate, $comment) = @_; - - my $snap = snapshot_prepare($vmid, $snapname, $save_vmstate, $comment); - - $save_vmstate = 0 if !$snap->{vmstate}; - - my $conf = PVE::LXC::Config->load_config($vmid); - - my ($running, $freezefs) = check_freeze_needed($vmid, $conf, $snap->{vmstate}); - - my $drivehash = {}; - - eval { - if ($freezefs) { - PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]); - sync_container_namespace($vmid); - } - - my $storecfg = PVE::Storage::config(); - foreach_mountpoint($conf, sub { - my ($ms, $mountpoint) = @_; - - return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup}; - PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname); - $drivehash->{$ms} = 1; - }); - }; - my $err = $@; - - if ($running) { - if ($freezefs) { - eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); }; - warn $@ if $@; - } - } - - if ($err) { - warn "snapshot create failed: starting cleanup\n"; - eval { snapshot_delete($vmid, $snapname, 1, $drivehash); }; - warn "$@" if $@; - die "$err\n"; - } - - snapshot_commit($vmid, $snapname); -} - -# Note: $drivehash is only set when called from snapshot_create. -sub snapshot_delete { - my ($vmid, $snapname, $force, $drivehash) = @_; - - my $prepare = 1; - - my $snap; - my $unused = []; - - my $unlink_parent = sub { - my ($confref, $new_parent) = @_; - - if ($confref->{parent} && $confref->{parent} eq $snapname) { - if ($new_parent) { - $confref->{parent} = $new_parent; - } else { - delete $confref->{parent}; - } - } - }; - - my $updatefn = sub { - my ($remove_drive) = @_; - - my $conf = PVE::LXC::Config->load_config($vmid); - - if (!$drivehash) { - PVE::LXC::Config->check_lock($conf); - die "you can't delete a snapshot if vm is a template\n" - if PVE::LXC::Config->is_template($conf); - } - - $snap = $conf->{snapshots}->{$snapname}; - - die "snapshot '$snapname' does not exist\n" if !defined($snap); - - # remove parent refs - if (!$prepare) { - &$unlink_parent($conf, $snap->{parent}); - foreach my $sn (keys %{$conf->{snapshots}}) { - next if $sn eq $snapname; - &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent}); - } - } - - if ($remove_drive) { - if ($remove_drive eq 'vmstate') { - die "implement me - saving vmstate\n"; - } else { - my $value = $snap->{$remove_drive}; - my $mountpoint = $remove_drive eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1); - delete $snap->{$remove_drive}; - add_unused_volume($snap, $mountpoint->{volume}); - } - } - - if ($prepare) { - $snap->{snapstate} = 'delete'; - } else { - delete $conf->{snapshots}->{$snapname}; - delete $conf->{lock} if $drivehash; - foreach my $volid (@$unused) { - add_unused_volume($conf, $volid); - } - } - - PVE::LXC::Config->write_config($vmid, $conf); - }; - - PVE::LXC::Config->lock_config($vmid, $updatefn); - - # now remove vmstate file - # never set for LXC! - my $storecfg = PVE::Storage::config(); - - if ($snap->{vmstate}) { - die "implement me - saving vmstate\n"; - }; - - # now remove all volume snapshots - foreach_mountpoint($snap, sub { - my ($ms, $mountpoint) = @_; - - return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup}; - if (!$drivehash || $drivehash->{$ms}) { - eval { PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname); }; - if (my $err = $@) { - die $err if !$force; - warn $err; - } - } - - # save changes (remove mp from snapshot) - PVE::LXC::Config->lock_config($vmid, $updatefn, $ms) if !$force; - push @$unused, $mountpoint->{volume}; - }); - - # now cleanup config - $prepare = 0; - PVE::LXC::Config->lock_config($vmid, $updatefn); -} - -sub snapshot_rollback { - my ($vmid, $snapname) = @_; - - my $prepare = 1; - - my $storecfg = PVE::Storage::config(); - - my $conf = PVE::LXC::Config->load_config($vmid); - - my $get_snapshot_config = sub { - - die "you can't rollback if vm is a template\n" if PVE::LXC::Config->is_template($conf); - - my $res = $conf->{snapshots}->{$snapname}; - - die "snapshot '$snapname' does not exist\n" if !defined($res); - - return $res; - }; - - my $snap = &$get_snapshot_config(); - - foreach_mountpoint($snap, sub { - my ($ms, $mountpoint) = @_; - - PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname); - }); - - my $updatefn = sub { - - $conf = PVE::LXC::Config->load_config($vmid); - - $snap = &$get_snapshot_config(); - - die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" - if $snap->{snapstate}; - - if ($prepare) { - PVE::LXC::Config->check_lock($conf); - PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill']) - if check_running($vmid); - } - - die "unable to rollback vm $vmid: vm is running\n" - if check_running($vmid); - - if ($prepare) { - $conf->{lock} = 'rollback'; - } else { - die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback'); - delete $conf->{lock}; - } - - my $forcemachine; - - if (!$prepare) { - # copy snapshot config to current config - $conf = &$snapshot_apply_config($conf, $snap); - $conf->{parent} = $snapname; - } - - PVE::LXC::Config->write_config($vmid, $conf); - - if (!$prepare && $snap->{vmstate}) { - die "implement me - save vmstate\n"; - } - }; - - PVE::LXC::Config->lock_config($vmid, $updatefn); - - foreach_mountpoint($snap, sub { - my ($ms, $mountpoint) = @_; - - PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname); - }); - - $prepare = 0; - PVE::LXC::Config->lock_config($vmid, $updatefn); -} - sub template_create { my ($vmid, $conf) = @_; diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm index f37586a..f041450 100644 --- a/src/PVE/LXC/Config.pm +++ b/src/PVE/LXC/Config.pm @@ -25,6 +25,12 @@ sub guest_type { return "CT"; } +sub __config_max_unused_disks { + my ($class) = @_; + + return $MAX_UNUSED_DISKS; +} + sub config_file_lock { my ($class, $vmid) = @_; @@ -38,6 +44,122 @@ sub cfs_config_path { return "nodes/$node/lxc/$vmid.conf"; } +sub has_feature { + my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_; + my $err; + + PVE::LXC::foreach_mountpoint($conf, sub { + my ($ms, $mountpoint) = @_; + + return if $err; # skip further test + return if $backup_only && $ms ne 'rootfs' && !$mountpoint->{backup}; + + $err = 1 + if !PVE::Storage::volume_has_feature($storecfg, $feature, + $mountpoint->{volume}, + $snapname, $running); + }); + + return $err ? 0 : 1; +} + +sub __snapshot_save_vmstate { + my ($class, $vmid, $conf, $snapname, $storecfg) = @_; + die "implement me - snapshot_save_vmstate\n"; +} + +sub __snapshot_check_running { + my ($class, $vmid) = @_; + return PVE::LXC::check_running($vmid); +} + +sub __snapshot_check_freeze_needed { + my ($class, $vmid, $config, $save_vmstate) = @_; + + my $ret = $class->__snapshot_check_running($vmid); + return ($ret, $ret); +} + +sub __snapshot_freeze { + my ($class, $vmid, $unfreeze) = @_; + + if ($unfreeze) { + eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); }; + warn $@ if $@; + } else { + PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]); + PVE::LXC::sync_container_namespace($vmid); + } +} + +sub __snapshot_create_vol_snapshot { + my ($class, $vmid, $ms, $mountpoint, $snapname) = @_; + + my $storecfg = PVE::Storage::config(); + + return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup}; + PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname); +} + +sub __snapshot_delete_remove_drive { + my ($class, $snap, $remove_drive) = @_; + + if ($remove_drive eq 'vmstate') { + die "implement me - saving vmstate\n"; + } else { + my $value = $snap->{$remove_drive}; + my $mountpoint = $remove_drive eq 'rootfs' ? PVE::LXC::parse_ct_rootfs($value, 1) : PVE::LXC::parse_ct_mountpoint($value, 1); + delete $snap->{$remove_drive}; + $class->add_unused_volume($snap, $mountpoint->{volume}); + } +} + +sub __snapshot_delete_vmstate_file { + my ($class, $snap, $force) = @_; + + die "implement me - saving vmstate\n"; +} + +sub __snapshot_delete_vol_snapshot { + my ($class, $vmid, $ms, $mountpoint, $snapname) = @_; + + my $storecfg = PVE::Storage::config(); + PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname); +} + +sub __snapshot_rollback_vol_possible { + my ($class, $mountpoint, $snapname) = @_; + + my $storecfg = PVE::Storage::config(); + PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname); +} + +sub __snapshot_rollback_vol_rollback { + my ($class, $mountpoint, $snapname) = @_; + + my $storecfg = PVE::Storage::config(); + PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname); +} + +sub __snapshot_rollback_vm_stop { + my ($class, $vmid) = @_; + + PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill']) + if $class->__snapshot_check_running($vmid); +} + +sub __snapshot_rollback_vm_start { + my ($class, $vmid, $vmstate, $forcemachine); + + die "implement me - save vmstate\n"; +} + +sub __snapshot_foreach_volume { + my ($class, $conf, $func) = @_; + + PVE::LXC::foreach_mountpoint($conf, $func); +} + # END implemented abstract methods from PVE::AbstractConfig return 1; diff --git a/src/PVE/VZDump/LXC.pm b/src/PVE/VZDump/LXC.pm index 2e431f9..a91fabd 100644 --- a/src/PVE/VZDump/LXC.pm +++ b/src/PVE/VZDump/LXC.pm @@ -130,7 +130,7 @@ sub prepare { }); if ($mode eq 'snapshot') { - if (!PVE::LXC::has_feature('snapshot', $conf, $storage_cfg, undef, undef, 1)) { + if (!PVE::LXC::Config->has_feature('snapshot', $conf, $storage_cfg, undef, undef, 1)) { die "mode failure - some volumes do not support snapshots\n"; } @@ -138,7 +138,7 @@ sub prepare { if ($conf->{snapshots} && $conf->{snapshots}->{vzdump}) { $self->loginfo("found old vzdump snapshot (force removal)"); - PVE::LXC::snapshot_delete($vmid, 'vzdump', 1); + PVE::LXC::Config->snapshot_delete($vmid, 'vzdump', 1); } my $rootdir = $default_mount_point; @@ -211,7 +211,7 @@ sub snapshot { $self->loginfo("create storage snapshot 'vzdump'"); # todo: freeze/unfreeze if we have more than one volid - PVE::LXC::snapshot_create($vmid, 'vzdump', 0, "vzdump backup snapshot"); + PVE::LXC::Config->snapshot_create($vmid, 'vzdump', 0, "vzdump backup snapshot"); $task->{cleanup}->{remove_snapshot} = 1; # reload config @@ -388,7 +388,7 @@ sub cleanup { if ($task->{cleanup}->{remove_snapshot}) { $self->loginfo("remove vzdump snapshot"); - PVE::LXC::snapshot_delete($vmid, 'vzdump', 0); + PVE::LXC::Config->snapshot_delete($vmid, 'vzdump', 0); } } diff --git a/src/test/snapshot-test.pm b/src/test/snapshot-test.pm index 60b63fb..95f3498 100644 --- a/src/test/snapshot-test.pm +++ b/src/test/snapshot-test.pm @@ -1,4 +1,4 @@ -package PVE::LXC; +package PVE::LXC::Test; use strict; use warnings; @@ -29,6 +29,15 @@ my $kill_possible; # Mocked methods +sub mocked_has_feature { + my ($feature, $conf, $storecfg, $snapname) = @_; + return $snapshot_possible; +} + +sub mocked_check_running { + return $running; +} + sub mocked_volume_snapshot { my ($storecfg, $volid, $snapname) = @_; die "Storage config not mocked! aborting\n" @@ -141,7 +150,7 @@ sub testcase_prepare { plan tests => 2; $@ = undef; eval { - PVE::LXC::snapshot_prepare($vmid, $snapname, $save_vmstate, $comment); + PVE::LXC::Config->__snapshot_prepare($vmid, $snapname, $save_vmstate, $comment); }; is($@, $exp_err, "\$@ correct"); ok(test_file("snapshot-expected/prepare/lxc/$vmid.conf", "snapshot-working/prepare/lxc/$vmid.conf"), "config file correct"); @@ -154,7 +163,7 @@ sub testcase_commit { plan tests => 2; $@ = undef; eval { - PVE::LXC::snapshot_commit($vmid, $snapname); + PVE::LXC::Config->__snapshot_commit($vmid, $snapname); }; is($@, $exp_err, "\$@ correct"); ok(test_file("snapshot-expected/commit/lxc/$vmid.conf", "snapshot-working/commit/lxc/$vmid.conf"), "config file correct"); @@ -171,7 +180,7 @@ sub testcase_create { $exp_vol_snap_delete = {} if !defined($exp_vol_snap_delete); $@ = undef; eval { - PVE::LXC::snapshot_create($vmid, $snapname, $save_vmstate, $comment); + PVE::LXC::Config->snapshot_create($vmid, $snapname, $save_vmstate, $comment); }; is($@, $exp_err, "\$@ correct"); is_deeply($vol_snapshot, $exp_vol_snap, "created correct volume snapshots"); @@ -188,7 +197,7 @@ sub testcase_delete { $exp_vol_snap_delete = {} if !defined($exp_vol_snap_delete); $@ = undef; eval { - PVE::LXC::snapshot_delete($vmid, $snapname, $force); + PVE::LXC::Config->snapshot_delete($vmid, $snapname, $force); }; is($@, $exp_err, "\$@ correct"); is_deeply($vol_snapshot_delete, $exp_vol_snap_delete, "deleted correct volume snapshots"); @@ -205,7 +214,7 @@ sub testcase_rollback { $exp_vol_snap_rollback = {} if !defined($exp_vol_snap_rollback); $@ = undef; eval { - PVE::LXC::snapshot_rollback($vmid, $snapname); + PVE::LXC::Config->snapshot_rollback($vmid, $snapname); }; is($@, $exp_err, "\$@ correct"); is_deeply($vol_snapshot_rollback, $exp_vol_snap_rollback, "rolled back to correct volume snapshots"); @@ -253,30 +262,24 @@ sub mocked_write_config { PVE::Tools::file_set_contents($filename, $raw); } -sub has_feature { - my ($feature, $conf, $storecfg, $snapname) = @_; - return $snapshot_possible; -} - -sub check_running { - return $running; -} # END mocked PVE::LXC methods -sub sync_container_namespace { - return; -} - -# END redefine PVE::LXC methods PVE::Tools::run_command("rm -rf snapshot-working"); PVE::Tools::run_command("cp -a snapshot-input snapshot-working"); +printf("\n"); +printf("Setting up Mocking for PVE::LXC and PVE::LXC::Config\n"); +my $lxc_module = new Test::MockModule('PVE::LXC'); +$lxc_module->mock('sync_container_namespace', sub { return; }); +$lxc_module->mock('check_running', \&mocked_check_running); + my $lxc_config_module = new Test::MockModule('PVE::LXC::Config'); $lxc_config_module->mock('config_file_lock', sub { return "snapshot-working/pve-test.lock"; }); $lxc_config_module->mock('cfs_config_path', \&mocked_cfs_config_path); $lxc_config_module->mock('load_config', \&mocked_load_config); $lxc_config_module->mock('write_config', \&mocked_write_config); +$lxc_config_module->mock('has_feature', \&mocked_has_feature); $running = 1; $freeze_possible = 1; -- 2.39.2