2011-08-23 07:47:04 +02:00
package PVE::API2::Qemu ;
use strict ;
use warnings ;
2011-10-20 11:18:46 +02:00
use Cwd 'abs_path' ;
2013-07-18 07:28:35 +02:00
use Net::SSLeay ;
2014-06-26 11:51:52 +02:00
use UUID ;
2017-02-07 12:18:34 +01:00
use POSIX ;
use IO::Socket::IP ;
2011-08-23 07:47:04 +02:00
2012-02-03 13:44:12 +01:00
use PVE::Cluster qw (cfs_read_file cfs_write_file ) ; ;
2011-08-23 07:47:04 +02:00
use PVE::SafeSyslog ;
use PVE::Tools qw( extract_param ) ;
2013-05-03 07:47:08 +02:00
use PVE::Exception qw( raise raise_param_exc raise_perm_exc ) ;
2011-08-23 07:47:04 +02:00
use PVE::Storage ;
use PVE::JSONSchema qw( get_standard_option ) ;
use PVE::RESTHandler ;
2017-05-23 18:01:13 +02:00
use PVE::ReplicationConfig ;
2017-05-24 09:20:06 +02:00
use PVE::GuestHelpers ;
2016-03-07 12:41:12 +01:00
use PVE::QemuConfig ;
2011-08-23 07:47:04 +02:00
use PVE::QemuServer ;
2011-09-14 12:02:08 +02:00
use PVE::QemuMigrate ;
2011-08-23 07:47:04 +02:00
use PVE::RPCEnvironment ;
use PVE::AccessControl ;
use PVE::INotify ;
2013-03-05 10:22:18 +01:00
use PVE::Network ;
2015-08-19 10:33:05 +02:00
use PVE::Firewall ;
2014-05-06 11:27:10 +02:00
use PVE::API2::Firewall::VM ;
2017-02-06 16:13:30 +01:00
BEGIN {
if ( ! $ ENV { PVE_GENERATING_DOCS } ) {
require PVE::HA::Env::PVE2 ;
import PVE::HA::Env:: PVE2 ;
require PVE::HA::Config ;
import PVE::HA:: Config ;
}
}
2011-08-23 07:47:04 +02:00
use Data::Dumper ; # fixme: remove
use base qw( PVE::RESTHandler ) ;
my $ opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal." ;
my $ resolve_cdrom_alias = sub {
my $ param = shift ;
if ( my $ value = $ param - > { cdrom } ) {
$ value . = ",media=cdrom" if $ value !~ m/media=/ ;
$ param - > { ide2 } = $ value ;
delete $ param - > { cdrom } ;
}
} ;
2017-06-30 09:31:29 +02:00
my $ NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$! ;
2012-02-03 10:23:50 +01:00
my $ check_storage_access = sub {
2012-02-06 12:19:35 +01:00
my ( $ rpcenv , $ authuser , $ storecfg , $ vmid , $ settings , $ default_storage ) = @ _ ;
2012-02-02 06:39:38 +01:00
2012-02-03 10:23:50 +01:00
PVE::QemuServer:: foreach_drive ( $ settings , sub {
my ( $ ds , $ drive ) = @ _ ;
2012-02-02 06:39:38 +01:00
2012-02-03 10:23:50 +01:00
my $ isCDROM = PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
2012-02-02 06:39:38 +01:00
2012-02-03 10:23:50 +01:00
my $ volid = $ drive - > { file } ;
2012-02-02 06:39:38 +01:00
2012-02-03 10:49:37 +01:00
if ( ! $ volid || $ volid eq 'none' ) {
# nothing to check
2012-02-20 07:17:10 +01:00
} elsif ( $ isCDROM && ( $ volid eq 'cdrom' ) ) {
$ rpcenv - > check ( $ authuser , "/" , [ 'Sys.Console' ] ) ;
2017-06-30 09:31:29 +02:00
} elsif ( ! $ isCDROM && ( $ volid =~ $ NEW_DISK_RE ) ) {
2012-02-02 06:39:38 +01:00
my ( $ storeid , $ size ) = ( $ 2 || $ default_storage , $ 3 ) ;
die "no storage ID specified (and no default storage)\n" if ! $ storeid ;
2012-02-06 12:19:35 +01:00
$ rpcenv - > check ( $ authuser , "/storage/$storeid" , [ 'Datastore.AllocateSpace' ] ) ;
2017-06-19 13:54:49 +02:00
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ storeid ) ;
raise_param_exc ( { storage = > "storage '$storeid' does not support vm images" } )
if ! $ scfg - > { content } - > { images } ;
2012-02-02 06:39:38 +01:00
} else {
2017-01-18 17:22:34 +01:00
PVE::Storage:: check_volume_access ( $ rpcenv , $ authuser , $ storecfg , $ vmid , $ volid ) ;
2012-02-02 06:39:38 +01:00
}
} ) ;
2017-05-15 14:11:56 +02:00
$ rpcenv - > check ( $ authuser , "/storage/$settings->{vmstatestorage}" , [ 'Datastore.AllocateSpace' ] )
if defined ( $ settings - > { vmstatestorage } ) ;
2012-02-03 10:23:50 +01:00
} ;
2012-02-02 06:39:38 +01:00
2013-05-02 11:42:22 +02:00
my $ check_storage_access_clone = sub {
2013-04-30 07:08:27 +02:00
my ( $ rpcenv , $ authuser , $ storecfg , $ conf , $ storage ) = @ _ ;
2013-04-29 09:30:15 +02:00
2013-04-30 11:44:39 +02:00
my $ sharedvm = 1 ;
2013-04-29 09:30:15 +02:00
PVE::QemuServer:: foreach_drive ( $ conf , sub {
my ( $ ds , $ drive ) = @ _ ;
my $ isCDROM = PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my $ volid = $ drive - > { file } ;
return if ! $ volid || $ volid eq 'none' ;
if ( $ isCDROM ) {
if ( $ volid eq 'cdrom' ) {
$ rpcenv - > check ( $ authuser , "/" , [ 'Sys.Console' ] ) ;
} else {
2013-04-30 11:46:38 +02:00
# we simply allow access
2013-04-30 11:44:39 +02:00
my ( $ sid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ sid ) ;
$ sharedvm = 0 if ! $ scfg - > { shared } ;
2013-04-29 09:30:15 +02:00
}
} else {
2013-04-30 11:44:39 +02:00
my ( $ sid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ sid ) ;
$ sharedvm = 0 if ! $ scfg - > { shared } ;
2013-04-30 07:08:27 +02:00
$ sid = $ storage if $ storage ;
2013-04-29 09:30:15 +02:00
$ rpcenv - > check ( $ authuser , "/storage/$sid" , [ 'Datastore.AllocateSpace' ] ) ;
}
} ) ;
2013-04-30 11:44:39 +02:00
2017-05-15 14:11:56 +02:00
$ rpcenv - > check ( $ authuser , "/storage/$conf->{vmstatestorage}" , [ 'Datastore.AllocateSpace' ] )
if defined ( $ conf - > { vmstatestorage } ) ;
2013-04-30 11:44:39 +02:00
return $ sharedvm ;
2013-04-29 09:30:15 +02:00
} ;
2012-02-03 10:23:50 +01:00
# Note: $pool is only needed when creating a VM, because pool permissions
# are automatically inherited if VM already exists inside a pool.
my $ create_disks = sub {
my ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , $ pool , $ settings , $ default_storage ) = @ _ ;
2012-02-02 06:39:38 +01:00
my $ vollist = [] ;
2012-02-03 10:23:50 +01:00
my $ res = { } ;
2017-05-06 17:00:20 +02:00
my $ code = sub {
2012-02-03 10:23:50 +01:00
my ( $ ds , $ disk ) = @ _ ;
my $ volid = $ disk - > { file } ;
2012-02-20 07:17:10 +01:00
if ( ! $ volid || $ volid eq 'none' || $ volid eq 'cdrom' ) {
2012-12-31 10:55:52 +01:00
delete $ disk - > { size } ;
$ res - > { $ ds } = PVE::QemuServer:: print_drive ( $ vmid , $ disk ) ;
2017-06-27 09:42:55 +02:00
} elsif ( $ volid =~ $ NEW_DISK_RE ) {
2012-02-03 10:23:50 +01:00
my ( $ storeid , $ size ) = ( $ 2 || $ default_storage , $ 3 ) ;
die "no storage ID specified (and no default storage)\n" if ! $ storeid ;
my $ defformat = PVE::Storage:: storage_default_format ( $ storecfg , $ storeid ) ;
my $ fmt = $ disk - > { format } || $ defformat ;
2016-09-08 11:03:00 +02:00
2017-09-11 08:40:27 +02:00
$ size = PVE::Tools:: convert_size ( $ size , 'gb' = > 'kb' ) ; # vdisk_alloc uses kb
2016-09-08 11:03:00 +02:00
my $ volid ;
if ( $ ds eq 'efidisk0' ) {
2017-09-11 08:40:28 +02:00
( $ volid , $ size ) = PVE::QemuServer:: create_efidisk ( $ storecfg , $ storeid , $ vmid , $ fmt ) ;
2016-09-08 11:03:00 +02:00
} else {
2017-09-11 08:40:27 +02:00
$ volid = PVE::Storage:: vdisk_alloc ( $ storecfg , $ storeid , $ vmid , $ fmt , undef , $ size ) ;
2016-09-08 11:03:00 +02:00
}
2012-02-02 06:39:38 +01:00
push @$ vollist , $ volid ;
2017-09-11 08:40:27 +02:00
$ disk - > { file } = $ volid ;
$ disk - > { size } = PVE::Tools:: convert_size ( $ size , 'kb' = > 'b' ) ;
2012-02-03 10:23:50 +01:00
delete $ disk - > { format } ; # no longer needed
$ res - > { $ ds } = PVE::QemuServer:: print_drive ( $ vmid , $ disk ) ;
} else {
2012-07-27 11:59:42 +02:00
2017-01-18 17:22:34 +01:00
PVE::Storage:: check_volume_access ( $ rpcenv , $ authuser , $ storecfg , $ vmid , $ volid ) ;
2013-04-30 11:46:38 +02:00
2013-06-11 07:26:43 +02:00
my $ volid_is_new = 1 ;
2012-06-23 09:07:32 +02:00
2013-06-11 07:26:43 +02:00
if ( $ conf - > { $ ds } ) {
my $ olddrive = PVE::QemuServer:: parse_drive ( $ ds , $ conf - > { $ ds } ) ;
$ volid_is_new = undef if $ olddrive - > { file } && $ olddrive - > { file } eq $ volid ;
2012-07-27 11:59:42 +02:00
}
2013-04-30 11:46:38 +02:00
2013-06-11 07:22:13 +02:00
if ( $ volid_is_new ) {
2013-06-10 09:27:04 +02:00
2013-06-11 07:26:43 +02:00
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid , 1 ) ;
2013-06-10 09:27:04 +02:00
PVE::Storage:: activate_volumes ( $ storecfg , [ $ volid ] ) if $ storeid ;
2013-06-11 07:22:13 +02:00
my $ size = PVE::Storage:: volume_size_info ( $ storecfg , $ volid ) ;
die "volume $volid does not exists\n" if ! $ size ;
$ disk - > { size } = $ size ;
2013-06-10 09:27:04 +02:00
}
2012-08-01 13:22:43 +02:00
$ res - > { $ ds } = PVE::QemuServer:: print_drive ( $ vmid , $ disk ) ;
2012-02-02 06:39:38 +01:00
}
2017-05-06 17:00:20 +02:00
} ;
eval { PVE::QemuServer:: foreach_drive ( $ settings , $ code ) ; } ;
2012-02-02 06:39:38 +01:00
# free allocated images on error
if ( my $ err = $@ ) {
syslog ( 'err' , "VM $vmid creating disks failed" ) ;
foreach my $ volid ( @$ vollist ) {
eval { PVE::Storage:: vdisk_free ( $ storecfg , $ volid ) ; } ;
warn $@ if $@ ;
}
die $ err ;
}
# modify vm config if everything went well
2012-02-03 10:23:50 +01:00
foreach my $ ds ( keys %$ res ) {
$ conf - > { $ ds } = $ res - > { $ ds } ;
2012-02-02 06:39:38 +01:00
}
return $ vollist ;
} ;
2016-06-01 11:12:42 +02:00
my $ cpuoptions = {
'cores' = > 1 ,
'cpu' = > 1 ,
'cpulimit' = > 1 ,
'cpuunits' = > 1 ,
'numa' = > 1 ,
'smp' = > 1 ,
'sockets' = > 1 ,
2016-07-11 14:44:24 +02:00
'vcpus' = > 1 ,
2016-06-01 11:12:42 +02:00
} ;
my $ memoryoptions = {
'memory' = > 1 ,
'balloon' = > 1 ,
'shares' = > 1 ,
} ;
my $ hwtypeoptions = {
'acpi' = > 1 ,
'hotplug' = > 1 ,
'kvm' = > 1 ,
'machine' = > 1 ,
'scsihw' = > 1 ,
'smbios1' = > 1 ,
'tablet' = > 1 ,
'vga' = > 1 ,
'watchdog' = > 1 ,
} ;
2016-06-01 12:37:20 +02:00
my $ generaloptions = {
2016-06-01 11:12:42 +02:00
'agent' = > 1 ,
'autostart' = > 1 ,
'bios' = > 1 ,
'description' = > 1 ,
'keyboard' = > 1 ,
'localtime' = > 1 ,
'migrate_downtime' = > 1 ,
'migrate_speed' = > 1 ,
'name' = > 1 ,
'onboot' = > 1 ,
'ostype' = > 1 ,
'protection' = > 1 ,
'reboot' = > 1 ,
'startdate' = > 1 ,
'startup' = > 1 ,
'tdf' = > 1 ,
'template' = > 1 ,
} ;
my $ vmpoweroptions = {
'freeze' = > 1 ,
} ;
my $ diskoptions = {
'boot' = > 1 ,
'bootdisk' = > 1 ,
2017-05-15 14:11:56 +02:00
'vmstatestorage' = > 1 ,
2016-06-01 11:12:42 +02:00
} ;
2012-02-02 06:39:38 +01:00
my $ check_vm_modify_config_perm = sub {
2012-02-03 10:23:50 +01:00
my ( $ rpcenv , $ authuser , $ vmid , $ pool , $ key_list ) = @ _ ;
2012-02-02 06:39:38 +01:00
2012-02-07 10:44:43 +01:00
return 1 if $ authuser eq 'root@pam' ;
2012-02-02 06:39:38 +01:00
2012-02-03 10:23:50 +01:00
foreach my $ opt ( @$ key_list ) {
2012-02-02 06:39:38 +01:00
# disk checks need to be done somewhere else
2016-03-03 15:45:15 +01:00
next if PVE::QemuServer:: is_valid_drivename ( $ opt ) ;
2016-06-01 11:12:42 +02:00
next if $ opt eq 'cdrom' ;
2016-06-10 09:26:14 +02:00
next if $ opt =~ m/^unused\d+$/ ;
2012-02-02 06:39:38 +01:00
2016-06-01 12:37:20 +02:00
if ( $ cpuoptions - > { $ opt } || $ opt =~ m/^numa\d+$/ ) {
2012-02-02 06:39:38 +01:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.CPU' ] ) ;
2016-06-01 11:12:42 +02:00
} elsif ( $ memoryoptions - > { $ opt } ) {
2012-02-02 06:39:38 +01:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Memory' ] ) ;
2016-06-01 11:12:42 +02:00
} elsif ( $ hwtypeoptions - > { $ opt } ) {
2012-02-02 06:39:38 +01:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.HWType' ] ) ;
2016-06-01 12:37:20 +02:00
} elsif ( $ generaloptions - > { $ opt } ) {
2016-06-01 11:12:42 +02:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Options' ] ) ;
# special case for startup since it changes host behaviour
if ( $ opt eq 'startup' ) {
$ rpcenv - > check_full ( $ authuser , "/" , [ 'Sys.Modify' ] ) ;
}
} elsif ( $ vmpoweroptions - > { $ opt } ) {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.PowerMgmt' ] ) ;
} elsif ( $ diskoptions - > { $ opt } ) {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Disk' ] ) ;
2012-02-02 06:39:38 +01:00
} elsif ( $ opt =~ m/^net\d+$/ ) {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Network' ] ) ;
} else {
2016-06-01 11:12:42 +02:00
# catches usb\d+, hostpci\d+, args, lock, etc.
# new options will be checked here
die "only root can set '$opt' config\n" ;
2012-02-02 06:39:38 +01:00
}
}
return 1 ;
} ;
2011-08-23 07:47:04 +02:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vmlist' ,
path = > '' ,
2011-08-23 07:47:04 +02:00
method = > 'GET' ,
description = > "Virtual machine index (per node)." ,
2012-02-02 06:39:38 +01:00
permissions = > {
description = > "Only list VMs where you have VM.Audit permissons on /vms/<vmid>." ,
user = > 'all' ,
} ,
2011-08-23 07:47:04 +02:00
proxyto = > 'node' ,
protected = > 1 , # qemu pid files are only readable by root
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2016-02-05 08:46:34 +01:00
full = > {
type = > 'boolean' ,
optional = > 1 ,
description = > "Determine the full status of active VMs." ,
} ,
2011-08-23 07:47:04 +02:00
} ,
} ,
returns = > {
type = > 'array' ,
items = > {
type = > "object" ,
properties = > { } ,
} ,
links = > [ { rel = > 'child' , href = > "{vmid}" } ] ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
2012-02-02 06:39:38 +01:00
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
2016-02-05 08:46:34 +01:00
my $ vmstatus = PVE::QemuServer:: vmstatus ( undef , $ param - > { full } ) ;
2011-08-23 07:47:04 +02:00
2012-02-02 06:39:38 +01:00
my $ res = [] ;
foreach my $ vmid ( keys %$ vmstatus ) {
next if ! $ rpcenv - > check ( $ authuser , "/vms/$vmid" , [ 'VM.Audit' ] , 1 ) ;
my $ data = $ vmstatus - > { $ vmid } ;
2014-09-17 15:51:47 +02:00
$ data - > { vmid } = int ( $ vmid ) ;
2012-02-02 06:39:38 +01:00
push @$ res , $ data ;
}
2011-08-23 07:47:04 +02:00
2012-02-02 06:39:38 +01:00
return $ res ;
2011-08-23 07:47:04 +02:00
} } ) ;
2013-05-03 09:07:53 +02:00
2011-08-23 07:47:04 +02:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'create_vm' ,
path = > '' ,
2011-08-23 07:47:04 +02:00
method = > 'POST' ,
2011-10-17 13:49:48 +02:00
description = > "Create or restore a virtual machine." ,
2012-02-02 06:39:38 +01:00
permissions = > {
2013-05-03 07:47:08 +02:00
description = > "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
"For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
"If you create disks you need 'Datastore.AllocateSpace' on any used storage." ,
user = > 'all' , # check inside
2012-02-02 06:39:38 +01:00
} ,
2011-08-23 07:47:04 +02:00
protected = > 1 ,
proxyto = > 'node' ,
parameters = > {
additionalProperties = > 0 ,
properties = > PVE::QemuServer:: json_config_properties (
{
node = > get_standard_option ( 'pve-node' ) ,
2015-09-06 16:01:59 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::Cluster:: complete_next_vmid } ) ,
2011-10-17 13:49:48 +02:00
archive = > {
description = > "The backup file." ,
type = > 'string' ,
optional = > 1 ,
maxLength = > 255 ,
2015-09-06 16:01:59 +02:00
completion = > \ & PVE::QemuServer:: complete_backup_archives ,
2011-10-17 13:49:48 +02:00
} ,
storage = > get_standard_option ( 'pve-storage-id' , {
description = > "Default storage." ,
optional = > 1 ,
2015-09-07 08:13:07 +02:00
completion = > \ & PVE::QemuServer:: complete_storage ,
2011-10-17 13:49:48 +02:00
} ) ,
force = > {
2012-01-27 09:35:26 +01:00
optional = > 1 ,
2011-10-17 13:49:48 +02:00
type = > 'boolean' ,
description = > "Allow to overwrite existing VM." ,
2011-10-18 09:14:05 +02:00
requires = > 'archive' ,
} ,
unique = > {
2012-01-27 09:35:26 +01:00
optional = > 1 ,
2011-10-18 09:14:05 +02:00
type = > 'boolean' ,
description = > "Assign a unique random ethernet address." ,
requires = > 'archive' ,
2011-10-17 13:49:48 +02:00
} ,
2013-04-30 11:46:38 +02:00
pool = > {
2012-02-02 06:39:38 +01:00
optional = > 1 ,
type = > 'string' , format = > 'pve-poolid' ,
description = > "Add the VM to the specified pool." ,
} ,
2011-08-23 07:47:04 +02:00
} ) ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
2011-08-23 07:47:04 +02:00
code = > sub {
my ( $ param ) = @ _ ;
2011-10-10 13:17:40 +02:00
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-10-10 13:17:40 +02:00
2011-08-23 07:47:04 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
2011-10-17 13:49:48 +02:00
my $ archive = extract_param ( $ param , 'archive' ) ;
my $ storage = extract_param ( $ param , 'storage' ) ;
2011-10-18 09:14:05 +02:00
my $ force = extract_param ( $ param , 'force' ) ;
my $ unique = extract_param ( $ param , 'unique' ) ;
2013-04-30 11:46:38 +02:00
2012-02-02 06:39:38 +01:00
my $ pool = extract_param ( $ param , 'pool' ) ;
2011-10-18 09:14:05 +02:00
2016-03-07 12:41:12 +01:00
my $ filename = PVE::QemuConfig - > config_file ( $ vmid ) ;
2012-01-27 09:35:26 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2011-08-23 07:47:04 +02:00
2011-10-17 13:49:48 +02:00
PVE::Cluster:: check_cfs_quorum ( ) ;
2011-08-23 07:47:04 +02:00
2012-02-02 06:39:38 +01:00
if ( defined ( $ pool ) ) {
$ rpcenv - > check_pool_exist ( $ pool ) ;
2013-04-30 11:46:38 +02:00
}
2012-02-02 06:39:38 +01:00
2012-02-06 12:19:35 +01:00
$ rpcenv - > check ( $ authuser , "/storage/$storage" , [ 'Datastore.AllocateSpace' ] )
2012-02-02 06:39:38 +01:00
if defined ( $ storage ) ;
2013-05-03 07:47:08 +02:00
if ( $ rpcenv - > check ( $ authuser , "/vms/$vmid" , [ 'VM.Allocate' ] , 1 ) ) {
# OK
} elsif ( $ pool && $ rpcenv - > check ( $ authuser , "/pool/$pool" , [ 'VM.Allocate' ] , 1 ) ) {
# OK
} elsif ( $ archive && $ force && ( - f $ filename ) &&
$ rpcenv - > check ( $ authuser , "/vms/$vmid" , [ 'VM.Backup' ] , 1 ) ) {
# OK: user has VM.Backup permissions, and want to restore an existing VM
} else {
raise_perm_exc ( ) ;
}
2012-01-27 09:35:26 +01:00
if ( ! $ archive ) {
2011-10-17 13:49:48 +02:00
& $ resolve_cdrom_alias ( $ param ) ;
2011-08-23 07:47:04 +02:00
2012-02-06 12:19:35 +01:00
& $ check_storage_access ( $ rpcenv , $ authuser , $ storecfg , $ vmid , $ param , $ storage ) ;
2012-02-03 10:23:50 +01:00
& $ check_vm_modify_config_perm ( $ rpcenv , $ authuser , $ vmid , $ pool , [ keys %$ param ] ) ;
2011-10-17 13:49:48 +02:00
foreach my $ opt ( keys %$ param ) {
2016-03-03 15:45:15 +01:00
if ( PVE::QemuServer:: is_valid_drivename ( $ opt ) ) {
2011-10-17 13:49:48 +02:00
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ param - > { $ opt } ) ;
raise_param_exc ( { $ opt = > "unable to parse drive options" } ) if ! $ drive ;
2012-01-27 09:35:26 +01:00
2011-10-17 13:49:48 +02:00
PVE::QemuServer:: cleanup_drive_path ( $ opt , $ storecfg , $ drive ) ;
$ param - > { $ opt } = PVE::QemuServer:: print_drive ( $ vmid , $ drive ) ;
}
2011-08-23 07:47:04 +02:00
}
2011-10-17 13:49:48 +02:00
PVE::QemuServer:: add_random_macs ( $ param ) ;
2011-10-18 09:14:05 +02:00
} else {
my $ keystr = join ( ' ' , keys %$ param ) ;
2011-10-20 10:51:28 +02:00
raise_param_exc ( { archive = > "option conflicts with other options ($keystr)" } ) if $ keystr ;
2011-10-20 11:18:46 +02:00
if ( $ archive eq '-' ) {
2012-01-27 09:35:26 +01:00
die "pipe requires cli environment\n"
2012-04-02 10:52:05 +02:00
if $ rpcenv - > { type } ne 'cli' ;
2011-10-20 11:18:46 +02:00
} else {
2017-01-18 17:22:34 +01:00
PVE::Storage:: check_volume_access ( $ rpcenv , $ authuser , $ storecfg , $ vmid , $ archive ) ;
2013-10-01 12:41:06 +02:00
$ archive = PVE::Storage:: abs_filesystem_path ( $ storecfg , $ archive ) ;
2011-11-23 07:30:43 +01:00
}
2011-08-23 07:47:04 +02:00
}
2011-10-17 13:49:48 +02:00
my $ restorefn = sub {
2015-09-17 12:11:59 +02:00
my $ vmlist = PVE::Cluster:: get_vmlist ( ) ;
if ( $ vmlist - > { ids } - > { $ vmid } ) {
2015-09-17 13:10:19 +02:00
my $ current_node = $ vmlist - > { ids } - > { $ vmid } - > { node } ;
if ( $ current_node eq $ node ) {
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2011-10-17 13:49:48 +02:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_protection ( $ conf , "unable to restore VM $vmid" ) ;
2011-10-17 13:49:48 +02:00
2015-09-17 12:11:59 +02:00
die "unable to restore vm $vmid - config file already exists\n"
if ! $ force ;
die "unable to restore vm $vmid - vm is running\n"
if PVE::QemuServer:: check_running ( $ vmid ) ;
2016-09-15 13:23:12 +02:00
die "unable to restore vm $vmid - vm is a template\n"
if PVE::QemuConfig - > is_template ( $ conf ) ;
2015-09-17 12:11:59 +02:00
} else {
2015-09-17 13:10:19 +02:00
die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n" ;
2015-09-17 12:11:59 +02:00
}
2011-10-17 13:49:48 +02:00
}
my $ realcmd = sub {
2012-02-02 06:39:38 +01:00
PVE::QemuServer:: restore_archive ( $ archive , $ vmid , $ authuser , {
2011-10-18 09:14:05 +02:00
storage = > $ storage ,
2012-02-02 06:39:38 +01:00
pool = > $ pool ,
2011-10-18 09:14:05 +02:00
unique = > $ unique } ) ;
2012-02-03 13:44:12 +01:00
2013-05-14 12:01:57 +02:00
PVE::AccessControl:: add_vm_to_pool ( $ vmid , $ pool ) if $ pool ;
2011-10-17 13:49:48 +02:00
} ;
2017-06-27 15:12:03 +02:00
# ensure no old replication state are exists
PVE::ReplicationState:: delete_guest_states ( $ vmid ) ;
2012-02-02 06:39:38 +01:00
return $ rpcenv - > fork_worker ( 'qmrestore' , $ vmid , $ authuser , $ realcmd ) ;
2011-10-17 13:49:48 +02:00
} ;
2011-08-23 07:47:04 +02:00
my $ createfn = sub {
2012-03-27 08:50:04 +02:00
# test after locking
2015-10-05 12:43:07 +02:00
PVE::Cluster:: check_vmid_unused ( $ vmid ) ;
2011-08-23 07:47:04 +02:00
2017-06-27 15:12:03 +02:00
# ensure no old replication state are exists
PVE::ReplicationState:: delete_guest_states ( $ vmid ) ;
2011-10-10 13:17:40 +02:00
my $ realcmd = sub {
2011-08-23 07:47:04 +02:00
2011-10-10 13:17:40 +02:00
my $ vollist = [] ;
2011-08-23 07:47:04 +02:00
2012-02-02 14:01:08 +01:00
my $ conf = $ param ;
2011-10-10 13:17:40 +02:00
eval {
2012-02-03 10:23:50 +01:00
2012-02-02 14:01:08 +01:00
$ vollist = & $ create_disks ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , $ pool , $ param , $ storage ) ;
2011-08-23 07:47:04 +02:00
2017-09-13 16:10:25 +02:00
if ( ! $ conf - > { bootdisk } ) {
my $ firstdisk = PVE::QemuServer:: resolve_first_disk ( $ conf ) ;
$ conf - > { bootdisk } = $ firstdisk if $ firstdisk ;
2011-10-10 13:17:40 +02:00
}
2011-08-23 07:47:04 +02:00
2014-06-26 11:51:52 +02:00
# auto generate uuid if user did not specify smbios1 option
if ( ! $ conf - > { smbios1 } ) {
2017-09-13 16:10:25 +02:00
$ conf - > { smbios1 } = PVE::QemuServer:: generate_smbios1_uuid ( ) ;
2014-06-26 11:51:52 +02:00
}
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2012-02-03 13:13:47 +01:00
2011-10-10 13:17:40 +02:00
} ;
my $ err = $@ ;
2011-08-23 07:47:04 +02:00
2011-10-10 13:17:40 +02:00
if ( $ err ) {
foreach my $ volid ( @$ vollist ) {
eval { PVE::Storage:: vdisk_free ( $ storecfg , $ volid ) ; } ;
warn $@ if $@ ;
}
die "create failed - $err" ;
}
2012-02-03 13:44:12 +01:00
2013-05-14 12:01:57 +02:00
PVE::AccessControl:: add_vm_to_pool ( $ vmid , $ pool ) if $ pool ;
2011-10-10 13:17:40 +02:00
} ;
2012-02-02 06:39:38 +01:00
return $ rpcenv - > fork_worker ( 'qmcreate' , $ vmid , $ authuser , $ realcmd ) ;
2011-10-10 13:17:40 +02:00
} ;
2016-03-07 12:41:12 +01:00
return PVE::QemuConfig - > lock_config_full ( $ vmid , 1 , $ archive ? $ restorefn : $ createfn ) ;
2011-08-23 07:47:04 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
name = > 'vmdiridx' ,
2012-01-27 09:35:26 +01:00
path = > '{vmid}' ,
2011-08-23 07:47:04 +02:00
method = > 'GET' ,
proxyto = > 'node' ,
description = > "Directory index" ,
2012-02-02 06:39:38 +01:00
permissions = > {
user = > 'all' ,
} ,
2011-08-23 07:47:04 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
} ,
} ,
returns = > {
type = > 'array' ,
items = > {
type = > "object" ,
properties = > {
subdir = > { type = > 'string' } ,
} ,
} ,
links = > [ { rel = > 'child' , href = > "{subdir}" } ] ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ res = [
{ subdir = > 'config' } ,
2015-01-07 08:14:14 +01:00
{ subdir = > 'pending' } ,
2011-08-23 07:47:04 +02:00
{ subdir = > 'status' } ,
{ subdir = > 'unlink' } ,
{ subdir = > 'vncproxy' } ,
2017-12-11 14:55:32 +01:00
{ subdir = > 'termproxy' } ,
2011-09-14 12:02:08 +02:00
{ subdir = > 'migrate' } ,
2012-08-08 08:26:58 +02:00
{ subdir = > 'resize' } ,
2013-05-27 06:22:05 +02:00
{ subdir = > 'move' } ,
2011-08-23 07:47:04 +02:00
{ subdir = > 'rrd' } ,
{ subdir = > 'rrddata' } ,
2011-11-09 08:26:46 +01:00
{ subdir = > 'monitor' } ,
2016-11-03 15:26:50 +01:00
{ subdir = > 'agent' } ,
2012-09-10 07:32:33 +02:00
{ subdir = > 'snapshot' } ,
2013-06-25 12:09:05 +02:00
{ subdir = > 'spiceproxy' } ,
2013-11-20 06:54:06 +01:00
{ subdir = > 'sendkey' } ,
2014-05-06 11:27:10 +02:00
{ subdir = > 'firewall' } ,
2011-08-23 07:47:04 +02:00
] ;
2012-01-27 09:35:26 +01:00
2011-08-23 07:47:04 +02:00
return $ res ;
} } ) ;
2014-05-06 11:27:10 +02:00
__PACKAGE__ - > register_method ( {
2014-11-21 12:31:56 +01:00
subclass = > "PVE::API2::Firewall::VM" ,
2014-05-06 11:27:10 +02:00
path = > '{vmid}/firewall' ,
} ) ;
2011-08-23 07:47:04 +02:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'rrd' ,
path = > '{vmid}/rrd' ,
2011-08-23 07:47:04 +02:00
method = > 'GET' ,
protected = > 1 , # fixme: can we avoid that?
permissions = > {
2012-01-23 11:59:28 +01:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Audit' ] ] ,
2011-08-23 07:47:04 +02:00
} ,
description = > "Read VM RRD statistics (returns PNG)" ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
timeframe = > {
description = > "Specify the time frame you are interested in." ,
type = > 'string' ,
enum = > [ 'hour' , 'day' , 'week' , 'month' , 'year' ] ,
} ,
ds = > {
description = > "The list of datasources you want to display." ,
type = > 'string' , format = > 'pve-configid-list' ,
} ,
cf = > {
description = > "The RRD consolidation function" ,
type = > 'string' ,
enum = > [ 'AVERAGE' , 'MAX' ] ,
optional = > 1 ,
} ,
} ,
} ,
returns = > {
type = > "object" ,
properties = > {
filename = > { type = > 'string' } ,
} ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
return PVE::Cluster:: create_rrd_graph (
2012-01-27 09:35:26 +01:00
"pve2-vm/$param->{vmid}" , $ param - > { timeframe } ,
2011-08-23 07:47:04 +02:00
$ param - > { ds } , $ param - > { cf } ) ;
2012-01-27 09:35:26 +01:00
2011-08-23 07:47:04 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'rrddata' ,
path = > '{vmid}/rrddata' ,
2011-08-23 07:47:04 +02:00
method = > 'GET' ,
protected = > 1 , # fixme: can we avoid that?
permissions = > {
2012-01-23 11:59:28 +01:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Audit' ] ] ,
2011-08-23 07:47:04 +02:00
} ,
description = > "Read VM RRD statistics" ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
timeframe = > {
description = > "Specify the time frame you are interested in." ,
type = > 'string' ,
enum = > [ 'hour' , 'day' , 'week' , 'month' , 'year' ] ,
} ,
cf = > {
description = > "The RRD consolidation function" ,
type = > 'string' ,
enum = > [ 'AVERAGE' , 'MAX' ] ,
optional = > 1 ,
} ,
} ,
} ,
returns = > {
type = > "array" ,
items = > {
type = > "object" ,
properties = > { } ,
} ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
return PVE::Cluster:: create_rrd_data (
"pve2-vm/$param->{vmid}" , $ param - > { timeframe } , $ param - > { cf } ) ;
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_config' ,
path = > '{vmid}/config' ,
2011-08-23 07:47:04 +02:00
method = > 'GET' ,
proxyto = > 'node' ,
2014-11-21 11:25:45 +01:00
description = > "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API)." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Audit' ] ] ,
} ,
2011-08-23 07:47:04 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2015-01-07 08:31:32 +01:00
current = > {
description = > "Get current values (instead of pending values)." ,
optional = > 1 ,
default = > 0 ,
type = > 'boolean' ,
} ,
2011-08-23 07:47:04 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-08-23 07:47:04 +02:00
type = > "object" ,
2011-09-07 11:41:34 +02:00
properties = > {
digest = > {
type = > 'string' ,
description = > 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.' ,
}
} ,
2011-08-23 07:47:04 +02:00
} ,
code = > sub {
my ( $ param ) = @ _ ;
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ param - > { vmid } ) ;
2011-08-23 07:47:04 +02:00
2012-09-07 13:07:23 +02:00
delete $ conf - > { snapshots } ;
2015-01-07 08:31:32 +01:00
if ( ! $ param - > { current } ) {
2015-02-27 12:56:38 +01:00
foreach my $ opt ( keys % { $ conf - > { pending } } ) {
2015-01-07 08:31:32 +01:00
next if $ opt eq 'delete' ;
my $ value = $ conf - > { pending } - > { $ opt } ;
next if ref ( $ value ) ; # just to be sure
$ conf - > { $ opt } = $ value ;
}
2015-08-18 09:36:09 +02:00
my $ pending_delete_hash = PVE::QemuServer:: split_flagged_list ( $ conf - > { pending } - > { delete } ) ;
foreach my $ opt ( keys %$ pending_delete_hash ) {
2015-01-07 08:31:32 +01:00
delete $ conf - > { $ opt } if $ conf - > { $ opt } ;
}
}
2014-11-21 11:25:45 +01:00
delete $ conf - > { pending } ;
2012-09-07 13:07:23 +02:00
2011-08-23 07:47:04 +02:00
return $ conf ;
} } ) ;
2014-11-21 11:25:45 +01:00
__PACKAGE__ - > register_method ( {
name = > 'vm_pending' ,
path = > '{vmid}/pending' ,
method = > 'GET' ,
proxyto = > 'node' ,
description = > "Get virtual machine configuration, including pending changes." ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Audit' ] ] ,
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2014-11-21 11:25:45 +01:00
} ,
} ,
returns = > {
type = > "array" ,
items = > {
type = > "object" ,
properties = > {
key = > {
description = > "Configuration option name." ,
type = > 'string' ,
} ,
value = > {
description = > "Current value." ,
type = > 'string' ,
optional = > 1 ,
} ,
pending = > {
description = > "Pending value." ,
type = > 'string' ,
optional = > 1 ,
} ,
delete = > {
2015-08-18 09:36:09 +02:00
description = > "Indicates a pending delete request if present and not 0. " .
"The value 2 indicates a force-delete request." ,
type = > 'integer' ,
minimum = > 0 ,
maximum = > 2 ,
2014-11-21 11:25:45 +01:00
optional = > 1 ,
} ,
} ,
} ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ param - > { vmid } ) ;
2014-11-21 11:25:45 +01:00
2015-08-18 09:36:09 +02:00
my $ pending_delete_hash = PVE::QemuServer:: split_flagged_list ( $ conf - > { pending } - > { delete } ) ;
2014-11-21 11:25:45 +01:00
my $ res = [] ;
2015-02-27 12:56:38 +01:00
foreach my $ opt ( keys %$ conf ) {
2014-11-21 11:25:45 +01:00
next if ref ( $ conf - > { $ opt } ) ;
my $ item = { key = > $ opt } ;
$ item - > { value } = $ conf - > { $ opt } if defined ( $ conf - > { $ opt } ) ;
$ item - > { pending } = $ conf - > { pending } - > { $ opt } if defined ( $ conf - > { pending } - > { $ opt } ) ;
2015-08-18 09:36:09 +02:00
$ item - > { delete } = ( $ pending_delete_hash - > { $ opt } ? 2 : 1 ) if exists $ pending_delete_hash - > { $ opt } ;
2014-11-21 11:25:45 +01:00
push @$ res , $ item ;
}
2015-02-27 12:56:38 +01:00
foreach my $ opt ( keys % { $ conf - > { pending } } ) {
2014-11-21 11:25:45 +01:00
next if $ opt eq 'delete' ;
next if ref ( $ conf - > { pending } - > { $ opt } ) ; # just to be sure
2015-01-12 15:32:17 +01:00
next if defined ( $ conf - > { $ opt } ) ;
2014-11-21 11:25:45 +01:00
my $ item = { key = > $ opt } ;
$ item - > { pending } = $ conf - > { pending } - > { $ opt } ;
push @$ res , $ item ;
}
2015-08-18 09:36:09 +02:00
while ( my ( $ opt , $ force ) = each %$ pending_delete_hash ) {
2014-11-21 11:25:45 +01:00
next if $ conf - > { pending } - > { $ opt } ; # just to be sure
next if $ conf - > { $ opt } ;
2015-08-18 09:36:09 +02:00
my $ item = { key = > $ opt , delete = > ( $ force ? 2 : 1 ) } ;
2014-11-21 11:25:45 +01:00
push @$ res , $ item ;
}
return $ res ;
} } ) ;
2013-06-07 11:41:58 +02:00
# POST/PUT {vmid}/config implementation
#
# The original API used PUT (idempotent) an we assumed that all operations
# are fast. But it turned out that almost any configuration change can
# involve hot-plug actions, or disk alloc/free. Such actions can take long
# time to complete and have side effects (not idempotent).
#
2013-06-11 07:26:43 +02:00
# The new implementation uses POST and forks a worker process. We added
2013-06-07 11:41:58 +02:00
# a new option 'background_delay'. If specified we wait up to
2013-06-11 07:26:43 +02:00
# 'background_delay' second for the worker task to complete. It returns null
2013-06-07 11:41:58 +02:00
# if the task is finished within that time, else we return the UPID.
2013-06-11 07:26:43 +02:00
2013-06-07 11:41:58 +02:00
my $ update_vm_api = sub {
my ( $ param , $ sync ) = @ _ ;
2012-02-02 06:39:38 +01:00
2013-06-07 11:41:58 +02:00
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ vmid = extract_param ( $ param , 'vmid' ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ digest = extract_param ( $ param , 'digest' ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ background_delay = extract_param ( $ param , 'background_delay' ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my @ paramarr = ( ) ; # used for log message
2017-09-06 11:32:13 +02:00
foreach my $ key ( sort keys %$ param ) {
2015-10-29 17:44:17 +01:00
push @ paramarr , "-$key" , $ param - > { $ key } ;
2013-06-07 11:41:58 +02:00
}
2012-02-02 07:19:46 +01:00
2013-06-07 11:41:58 +02:00
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
raise_param_exc ( { skiplock = > "Only root may use this option." } )
if $ skiplock && $ authuser ne 'root@pam' ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ delete_str = extract_param ( $ param , 'delete' ) ;
2012-02-02 07:19:46 +01:00
2015-01-07 11:09:30 +01:00
my $ revert_str = extract_param ( $ param , 'revert' ) ;
2013-06-07 11:41:58 +02:00
my $ force = extract_param ( $ param , 'force' ) ;
2012-02-02 08:35:11 +01:00
2015-01-07 11:09:30 +01:00
die "no options specified\n" if ! $ delete_str && ! $ revert_str && ! scalar ( keys %$ param ) ;
2012-12-28 14:06:46 +01:00
2013-06-07 11:41:58 +02:00
my $ storecfg = PVE::Storage:: config ( ) ;
2012-02-02 08:35:11 +01:00
2013-06-07 11:41:58 +02:00
my $ defaults = PVE::QemuServer:: load_defaults ( ) ;
2012-02-02 08:35:11 +01:00
2013-06-07 11:41:58 +02:00
& $ resolve_cdrom_alias ( $ param ) ;
2012-02-02 07:19:46 +01:00
2013-06-07 11:41:58 +02:00
# now try to verify all parameters
2012-02-03 10:23:50 +01:00
2015-01-07 11:09:30 +01:00
my $ revert = { } ;
foreach my $ opt ( PVE::Tools:: split_list ( $ revert_str ) ) {
if ( ! PVE::QemuServer:: option_exists ( $ opt ) ) {
raise_param_exc ( { revert = > "unknown option '$opt'" } ) ;
}
raise_param_exc ( { delete = > "you can't use '-$opt' and " .
"-revert $opt' at the same time" } )
if defined ( $ param - > { $ opt } ) ;
$ revert - > { $ opt } = 1 ;
}
2013-06-07 11:41:58 +02:00
my @ delete = ( ) ;
foreach my $ opt ( PVE::Tools:: split_list ( $ delete_str ) ) {
$ opt = 'ide2' if $ opt eq 'cdrom' ;
2015-01-07 11:09:30 +01:00
2013-06-07 11:41:58 +02:00
raise_param_exc ( { delete = > "you can't use '-$opt' and " .
"-delete $opt' at the same time" } )
if defined ( $ param - > { $ opt } ) ;
2013-06-11 07:26:43 +02:00
2015-01-07 11:09:30 +01:00
raise_param_exc ( { revert = > "you can't use '-delete $opt' and " .
"-revert $opt' at the same time" } )
if $ revert - > { $ opt } ;
2013-06-07 11:41:58 +02:00
if ( ! PVE::QemuServer:: option_exists ( $ opt ) ) {
raise_param_exc ( { delete = > "unknown option '$opt'" } ) ;
2012-02-02 07:19:46 +01:00
}
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
push @ delete , $ opt ;
}
2017-06-27 09:42:55 +02:00
my $ repl_conf = PVE::ReplicationConfig - > new ( ) ;
my $ is_replicated = $ repl_conf - > check_for_existing_jobs ( $ vmid , 1 ) ;
my $ check_replication = sub {
my ( $ drive ) = @ _ ;
return if ! $ is_replicated ;
my $ volid = $ drive - > { file } ;
return if ! $ volid || ! ( $ drive - > { replicate } // 1 ) ;
return if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my ( $ storeid , $ format ) ;
if ( $ volid =~ $ NEW_DISK_RE ) {
$ storeid = $ 2 ;
$ format = $ drive - > { format } || PVE::Storage:: storage_default_format ( $ storecfg , $ storeid ) ;
} else {
( $ storeid , undef ) = PVE::Storage:: parse_volume_id ( $ volid , 1 ) ;
$ format = ( PVE::Storage:: parse_volname ( $ storecfg , $ volid ) ) [ 6 ] ;
}
return if PVE::Storage:: storage_can_replicate ( $ storecfg , $ storeid , $ format ) ;
2017-06-27 15:00:06 +02:00
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ storeid ) ;
return if $ scfg - > { shared } ;
2017-06-27 09:42:55 +02:00
die "cannot add non-replicatable volume to a replicated VM\n" ;
} ;
2013-06-07 11:41:58 +02:00
foreach my $ opt ( keys %$ param ) {
2016-03-03 15:45:15 +01:00
if ( PVE::QemuServer:: is_valid_drivename ( $ opt ) ) {
2013-06-07 11:41:58 +02:00
# cleanup drive path
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ param - > { $ opt } ) ;
2016-11-03 08:17:15 +01:00
raise_param_exc ( { $ opt = > "unable to parse drive options" } ) if ! $ drive ;
2013-06-07 11:41:58 +02:00
PVE::QemuServer:: cleanup_drive_path ( $ opt , $ storecfg , $ drive ) ;
2017-06-27 09:42:55 +02:00
$ check_replication - > ( $ drive ) ;
2013-06-07 11:41:58 +02:00
$ param - > { $ opt } = PVE::QemuServer:: print_drive ( $ vmid , $ drive ) ;
} elsif ( $ opt =~ m/^net(\d+)$/ ) {
# add macaddr
my $ net = PVE::QemuServer:: parse_net ( $ param - > { $ opt } ) ;
$ param - > { $ opt } = PVE::QemuServer:: print_net ( $ net ) ;
2012-02-02 08:35:11 +01:00
}
2013-06-07 11:41:58 +02:00
}
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
& $ check_vm_modify_config_perm ( $ rpcenv , $ authuser , $ vmid , undef , [ @ delete ] ) ;
2012-02-03 10:23:50 +01:00
2013-06-07 11:41:58 +02:00
& $ check_vm_modify_config_perm ( $ rpcenv , $ authuser , $ vmid , undef , [ keys %$ param ] ) ;
2012-02-03 10:23:50 +01:00
2013-06-07 11:41:58 +02:00
& $ check_storage_access ( $ rpcenv , $ authuser , $ storecfg , $ vmid , $ param ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ updatefn = sub {
2011-08-23 07:47:04 +02:00
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
die "checksum missmatch (file change by other user?)\n"
if $ digest && $ digest ne $ conf - > { digest } ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_lock ( $ conf ) if ! $ skiplock ;
2013-06-11 07:26:43 +02:00
2015-01-07 11:09:30 +01:00
foreach my $ opt ( keys %$ revert ) {
if ( defined ( $ conf - > { $ opt } ) ) {
$ param - > { $ opt } = $ conf - > { $ opt } ;
} elsif ( defined ( $ conf - > { pending } - > { $ opt } ) ) {
push @ delete , $ opt ;
}
}
2013-06-07 11:41:58 +02:00
if ( $ param - > { memory } || defined ( $ param - > { balloon } ) ) {
2014-11-17 09:37:11 +01:00
my $ maxmem = $ param - > { memory } || $ conf - > { pending } - > { memory } || $ conf - > { memory } || $ defaults - > { memory } ;
my $ balloon = defined ( $ param - > { balloon } ) ? $ param - > { balloon } : $ conf - > { pending } - > { balloon } || $ conf - > { balloon } ;
2013-06-11 07:26:43 +02:00
2013-06-07 11:41:58 +02:00
die "balloon value too large (must be smaller than assigned memory)\n"
if $ balloon && $ balloon > $ maxmem ;
}
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
PVE::Cluster:: log_msg ( 'info' , $ authuser , "update VM $vmid: " . join ( ' ' , @ paramarr ) ) ;
2011-08-23 07:47:04 +02:00
2013-06-07 11:41:58 +02:00
my $ worker = sub {
2012-12-28 14:06:46 +01:00
2013-06-07 11:41:58 +02:00
print "update VM $vmid: " . join ( ' ' , @ paramarr ) . "\n" ;
2012-01-26 19:43:48 +01:00
2014-11-14 10:33:27 +01:00
# write updates to pending section
2014-11-19 12:59:02 +01:00
my $ modified = { } ; # record what $option we modify
2014-11-14 10:33:27 +01:00
foreach my $ opt ( @ delete ) {
2014-11-19 12:59:02 +01:00
$ modified - > { $ opt } = 1 ;
2016-03-07 12:41:12 +01:00
$ conf = PVE::QemuConfig - > load_config ( $ vmid ) ; # update/reload
2017-05-29 10:37:23 +02:00
if ( ! defined ( $ conf - > { $ opt } ) && ! defined ( $ conf - > { pending } - > { $ opt } ) ) {
2017-03-17 11:01:32 +01:00
warn "cannot delete '$opt' - not set in current configuration!\n" ;
$ modified - > { $ opt } = 0 ;
next ;
}
2014-11-14 10:33:27 +01:00
if ( $ opt =~ m/^unused/ ) {
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ conf - > { $ opt } ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_protection ( $ conf , "can't remove unused disk '$drive->{file}'" ) ;
2015-09-17 12:11:59 +02:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , undef , [ 'VM.Config.Disk' ] ) ;
2015-08-12 13:38:36 +02:00
if ( PVE::QemuServer:: try_deallocate_drive ( $ storecfg , $ vmid , $ conf , $ opt , $ drive , $ rpcenv , $ authuser ) ) {
delete $ conf - > { $ opt } ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2014-11-14 10:33:27 +01:00
}
2016-03-03 15:45:15 +01:00
} elsif ( PVE::QemuServer:: is_valid_drivename ( $ opt ) ) {
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_protection ( $ conf , "can't remove drive '$opt'" ) ;
2014-11-14 10:33:27 +01:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , undef , [ 'VM.Config.Disk' ] ) ;
2014-11-17 07:08:44 +01:00
PVE::QemuServer:: vmconfig_register_unused_drive ( $ storecfg , $ vmid , $ conf , PVE::QemuServer:: parse_drive ( $ opt , $ conf - > { pending } - > { $ opt } ) )
2014-11-14 10:33:27 +01:00
if defined ( $ conf - > { pending } - > { $ opt } ) ;
2015-08-12 13:38:36 +02:00
PVE::QemuServer:: vmconfig_delete_pending_option ( $ conf , $ opt , $ force ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2014-11-14 10:33:27 +01:00
} else {
2015-08-12 13:38:36 +02:00
PVE::QemuServer:: vmconfig_delete_pending_option ( $ conf , $ opt , $ force ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2014-11-14 10:33:27 +01:00
}
2012-01-27 09:53:48 +01:00
}
2011-08-23 07:47:04 +02:00
2014-11-14 10:33:27 +01:00
foreach my $ opt ( keys %$ param ) { # add/change
2014-11-19 12:59:02 +01:00
$ modified - > { $ opt } = 1 ;
2016-03-07 12:41:12 +01:00
$ conf = PVE::QemuConfig - > load_config ( $ vmid ) ; # update/reload
2014-11-14 10:33:27 +01:00
next if defined ( $ conf - > { pending } - > { $ opt } ) && ( $ param - > { $ opt } eq $ conf - > { pending } - > { $ opt } ) ; # skip if nothing changed
2016-03-03 15:45:15 +01:00
if ( PVE::QemuServer:: is_valid_drivename ( $ opt ) ) {
2014-11-14 10:33:27 +01:00
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ param - > { $ opt } ) ;
if ( PVE::QemuServer:: drive_is_cdrom ( $ drive ) ) { # CDROM
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , undef , [ 'VM.Config.CDROM' ] ) ;
} else {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , undef , [ 'VM.Config.Disk' ] ) ;
}
2014-11-17 07:08:44 +01:00
PVE::QemuServer:: vmconfig_register_unused_drive ( $ storecfg , $ vmid , $ conf , PVE::QemuServer:: parse_drive ( $ opt , $ conf - > { pending } - > { $ opt } ) )
2014-11-14 10:33:27 +01:00
if defined ( $ conf - > { pending } - > { $ opt } ) ;
& $ create_disks ( $ rpcenv , $ authuser , $ conf - > { pending } , $ storecfg , $ vmid , undef , { $ opt = > $ param - > { $ opt } } ) ;
} else {
$ conf - > { pending } - > { $ opt } = $ param - > { $ opt } ;
}
2014-11-17 07:08:44 +01:00
PVE::QemuServer:: vmconfig_undelete_pending_option ( $ conf , $ opt ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2014-11-14 10:33:27 +01:00
}
# remove pending changes when nothing changed
2016-03-07 12:41:12 +01:00
$ conf = PVE::QemuConfig - > load_config ( $ vmid ) ; # update/reload
2014-11-19 09:20:09 +01:00
my $ changes = PVE::QemuServer:: vmconfig_cleanup_pending ( $ conf ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) if $ changes ;
2014-11-14 10:33:27 +01:00
return if ! scalar ( keys % { $ conf - > { pending } } ) ;
2012-12-28 14:06:46 +01:00
my $ running = PVE::QemuServer:: check_running ( $ vmid ) ;
2014-11-14 11:45:14 +01:00
# apply pending changes
2016-03-07 12:41:12 +01:00
$ conf = PVE::QemuConfig - > load_config ( $ vmid ) ; # update/reload
2014-11-14 11:45:14 +01:00
2014-11-19 12:59:02 +01:00
if ( $ running ) {
my $ errors = { } ;
PVE::QemuServer:: vmconfig_hotplug_pending ( $ vmid , $ conf , $ storecfg , $ modified , $ errors ) ;
raise_param_exc ( $ errors ) if scalar ( keys %$ errors ) ;
} else {
PVE::QemuServer:: vmconfig_apply_pending ( $ vmid , $ conf , $ storecfg , $ running ) ;
}
2012-02-02 08:35:11 +01:00
2014-11-25 12:19:52 +01:00
return ;
2012-01-27 09:53:48 +01:00
} ;
2013-06-07 11:41:58 +02:00
if ( $ sync ) {
& $ worker ( ) ;
return undef ;
} else {
my $ upid = $ rpcenv - > fork_worker ( 'qmconfig' , $ vmid , $ authuser , $ worker ) ;
2012-01-20 11:42:08 +01:00
2013-06-07 11:41:58 +02:00
if ( $ background_delay ) {
# Note: It would be better to do that in the Event based HTTPServer
2013-06-11 07:26:43 +02:00
# to avoid blocking call to sleep.
2013-06-07 11:41:58 +02:00
my $ end_time = time ( ) + $ background_delay ;
my $ task = PVE::Tools:: upid_decode ( $ upid ) ;
my $ running = 1 ;
while ( time ( ) < $ end_time ) {
$ running = PVE::ProcFSTools:: check_process_running ( $ task - > { pid } , $ task - > { pstart } ) ;
last if ! $ running ;
sleep ( 1 ) ; # this gets interrupted when child process ends
}
if ( ! $ running ) {
my $ status = PVE::Tools:: upid_read_status ( $ upid ) ;
return undef if $ status eq 'OK' ;
die $ status ;
}
2013-06-11 07:26:43 +02:00
}
2013-06-07 11:41:58 +02:00
return $ upid ;
}
} ;
2016-03-07 12:41:12 +01:00
return PVE::QemuConfig - > lock_config ( $ vmid , $ updatefn ) ;
2013-06-07 11:41:58 +02:00
} ;
my $ vm_config_perm_list = [
'VM.Config.Disk' ,
'VM.Config.CDROM' ,
'VM.Config.CPU' ,
'VM.Config.Memory' ,
'VM.Config.Network' ,
'VM.Config.HWType' ,
'VM.Config.Options' ,
] ;
__PACKAGE__ - > register_method ( {
name = > 'update_vm_async' ,
path = > '{vmid}/config' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Set virtual machine options (asynchrounous API)." ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , $ vm_config_perm_list , any = > 1 ] ,
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > PVE::QemuServer:: json_config_properties (
{
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
skiplock = > get_standard_option ( 'skiplock' ) ,
delete = > {
type = > 'string' , format = > 'pve-configid-list' ,
description = > "A list of settings you want to delete." ,
optional = > 1 ,
} ,
2015-01-08 09:04:18 +01:00
revert = > {
type = > 'string' , format = > 'pve-configid-list' ,
description = > "Revert a pending change." ,
optional = > 1 ,
} ,
2013-06-07 11:41:58 +02:00
force = > {
type = > 'boolean' ,
description = > $ opt_force_description ,
optional = > 1 ,
requires = > 'delete' ,
} ,
digest = > {
type = > 'string' ,
description = > 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.' ,
maxLength = > 40 ,
optional = > 1 ,
} ,
background_delay = > {
type = > 'integer' ,
description = > "Time to wait for the task to finish. We return 'null' if the task finish within that time." ,
minimum = > 1 ,
maximum = > 30 ,
optional = > 1 ,
} ,
} ) ,
} ,
returns = > {
type = > 'string' ,
optional = > 1 ,
} ,
code = > $ update_vm_api ,
} ) ;
__PACKAGE__ - > register_method ( {
name = > 'update_vm' ,
path = > '{vmid}/config' ,
method = > 'PUT' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation." ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , $ vm_config_perm_list , any = > 1 ] ,
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > PVE::QemuServer:: json_config_properties (
{
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2013-06-07 11:41:58 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
delete = > {
type = > 'string' , format = > 'pve-configid-list' ,
description = > "A list of settings you want to delete." ,
optional = > 1 ,
} ,
2015-01-08 09:04:18 +01:00
revert = > {
type = > 'string' , format = > 'pve-configid-list' ,
description = > "Revert a pending change." ,
optional = > 1 ,
} ,
2013-06-07 11:41:58 +02:00
force = > {
type = > 'boolean' ,
description = > $ opt_force_description ,
optional = > 1 ,
requires = > 'delete' ,
} ,
digest = > {
type = > 'string' ,
description = > 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.' ,
maxLength = > 40 ,
optional = > 1 ,
} ,
} ) ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
& $ update_vm_api ( $ param , 1 ) ;
2011-08-23 07:47:04 +02:00
return undef ;
2013-06-07 11:41:58 +02:00
}
} ) ;
2011-08-23 07:47:04 +02:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'destroy_vm' ,
path = > '{vmid}' ,
2011-08-23 07:47:04 +02:00
method = > 'DELETE' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Destroy the vm (also delete all used/owned volumes)." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Allocate' ] ] ,
} ,
2011-08-23 07:47:04 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid_stopped } ) ,
2011-09-14 12:02:08 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
2011-08-23 07:47:04 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
2011-08-23 07:47:04 +02:00
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-08-23 07:47:04 +02:00
my $ vmid = $ param - > { vmid } ;
my $ skiplock = $ param - > { skiplock } ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-08-23 07:47:04 +02:00
2011-10-10 13:17:40 +02:00
# test if VM exists
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2011-10-10 13:17:40 +02:00
2012-01-27 09:35:26 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2011-08-23 07:47:04 +02:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_protection ( $ conf , "can't remove VM $vmid" ) ;
2015-09-03 15:35:37 +02:00
2015-08-28 18:16:08 +02:00
die "unable to remove VM $vmid - used in HA resources\n"
if PVE::HA::Config:: vm_is_ha_managed ( $ vmid ) ;
2015-08-28 13:26:40 +02:00
2017-05-23 18:01:13 +02:00
# do not allow destroy if there are replication jobs
my $ repl_conf = PVE::ReplicationConfig - > new ( ) ;
$ repl_conf - > check_for_existing_jobs ( $ vmid ) ;
2015-11-07 12:09:01 +01:00
# early tests (repeat after locking)
die "VM $vmid is running - destroy failed\n"
if PVE::QemuServer:: check_running ( $ vmid ) ;
2011-10-10 13:17:40 +02:00
my $ realcmd = sub {
2011-11-25 08:05:36 +01:00
my $ upid = shift ;
syslog ( 'info' , "destroy VM $vmid: $upid\n" ) ;
2011-10-10 13:17:40 +02:00
PVE::QemuServer:: vm_destroy ( $ storecfg , $ vmid , $ skiplock ) ;
2012-02-03 13:44:12 +01:00
2015-08-13 12:21:36 +02:00
PVE::AccessControl:: remove_vm_access ( $ vmid ) ;
2015-08-19 10:33:05 +02:00
PVE::Firewall:: remove_vmfw_conf ( $ vmid ) ;
2011-10-10 13:17:40 +02:00
} ;
2011-08-23 07:47:04 +02:00
2012-02-02 06:39:38 +01:00
return $ rpcenv - > fork_worker ( 'qmdestroy' , $ vmid , $ authuser , $ realcmd ) ;
2011-08-23 07:47:04 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'unlink' ,
path = > '{vmid}/unlink' ,
2011-08-23 07:47:04 +02:00
method = > 'PUT' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Unlink/delete disk images." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Config.Disk' ] ] ,
} ,
2011-08-23 07:47:04 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2011-08-23 07:47:04 +02:00
idlist = > {
type = > 'string' , format = > 'pve-configid-list' ,
description = > "A list of disk IDs you want to delete." ,
} ,
force = > {
type = > 'boolean' ,
description = > $ opt_force_description ,
optional = > 1 ,
} ,
} ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
$ param - > { delete } = extract_param ( $ param , 'idlist' ) ;
__PACKAGE__ - > update_vm ( $ param ) ;
return undef ;
} } ) ;
my $ sslcert ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vncproxy' ,
path = > '{vmid}/vncproxy' ,
2011-08-23 07:47:04 +02:00
method = > 'POST' ,
protected = > 1 ,
permissions = > {
2012-01-23 11:59:28 +01:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Console' ] ] ,
2011-08-23 07:47:04 +02:00
} ,
description = > "Creates a TCP VNC proxy connections." ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
2014-06-03 10:22:00 +02:00
websocket = > {
optional = > 1 ,
type = > 'boolean' ,
description = > "starts websockify instead of vncproxy" ,
} ,
2011-08-23 07:47:04 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-08-23 07:47:04 +02:00
additionalProperties = > 0 ,
properties = > {
user = > { type = > 'string' } ,
ticket = > { type = > 'string' } ,
cert = > { type = > 'string' } ,
port = > { type = > 'integer' } ,
upid = > { type = > 'string' } ,
} ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-08-23 07:47:04 +02:00
my $ vmid = $ param - > { vmid } ;
my $ node = $ param - > { node } ;
2014-06-17 08:02:43 +02:00
my $ websocket = $ param - > { websocket } ;
2011-08-23 07:47:04 +02:00
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid , $ node ) ; # check if VM exists
2013-07-31 09:19:36 +02:00
2012-01-19 09:31:40 +01:00
my $ authpath = "/vms/$vmid" ;
2012-02-02 06:39:38 +01:00
my $ ticket = PVE::AccessControl:: assemble_vnc_ticket ( $ authuser , $ authpath ) ;
2012-01-19 09:31:40 +01:00
2011-08-23 07:47:04 +02:00
$ sslcert = PVE::Tools:: file_get_contents ( "/etc/pve/pve-root-ca.pem" , 8192 )
if ! $ sslcert ;
2015-05-12 12:14:03 +02:00
my ( $ remip , $ family ) ;
2013-07-31 09:19:36 +02:00
my $ remcmd = [] ;
2012-01-27 09:35:26 +01:00
2011-11-03 07:39:01 +01:00
if ( $ node ne 'localhost' && $ node ne PVE::INotify:: nodename ( ) ) {
2015-05-12 12:14:03 +02:00
( $ remip , $ family ) = PVE::Cluster:: remote_node_ip ( $ node ) ;
2014-06-03 10:22:00 +02:00
# NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2017-11-17 10:22:55 +01:00
$ remcmd = [ '/usr/bin/ssh' , '-e' , 'none' , '-T' , '-o' , 'BatchMode=yes' , $ remip ] ;
2015-05-12 12:14:03 +02:00
} else {
$ family = PVE::Tools:: get_host_address_family ( $ node ) ;
2011-08-23 07:47:04 +02:00
}
2015-05-12 12:14:03 +02:00
my $ port = PVE::Tools:: next_vnc_port ( $ family ) ;
2012-01-27 09:35:26 +01:00
my $ timeout = 10 ;
2011-08-23 07:47:04 +02:00
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "starting vnc proxy $upid\n" ) ;
2013-07-31 09:19:36 +02:00
my $ cmd ;
2011-08-23 07:47:04 +02:00
2013-08-28 11:31:31 +02:00
if ( $ conf - > { vga } && ( $ conf - > { vga } =~ m/^serial\d+$/ ) ) {
2013-07-31 09:19:36 +02:00
2014-06-03 10:22:00 +02:00
2017-12-12 09:54:50 +01:00
my $ termcmd = [ '/usr/sbin/qm' , 'terminal' , $ vmid , '-iface' , $ conf - > { vga } , '-escape' , '0' ] ;
2017-12-11 14:55:33 +01:00
2013-07-31 09:19:36 +02:00
$ cmd = [ '/usr/bin/vncterm' , '-rfbport' , $ port ,
2013-09-05 07:44:52 +02:00
'-timeout' , $ timeout , '-authpath' , $ authpath ,
2017-12-11 14:55:33 +01:00
'-perm' , 'Sys.Console' ] ;
if ( $ param - > { websocket } ) {
$ ENV { PVE_VNC_TICKET } = $ ticket ; # pass ticket to vncterm
push @$ cmd , '-notls' , '-listen' , 'localhost' ;
}
push @$ cmd , '-c' , @$ remcmd , @$ termcmd ;
2017-02-07 12:18:34 +01:00
PVE::Tools:: run_command ( $ cmd ) ;
2017-12-11 14:55:33 +01:00
2013-07-31 09:19:36 +02:00
} else {
2011-08-23 07:47:04 +02:00
2014-06-18 11:02:57 +02:00
$ ENV { LC_PVE_TICKET } = $ ticket if $ websocket ; # set ticket with "qm vncproxy"
2017-02-07 12:18:34 +01:00
$ cmd = [ @$ remcmd , "/usr/sbin/qm" , 'vncproxy' , $ vmid ] ;
my $ sock = IO::Socket::IP - > new (
2017-05-16 16:21:01 +02:00
ReuseAddr = > 1 ,
2017-02-07 12:18:34 +01:00
Listen = > 1 ,
LocalPort = > $ port ,
Proto = > 'tcp' ,
GetAddrInfoFlags = > 0 ,
) or die "failed to create socket: $!\n" ;
# Inside the worker we shouldn't have any previous alarms
# running anyway...:
alarm ( 0 ) ;
local $ SIG { ALRM } = sub { die "connection timed out\n" } ;
alarm $ timeout ;
accept ( my $ cli , $ sock ) or die "connection failed: $!\n" ;
2017-04-18 09:24:40 +02:00
alarm ( 0 ) ;
2017-02-07 12:18:34 +01:00
close ( $ sock ) ;
if ( PVE::Tools:: run_command ( $ cmd ,
output = > '>&' . fileno ( $ cli ) ,
input = > '<&' . fileno ( $ cli ) ,
noerr = > 1 ) != 0 )
{
die "Failed to run vncproxy.\n" ;
}
2013-07-31 09:19:36 +02:00
}
2011-08-23 07:47:04 +02:00
return ;
} ;
2017-01-23 13:51:21 +01:00
my $ upid = $ rpcenv - > fork_worker ( 'vncproxy' , $ vmid , $ authuser , $ realcmd , 1 ) ;
2011-08-23 07:47:04 +02:00
2012-10-24 08:59:31 +02:00
PVE::Tools:: wait_for_vnc_port ( $ port ) ;
2011-08-23 07:47:04 +02:00
return {
2012-02-02 06:39:38 +01:00
user = > $ authuser ,
2011-08-23 07:47:04 +02:00
ticket = > $ ticket ,
2012-01-27 09:35:26 +01:00
port = > $ port ,
upid = > $ upid ,
cert = > $ sslcert ,
2011-08-23 07:47:04 +02:00
} ;
} } ) ;
2017-12-11 14:55:32 +01:00
__PACKAGE__ - > register_method ( {
name = > 'termproxy' ,
path = > '{vmid}/termproxy' ,
method = > 'POST' ,
protected = > 1 ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Console' ] ] ,
} ,
description = > "Creates a TCP proxy connections." ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
serial = > {
optional = > 1 ,
type = > 'string' ,
enum = > [ qw( serial0 serial1 serial2 serial3 ) ] ,
description = > "opens a serial terminal (defaults to display)" ,
} ,
} ,
} ,
returns = > {
additionalProperties = > 0 ,
properties = > {
user = > { type = > 'string' } ,
ticket = > { type = > 'string' } ,
port = > { type = > 'integer' } ,
upid = > { type = > 'string' } ,
} ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ vmid = $ param - > { vmid } ;
my $ node = $ param - > { node } ;
my $ serial = $ param - > { serial } ;
my $ conf = PVE::QemuConfig - > load_config ( $ vmid , $ node ) ; # check if VM exists
if ( ! defined ( $ serial ) ) {
if ( $ conf - > { vga } && $ conf - > { vga } =~ m/^serial\d+$/ ) {
$ serial = $ conf - > { vga } ;
}
}
my $ authpath = "/vms/$vmid" ;
my $ ticket = PVE::AccessControl:: assemble_vnc_ticket ( $ authuser , $ authpath ) ;
my ( $ remip , $ family ) ;
if ( $ node ne 'localhost' && $ node ne PVE::INotify:: nodename ( ) ) {
( $ remip , $ family ) = PVE::Cluster:: remote_node_ip ( $ node ) ;
} else {
$ family = PVE::Tools:: get_host_address_family ( $ node ) ;
}
my $ port = PVE::Tools:: next_vnc_port ( $ family ) ;
my $ remcmd = $ remip ?
[ '/usr/bin/ssh' , '-e' , 'none' , '-t' , $ remip , '--' ] : [] ;
2017-12-12 09:54:50 +01:00
my $ termcmd = [ '/usr/sbin/qm' , 'terminal' , $ vmid , '-escape' , '0' ] ;
2017-12-11 14:55:32 +01:00
push @$ termcmd , '-iface' , $ serial if $ serial ;
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "starting qemu termproxy $upid\n" ) ;
my $ cmd = [ '/usr/bin/termproxy' , $ port , '--path' , $ authpath ,
'--perm' , 'VM.Console' , '--' ] ;
push @$ cmd , @$ remcmd , @$ termcmd ;
PVE::Tools:: run_command ( $ cmd ) ;
} ;
my $ upid = $ rpcenv - > fork_worker ( 'vncproxy' , $ vmid , $ authuser , $ realcmd , 1 ) ;
PVE::Tools:: wait_for_vnc_port ( $ port ) ;
return {
user = > $ authuser ,
ticket = > $ ticket ,
port = > $ port ,
upid = > $ upid ,
} ;
} } ) ;
2014-06-18 11:02:57 +02:00
__PACKAGE__ - > register_method ( {
name = > 'vncwebsocket' ,
path = > '{vmid}/vncwebsocket' ,
method = > 'GET' ,
permissions = > {
2014-06-24 16:10:30 +02:00
description = > "You also need to pass a valid ticket (vncticket)." ,
2014-06-18 11:02:57 +02:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Console' ] ] ,
} ,
2014-06-24 07:44:17 +02:00
description = > "Opens a weksocket for VNC traffic." ,
2014-06-18 11:02:57 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
2014-06-24 16:10:30 +02:00
vncticket = > {
description = > "Ticket from previous call to vncproxy." ,
type = > 'string' ,
maxLength = > 512 ,
} ,
2014-06-18 11:02:57 +02:00
port = > {
description = > "Port number returned by previous vncproxy call." ,
type = > 'integer' ,
minimum = > 5900 ,
maximum = > 5999 ,
} ,
} ,
} ,
returns = > {
type = > "object" ,
properties = > {
port = > { type = > 'string' } ,
} ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ vmid = $ param - > { vmid } ;
my $ node = $ param - > { node } ;
2014-06-24 16:10:30 +02:00
my $ authpath = "/vms/$vmid" ;
PVE::AccessControl:: verify_vnc_ticket ( $ param - > { vncticket } , $ authuser , $ authpath ) ;
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid , $ node ) ; # VM exists ?
2014-06-18 11:02:57 +02:00
# Note: VNC ports are acessible from outside, so we do not gain any
# security if we verify that $param->{port} belongs to VM $vmid. This
# check is done by verifying the VNC ticket (inside VNC protocol).
my $ port = $ param - > { port } ;
2014-11-21 12:31:56 +01:00
2014-06-18 11:02:57 +02:00
return { port = > $ port } ;
} } ) ;
2013-06-25 12:09:05 +02:00
__PACKAGE__ - > register_method ( {
name = > 'spiceproxy' ,
path = > '{vmid}/spiceproxy' ,
2013-12-09 08:17:52 +01:00
method = > 'POST' ,
2013-06-25 12:09:05 +02:00
protected = > 1 ,
2013-12-09 08:17:52 +01:00
proxyto = > 'node' ,
2013-06-25 12:09:05 +02:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Console' ] ] ,
} ,
description = > "Returns a SPICE configuration to connect to the VM." ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
2013-12-10 10:46:50 +01:00
proxy = > get_standard_option ( 'spice-proxy' , { optional = > 1 } ) ,
2013-06-25 12:09:05 +02:00
} ,
} ,
2013-12-10 10:46:50 +01:00
returns = > get_standard_option ( 'remote-viewer-config' ) ,
2013-06-25 12:09:05 +02:00
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ vmid = $ param - > { vmid } ;
my $ node = $ param - > { node } ;
2013-07-18 08:00:03 +02:00
my $ proxy = $ param - > { proxy } ;
2013-06-25 12:09:05 +02:00
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid , $ node ) ;
2015-08-27 14:56:36 +02:00
my $ title = "VM $vmid" ;
$ title . = " - " . $ conf - > { name } if $ conf - > { name } ;
2013-06-25 12:09:05 +02:00
2013-07-17 11:33:02 +02:00
my $ port = PVE::QemuServer:: spice_port ( $ vmid ) ;
2013-12-10 10:46:50 +01:00
2014-11-21 12:31:56 +01:00
my ( $ ticket , undef , $ remote_viewer_config ) =
2013-12-10 10:46:50 +01:00
PVE::AccessControl:: remote_viewer_config ( $ authuser , $ vmid , $ node , $ proxy , $ title , $ port ) ;
2014-11-21 12:31:56 +01:00
2013-06-25 12:09:05 +02:00
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "set_password" , protocol = > 'spice' , password = > $ ticket ) ;
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "expire_password" , protocol = > 'spice' , time = > "+30" ) ;
2014-11-21 12:31:56 +01:00
2013-12-10 10:46:50 +01:00
return $ remote_viewer_config ;
2013-06-25 12:09:05 +02:00
} } ) ;
2011-10-10 13:17:40 +02:00
__PACKAGE__ - > register_method ( {
name = > 'vmcmdidx' ,
2012-01-27 09:35:26 +01:00
path = > '{vmid}/status' ,
2011-10-10 13:17:40 +02:00
method = > 'GET' ,
proxyto = > 'node' ,
description = > "Directory index" ,
2012-02-02 06:39:38 +01:00
permissions = > {
user = > 'all' ,
} ,
2011-10-10 13:17:40 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
} ,
} ,
returns = > {
type = > 'array' ,
items = > {
type = > "object" ,
properties = > {
subdir = > { type = > 'string' } ,
} ,
} ,
links = > [ { rel = > 'child' , href = > "{subdir}" } ] ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
# test if VM exists
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ param - > { vmid } ) ;
2011-10-10 13:17:40 +02:00
my $ res = [
{ subdir = > 'current' } ,
{ subdir = > 'start' } ,
{ subdir = > 'stop' } ,
] ;
2012-01-27 09:35:26 +01:00
2011-10-10 13:17:40 +02:00
return $ res ;
} } ) ;
2011-08-23 07:47:04 +02:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_status' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/status/current' ,
2011-08-23 07:47:04 +02:00
method = > 'GET' ,
proxyto = > 'node' ,
protected = > 1 , # qemu pid files are only readable by root
description = > "Get virtual machine status." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Audit' ] ] ,
} ,
2011-08-23 07:47:04 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
} ,
} ,
returns = > { type = > 'object' } ,
code = > sub {
my ( $ param ) = @ _ ;
# test if VM exists
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ param - > { vmid } ) ;
2011-08-23 07:47:04 +02:00
2012-07-13 09:25:58 +02:00
my $ vmstatus = PVE::QemuServer:: vmstatus ( $ param - > { vmid } , 1 ) ;
2011-12-22 13:10:27 +01:00
my $ status = $ vmstatus - > { $ param - > { vmid } } ;
2011-08-23 07:47:04 +02:00
2016-04-18 14:52:02 +02:00
$ status - > { ha } = PVE::HA::Config:: get_service_status ( "vm:$param->{vmid}" ) ;
2011-12-22 13:10:27 +01:00
2013-07-24 11:42:48 +02:00
$ status - > { spice } = 1 if PVE::QemuServer:: vga_conf_has_spice ( $ conf - > { vga } ) ;
2013-06-26 13:37:14 +02:00
2011-12-22 13:10:27 +01:00
return $ status ;
2011-08-23 07:47:04 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_start' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/status/start' ,
method = > 'POST' ,
2011-08-23 07:47:04 +02:00
protected = > 1 ,
proxyto = > 'node' ,
2011-10-10 13:17:40 +02:00
description = > "Start virtual machine." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.PowerMgmt' ] ] ,
} ,
2011-08-23 07:47:04 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-12-09 11:40:34 +01:00
vmid = > get_standard_option ( 'pve-vmid' ,
{ completion = > \ & PVE::QemuServer:: complete_vmid_stopped } ) ,
2011-09-14 12:02:08 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
stateuri = > get_standard_option ( 'pve-qm-stateuri' ) ,
2012-08-21 12:21:51 +02:00
migratedfrom = > get_standard_option ( 'pve-node' , { optional = > 1 } ) ,
2016-10-31 09:42:31 +01:00
migration_type = > {
type = > 'string' ,
enum = > [ 'secure' , 'insecure' ] ,
description = > "Migration traffic is encrypted using an SSH " .
"tunnel by default. On secure, completely private networks " .
"this can be disabled to increase performance." ,
optional = > 1 ,
} ,
migration_network = > {
2016-11-23 06:40:33 +01:00
type = > 'string' , format = > 'CIDR' ,
2016-10-31 09:42:31 +01:00
description = > "CIDR of the (sub) network that is used for migration." ,
optional = > 1 ,
} ,
2013-06-05 09:27:31 +02:00
machine = > get_standard_option ( 'pve-qm-machine' ) ,
2017-01-03 15:03:15 +01:00
targetstorage = > {
2017-01-05 09:54:07 +01:00
description = > "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)" ,
2017-01-03 15:03:15 +01:00
type = > 'string' ,
optional = > 1
}
2011-08-23 07:47:04 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
2011-08-23 07:47:04 +02:00
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-08-23 07:47:04 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
2013-06-05 09:27:31 +02:00
my $ machine = extract_param ( $ param , 'machine' ) ;
2011-09-14 12:02:08 +02:00
my $ stateuri = extract_param ( $ param , 'stateuri' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { stateuri = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ stateuri && $ authuser ne 'root@pam' ;
2011-09-14 12:02:08 +02:00
2011-08-23 07:47:04 +02:00
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-08-23 07:47:04 +02:00
2012-08-21 12:21:51 +02:00
my $ migratedfrom = extract_param ( $ param , 'migratedfrom' ) ;
raise_param_exc ( { migratedfrom = > "Only root may use this option." } )
if $ migratedfrom && $ authuser ne 'root@pam' ;
2016-10-31 09:42:31 +01:00
my $ migration_type = extract_param ( $ param , 'migration_type' ) ;
raise_param_exc ( { migration_type = > "Only root may use this option." } )
if $ migration_type && $ authuser ne 'root@pam' ;
my $ migration_network = extract_param ( $ param , 'migration_network' ) ;
raise_param_exc ( { migration_network = > "Only root may use this option." } )
if $ migration_network && $ authuser ne 'root@pam' ;
2017-01-03 15:03:15 +01:00
my $ targetstorage = extract_param ( $ param , 'targetstorage' ) ;
raise_param_exc ( { targetstorage = > "Only root may use this option." } )
if $ targetstorage && $ authuser ne 'root@pam' ;
raise_param_exc ( { targetstorage = > "targetstorage can only by used with migratedfrom." } )
if $ targetstorage && ! $ migratedfrom ;
2013-07-24 12:13:16 +02:00
# read spice ticket from STDIN
my $ spice_ticket ;
if ( $ stateuri && ( $ stateuri eq 'tcp' ) && $ migratedfrom && ( $ rpcenv - > { type } eq 'cli' ) ) {
2018-01-22 10:52:11 +01:00
if ( defined ( my $ line = <STDIN> ) ) {
2013-08-12 09:47:02 +02:00
chomp $ line ;
$ spice_ticket = $ line ;
}
2013-07-24 12:13:16 +02:00
}
2016-01-08 14:32:49 +01:00
PVE::Cluster:: check_cfs_quorum ( ) ;
2012-01-27 09:35:26 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2011-10-10 13:17:40 +02:00
2015-03-27 12:47:56 +01:00
if ( PVE::HA::Config:: vm_is_ha_managed ( $ vmid ) && ! $ stateuri &&
2012-03-27 12:21:15 +02:00
$ rpcenv - > { type } ne 'ha' ) {
2011-10-10 13:17:40 +02:00
2012-03-27 10:37:39 +02:00
my $ hacmd = sub {
my $ upid = shift ;
2011-10-10 13:17:40 +02:00
2015-04-17 13:10:32 +02:00
my $ service = "vm:$vmid" ;
2011-10-10 13:17:40 +02:00
2016-11-18 17:42:17 +01:00
my $ cmd = [ 'ha-manager' , 'set' , $ service , '--state' , 'started' ] ;
2012-03-27 10:37:39 +02:00
2017-06-21 07:20:11 +02:00
print "Requesting HA start for VM $vmid\n" ;
2012-03-27 10:37:39 +02:00
PVE::Tools:: run_command ( $ cmd ) ;
return ;
} ;
return $ rpcenv - > fork_worker ( 'hastart' , $ vmid , $ authuser , $ hacmd ) ;
} else {
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "start VM $vmid: $upid\n" ) ;
2013-09-05 07:44:52 +02:00
PVE::QemuServer:: vm_start ( $ storecfg , $ vmid , $ stateuri , $ skiplock , $ migratedfrom , undef ,
2017-01-03 15:03:15 +01:00
$ machine , $ spice_ticket , $ migration_network , $ migration_type , $ targetstorage ) ;
2012-03-27 10:37:39 +02:00
return ;
} ;
2011-10-10 13:17:40 +02:00
2012-03-27 10:37:39 +02:00
return $ rpcenv - > fork_worker ( 'qmstart' , $ vmid , $ authuser , $ realcmd ) ;
}
2011-10-10 13:17:40 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_stop' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/status/stop' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
2016-03-04 06:22:22 +01:00
description = > "Stop virtual machine. The qemu process will exit immediately. This" .
2016-03-03 13:46:07 +01:00
"is akin to pulling the power plug of a running computer and may damage the VM data" ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.PowerMgmt' ] ] ,
} ,
2011-10-10 13:17:40 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-12-09 11:40:34 +01:00
vmid = > get_standard_option ( 'pve-vmid' ,
{ completion = > \ & PVE::QemuServer:: complete_vmid_running } ) ,
2011-10-10 13:17:40 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
2014-07-29 06:51:02 +02:00
migratedfrom = > get_standard_option ( 'pve-node' , { optional = > 1 } ) ,
2011-10-11 11:58:34 +02:00
timeout = > {
description = > "Wait maximal timeout seconds." ,
type = > 'integer' ,
minimum = > 0 ,
optional = > 1 ,
2012-01-17 11:56:56 +01:00
} ,
keepActive = > {
2016-10-13 14:33:28 +02:00
description = > "Do not deactivate storage volumes." ,
2012-01-17 11:56:56 +01:00
type = > 'boolean' ,
optional = > 1 ,
default = > 0 ,
2011-10-11 11:58:34 +02:00
}
2011-10-10 13:17:40 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-10-10 13:17:40 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-10-10 13:17:40 +02:00
2012-01-17 11:56:56 +01:00
my $ keepActive = extract_param ( $ param , 'keepActive' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { keepActive = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ keepActive && $ authuser ne 'root@pam' ;
2012-01-17 11:56:56 +01:00
2012-08-23 10:28:41 +02:00
my $ migratedfrom = extract_param ( $ param , 'migratedfrom' ) ;
raise_param_exc ( { migratedfrom = > "Only root may use this option." } )
if $ migratedfrom && $ authuser ne 'root@pam' ;
2011-11-25 08:05:36 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2015-03-27 12:47:56 +01:00
if ( PVE::HA::Config:: vm_is_ha_managed ( $ vmid ) && ( $ rpcenv - > { type } ne 'ha' ) && ! defined ( $ migratedfrom ) ) {
2011-10-10 13:17:40 +02:00
2012-03-27 10:37:39 +02:00
my $ hacmd = sub {
my $ upid = shift ;
2011-10-10 13:17:40 +02:00
2015-04-17 13:10:32 +02:00
my $ service = "vm:$vmid" ;
2011-10-11 11:58:34 +02:00
2016-11-23 06:23:21 +01:00
my $ cmd = [ 'ha-manager' , 'set' , $ service , '--state' , 'stopped' ] ;
2012-03-27 10:37:39 +02:00
2017-06-21 07:20:11 +02:00
print "Requesting HA stop for VM $vmid\n" ;
2012-03-27 10:37:39 +02:00
PVE::Tools:: run_command ( $ cmd ) ;
2011-10-10 13:17:40 +02:00
2012-03-27 10:37:39 +02:00
return ;
} ;
return $ rpcenv - > fork_worker ( 'hastop' , $ vmid , $ authuser , $ hacmd ) ;
} else {
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "stop VM $vmid: $upid\n" ) ;
PVE::QemuServer:: vm_stop ( $ storecfg , $ vmid , $ skiplock , 0 ,
2012-08-23 10:28:41 +02:00
$ param - > { timeout } , 0 , 1 , $ keepActive , $ migratedfrom ) ;
2012-03-27 10:37:39 +02:00
return ;
} ;
return $ rpcenv - > fork_worker ( 'qmstop' , $ vmid , $ authuser , $ realcmd ) ;
}
2011-10-10 13:17:40 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_reset' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/status/reset' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Reset virtual machine." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.PowerMgmt' ] ] ,
} ,
2011-10-10 13:17:40 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-12-09 11:40:34 +01:00
vmid = > get_standard_option ( 'pve-vmid' ,
{ completion = > \ & PVE::QemuServer:: complete_vmid_running } ) ,
2011-10-10 13:17:40 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-10-10 13:17:40 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-10-10 13:17:40 +02:00
2011-11-25 08:05:36 +01:00
die "VM $vmid not running\n" if ! PVE::QemuServer:: check_running ( $ vmid ) ;
2011-10-10 13:17:40 +02:00
my $ realcmd = sub {
my $ upid = shift ;
2011-08-23 07:47:04 +02:00
PVE::QemuServer:: vm_reset ( $ vmid , $ skiplock ) ;
2011-10-10 13:17:40 +02:00
return ;
} ;
2012-02-02 06:39:38 +01:00
return $ rpcenv - > fork_worker ( 'qmreset' , $ vmid , $ authuser , $ realcmd ) ;
2011-10-10 13:17:40 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_shutdown' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/status/shutdown' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
2016-03-03 13:46:07 +01:00
description = > "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
"This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.PowerMgmt' ] ] ,
} ,
2011-10-10 13:17:40 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-12-09 11:40:34 +01:00
vmid = > get_standard_option ( 'pve-vmid' ,
{ completion = > \ & PVE::QemuServer:: complete_vmid_running } ) ,
2011-10-10 13:17:40 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
2011-10-11 11:58:34 +02:00
timeout = > {
description = > "Wait maximal timeout seconds." ,
type = > 'integer' ,
minimum = > 0 ,
optional = > 1 ,
2011-12-15 12:47:39 +01:00
} ,
forceStop = > {
description = > "Make sure the VM stops." ,
type = > 'boolean' ,
optional = > 1 ,
default = > 0 ,
2012-01-17 11:56:56 +01:00
} ,
keepActive = > {
2016-10-13 14:33:28 +02:00
description = > "Do not deactivate storage volumes." ,
2012-01-17 11:56:56 +01:00
type = > 'boolean' ,
optional = > 1 ,
default = > 0 ,
2011-10-11 11:58:34 +02:00
}
2011-10-10 13:17:40 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-10-10 13:17:40 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-10-10 13:17:40 +02:00
2012-01-17 11:56:56 +01:00
my $ keepActive = extract_param ( $ param , 'keepActive' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { keepActive = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ keepActive && $ authuser ne 'root@pam' ;
2012-01-17 11:56:56 +01:00
2011-11-30 09:33:43 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2016-04-12 10:54:21 +02:00
my $ shutdown = 1 ;
# if vm is paused, do not shutdown (but stop if forceStop = 1)
# otherwise, we will infer a shutdown command, but run into the timeout,
# then when the vm is resumed, it will instantly shutdown
#
# checking the qmp status here to get feedback to the gui/cli/api
# and the status query should not take too long
my $ qmpstatus ;
eval {
$ qmpstatus = PVE::QemuServer:: vm_qmp_command ( $ vmid , { execute = > "query-status" } , 0 ) ;
} ;
my $ err = $@ if $@ ;
if ( ! $ err && $ qmpstatus - > { status } eq "paused" ) {
if ( $ param - > { forceStop } ) {
warn "VM is paused - stop instead of shutdown\n" ;
$ shutdown = 0 ;
} else {
die "VM is paused - cannot shutdown\n" ;
}
}
2016-11-23 08:03:08 +01:00
if ( PVE::HA::Config:: vm_is_ha_managed ( $ vmid ) &&
( $ rpcenv - > { type } ne 'ha' ) ) {
2011-10-10 13:17:40 +02:00
2016-11-23 08:03:08 +01:00
my $ hacmd = sub {
my $ upid = shift ;
2011-10-10 13:17:40 +02:00
2016-11-23 08:03:08 +01:00
my $ service = "vm:$vmid" ;
2011-10-11 11:58:34 +02:00
2016-11-23 08:03:08 +01:00
my $ cmd = [ 'ha-manager' , 'set' , $ service , '--state' , 'stopped' ] ;
2017-06-21 07:20:11 +02:00
print "Requesting HA stop for VM $vmid\n" ;
2016-11-23 08:03:08 +01:00
PVE::Tools:: run_command ( $ cmd ) ;
return ;
} ;
return $ rpcenv - > fork_worker ( 'hastop' , $ vmid , $ authuser , $ hacmd ) ;
} else {
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "shutdown VM $vmid: $upid\n" ) ;
2011-10-10 13:17:40 +02:00
2016-11-23 08:03:08 +01:00
PVE::QemuServer:: vm_stop ( $ storecfg , $ vmid , $ skiplock , 0 , $ param - > { timeout } ,
$ shutdown , $ param - > { forceStop } , $ keepActive ) ;
return ;
} ;
return $ rpcenv - > fork_worker ( 'qmshutdown' , $ vmid , $ authuser , $ realcmd ) ;
}
2011-10-10 13:17:40 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_suspend' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/status/suspend' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Suspend virtual machine." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.PowerMgmt' ] ] ,
} ,
2011-10-10 13:17:40 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-12-09 11:40:34 +01:00
vmid = > get_standard_option ( 'pve-vmid' ,
{ completion = > \ & PVE::QemuServer:: complete_vmid_running } ) ,
2011-10-10 13:17:40 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-10-10 13:17:40 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-10-10 13:17:40 +02:00
2011-11-25 08:05:36 +01:00
die "VM $vmid not running\n" if ! PVE::QemuServer:: check_running ( $ vmid ) ;
2011-10-10 13:17:40 +02:00
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "suspend VM $vmid: $upid\n" ) ;
2011-08-23 07:47:04 +02:00
PVE::QemuServer:: vm_suspend ( $ vmid , $ skiplock ) ;
2011-10-10 13:17:40 +02:00
return ;
} ;
2012-02-02 06:39:38 +01:00
return $ rpcenv - > fork_worker ( 'qmsuspend' , $ vmid , $ authuser , $ realcmd ) ;
2011-10-10 13:17:40 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_resume' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/status/resume' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Resume virtual machine." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.PowerMgmt' ] ] ,
} ,
2011-10-10 13:17:40 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-12-09 11:40:34 +01:00
vmid = > get_standard_option ( 'pve-vmid' ,
{ completion = > \ & PVE::QemuServer:: complete_vmid_running } ) ,
2011-10-10 13:17:40 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
2015-10-14 11:06:06 +02:00
nocheck = > { type = > 'boolean' , optional = > 1 } ,
2011-10-10 13:17:40 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-10-10 13:17:40 +02:00
type = > 'string' ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-10-10 13:17:40 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-10-10 13:17:40 +02:00
2015-10-14 11:06:06 +02:00
my $ nocheck = extract_param ( $ param , 'nocheck' ) ;
die "VM $vmid not running\n" if ! PVE::QemuServer:: check_running ( $ vmid , $ nocheck ) ;
2011-11-25 08:05:36 +01:00
2011-10-10 13:17:40 +02:00
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "resume VM $vmid: $upid\n" ) ;
2015-10-14 11:06:06 +02:00
PVE::QemuServer:: vm_resume ( $ vmid , $ skiplock , $ nocheck ) ;
2011-08-23 07:47:04 +02:00
2011-10-10 13:17:40 +02:00
return ;
} ;
2012-02-02 06:39:38 +01:00
return $ rpcenv - > fork_worker ( 'qmresume' , $ vmid , $ authuser , $ realcmd ) ;
2011-10-10 13:17:40 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'vm_sendkey' ,
2011-10-10 13:17:40 +02:00
path = > '{vmid}/sendkey' ,
method = > 'PUT' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Send key event to virtual machine." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Console' ] ] ,
} ,
2011-10-10 13:17:40 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-12-09 11:40:34 +01:00
vmid = > get_standard_option ( 'pve-vmid' ,
{ completion = > \ & PVE::QemuServer:: complete_vmid_running } ) ,
2011-10-10 13:17:40 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
key = > {
description = > "The key (qemu monitor encoding)." ,
type = > 'string'
}
} ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-10-10 13:17:40 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ skiplock && $ authuser ne 'root@pam' ;
2011-10-10 13:17:40 +02:00
PVE::QemuServer:: vm_sendkey ( $ vmid , $ skiplock , $ param - > { key } ) ;
return ;
2011-08-23 07:47:04 +02:00
} } ) ;
2012-12-27 16:06:55 +01:00
__PACKAGE__ - > register_method ( {
name = > 'vm_feature' ,
path = > '{vmid}/feature' ,
method = > 'GET' ,
proxyto = > 'node' ,
2013-04-30 11:46:38 +02:00
protected = > 1 ,
2012-12-27 16:06:55 +01:00
description = > "Check if feature for virtual machine is available." ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Audit' ] ] ,
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
feature = > {
description = > "Feature to check." ,
type = > 'string' ,
2013-05-03 12:30:43 +02:00
enum = > [ 'snapshot' , 'clone' , 'copy' ] ,
2012-12-27 16:06:55 +01:00
} ,
snapname = > get_standard_option ( 'pve-snapshot-name' , {
optional = > 1 ,
} ) ,
} ,
} ,
returns = > {
2013-05-06 08:56:17 +02:00
type = > "object" ,
properties = > {
hasFeature = > { type = > 'boolean' } ,
2013-06-11 07:26:43 +02:00
nodes = > {
2013-05-06 08:56:17 +02:00
type = > 'array' ,
items = > { type = > 'string' } ,
}
} ,
2012-12-27 16:06:55 +01:00
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ snapname = extract_param ( $ param , 'snapname' ) ;
my $ feature = extract_param ( $ param , 'feature' ) ;
my $ running = PVE::QemuServer:: check_running ( $ vmid ) ;
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2012-12-27 16:06:55 +01:00
if ( $ snapname ) {
my $ snap = $ conf - > { snapshots } - > { $ snapname } ;
die "snapshot '$snapname' does not exist\n" if ! defined ( $ snap ) ;
$ conf = $ snap ;
}
my $ storecfg = PVE::Storage:: config ( ) ;
2013-05-06 08:56:17 +02:00
my $ nodelist = PVE::QemuServer:: shared_nodes ( $ conf , $ storecfg ) ;
2016-03-07 12:41:13 +01:00
my $ hasFeature = PVE::QemuConfig - > has_feature ( $ feature , $ conf , $ storecfg , $ snapname , $ running ) ;
2013-06-11 07:26:43 +02:00
2013-05-06 08:56:17 +02:00
return {
hasFeature = > $ hasFeature ,
nodes = > [ keys %$ nodelist ] ,
2013-06-11 07:26:43 +02:00
} ;
2012-12-27 16:06:55 +01:00
} } ) ;
2013-04-29 09:30:15 +02:00
__PACKAGE__ - > register_method ( {
2013-05-02 11:42:22 +02:00
name = > 'clone_vm' ,
path = > '{vmid}/clone' ,
2013-04-29 09:30:15 +02:00
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
2013-04-30 06:17:49 +02:00
description = > "Create a copy of virtual machine/template." ,
2013-04-29 09:30:15 +02:00
permissions = > {
2013-05-02 11:42:22 +02:00
description = > "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2013-04-29 09:30:15 +02:00
"on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
"'Datastore.AllocateSpace' on any used storage." ,
2013-04-30 11:46:38 +02:00
check = >
[ 'and' ,
2013-05-02 11:42:22 +02:00
[ 'perm' , '/vms/{vmid}' , [ 'VM.Clone' ] ] ,
2013-04-30 11:46:38 +02:00
[ 'or' ,
2013-04-29 09:30:15 +02:00
[ 'perm' , '/vms/{newid}' , [ 'VM.Allocate' ] ] ,
[ 'perm' , '/pool/{pool}' , [ 'VM.Allocate' ] , require_param = > 'pool' ] ,
] ,
]
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2013-05-02 11:42:22 +02:00
newid = > get_standard_option ( 'pve-vmid' , { description = > 'VMID for the clone.' } ) ,
2013-04-30 09:54:34 +02:00
name = > {
optional = > 1 ,
type = > 'string' , format = > 'dns-name' ,
description = > "Set a name for the new VM." ,
} ,
description = > {
optional = > 1 ,
type = > 'string' ,
description = > "Description for the new VM." ,
} ,
2013-04-30 11:46:38 +02:00
pool = > {
2013-04-29 09:30:15 +02:00
optional = > 1 ,
type = > 'string' , format = > 'pve-poolid' ,
description = > "Add the new VM to the specified pool." ,
} ,
2013-04-30 09:31:23 +02:00
snapname = > get_standard_option ( 'pve-snapshot-name' , {
optional = > 1 ,
} ) ,
2013-04-30 07:08:27 +02:00
storage = > get_standard_option ( 'pve-storage-id' , {
2013-05-02 11:42:22 +02:00
description = > "Target storage for full clone." ,
2013-04-30 07:40:43 +02:00
requires = > 'full' ,
2013-04-30 07:08:27 +02:00
optional = > 1 ,
} ) ,
2013-04-30 11:44:39 +02:00
'format' = > {
2013-04-30 10:34:08 +02:00
description = > "Target format for file storage." ,
requires = > 'full' ,
type = > 'string' ,
optional = > 1 ,
2013-04-30 11:44:39 +02:00
enum = > [ 'raw' , 'qcow2' , 'vmdk' ] ,
2013-04-30 10:34:08 +02:00
} ,
2013-04-29 09:30:15 +02:00
full = > {
optional = > 1 ,
2013-04-30 11:44:39 +02:00
type = > 'boolean' ,
description = > "Create a full copy of all disk. This is always done when " .
2013-05-02 11:42:22 +02:00
"you clone a normal VM. For VM templates, we try to create a linked clone by default." ,
2013-04-29 09:30:15 +02:00
default = > 0 ,
} ,
2013-04-30 11:46:38 +02:00
target = > get_standard_option ( 'pve-node' , {
2013-04-30 11:44:39 +02:00
description = > "Target node. Only allowed if the original VM is on shared storage." ,
optional = > 1 ,
} ) ,
} ,
2013-04-29 09:30:15 +02:00
} ,
returns = > {
type = > 'string' ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2013-04-30 11:44:39 +02:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2013-04-29 09:30:15 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ newid = extract_param ( $ param , 'newid' ) ;
my $ pool = extract_param ( $ param , 'pool' ) ;
if ( defined ( $ pool ) ) {
$ rpcenv - > check_pool_exist ( $ pool ) ;
}
2013-04-30 11:44:39 +02:00
my $ snapname = extract_param ( $ param , 'snapname' ) ;
2013-04-30 09:31:23 +02:00
2013-04-30 07:08:27 +02:00
my $ storage = extract_param ( $ param , 'storage' ) ;
2013-04-30 10:34:08 +02:00
my $ format = extract_param ( $ param , 'format' ) ;
2013-04-30 11:44:39 +02:00
my $ target = extract_param ( $ param , 'target' ) ;
my $ localnode = PVE::INotify:: nodename ( ) ;
2013-05-02 06:33:45 +02:00
undef $ target if $ target && ( $ target eq $ localnode || $ target eq 'localhost' ) ;
2013-04-30 11:44:39 +02:00
PVE::Cluster:: check_node_exists ( $ target ) if $ target ;
2013-04-29 09:30:15 +02:00
my $ storecfg = PVE::Storage:: config ( ) ;
2013-05-13 11:16:21 +02:00
if ( $ storage ) {
# check if storage is enabled on local node
PVE::Storage:: storage_check_enabled ( $ storecfg , $ storage ) ;
if ( $ target ) {
# check if storage is available on target node
PVE::Storage:: storage_check_node ( $ storecfg , $ storage , $ target ) ;
# clone only works if target storage is shared
my $ scfg = PVE::Storage:: storage_config ( $ storecfg , $ storage ) ;
die "can't clone to non-shared storage '$storage'\n" if ! $ scfg - > { shared } ;
}
}
2013-04-30 11:44:39 +02:00
PVE::Cluster:: check_cfs_quorum ( ) ;
2013-04-29 09:30:15 +02:00
2013-04-30 07:40:43 +02:00
my $ running = PVE::QemuServer:: check_running ( $ vmid ) || 0 ;
# exclusive lock if VM is running - else shared lock is enough;
my $ shared_lock = $ running ? 0 : 1 ;
2013-05-02 11:42:22 +02:00
my $ clonefn = sub {
2013-04-29 09:30:15 +02:00
2013-05-02 07:10:46 +02:00
# do all tests after lock
# we also try to do all tests before we fork the worker
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2013-04-29 09:30:15 +02:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_lock ( $ conf ) ;
2013-04-29 09:30:15 +02:00
2013-04-30 07:40:43 +02:00
my $ verify_running = PVE::QemuServer:: check_running ( $ vmid ) || 0 ;
2013-04-29 09:30:15 +02:00
2013-04-30 07:40:43 +02:00
die "unexpected state change\n" if $ verify_running != $ running ;
2013-04-29 09:30:15 +02:00
2013-04-30 11:46:38 +02:00
die "snapshot '$snapname' does not exist\n"
if $ snapname && ! defined ( $ conf - > { snapshots } - > { $ snapname } ) ;
2013-04-29 09:30:15 +02:00
2013-04-30 11:46:38 +02:00
my $ oldconf = $ snapname ? $ conf - > { snapshots } - > { $ snapname } : $ conf ;
2013-04-30 09:31:23 +02:00
2013-05-02 11:42:22 +02:00
my $ sharedvm = & $ check_storage_access_clone ( $ rpcenv , $ authuser , $ storecfg , $ oldconf , $ storage ) ;
2013-04-29 09:30:15 +02:00
2013-05-02 11:42:22 +02:00
die "can't clone VM to node '$target' (VM uses local storage)\n" if $ target && ! $ sharedvm ;
2013-04-30 11:46:38 +02:00
2016-03-07 12:41:12 +01:00
my $ conffile = PVE::QemuConfig - > config_file ( $ newid ) ;
2013-04-29 09:30:15 +02:00
die "unable to create VM $newid: config file already exists\n"
if - f $ conffile ;
2013-05-02 11:42:22 +02:00
my $ newconf = { lock = > 'clone' } ;
2013-05-02 07:10:46 +02:00
my $ drives = { } ;
2015-10-29 15:02:35 +01:00
my $ fullclone = { } ;
2013-05-02 07:10:46 +02:00
my $ vollist = [] ;
foreach my $ opt ( keys %$ oldconf ) {
my $ value = $ oldconf - > { $ opt } ;
# do not copy snapshot related info
next if $ opt eq 'snapshots' || $ opt eq 'parent' || $ opt eq 'snaptime' ||
$ opt eq 'vmstate' || $ opt eq 'snapstate' ;
2015-08-20 09:14:34 +02:00
# no need to copy unused images, because VMID(owner) changes anyways
next if $ opt =~ m/^unused\d+$/ ;
2013-05-02 07:10:46 +02:00
# always change MAC! address
if ( $ opt =~ m/^net(\d+)$/ ) {
my $ net = PVE::QemuServer:: parse_net ( $ value ) ;
2016-07-13 16:25:44 +02:00
my $ dc = PVE::Cluster:: cfs_read_file ( 'datacenter.cfg' ) ;
$ net - > { macaddr } = PVE::Tools:: random_ether_addr ( $ dc - > { mac_prefix } ) ;
2013-05-02 07:10:46 +02:00
$ newconf - > { $ opt } = PVE::QemuServer:: print_net ( $ net ) ;
2016-03-03 15:45:15 +01:00
} elsif ( PVE::QemuServer:: is_valid_drivename ( $ opt ) ) {
2013-10-14 07:35:04 +02:00
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ value ) ;
die "unable to parse drive options for '$opt'\n" if ! $ drive ;
2013-05-02 07:10:46 +02:00
if ( PVE::QemuServer:: drive_is_cdrom ( $ drive ) ) {
$ newconf - > { $ opt } = $ value ; # simply copy configuration
} else {
2014-07-04 10:25:48 +02:00
if ( $ param - > { full } ) {
2017-05-23 10:39:45 +02:00
die "Full clone feature is not supported for drive '$opt'\n"
2013-05-02 07:17:15 +02:00
if ! PVE::Storage:: volume_has_feature ( $ storecfg , 'copy' , $ drive - > { file } , $ snapname , $ running ) ;
2015-10-29 15:02:35 +01:00
$ fullclone - > { $ opt } = 1 ;
2014-07-04 10:25:48 +02:00
} else {
# not full means clone instead of copy
2017-05-23 10:39:45 +02:00
die "Linked clone feature is not supported for drive '$opt'\n"
2014-07-04 10:25:48 +02:00
if ! PVE::Storage:: volume_has_feature ( $ storecfg , 'clone' , $ drive - > { file } , $ snapname , $ running ) ;
2013-05-02 07:17:15 +02:00
}
2013-05-02 07:10:46 +02:00
$ drives - > { $ opt } = $ drive ;
push @$ vollist , $ drive - > { file } ;
}
} else {
# copy everything else
$ newconf - > { $ opt } = $ value ;
}
}
2014-08-26 09:20:09 +02:00
# auto generate a new uuid
my ( $ uuid , $ uuid_str ) ;
UUID:: generate ( $ uuid ) ;
UUID:: unparse ( $ uuid , $ uuid_str ) ;
my $ smbios1 = PVE::QemuServer:: parse_smbios1 ( $ newconf - > { smbios1 } || '' ) ;
2014-11-21 12:31:56 +01:00
$ smbios1 - > { uuid } = $ uuid_str ;
2014-08-26 09:20:09 +02:00
$ newconf - > { smbios1 } = PVE::QemuServer:: print_smbios1 ( $ smbios1 ) ;
2013-05-02 07:10:46 +02:00
delete $ newconf - > { template } ;
if ( $ param - > { name } ) {
$ newconf - > { name } = $ param - > { name } ;
} else {
2013-05-29 11:18:54 +02:00
if ( $ oldconf - > { name } ) {
$ newconf - > { name } = "Copy-of-$oldconf->{name}" ;
} else {
$ newconf - > { name } = "Copy-of-VM-$vmid" ;
}
2013-05-02 07:10:46 +02:00
}
2013-05-03 08:39:41 +02:00
2013-05-02 07:10:46 +02:00
if ( $ param - > { description } ) {
$ newconf - > { description } = $ param - > { description } ;
}
2013-04-29 09:30:15 +02:00
# create empty/temp config - this fails if VM already exists on other node
2013-05-02 11:42:22 +02:00
PVE::Tools:: file_set_contents ( $ conffile , "# qmclone temporary file\nlock: clone\n" ) ;
2013-04-29 09:30:15 +02:00
my $ realcmd = sub {
my $ upid = shift ;
2013-04-29 10:42:35 +02:00
my $ newvollist = [] ;
2017-01-03 15:03:14 +01:00
my $ jobs = { } ;
2013-04-29 09:30:15 +02:00
2013-04-29 10:42:35 +02:00
eval {
2017-09-06 13:29:06 +02:00
local $ SIG { INT } =
local $ SIG { TERM } =
local $ SIG { QUIT } =
local $ SIG { HUP } = sub { die "interrupted by signal\n" ; } ;
2013-04-30 11:46:38 +02:00
2016-01-22 11:54:34 +01:00
PVE::Storage:: activate_volumes ( $ storecfg , $ vollist , $ snapname ) ;
2013-04-29 09:30:15 +02:00
2017-01-03 15:03:14 +01:00
my $ total_jobs = scalar ( keys % { $ drives } ) ;
my $ i = 1 ;
2013-05-02 07:10:46 +02:00
foreach my $ opt ( keys %$ drives ) {
my $ drive = $ drives - > { $ opt } ;
2017-01-05 09:16:28 +01:00
my $ skipcomplete = ( $ total_jobs != $ i ) ; # finish after last drive
2013-05-03 08:39:41 +02:00
2013-05-29 08:32:10 +02:00
my $ newdrive = PVE::QemuServer:: clone_disk ( $ storecfg , $ vmid , $ running , $ opt , $ drive , $ snapname ,
2017-01-05 09:16:28 +01:00
$ newid , $ storage , $ format , $ fullclone - > { $ opt } , $ newvollist ,
$ jobs , $ skipcomplete , $ oldconf - > { agent } ) ;
2013-05-02 18:18:04 +02:00
2013-05-29 08:32:10 +02:00
$ newconf - > { $ opt } = PVE::QemuServer:: print_drive ( $ vmid , $ newdrive ) ;
2013-05-03 08:39:41 +02:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ newid , $ newconf ) ;
2017-01-03 15:03:14 +01:00
$ i + + ;
2013-05-02 07:10:46 +02:00
}
2013-04-29 10:42:35 +02:00
delete $ newconf - > { lock } ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ newid , $ newconf ) ;
2013-04-30 11:44:39 +02:00
if ( $ target ) {
2013-11-19 08:15:54 +01:00
# always deactivate volumes - avoid lvm LVs to be active on several nodes
2016-02-24 12:00:32 +01:00
PVE::Storage:: deactivate_volumes ( $ storecfg , $ vollist , $ snapname ) if ! $ running ;
2016-07-13 12:44:12 +02:00
PVE::Storage:: deactivate_volumes ( $ storecfg , $ newvollist ) ;
2013-11-19 08:15:54 +01:00
2016-03-07 12:41:12 +01:00
my $ newconffile = PVE::QemuConfig - > config_file ( $ newid , $ target ) ;
2013-04-30 11:44:39 +02:00
die "Failed to move config to node '$target' - rename failed: $!\n"
if ! rename ( $ conffile , $ newconffile ) ;
}
2013-05-03 09:07:53 +02:00
2013-05-14 12:01:57 +02:00
PVE::AccessControl:: add_vm_to_pool ( $ newid , $ pool ) if $ pool ;
2013-04-29 09:30:15 +02:00
} ;
2013-04-30 11:46:38 +02:00
if ( my $ err = $@ ) {
2013-04-29 09:30:15 +02:00
unlink $ conffile ;
2017-01-03 15:03:14 +01:00
eval { PVE::QemuServer:: qemu_blockjobs_cancel ( $ vmid , $ jobs ) } ;
2013-04-29 10:42:35 +02:00
sleep 1 ; # some storage like rbd need to wait before release volume - really?
foreach my $ volid ( @$ newvollist ) {
eval { PVE::Storage:: vdisk_free ( $ storecfg , $ volid ) ; } ;
warn $@ if $@ ;
}
2013-05-02 11:42:22 +02:00
die "clone failed: $err" ;
2013-04-29 09:30:15 +02:00
}
return ;
} ;
2015-08-21 17:13:58 +02:00
PVE::Firewall:: clone_vmfw_conf ( $ vmid , $ newid ) ;
2013-05-02 11:42:22 +02:00
return $ rpcenv - > fork_worker ( 'qmclone' , $ vmid , $ authuser , $ realcmd ) ;
2013-04-29 09:30:15 +02:00
} ;
2016-03-07 12:41:12 +01:00
return PVE::QemuConfig - > lock_config_mode ( $ vmid , 1 , $ shared_lock , sub {
2013-04-29 09:30:15 +02:00
# Aquire exclusive lock lock for $newid
2016-03-07 12:41:12 +01:00
return PVE::QemuConfig - > lock_config_full ( $ newid , 1 , $ clonefn ) ;
2013-04-29 09:30:15 +02:00
} ) ;
} } ) ;
2013-05-27 06:22:05 +02:00
__PACKAGE__ - > register_method ( {
2013-05-31 08:46:21 +02:00
name = > 'move_vm_disk' ,
path = > '{vmid}/move_disk' ,
2013-05-29 12:07:56 +02:00
method = > 'POST' ,
2013-05-27 06:22:05 +02:00
protected = > 1 ,
proxyto = > 'node' ,
description = > "Move volume to different storage." ,
permissions = > {
2016-11-30 13:07:52 +01:00
description = > "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage." ,
check = > [ 'and' ,
[ 'perm' , '/vms/{vmid}' , [ 'VM.Config.Disk' ] ] ,
[ 'perm' , '/storage/{storage}' , [ 'Datastore.AllocateSpace' ] ] ,
] ,
2013-05-27 06:22:05 +02:00
} ,
parameters = > {
additionalProperties = > 0 ,
2016-11-30 13:07:52 +01:00
properties = > {
2013-05-27 06:22:05 +02:00
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2013-05-27 06:22:05 +02:00
disk = > {
type = > 'string' ,
description = > "The disk you want to move." ,
2016-03-03 15:45:15 +01:00
enum = > [ PVE::QemuServer:: valid_drive_names ( ) ] ,
2013-05-27 06:22:05 +02:00
} ,
2015-09-07 08:13:07 +02:00
storage = > get_standard_option ( 'pve-storage-id' , {
description = > "Target storage." ,
completion = > \ & PVE::QemuServer:: complete_storage ,
} ) ,
2013-05-29 12:15:30 +02:00
'format' = > {
2013-05-27 06:22:05 +02:00
type = > 'string' ,
description = > "Target Format." ,
enum = > [ 'raw' , 'qcow2' , 'vmdk' ] ,
optional = > 1 ,
} ,
2013-05-31 10:56:16 +02:00
delete = > {
type = > 'boolean' ,
description = > "Delete the original disk after successful copy. By default the original disk is kept as unused disk." ,
optional = > 1 ,
default = > 0 ,
} ,
2013-05-27 06:22:05 +02:00
digest = > {
type = > 'string' ,
description = > 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.' ,
maxLength = > 40 ,
optional = > 1 ,
} ,
} ,
} ,
2013-05-29 12:07:56 +02:00
returns = > {
type = > 'string' ,
description = > "the task ID." ,
} ,
2013-05-27 06:22:05 +02:00
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ digest = extract_param ( $ param , 'digest' ) ;
my $ disk = extract_param ( $ param , 'disk' ) ;
my $ storeid = extract_param ( $ param , 'storage' ) ;
my $ format = extract_param ( $ param , 'format' ) ;
my $ storecfg = PVE::Storage:: config ( ) ;
my $ updatefn = sub {
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2013-05-27 06:22:05 +02:00
2016-07-22 07:53:53 +02:00
PVE::QemuConfig - > check_lock ( $ conf ) ;
2013-05-27 06:22:05 +02:00
die "checksum missmatch (file change by other user?)\n"
if $ digest && $ digest ne $ conf - > { digest } ;
die "disk '$disk' does not exist\n" if ! $ conf - > { $ disk } ;
my $ drive = PVE::QemuServer:: parse_drive ( $ disk , $ conf - > { $ disk } ) ;
2013-05-31 10:56:16 +02:00
my $ old_volid = $ drive - > { file } || die "disk '$disk' has no associated volume\n" ;
2013-05-27 06:22:05 +02:00
die "you can't move a cdrom\n" if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
2013-05-29 12:07:56 +02:00
my $ oldfmt ;
2013-05-31 10:56:16 +02:00
my ( $ oldstoreid , $ oldvolname ) = PVE::Storage:: parse_volume_id ( $ old_volid ) ;
2013-05-27 06:22:05 +02:00
if ( $ oldvolname =~ m/\.(raw|qcow2|vmdk)$/ ) {
$ oldfmt = $ 1 ;
}
2013-06-11 07:26:43 +02:00
die "you can't move on the same storage with same format\n" if $ oldstoreid eq $ storeid &&
2013-05-29 12:07:56 +02:00
( ! $ format || ! $ oldfmt || $ oldfmt eq $ format ) ;
2013-05-27 06:22:05 +02:00
2016-07-01 14:18:31 +02:00
# this only checks snapshots because $disk is passed!
my $ snapshotted = PVE::QemuServer:: is_volume_in_use ( $ storecfg , $ conf , $ disk , $ old_volid ) ;
die "you can't move a disk with snapshots and delete the source\n"
if $ snapshotted && $ param - > { delete } ;
2013-05-27 06:22:05 +02:00
PVE::Cluster:: log_msg ( 'info' , $ authuser , "move disk VM $vmid: move --disk $disk --storage $storeid" ) ;
my $ running = PVE::QemuServer:: check_running ( $ vmid ) ;
2013-05-29 12:07:56 +02:00
PVE::Storage:: activate_volumes ( $ storecfg , [ $ drive - > { file } ] ) ;
2013-05-27 06:22:05 +02:00
my $ realcmd = sub {
my $ newvollist = [] ;
eval {
2017-09-14 15:19:39 +02:00
local $ SIG { INT } =
local $ SIG { TERM } =
local $ SIG { QUIT } =
local $ SIG { HUP } = sub { die "interrupted by signal\n" ; } ;
2013-05-27 06:22:05 +02:00
2016-07-01 14:18:31 +02:00
warn "moving disk with snapshots, snapshots will not be moved!\n"
if $ snapshotted ;
2013-05-29 12:07:56 +02:00
my $ newdrive = PVE::QemuServer:: clone_disk ( $ storecfg , $ vmid , $ running , $ disk , $ drive , undef ,
$ vmid , $ storeid , $ format , 1 , $ newvollist ) ;
$ conf - > { $ disk } = PVE::QemuServer:: print_drive ( $ vmid , $ newdrive ) ;
2016-03-07 12:41:14 +01:00
PVE::QemuConfig - > add_unused_volume ( $ conf , $ old_volid ) if ! $ param - > { delete } ;
2013-06-11 07:26:43 +02:00
2017-01-25 14:58:14 +01:00
# convert moved disk to base if part of template
PVE::QemuServer:: template_create ( $ vmid , $ conf , $ disk )
if PVE::QemuConfig - > is_template ( $ conf ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2014-01-29 06:44:06 +01:00
2014-11-21 12:31:56 +01:00
eval {
2014-01-29 06:44:06 +01:00
# try to deactivate volumes - avoid lvm LVs to be active on several nodes
2014-11-21 12:31:56 +01:00
PVE::Storage:: deactivate_volumes ( $ storecfg , [ $ newdrive - > { file } ] )
2014-01-29 06:44:06 +01:00
if ! $ running ;
} ;
warn $@ if $@ ;
2013-05-27 06:22:05 +02:00
} ;
if ( my $ err = $@ ) {
foreach my $ volid ( @$ newvollist ) {
eval { PVE::Storage:: vdisk_free ( $ storecfg , $ volid ) ; } ;
warn $@ if $@ ;
}
die "storage migration failed: $err" ;
}
2013-05-31 10:56:16 +02:00
if ( $ param - > { delete } ) {
2016-07-01 14:18:32 +02:00
eval {
PVE::Storage:: deactivate_volumes ( $ storecfg , [ $ old_volid ] ) ;
PVE::Storage:: vdisk_free ( $ storecfg , $ old_volid ) ;
} ;
warn $@ if $@ ;
2013-05-31 10:56:16 +02:00
}
2013-05-27 06:22:05 +02:00
} ;
return $ rpcenv - > fork_worker ( 'qmmove' , $ vmid , $ authuser , $ realcmd ) ;
} ;
2013-05-29 12:07:56 +02:00
2016-03-07 12:41:12 +01:00
return PVE::QemuConfig - > lock_config ( $ vmid , $ updatefn ) ;
2013-05-27 06:22:05 +02:00
} } ) ;
2011-09-14 12:02:08 +02:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'migrate_vm' ,
2011-09-14 12:02:08 +02:00
path = > '{vmid}/migrate' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Migrate virtual machine. Creates a new migration task." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Migrate' ] ] ,
} ,
2011-09-14 12:02:08 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
target = > get_standard_option ( 'pve-node' , {
description = > "Target node." ,
completion = > \ & PVE::Cluster:: complete_migration_target ,
} ) ,
2011-09-14 12:02:08 +02:00
online = > {
type = > 'boolean' ,
description = > "Use online/live migration." ,
optional = > 1 ,
} ,
force = > {
type = > 'boolean' ,
description = > "Allow to migrate VMs which use local devices. Only root may use this option." ,
optional = > 1 ,
} ,
2016-10-31 09:42:31 +01:00
migration_type = > {
type = > 'string' ,
enum = > [ 'secure' , 'insecure' ] ,
2016-11-30 13:07:52 +01:00
description = > "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance." ,
2016-10-31 09:42:31 +01:00
optional = > 1 ,
} ,
migration_network = > {
2016-11-30 13:07:52 +01:00
type = > 'string' , format = > 'CIDR' ,
2016-10-31 09:42:31 +01:00
description = > "CIDR of the (sub) network that is used for migration." ,
optional = > 1 ,
} ,
2017-01-06 10:15:07 +01:00
"with-local-disks" = > {
type = > 'boolean' ,
description = > "Enable live storage migration for local disk" ,
2017-01-03 15:03:17 +01:00
optional = > 1 ,
2017-01-06 10:15:07 +01:00
} ,
targetstorage = > get_standard_option ( 'pve-storage-id' , {
description = > "Default target storage." ,
optional = > 1 ,
completion = > \ & PVE::QemuServer:: complete_storage ,
} ) ,
2011-09-14 12:02:08 +02:00
} ,
} ,
2012-01-27 09:35:26 +01:00
returns = > {
2011-09-14 12:02:08 +02:00
type = > 'string' ,
description = > "the task ID." ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
2012-02-02 06:39:38 +01:00
my $ authuser = $ rpcenv - > get_user ( ) ;
2011-09-14 12:02:08 +02:00
my $ target = extract_param ( $ param , 'target' ) ;
my $ localnode = PVE::INotify:: nodename ( ) ;
raise_param_exc ( { target = > "target is local node." } ) if $ target eq $ localnode ;
PVE::Cluster:: check_cfs_quorum ( ) ;
PVE::Cluster:: check_node_exists ( $ target ) ;
my $ targetip = PVE::Cluster:: remote_node_ip ( $ target ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
2017-01-05 09:54:07 +01:00
raise_param_exc ( { targetstorage = > "Live storage migration can only be done online." } )
2017-01-03 15:03:17 +01:00
if ! $ param - > { online } && $ param - > { targetstorage } ;
2012-01-27 09:35:26 +01:00
raise_param_exc ( { force = > "Only root may use this option." } )
2012-02-02 06:39:38 +01:00
if $ param - > { force } && $ authuser ne 'root@pam' ;
2011-09-14 12:02:08 +02:00
2016-10-31 09:42:31 +01:00
raise_param_exc ( { migration_type = > "Only root may use this option." } )
if $ param - > { migration_type } && $ authuser ne 'root@pam' ;
# allow root only until better network permissions are available
raise_param_exc ( { migration_network = > "Only root may use this option." } )
if $ param - > { migration_network } && $ authuser ne 'root@pam' ;
2011-09-14 12:02:08 +02:00
# test if VM exists
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2011-09-14 12:02:08 +02:00
# try to detect errors early
2011-11-25 13:25:32 +01:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_lock ( $ conf ) ;
2011-11-25 13:25:32 +01:00
2011-09-14 12:02:08 +02:00
if ( PVE::QemuServer:: check_running ( $ vmid ) ) {
2012-01-27 09:35:26 +01:00
die "cant migrate running VM without --online\n"
2011-09-14 12:02:08 +02:00
if ! $ param - > { online } ;
}
2012-03-30 09:13:31 +02:00
my $ storecfg = PVE::Storage:: config ( ) ;
2017-04-21 03:39:34 +02:00
if ( $ param - > { targetstorage } ) {
PVE::Storage:: storage_check_node ( $ storecfg , $ param - > { targetstorage } , $ target ) ;
} else {
PVE::QemuServer:: check_storage_availability ( $ storecfg , $ conf , $ target ) ;
}
2012-03-30 09:13:31 +02:00
2015-03-27 12:47:56 +01:00
if ( PVE::HA::Config:: vm_is_ha_managed ( $ vmid ) && $ rpcenv - > { type } ne 'ha' ) {
2011-09-14 12:02:08 +02:00
2012-03-27 10:37:39 +02:00
my $ hacmd = sub {
my $ upid = shift ;
2011-09-14 12:02:08 +02:00
2015-04-17 13:10:32 +02:00
my $ service = "vm:$vmid" ;
2012-03-27 10:37:39 +02:00
2015-03-27 12:47:56 +01:00
my $ cmd = [ 'ha-manager' , 'migrate' , $ service , $ target ] ;
2012-03-27 10:37:39 +02:00
2017-06-21 07:20:11 +02:00
print "Requesting HA migration for VM $vmid to node $target\n" ;
2012-03-27 10:37:39 +02:00
PVE::Tools:: run_command ( $ cmd ) ;
return ;
} ;
return $ rpcenv - > fork_worker ( 'hamigrate' , $ vmid , $ authuser , $ hacmd ) ;
} else {
2017-06-12 11:05:46 +02:00
my $ realcmd = sub {
PVE::QemuMigrate - > migrate ( $ target , $ targetip , $ vmid , $ param ) ;
} ;
2012-03-27 10:37:39 +02:00
2017-06-12 11:05:46 +02:00
my $ worker = sub {
return PVE::GuestHelpers:: guest_migration_lock ( $ vmid , 10 , $ realcmd ) ;
2012-03-27 10:37:39 +02:00
} ;
2017-06-12 11:05:46 +02:00
return $ rpcenv - > fork_worker ( 'qmigrate' , $ vmid , $ authuser , $ worker ) ;
2012-03-27 10:37:39 +02:00
}
2011-09-14 12:02:08 +02:00
} } ) ;
2011-08-23 07:47:04 +02:00
2011-11-09 08:26:46 +01:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'monitor' ,
path = > '{vmid}/monitor' ,
2011-11-09 08:26:46 +01:00
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Execute Qemu monitor commands." ,
2012-02-02 06:39:38 +01:00
permissions = > {
2016-11-23 09:17:07 +01:00
description = > "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')" ,
2016-11-30 13:07:52 +01:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Monitor' ] ] ,
2012-02-02 06:39:38 +01:00
} ,
2011-11-09 08:26:46 +01:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
command = > {
type = > 'string' ,
description = > "The monitor command." ,
}
} ,
} ,
returns = > { type = > 'string' } ,
code = > sub {
my ( $ param ) = @ _ ;
2016-11-23 09:17:07 +01:00
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ is_ro = sub {
my $ command = shift ;
return $ command =~ m/^\s*info(\s+|$)/
|| $ command =~ m/^\s*help\s*$/ ;
} ;
$ rpcenv - > check_full ( $ authuser , "/" , [ 'Sys.Modify' ] )
if ! & $ is_ro ( $ param - > { command } ) ;
2011-11-09 08:26:46 +01:00
my $ vmid = $ param - > { vmid } ;
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ; # check if VM exists
2011-11-09 08:26:46 +01:00
my $ res = '' ;
eval {
2012-07-13 08:56:13 +02:00
$ res = PVE::QemuServer:: vm_human_monitor_command ( $ vmid , $ param - > { command } ) ;
2011-11-09 08:26:46 +01:00
} ;
$ res = "ERROR: $@" if $@ ;
return $ res ;
} } ) ;
2016-12-01 06:45:00 +01:00
my $ guest_agent_commands = [
2016-12-01 07:55:51 +01:00
'ping' ,
'get-time' ,
'info' ,
'fsfreeze-status' ,
'fsfreeze-freeze' ,
'fsfreeze-thaw' ,
'fstrim' ,
'network-get-interfaces' ,
'get-vcpus' ,
'get-fsinfo' ,
'get-memory-blocks' ,
'get-memory-block-info' ,
'suspend-hybrid' ,
'suspend-ram' ,
'suspend-disk' ,
'shutdown' ,
2016-12-01 06:45:00 +01:00
] ;
2016-11-03 15:26:50 +01:00
__PACKAGE__ - > register_method ( {
name = > 'agent' ,
path = > '{vmid}/agent' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Execute Qemu Guest Agent commands." ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Monitor' ] ] ,
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2016-11-30 12:47:03 +01:00
vmid = > get_standard_option ( 'pve-vmid' , {
completion = > \ & PVE::QemuServer:: complete_vmid_running } ) ,
2016-11-03 15:26:50 +01:00
command = > {
type = > 'string' ,
description = > "The QGA command." ,
2016-12-01 06:45:00 +01:00
enum = > $ guest_agent_commands ,
2016-11-30 13:07:52 +01:00
} ,
2016-11-03 15:26:50 +01:00
} ,
} ,
2016-12-01 06:58:36 +01:00
returns = > {
type = > 'object' ,
description = > " Returns an object with a single `result` property . The type of that
property depends on the executed command . " ,
} ,
2016-11-03 15:26:50 +01:00
code = > sub {
my ( $ param ) = @ _ ;
my $ vmid = $ param - > { vmid } ;
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ; # check if VM exists
die "No Qemu Guest Agent\n" if ! defined ( $ conf - > { agent } ) ;
die "VM $vmid is not running\n" if ! PVE::QemuServer:: check_running ( $ vmid ) ;
2016-12-01 07:55:51 +01:00
my $ cmd = $ param - > { command } ;
my $ res = PVE::QemuServer:: vm_mon_cmd ( $ vmid , "guest-$cmd" ) ;
2016-11-03 15:26:50 +01:00
2016-12-01 06:58:36 +01:00
return { result = > $ res } ;
2016-11-03 15:26:50 +01:00
} } ) ;
2012-08-07 12:05:17 +02:00
__PACKAGE__ - > register_method ( {
name = > 'resize_vm' ,
2012-08-08 07:34:36 +02:00
path = > '{vmid}/resize' ,
2012-08-07 12:05:17 +02:00
method = > 'PUT' ,
protected = > 1 ,
proxyto = > 'node' ,
2012-08-08 08:26:58 +02:00
description = > "Extend volume size." ,
2012-08-07 12:05:17 +02:00
permissions = > {
2012-08-08 07:36:51 +02:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Config.Disk' ] ] ,
2012-08-07 12:05:17 +02:00
} ,
parameters = > {
additionalProperties = > 0 ,
2012-08-08 08:26:58 +02:00
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2012-08-08 08:26:58 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
disk = > {
type = > 'string' ,
description = > "The disk you want to resize." ,
2016-03-03 15:45:15 +01:00
enum = > [ PVE::QemuServer:: valid_drive_names ( ) ] ,
2012-08-08 08:26:58 +02:00
} ,
size = > {
type = > 'string' ,
2012-08-08 09:25:54 +02:00
pattern = > '\+?\d+(\.\d+)?[KMGT]?' ,
2017-03-01 10:31:54 +01:00
description = > "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported." ,
2012-08-08 08:26:58 +02:00
} ,
digest = > {
type = > 'string' ,
description = > 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.' ,
maxLength = > 40 ,
optional = > 1 ,
} ,
} ,
2012-08-07 12:05:17 +02:00
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ digest = extract_param ( $ param , 'digest' ) ;
2012-08-08 08:26:58 +02:00
my $ disk = extract_param ( $ param , 'disk' ) ;
2013-04-30 11:46:38 +02:00
2012-08-08 08:26:58 +02:00
my $ sizestr = extract_param ( $ param , 'size' ) ;
2012-08-07 12:05:17 +02:00
2012-08-08 09:25:54 +02:00
my $ skiplock = extract_param ( $ param , 'skiplock' ) ;
2012-08-07 12:05:17 +02:00
raise_param_exc ( { skiplock = > "Only root may use this option." } )
if $ skiplock && $ authuser ne 'root@pam' ;
my $ storecfg = PVE::Storage:: config ( ) ;
my $ updatefn = sub {
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2012-08-07 12:05:17 +02:00
die "checksum missmatch (file change by other user?)\n"
if $ digest && $ digest ne $ conf - > { digest } ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_lock ( $ conf ) if ! $ skiplock ;
2012-08-07 12:05:17 +02:00
2012-08-08 09:25:54 +02:00
die "disk '$disk' does not exist\n" if ! $ conf - > { $ disk } ;
my $ drive = PVE::QemuServer:: parse_drive ( $ disk , $ conf - > { $ disk } ) ;
2015-08-17 14:27:11 +02:00
my ( undef , undef , undef , undef , undef , undef , $ format ) =
PVE::Storage:: parse_volname ( $ storecfg , $ drive - > { file } ) ;
die "can't resize volume: $disk if snapshot exists\n"
if % { $ conf - > { snapshots } } && $ format eq 'qcow2' ;
2012-08-08 09:25:54 +02:00
my $ volid = $ drive - > { file } ;
die "disk '$disk' has no associated volume\n" if ! $ volid ;
die "you can't resize a cdrom\n" if PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
$ rpcenv - > check ( $ authuser , "/storage/$storeid" , [ 'Datastore.AllocateSpace' ] ) ;
2016-04-13 15:59:43 +02:00
PVE::Storage:: activate_volumes ( $ storecfg , [ $ volid ] ) ;
2012-08-08 09:25:54 +02:00
my $ size = PVE::Storage:: volume_size_info ( $ storecfg , $ volid , 5 ) ;
die "internal error" if $ sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/ ;
my ( $ ext , $ newsize , $ unit ) = ( $ 1 , $ 2 , $ 4 ) ;
if ( $ unit ) {
if ( $ unit eq 'K' ) {
$ newsize = $ newsize * 1024 ;
} elsif ( $ unit eq 'M' ) {
$ newsize = $ newsize * 1024 * 1024 ;
} elsif ( $ unit eq 'G' ) {
$ newsize = $ newsize * 1024 * 1024 * 1024 ;
} elsif ( $ unit eq 'T' ) {
$ newsize = $ newsize * 1024 * 1024 * 1024 * 1024 ;
}
}
$ newsize += $ size if $ ext ;
$ newsize = int ( $ newsize ) ;
2017-02-16 10:39:35 +01:00
die "shrinking disks is not supported\n" if $ newsize < $ size ;
2012-08-08 09:25:54 +02:00
return if $ size == $ newsize ;
2012-08-08 08:26:58 +02:00
PVE::Cluster:: log_msg ( 'info' , $ authuser , "update VM $vmid: resize --disk $disk --size $sizestr" ) ;
2012-08-07 12:05:17 +02:00
2012-08-08 09:25:54 +02:00
PVE::QemuServer:: qemu_block_resize ( $ vmid , "drive-$disk" , $ storecfg , $ volid , $ newsize ) ;
2013-04-30 11:46:38 +02:00
2012-08-08 09:25:54 +02:00
$ drive - > { size } = $ newsize ;
$ conf - > { $ disk } = PVE::QemuServer:: print_drive ( $ vmid , $ drive ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2012-08-08 09:25:54 +02:00
} ;
2012-08-07 12:05:17 +02:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > lock_config ( $ vmid , $ updatefn ) ;
2012-08-07 12:05:17 +02:00
return undef ;
} } ) ;
2012-09-06 10:33:35 +02:00
__PACKAGE__ - > register_method ( {
2012-09-10 07:32:33 +02:00
name = > 'snapshot_list' ,
2012-09-06 10:33:35 +02:00
path = > '{vmid}/snapshot' ,
2012-09-10 07:32:33 +02:00
method = > 'GET' ,
description = > "List all snapshots." ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Audit' ] ] ,
} ,
proxyto = > 'node' ,
protected = > 1 , # qemu pid files are only readable by root
parameters = > {
additionalProperties = > 0 ,
properties = > {
2016-10-19 11:57:53 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2012-09-10 07:32:33 +02:00
node = > get_standard_option ( 'pve-node' ) ,
} ,
} ,
returns = > {
type = > 'array' ,
items = > {
type = > "object" ,
properties = > { } ,
} ,
links = > [ { rel = > 'child' , href = > "{name}" } ] ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
2012-09-13 09:45:48 +02:00
my $ vmid = $ param - > { vmid } ;
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2012-09-10 07:32:33 +02:00
my $ snaphash = $ conf - > { snapshots } || { } ;
my $ res = [] ;
foreach my $ name ( keys %$ snaphash ) {
2012-09-11 08:33:20 +02:00
my $ d = $ snaphash - > { $ name } ;
2013-04-30 11:46:38 +02:00
my $ item = {
name = > $ name ,
snaptime = > $ d - > { snaptime } || 0 ,
2012-09-13 09:45:48 +02:00
vmstate = > $ d - > { vmstate } ? 1 : 0 ,
2012-09-11 08:45:39 +02:00
description = > $ d - > { description } || '' ,
} ;
2012-09-11 08:33:20 +02:00
$ item - > { parent } = $ d - > { parent } if $ d - > { parent } ;
2012-09-12 07:19:38 +02:00
$ item - > { snapstate } = $ d - > { snapstate } if $ d - > { snapstate } ;
2012-09-11 08:33:20 +02:00
push @$ res , $ item ;
}
2012-09-13 09:45:48 +02:00
my $ running = PVE::QemuServer:: check_running ( $ vmid , 1 ) ? 1 : 0 ;
my $ current = { name = > 'current' , digest = > $ conf - > { digest } , running = > $ running } ;
2012-09-13 09:13:39 +02:00
$ current - > { parent } = $ conf - > { parent } if $ conf - > { parent } ;
push @$ res , $ current ;
2012-09-10 07:32:33 +02:00
return $ res ;
} } ) ;
__PACKAGE__ - > register_method ( {
name = > 'snapshot' ,
path = > '{vmid}/snapshot' ,
method = > 'POST' ,
2012-09-06 10:33:35 +02:00
protected = > 1 ,
proxyto = > 'node' ,
description = > "Snapshot a VM." ,
permissions = > {
2012-09-10 09:31:53 +02:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Snapshot' ] ] ,
2012-09-06 10:33:35 +02:00
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2012-09-10 08:55:04 +02:00
snapname = > get_standard_option ( 'pve-snapshot-name' ) ,
2012-09-06 10:33:35 +02:00
vmstate = > {
optional = > 1 ,
type = > 'boolean' ,
description = > "Save the vmstate" ,
} ,
2012-09-11 09:00:26 +02:00
description = > {
optional = > 1 ,
type = > 'string' ,
description = > "A textual description or comment." ,
} ,
2012-09-06 10:33:35 +02:00
} ,
} ,
2012-09-10 07:32:33 +02:00
returns = > {
type = > 'string' ,
description = > "the task ID." ,
} ,
2012-09-06 10:33:35 +02:00
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ snapname = extract_param ( $ param , 'snapname' ) ;
2012-09-13 09:13:39 +02:00
die "unable to use snapshot name 'current' (reserved name)\n"
if $ snapname eq 'current' ;
2012-09-10 07:32:33 +02:00
my $ realcmd = sub {
2012-09-07 13:07:23 +02:00
PVE::Cluster:: log_msg ( 'info' , $ authuser , "snapshot VM $vmid: $snapname" ) ;
2016-03-07 12:41:13 +01:00
PVE::QemuConfig - > snapshot_create ( $ vmid , $ snapname , $ param - > { vmstate } ,
2014-12-03 15:40:33 +01:00
$ param - > { description } ) ;
2012-09-10 07:32:33 +02:00
} ;
return $ rpcenv - > fork_worker ( 'qmsnapshot' , $ vmid , $ authuser , $ realcmd ) ;
} } ) ;
2012-09-10 07:58:06 +02:00
__PACKAGE__ - > register_method ( {
name = > 'snapshot_cmd_idx' ,
path = > '{vmid}/snapshot/{snapname}' ,
description = > '' ,
method = > 'GET' ,
permissions = > {
user = > 'all' ,
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
vmid = > get_standard_option ( 'pve-vmid' ) ,
node = > get_standard_option ( 'pve-node' ) ,
2012-09-10 08:55:04 +02:00
snapname = > get_standard_option ( 'pve-snapshot-name' ) ,
2012-09-10 07:58:06 +02:00
} ,
} ,
returns = > {
type = > 'array' ,
items = > {
type = > "object" ,
properties = > { } ,
} ,
links = > [ { rel = > 'child' , href = > "{cmd}" } ] ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ res = [] ;
push @$ res , { cmd = > 'rollback' } ;
2012-09-11 09:24:18 +02:00
push @$ res , { cmd = > 'config' } ;
2012-09-10 07:58:06 +02:00
return $ res ;
} } ) ;
2012-09-11 09:24:18 +02:00
__PACKAGE__ - > register_method ( {
name = > 'update_snapshot_config' ,
path = > '{vmid}/snapshot/{snapname}/config' ,
method = > 'PUT' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Update snapshot metadata." ,
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Snapshot' ] ] ,
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
snapname = > get_standard_option ( 'pve-snapshot-name' ) ,
description = > {
optional = > 1 ,
type = > 'string' ,
description = > "A textual description or comment." ,
} ,
} ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ snapname = extract_param ( $ param , 'snapname' ) ;
return undef if ! defined ( $ param - > { description } ) ;
my $ updatefn = sub {
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2012-09-11 09:24:18 +02:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_lock ( $ conf ) ;
2012-09-11 09:24:18 +02:00
my $ snap = $ conf - > { snapshots } - > { $ snapname } ;
2013-04-30 11:46:38 +02:00
die "snapshot '$snapname' does not exist\n" if ! defined ( $ snap ) ;
2012-09-11 09:24:18 +02:00
$ snap - > { description } = $ param - > { description } if defined ( $ param - > { description } ) ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2012-09-11 09:24:18 +02:00
} ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > lock_config ( $ vmid , $ updatefn ) ;
2012-09-11 09:24:18 +02:00
return undef ;
} } ) ;
__PACKAGE__ - > register_method ( {
name = > 'get_snapshot_config' ,
path = > '{vmid}/snapshot/{snapname}/config' ,
method = > 'GET' ,
proxyto = > 'node' ,
description = > "Get snapshot configuration" ,
permissions = > {
2017-09-13 12:30:37 +02:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Snapshot' , 'VM.Snapshot.Rollback' ] , any = > 1 ] ,
2012-09-11 09:24:18 +02:00
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
snapname = > get_standard_option ( 'pve-snapshot-name' ) ,
} ,
} ,
returns = > { type = > "object" } ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ snapname = extract_param ( $ param , 'snapname' ) ;
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2012-09-11 09:24:18 +02:00
my $ snap = $ conf - > { snapshots } - > { $ snapname } ;
2013-04-30 11:46:38 +02:00
die "snapshot '$snapname' does not exist\n" if ! defined ( $ snap ) ;
2012-09-11 09:24:18 +02:00
return $ snap ;
} } ) ;
2012-09-10 07:32:33 +02:00
__PACKAGE__ - > register_method ( {
name = > 'rollback' ,
2012-09-10 07:58:06 +02:00
path = > '{vmid}/snapshot/{snapname}/rollback' ,
2012-09-10 07:32:33 +02:00
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Rollback VM state to specified snapshot." ,
permissions = > {
2017-09-13 12:30:37 +02:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Snapshot' , 'VM.Snapshot.Rollback' ] , any = > 1 ] ,
2012-09-10 07:32:33 +02:00
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2012-09-10 08:55:04 +02:00
snapname = > get_standard_option ( 'pve-snapshot-name' ) ,
2012-09-10 07:32:33 +02:00
} ,
} ,
returns = > {
type = > 'string' ,
description = > "the task ID." ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ snapname = extract_param ( $ param , 'snapname' ) ;
my $ realcmd = sub {
2012-09-07 13:07:23 +02:00
PVE::Cluster:: log_msg ( 'info' , $ authuser , "rollback snapshot VM $vmid: $snapname" ) ;
2016-03-07 12:41:13 +01:00
PVE::QemuConfig - > snapshot_rollback ( $ vmid , $ snapname ) ;
2012-09-10 07:32:33 +02:00
} ;
2017-06-12 10:38:22 +02:00
my $ worker = sub {
# hold migration lock, this makes sure that nobody create replication snapshots
return PVE::GuestHelpers:: guest_migration_lock ( $ vmid , 10 , $ realcmd ) ;
} ;
return $ rpcenv - > fork_worker ( 'qmrollback' , $ vmid , $ authuser , $ worker ) ;
2012-09-10 07:32:33 +02:00
} } ) ;
__PACKAGE__ - > register_method ( {
name = > 'delsnapshot' ,
path = > '{vmid}/snapshot/{snapname}' ,
method = > 'DELETE' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Delete a VM snapshot." ,
permissions = > {
2012-09-10 09:31:53 +02:00
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Snapshot' ] ] ,
2012-09-10 07:32:33 +02:00
} ,
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid } ) ,
2012-09-10 08:55:04 +02:00
snapname = > get_standard_option ( 'pve-snapshot-name' ) ,
2012-09-12 07:19:38 +02:00
force = > {
optional = > 1 ,
type = > 'boolean' ,
description = > "For removal from config file, even if removing disk snapshots fails." ,
} ,
2012-09-10 07:32:33 +02:00
} ,
} ,
returns = > {
type = > 'string' ,
description = > "the task ID." ,
} ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ snapname = extract_param ( $ param , 'snapname' ) ;
my $ realcmd = sub {
2012-09-07 13:07:23 +02:00
PVE::Cluster:: log_msg ( 'info' , $ authuser , "delete snapshot VM $vmid: $snapname" ) ;
2016-03-07 12:41:13 +01:00
PVE::QemuConfig - > snapshot_delete ( $ vmid , $ snapname , $ param - > { force } ) ;
2012-09-10 07:32:33 +02:00
} ;
2012-09-06 10:33:35 +02:00
2012-09-10 12:08:55 +02:00
return $ rpcenv - > fork_worker ( 'qmdelsnapshot' , $ vmid , $ authuser , $ realcmd ) ;
2012-09-06 10:33:35 +02:00
} } ) ;
2013-02-14 11:58:49 +01:00
__PACKAGE__ - > register_method ( {
name = > 'template' ,
path = > '{vmid}/template' ,
method = > 'POST' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Create a Template." ,
2013-04-19 08:37:32 +02:00
permissions = > {
2013-05-03 09:10:39 +02:00
description = > "You need 'VM.Allocate' permissions on /vms/{vmid}" ,
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Allocate' ] ] ,
2013-04-19 08:37:32 +02:00
} ,
2013-02-14 11:58:49 +01:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
2015-09-07 08:13:07 +02:00
vmid = > get_standard_option ( 'pve-vmid' , { completion = > \ & PVE::QemuServer:: complete_vmid_stopped } ) ,
2013-02-14 11:58:49 +01:00
disk = > {
optional = > 1 ,
type = > 'string' ,
description = > "If you want to convert only 1 disk to base image." ,
2016-03-03 15:45:15 +01:00
enum = > [ PVE::QemuServer:: valid_drive_names ( ) ] ,
2013-02-14 11:58:49 +01:00
} ,
} ,
} ,
returns = > { type = > 'null' } ,
code = > sub {
my ( $ param ) = @ _ ;
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
my $ disk = extract_param ( $ param , 'disk' ) ;
my $ updatefn = sub {
2016-03-07 12:41:12 +01:00
my $ conf = PVE::QemuConfig - > load_config ( $ vmid ) ;
2013-02-14 11:58:49 +01:00
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > check_lock ( $ conf ) ;
2013-02-14 11:58:49 +01:00
2013-04-30 11:46:38 +02:00
die "unable to create template, because VM contains snapshots\n"
2013-04-22 09:43:54 +02:00
if $ conf - > { snapshots } && scalar ( keys % { $ conf - > { snapshots } } ) ;
2013-04-22 07:05:54 +02:00
2013-04-30 11:46:38 +02:00
die "you can't convert a template to a template\n"
2016-03-07 12:41:12 +01:00
if PVE::QemuConfig - > is_template ( $ conf ) && ! $ disk ;
2013-04-22 07:05:54 +02:00
2013-04-30 11:46:38 +02:00
die "you can't convert a VM to template if VM is running\n"
2013-04-22 10:57:24 +02:00
if PVE::QemuServer:: check_running ( $ vmid ) ;
2013-04-22 07:10:58 +02:00
2013-02-14 11:58:49 +01:00
my $ realcmd = sub {
PVE::QemuServer:: template_create ( $ vmid , $ conf , $ disk ) ;
} ;
2013-04-22 07:08:51 +02:00
$ conf - > { template } = 1 ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > write_config ( $ vmid , $ conf ) ;
2013-04-22 07:08:51 +02:00
return $ rpcenv - > fork_worker ( 'qmtemplate' , $ vmid , $ authuser , $ realcmd ) ;
2013-02-14 11:58:49 +01:00
} ;
2016-03-07 12:41:12 +01:00
PVE::QemuConfig - > lock_config ( $ vmid , $ updatefn ) ;
2013-02-14 11:58:49 +01:00
return undef ;
} } ) ;
2011-08-23 07:47:04 +02:00
1 ;