2016-03-07 12:41:12 +01:00
package PVE::QemuConfig ;
use strict ;
use warnings ;
2016-03-07 12:41:13 +01:00
use PVE::AbstractConfig ;
use PVE::INotify ;
use PVE::QemuServer ;
use PVE::Storage ;
use PVE::Tools ;
2016-03-07 12:41:12 +01:00
use base qw( PVE::AbstractConfig ) ;
my $ nodename = PVE::INotify:: nodename ( ) ;
mkdir "/etc/pve/nodes/$nodename" ;
my $ confdir = "/etc/pve/nodes/$nodename/qemu-server" ;
mkdir $ confdir ;
my $ lock_dir = "/var/lock/qemu-server" ;
mkdir $ lock_dir ;
2018-11-05 14:02:15 +01:00
my $ MAX_UNUSED_DISKS = 256 ;
2016-03-07 12:41:12 +01:00
# BEGIN implemented abstract methods from PVE::AbstractConfig
sub guest_type {
return "VM" ;
}
sub __config_max_unused_disks {
2017-05-06 17:13:31 +02:00
my ( $ class ) = @ _ ;
2016-03-07 12:41:12 +01:00
return $ MAX_UNUSED_DISKS ;
}
sub config_file_lock {
my ( $ class , $ vmid ) = @ _ ;
return "$lock_dir/lock-$vmid.conf" ;
}
sub cfs_config_path {
my ( $ class , $ vmid , $ node ) = @ _ ;
$ node = $ nodename if ! $ node ;
return "nodes/$node/qemu-server/$vmid.conf" ;
}
2016-03-07 12:41:13 +01:00
sub has_feature {
my ( $ class , $ feature , $ conf , $ storecfg , $ snapname , $ running , $ backup_only ) = @ _ ;
my $ err ;
PVE::QemuServer:: foreach_drive ( $ conf , sub {
my ( $ ds , $ drive ) = @ _ ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
2017-06-26 16:05:30 +02:00
return if $ backup_only && defined ( $ drive - > { backup } ) && ! $ drive - > { backup } ;
2016-03-07 12:41:13 +01:00
my $ volid = $ drive - > { file } ;
$ err = 1 if ! PVE::Storage:: volume_has_feature ( $ storecfg , $ feature , $ volid , $ snapname , $ running ) ;
} ) ;
return $ err ? 0 : 1 ;
}
2017-05-06 17:13:31 +02:00
sub get_replicatable_volumes {
2017-06-13 09:01:27 +02:00
my ( $ class , $ storecfg , $ vmid , $ conf , $ cleanup , $ noerr ) = @ _ ;
2017-05-06 17:13:31 +02:00
my $ volhash = { } ;
my $ test_volid = sub {
2017-06-13 06:58:58 +02:00
my ( $ volid , $ attr ) = @ _ ;
2017-05-06 17:13:31 +02:00
2017-06-14 07:23:01 +02:00
return if $ attr - > { cdrom } ;
return if ! $ cleanup && ! $ attr - > { replicate } ;
2017-06-14 07:01:36 +02:00
if ( $ volid =~ m | ^ / | ) {
return if ! $ attr - > { replicate } ;
2017-06-14 07:23:01 +02:00
return if $ cleanup || $ noerr ;
2017-06-14 07:01:36 +02:00
die "unable to replicate local file/device '$volid'\n" ;
}
2017-06-13 06:30:57 +02:00
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid , $ noerr ) ;
return if ! $ storeid ;
2017-06-13 16:11:20 +02:00
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ storeid ) ;
2017-06-13 06:30:57 +02:00
return if $ scfg - > { shared } ;
2017-06-13 09:55:36 +02:00
my ( $ path , $ owner , $ vtype ) = PVE::Storage:: path ( $ storecfg , $ volid ) ;
return if ! $ owner || ( $ owner != $ vmid ) ;
2017-06-14 07:23:01 +02:00
if ( $ vtype ne 'images' ) {
return if $ cleanup || $ noerr ;
die "unable to replicate volume '$volid', type '$vtype'\n" ;
}
2017-05-06 17:13:31 +02:00
if ( ! PVE::Storage:: volume_has_feature ( $ storecfg , 'replicate' , $ volid ) ) {
2017-06-12 09:09:53 +02:00
return if $ cleanup || $ noerr ;
2017-05-06 17:13:31 +02:00
die "missing replicate feature on volume '$volid'\n" ;
}
$ volhash - > { $ volid } = 1 ;
} ;
2017-06-13 06:58:58 +02:00
PVE::QemuServer:: foreach_volid ( $ conf , $ test_volid ) ;
2017-05-06 17:13:31 +02:00
2017-06-13 11:51:14 +02:00
# add 'unusedX' volumes to volhash
foreach my $ key ( keys %$ conf ) {
if ( $ key =~ m/^unused/ ) {
$ test_volid - > ( $ conf - > { $ key } , { replicate = > 1 } ) ;
}
}
2017-05-06 17:13:31 +02:00
return $ volhash ;
}
2016-03-07 12:41:13 +01:00
sub __snapshot_save_vmstate {
2019-03-14 17:04:50 +01:00
my ( $ class , $ vmid , $ conf , $ snapname , $ storecfg , $ statestorage , $ suspend ) = @ _ ;
2016-03-07 12:41:13 +01:00
2017-05-15 14:11:57 +02:00
# first, use explicitly configured storage
2019-03-14 17:04:50 +01:00
# either directly via API, or via conf
my $ target = $ statestorage // $ conf - > { vmstatestorage } ;
2016-03-07 12:41:13 +01:00
if ( ! $ target ) {
2017-05-15 14:11:57 +02:00
my ( $ shared , $ local ) ;
2017-05-15 14:11:58 +02:00
PVE::QemuServer:: foreach_storage_used_by_vm ( $ conf , sub {
2016-03-07 12:41:13 +01:00
my ( $ sid ) = @ _ ;
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ sid ) ;
2017-05-15 14:11:57 +02:00
my $ dst = $ scfg - > { shared } ? \ $ shared : \ $ local ;
$$ dst = $ sid if ! $$ dst || $ scfg - > { path } ; # prefer file based storage
2016-03-07 12:41:13 +01:00
} ) ;
2017-05-15 14:11:57 +02:00
# second, use shared storage where VM has at least one disk
# third, use local storage where VM has at least one disk
# fall back to local storage
$ target = $ shared // $ local // 'local' ;
}
2016-03-07 12:41:13 +01:00
2019-07-24 15:56:43 +02:00
my $ defaults = PVE::QemuServer:: load_defaults ( ) ;
my $ mem_size = $ conf - > { memory } // $ defaults - > { memory } ;
2019-03-14 17:04:46 +01:00
my $ driver_state_size = 500 ; # assume 500MB is enough to safe all driver state;
2019-03-19 09:17:29 +01:00
# our savevm-start does live-save of the memory until the space left in the
# volume is just enough for the remaining memory content + internal state
# then it stops the vm and copies the rest so we reserve twice the
# memory content + state to minimize vm downtime
2019-07-24 15:56:43 +02:00
my $ size = $ mem_size * 2 + $ driver_state_size ;
2019-03-14 17:04:47 +01:00
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ target ) ;
2016-03-07 12:41:13 +01:00
my $ name = "vm-$vmid-state-$snapname" ;
$ name . = ".raw" if $ scfg - > { path } ; # add filename extension for file base storage
2019-03-14 17:04:47 +01:00
my $ statefile = PVE::Storage:: vdisk_alloc ( $ storecfg , $ target , $ vmid , 'raw' , $ name , $ size * 1024 ) ;
my $ runningmachine = PVE::QemuServer:: get_current_qemu_machine ( $ vmid ) ;
if ( $ suspend ) {
$ conf - > { vmstate } = $ statefile ;
$ conf - > { runningmachine } = $ runningmachine ;
} else {
my $ snap = $ conf - > { snapshots } - > { $ snapname } ;
$ snap - > { vmstate } = $ statefile ;
$ snap - > { runningmachine } = $ runningmachine ;
}
return $ statefile ;
2016-03-07 12:41:13 +01:00
}
sub __snapshot_check_running {
my ( $ class , $ vmid ) = @ _ ;
return PVE::QemuServer:: check_running ( $ vmid ) ;
}
sub __snapshot_check_freeze_needed {
my ( $ class , $ vmid , $ config , $ save_vmstate ) = @ _ ;
my $ running = $ class - > __snapshot_check_running ( $ vmid ) ;
2017-07-31 08:13:27 +02:00
if ( ! $ save_vmstate ) {
2018-08-01 20:29:04 +02:00
return ( $ running , $ running && PVE::QemuServer:: parse_guest_agent ( $ config ) - > { enabled } && PVE::QemuServer:: qga_check_running ( $ vmid ) ) ;
2016-03-07 12:41:13 +01:00
} else {
return ( $ running , 0 ) ;
}
}
sub __snapshot_freeze {
my ( $ class , $ vmid , $ unfreeze ) = @ _ ;
if ( $ unfreeze ) {
eval { PVE::QemuServer:: vm_mon_cmd ( $ vmid , "guest-fsfreeze-thaw" ) ; } ;
warn "guest-fsfreeze-thaw problems - $@" if $@ ;
} else {
eval { PVE::QemuServer:: vm_mon_cmd ( $ vmid , "guest-fsfreeze-freeze" ) ; } ;
warn "guest-fsfreeze-freeze problems - $@" if $@ ;
}
}
sub __snapshot_create_vol_snapshots_hook {
my ( $ class , $ vmid , $ snap , $ running , $ hook ) = @ _ ;
if ( $ running ) {
2016-11-14 13:23:02 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2016-03-07 12:41:13 +01:00
if ( $ hook eq "before" ) {
if ( $ snap - > { vmstate } ) {
my $ path = PVE::Storage:: path ( $ storecfg , $ snap - > { vmstate } ) ;
2016-11-14 13:23:02 +01:00
PVE::Storage:: activate_volumes ( $ storecfg , [ $ snap - > { vmstate } ] ) ;
2016-03-07 12:41:13 +01:00
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "savevm-start" , statefile = > $ path ) ;
for ( ; ; ) {
my $ stat = PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "query-savevm" ) ;
if ( ! $ stat - > { status } ) {
die "savevm not active\n" ;
} elsif ( $ stat - > { status } eq 'active' ) {
sleep ( 1 ) ;
next ;
} elsif ( $ stat - > { status } eq 'completed' ) {
last ;
} else {
die "query-savevm returned status '$stat->{status}'\n" ;
}
}
} else {
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "savevm-start" ) ;
}
} elsif ( $ hook eq "after" ) {
2016-11-14 13:23:02 +01:00
eval {
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "savevm-end" ) ;
PVE::Storage:: deactivate_volumes ( $ storecfg , [ $ snap - > { vmstate } ] ) if $ snap - > { vmstate } ;
} ;
2016-03-07 12:41:13 +01:00
warn $@ if $@ ;
} elsif ( $ hook eq "after-freeze" ) {
# savevm-end is async, we need to wait
for ( ; ; ) {
my $ stat = PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "query-savevm" ) ;
if ( ! $ stat - > { bytes } ) {
last ;
} else {
print "savevm not yet finished\n" ;
sleep ( 1 ) ;
next ;
}
}
}
}
}
sub __snapshot_create_vol_snapshot {
my ( $ class , $ vmid , $ ds , $ drive , $ snapname ) = @ _ ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my $ volid = $ drive - > { file } ;
my $ device = "drive-$ds" ;
my $ storecfg = PVE::Storage:: config ( ) ;
PVE::QemuServer:: qemu_volume_snapshot ( $ vmid , $ device , $ storecfg , $ volid , $ snapname ) ;
}
sub __snapshot_delete_remove_drive {
my ( $ class , $ snap , $ remove_drive ) = @ _ ;
if ( $ remove_drive eq 'vmstate' ) {
delete $ snap - > { $ remove_drive } ;
} else {
my $ drive = PVE::QemuServer:: parse_drive ( $ remove_drive , $ snap - > { $ remove_drive } ) ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my $ volid = $ drive - > { file } ;
delete $ snap - > { $ remove_drive } ;
$ class - > add_unused_volume ( $ snap , $ volid ) ;
}
}
sub __snapshot_delete_vmstate_file {
my ( $ class , $ snap , $ force ) = @ _ ;
my $ storecfg = PVE::Storage:: config ( ) ;
eval { PVE::Storage:: vdisk_free ( $ storecfg , $ snap - > { vmstate } ) ; } ;
if ( my $ err = $@ ) {
die $ err if ! $ force ;
warn $ err ;
}
}
sub __snapshot_delete_vol_snapshot {
my ( $ class , $ vmid , $ ds , $ drive , $ snapname , $ unused ) = @ _ ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my $ storecfg = PVE::Storage:: config ( ) ;
my $ volid = $ drive - > { file } ;
my $ device = "drive-$ds" ;
PVE::QemuServer:: qemu_volume_snapshot_delete ( $ vmid , $ device , $ storecfg , $ volid , $ snapname ) ;
push @$ unused , $ volid ;
}
2018-09-14 14:08:42 +02:00
sub __snapshot_rollback_hook {
my ( $ class , $ vmid , $ conf , $ snap , $ prepare , $ data ) = @ _ ;
if ( $ prepare ) {
# we save the machine of the current config
$ data - > { oldmachine } = $ conf - > { machine } ;
} else {
2018-09-17 15:49:14 +02:00
# if we have a 'runningmachine' entry in the snapshot we use that
# for the forcemachine parameter, else we use the old logic
2018-09-14 14:08:43 +02:00
if ( defined ( $ conf - > { runningmachine } ) ) {
$ data - > { forcemachine } = $ conf - > { runningmachine } ;
delete $ conf - > { runningmachine } ;
} else {
# Note: old code did not store 'machine', so we try to be smart
# and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
$ data - > { forcemachine } = $ conf - > { machine } || 'pc-i440fx-1.4' ;
# we remove the 'machine' configuration if not explicitly specified
# in the original config.
delete $ conf - > { machine } if $ snap - > { vmstate } && ! defined ( $ data - > { oldmachine } ) ;
}
2018-09-19 11:35:11 +02:00
if ( $ conf - > { vmgenid } ) {
2018-09-19 13:34:48 +02:00
# tell the VM that it's another generation, so it can react
# appropriately, e.g. dirty-mark copies of distributed databases or
# re-initializing its random number generator
2018-09-19 11:35:11 +02:00
$ conf - > { vmgenid } = PVE::QemuServer:: generate_uuid ( ) ;
}
2018-09-14 14:08:42 +02:00
}
return ;
}
2016-03-07 12:41:13 +01:00
sub __snapshot_rollback_vol_possible {
my ( $ class , $ drive , $ snapname ) = @ _ ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my $ storecfg = PVE::Storage:: config ( ) ;
my $ volid = $ drive - > { file } ;
PVE::Storage:: volume_rollback_is_possible ( $ storecfg , $ volid , $ snapname ) ;
}
sub __snapshot_rollback_vol_rollback {
my ( $ class , $ drive , $ snapname ) = @ _ ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my $ storecfg = PVE::Storage:: config ( ) ;
PVE::Storage:: volume_snapshot_rollback ( $ storecfg , $ drive - > { file } , $ snapname ) ;
}
sub __snapshot_rollback_vm_stop {
my ( $ class , $ vmid ) = @ _ ;
my $ storecfg = PVE::Storage:: config ( ) ;
PVE::QemuServer:: vm_stop ( $ storecfg , $ vmid , undef , undef , 5 , undef , undef ) ;
}
sub __snapshot_rollback_vm_start {
2018-09-14 14:08:42 +02:00
my ( $ class , $ vmid , $ vmstate , $ data ) = @ _ ;
2016-03-07 12:41:13 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
my $ statefile = PVE::Storage:: path ( $ storecfg , $ vmstate ) ;
2018-09-14 14:08:42 +02:00
PVE::QemuServer:: vm_start ( $ storecfg , $ vmid , $ statefile , undef , undef , undef , $ data - > { forcemachine } ) ;
2016-03-07 12:41:13 +01:00
}
2016-07-04 13:04:14 +02:00
sub __snapshot_rollback_get_unused {
my ( $ class , $ conf , $ snap ) = @ _ ;
my $ unused = [] ;
$ class - > __snapshot_foreach_volume ( $ conf , sub {
my ( $ vs , $ volume ) = @ _ ;
return if PVE::QemuServer:: drive_is_cdrom ( $ volume ) ;
my $ found = 0 ;
my $ volid = $ volume - > { file } ;
$ class - > __snapshot_foreach_volume ( $ snap , sub {
my ( $ ds , $ drive ) = @ _ ;
return if $ found ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
$ found = 1
if ( $ drive - > { file } && $ drive - > { file } eq $ volid ) ;
} ) ;
push @$ unused , $ volid if ! $ found ;
} ) ;
return $ unused ;
}
2016-03-07 12:41:13 +01:00
sub __snapshot_foreach_volume {
my ( $ class , $ conf , $ func ) = @ _ ;
PVE::QemuServer:: foreach_drive ( $ conf , $ func ) ;
}
2016-03-07 12:41:12 +01:00
# END implemented abstract methods from PVE::AbstractConfig
1 ;