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' ;
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 ;
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 ;
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 } ;
}
} ;
2012-02-02 06:39:38 +01:00
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' ] ) ;
2012-02-03 10:49:37 +01:00
} elsif ( ! $ isCDROM && ( $ volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/ ) ) {
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' ] ) ;
2012-02-02 06:39:38 +01:00
} else {
2012-03-29 11:09:52 +02:00
$ rpcenv - > check_volume_access ( $ authuser , $ storecfg , $ vmid , $ volid ) ;
2012-02-02 06:39:38 +01:00
}
} ) ;
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
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 = { } ;
PVE::QemuServer:: foreach_drive ( $ settings , sub {
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 ) ;
2012-02-03 10:49:37 +01:00
} elsif ( $ volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/ ) {
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 ;
2012-02-02 06:39:38 +01:00
my $ volid = PVE::Storage:: vdisk_alloc ( $ storecfg , $ storeid , $ vmid ,
$ fmt , undef , $ size * 1024 * 1024 ) ;
$ disk - > { file } = $ volid ;
2012-08-01 13:22:43 +02:00
$ disk - > { size } = $ size * 1024 * 1024 * 1024 ;
2012-02-02 06:39:38 +01:00
push @$ vollist , $ volid ;
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
2012-02-06 12:36:16 +01:00
my $ path = $ rpcenv - > check_volume_access ( $ authuser , $ storecfg , $ vmid , $ volid ) ;
2013-04-30 11:46:38 +02:00
2012-07-27 11:59:42 +02:00
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid , 1 ) ;
2012-06-23 09:07:32 +02:00
my $ foundvolid = undef ;
2012-07-27 11:59:42 +02:00
if ( $ storeid ) {
PVE::Storage:: activate_volumes ( $ storecfg , [ $ volid ] ) ;
my $ dl = PVE::Storage:: vdisk_list ( $ storecfg , $ storeid , undef ) ;
PVE::Storage:: foreach_volid ( $ dl , sub {
my ( $ volumeid ) = @ _ ;
if ( $ volumeid eq $ volid ) {
$ foundvolid = 1 ;
return ;
}
} ) ;
}
2013-04-30 11:46:38 +02:00
2012-06-23 09:07:32 +02:00
die "image '$path' does not exists\n" if ( ! ( - f $ path || - b $ path || $ foundvolid ) ) ;
2012-08-01 13:22:43 +02:00
my ( $ size ) = PVE::Storage:: volume_size_info ( $ storecfg , $ volid , 1 ) ;
$ disk - > { size } = $ size ;
$ res - > { $ ds } = PVE::QemuServer:: print_drive ( $ vmid , $ disk ) ;
2012-02-02 06:39:38 +01:00
}
2012-02-03 10:23:50 +01:00
} ) ;
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 ;
} ;
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
next if PVE::QemuServer:: valid_drivename ( $ opt ) ;
if ( $ opt eq 'sockets' || $ opt eq 'cores' ||
2013-04-30 11:46:38 +02:00
$ opt eq 'cpu' || $ opt eq 'smp' ||
2012-02-06 12:52:29 +01:00
$ opt eq 'cpulimit' || $ opt eq 'cpuunits' ) {
2012-02-02 06:39:38 +01:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.CPU' ] ) ;
} elsif ( $ opt eq 'boot' || $ opt eq 'bootdisk' ) {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Disk' ] ) ;
2012-12-28 13:04:19 +01:00
} elsif ( $ opt eq 'memory' || $ opt eq 'balloon' || $ opt eq 'shares' ) {
2012-02-02 06:39:38 +01:00
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Memory' ] ) ;
} elsif ( $ opt eq 'args' || $ opt eq 'lock' ) {
die "only root can set '$opt' config\n" ;
2013-04-30 11:46:38 +02:00
} elsif ( $ opt eq 'cpu' || $ opt eq 'kvm' || $ opt eq 'acpi' ||
2012-02-02 06:39:38 +01:00
$ opt eq 'vga' || $ opt eq 'watchdog' || $ opt eq 'tablet' ) {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.HWType' ] ) ;
} elsif ( $ opt =~ m/^net\d+$/ ) {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Network' ] ) ;
} else {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , $ pool , [ 'VM.Config.Options' ] ) ;
}
}
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' ) ,
} ,
} ,
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 ( ) ;
2011-08-23 07:47:04 +02:00
my $ vmstatus = PVE::QemuServer:: vmstatus ( ) ;
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 } ;
$ data - > { vmid } = $ vmid ;
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
} } ) ;
__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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
2011-10-17 13:49:48 +02:00
archive = > {
description = > "The backup file." ,
type = > 'string' ,
optional = > 1 ,
maxLength = > 255 ,
} ,
storage = > get_standard_option ( 'pve-storage-id' , {
description = > "Default storage." ,
optional = > 1 ,
} ) ,
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
2011-08-23 07:47:04 +02:00
my $ filename = PVE::QemuServer:: 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 ) {
if ( PVE::QemuServer:: valid_drivename ( $ opt ) ) {
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 {
2012-02-06 12:36:16 +01:00
my $ path = $ rpcenv - > check_volume_access ( $ authuser , $ storecfg , $ vmid , $ archive ) ;
2012-04-02 10:52:05 +02:00
PVE::Storage:: activate_volumes ( $ storecfg , [ $ archive ] )
if PVE::Storage:: parse_volume_id ( $ archive , 1 ) ;
2011-11-23 07:30:43 +01:00
die "can't find archive file '$archive'\n" if ! ( $ path && - f $ path ) ;
$ archive = $ path ;
}
2011-08-23 07:47:04 +02:00
}
2013-04-30 11:46:38 +02:00
my $ addVMtoPoolFn = sub {
2012-02-03 13:44:12 +01:00
my $ usercfg = cfs_read_file ( "user.cfg" ) ;
if ( my $ data = $ usercfg - > { pools } - > { $ pool } ) {
$ data - > { vms } - > { $ vmid } = 1 ;
$ usercfg - > { vms } - > { $ vmid } = $ pool ;
cfs_write_file ( "user.cfg" , $ usercfg ) ;
}
} ;
2011-10-17 13:49:48 +02:00
my $ restorefn = sub {
2013-04-29 09:30:15 +02:00
# fixme: this test does not work if VM exists on other node!
2011-10-17 13:49:48 +02:00
if ( - f $ filename ) {
2012-01-27 09:35:26 +01:00
die "unable to restore vm $vmid: config file already exists\n"
2011-10-18 09:14:05 +02:00
if ! $ force ;
2011-10-17 13:49:48 +02:00
2012-01-27 09:35:26 +01:00
die "unable to restore vm $vmid: vm is running\n"
2011-10-17 13:49:48 +02:00
if PVE::QemuServer:: check_running ( $ vmid ) ;
}
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
PVE::AccessControl:: lock_user_config ( $ addVMtoPoolFn , "can't add VM to pool" ) if $ pool ;
2011-10-17 13:49:48 +02:00
} ;
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
2012-01-27 09:35:26 +01:00
die "unable to create vm $vmid: config file already exists\n"
2011-08-23 07:47:04 +02:00
if - f $ filename ;
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
2011-10-10 13:17:40 +02:00
# try to be smart about bootdisk
my @ disks = PVE::QemuServer:: disknames ( ) ;
my $ firstdisk ;
foreach my $ ds ( reverse @ disks ) {
2012-02-02 14:01:08 +01:00
next if ! $ conf - > { $ ds } ;
my $ disk = PVE::QemuServer:: parse_drive ( $ ds , $ conf - > { $ ds } ) ;
2011-10-10 13:17:40 +02:00
next if PVE::QemuServer:: drive_is_cdrom ( $ disk ) ;
$ firstdisk = $ ds ;
}
2011-08-23 07:47:04 +02:00
2012-02-02 14:01:08 +01:00
if ( ! $ conf - > { bootdisk } && $ firstdisk ) {
$ conf - > { bootdisk } = $ firstdisk ;
2011-10-10 13:17:40 +02:00
}
2011-08-23 07:47:04 +02:00
2012-02-03 13:13:47 +01:00
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf ) ;
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
PVE::AccessControl:: lock_user_config ( $ addVMtoPoolFn , "can't add VM to 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
} ;
2012-03-27 08:50:04 +02:00
return PVE::QemuServer:: 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' } ,
{ subdir = > 'status' } ,
{ subdir = > 'unlink' } ,
{ subdir = > 'vncproxy' } ,
2011-09-14 12:02:08 +02:00
{ subdir = > 'migrate' } ,
2012-08-08 08:26:58 +02:00
{ subdir = > 'resize' } ,
2011-08-23 07:47:04 +02:00
{ subdir = > 'rrd' } ,
{ subdir = > 'rrddata' } ,
2011-11-09 08:26:46 +01:00
{ subdir = > 'monitor' } ,
2012-09-10 07:32:33 +02:00
{ subdir = > 'snapshot' } ,
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 ;
} } ) ;
__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' ,
description = > "Get virtual machine configuration." ,
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' ) ,
} ,
} ,
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 ) = @ _ ;
my $ conf = PVE::QemuServer:: load_config ( $ param - > { vmid } ) ;
2012-09-07 13:07:23 +02:00
delete $ conf - > { snapshots } ;
2011-08-23 07:47:04 +02:00
return $ conf ;
} } ) ;
2012-02-03 10:23:50 +01:00
my $ vm_is_volid_owner = sub {
my ( $ storecfg , $ vmid , $ volid ) = @ _ ;
if ( $ volid !~ m | ^ / | ) {
my ( $ path , $ owner ) ;
eval { ( $ path , $ owner ) = PVE::Storage:: path ( $ storecfg , $ volid ) ; } ;
if ( $ owner && ( $ owner == $ vmid ) ) {
return 1 ;
}
}
return undef ;
} ;
my $ test_deallocate_drive = sub {
my ( $ storecfg , $ vmid , $ key , $ drive , $ force ) = @ _ ;
if ( ! PVE::QemuServer:: drive_is_cdrom ( $ drive ) ) {
my $ volid = $ drive - > { file } ;
if ( & $ vm_is_volid_owner ( $ storecfg , $ vmid , $ volid ) ) {
if ( $ force || $ key =~ m/^unused/ ) {
my $ sid = PVE::Storage:: parse_volume_id ( $ volid ) ;
return $ sid ;
}
}
}
return undef ;
} ;
2012-02-02 10:41:08 +01:00
my $ delete_drive = sub {
2012-02-02 14:01:08 +01:00
my ( $ conf , $ storecfg , $ vmid , $ key , $ drive , $ force ) = @ _ ;
2012-02-02 10:41:08 +01:00
if ( ! PVE::QemuServer:: drive_is_cdrom ( $ drive ) ) {
my $ volid = $ drive - > { file } ;
2012-02-03 10:23:50 +01:00
if ( & $ vm_is_volid_owner ( $ storecfg , $ vmid , $ volid ) ) {
if ( $ force || $ key =~ m/^unused/ ) {
eval { PVE::Storage:: vdisk_free ( $ storecfg , $ volid ) ; } ;
2012-05-29 07:56:16 +02:00
die $@ if $@ ;
2012-02-03 10:23:50 +01:00
} else {
PVE::QemuServer:: add_unused_volume ( $ conf , $ volid , $ vmid ) ;
2012-02-02 10:41:08 +01:00
}
2012-02-03 10:23:50 +01:00
}
}
2012-02-15 10:48:55 +01:00
delete $ conf - > { $ key } ;
2012-02-03 10:23:50 +01:00
} ;
my $ vmconfig_delete_option = sub {
my ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , $ opt , $ force ) = @ _ ;
return if ! defined ( $ conf - > { $ opt } ) ;
my $ isDisk = PVE::QemuServer:: valid_drivename ( $ opt ) || ( $ opt =~ m/^unused/ ) ;
if ( $ isDisk ) {
$ rpcenv - > check_vm_perm ( $ authuser , $ vmid , undef , [ 'VM.Config.Disk' ] ) ;
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ conf - > { $ opt } ) ;
2013-04-30 11:46:38 +02:00
if ( my $ sid = & $ test_deallocate_drive ( $ storecfg , $ vmid , $ opt , $ drive , $ force ) ) {
2012-02-06 12:19:35 +01:00
$ rpcenv - > check ( $ authuser , "/storage/$sid" , [ 'Datastore.Allocate' ] ) ;
2012-02-02 10:41:08 +01:00
}
2012-02-03 10:23:50 +01:00
}
2013-02-19 10:22:11 +01:00
my $ unplugwarning = "" ;
if ( $ conf - > { ostype } && $ conf - > { ostype } eq 'l26' ) {
$ unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM" ;
} elsif ( $ conf - > { ostype } && $ conf - > { ostype } eq 'l24' ) {
$ unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options" ;
} elsif ( ! $ conf - > { ostype } || ( $ conf - > { ostype } && $ conf - > { ostype } eq 'other' ) ) {
$ unplugwarning = "<br>verify that your guest support acpi hotplug" ;
}
2013-02-19 12:02:50 +01:00
if ( $ opt eq 'tablet' ) {
PVE::QemuServer:: vm_deviceplug ( undef , $ conf , $ vmid , $ opt ) ;
} else {
die "error hot-unplug $opt $unplugwarning" if ! PVE::QemuServer:: vm_deviceunplug ( $ vmid , $ conf , $ opt ) ;
}
2013-02-18 13:44:34 +01:00
2012-02-03 10:23:50 +01:00
if ( $ isDisk ) {
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ conf - > { $ opt } ) ;
& $ delete_drive ( $ conf , $ storecfg , $ vmid , $ opt , $ drive , $ force ) ;
2012-02-02 10:41:08 +01:00
} else {
2012-02-03 10:23:50 +01:00
delete $ conf - > { $ opt } ;
}
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
} ;
2012-08-29 13:42:41 +02:00
my $ safe_num_ne = sub {
2012-08-01 13:15:57 +02:00
my ( $ a , $ b ) = @ _ ;
2013-04-30 11:46:38 +02:00
return 0 if ! defined ( $ a ) && ! defined ( $ b ) ;
return 1 if ! defined ( $ a ) ;
return 1 if ! defined ( $ b ) ;
2012-08-01 13:15:57 +02:00
return $ a != $ b ;
} ;
2012-02-03 10:23:50 +01:00
my $ vmconfig_update_disk = sub {
my ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , $ opt , $ value , $ force ) = @ _ ;
2012-02-02 10:41:08 +01:00
2012-02-03 10:23:50 +01:00
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ value ) ;
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' ] ) ;
}
if ( $ conf - > { $ opt } ) {
if ( my $ old_drive = PVE::QemuServer:: parse_drive ( $ opt , $ conf - > { $ opt } ) ) {
my $ media = $ drive - > { media } || 'disk' ;
my $ oldmedia = $ old_drive - > { media } || 'disk' ;
die "unable to change media type\n" if $ media ne $ oldmedia ;
if ( ! PVE::QemuServer:: drive_is_cdrom ( $ old_drive ) &&
( $ drive - > { file } ne $ old_drive - > { file } ) ) { # delete old disks
& $ vmconfig_delete_option ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , $ opt , $ force ) ;
$ conf = PVE::QemuServer:: load_config ( $ vmid ) ; # update/reload
}
2012-07-15 17:19:07 +02:00
2012-08-29 13:42:41 +02:00
if ( & $ safe_num_ne ( $ drive - > { mbps } , $ old_drive - > { mbps } ) ||
& $ safe_num_ne ( $ drive - > { mbps_rd } , $ old_drive - > { mbps_rd } ) ||
& $ safe_num_ne ( $ drive - > { mbps_wr } , $ old_drive - > { mbps_wr } ) ||
& $ safe_num_ne ( $ drive - > { iops } , $ old_drive - > { iops } ) ||
& $ safe_num_ne ( $ drive - > { iops_rd } , $ old_drive - > { iops_rd } ) ||
& $ safe_num_ne ( $ drive - > { iops_wr } , $ old_drive - > { iops_wr } ) ) {
2013-04-30 11:46:38 +02:00
PVE::QemuServer:: qemu_block_set_io_throttle ( $ vmid , "drive-$opt" , $ drive - > { mbps } * 1024 * 1024 ,
$ drive - > { mbps_rd } * 1024 * 1024 , $ drive - > { mbps_wr } * 1024 * 1024 ,
$ drive - > { iops } , $ drive - > { iops_rd } , $ drive - > { iops_wr } )
2012-08-29 13:42:41 +02:00
if ! PVE::QemuServer:: drive_is_cdrom ( $ drive ) ;
2012-07-15 17:19:07 +02:00
}
2012-02-03 10:23:50 +01:00
}
}
& $ create_disks ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , undef , { $ opt = > $ value } ) ;
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
$ conf = PVE::QemuServer:: load_config ( $ vmid ) ; # update/reload
$ drive = PVE::QemuServer:: parse_drive ( $ opt , $ conf - > { $ opt } ) ;
if ( PVE::QemuServer:: drive_is_cdrom ( $ drive ) ) { # cdrom
if ( PVE::QemuServer:: check_running ( $ vmid ) ) {
if ( $ drive - > { file } eq 'none' ) {
2012-06-25 10:02:58 +02:00
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "eject" , force = > JSON:: true , device = > "drive-$opt" ) ;
2012-02-03 10:23:50 +01:00
} else {
my $ path = PVE::QemuServer:: get_iso_path ( $ storecfg , $ vmid , $ drive - > { file } ) ;
2012-06-25 10:02:58 +02:00
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "eject" , force = > JSON:: true , device = > "drive-$opt" ) ; #force eject if locked
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "change" , device = > "drive-$opt" , target = > "$path" ) if $ path ;
2012-02-03 10:23:50 +01:00
}
}
} else { # hotplug new disks
die "error hotplug $opt" if ! PVE::QemuServer:: vm_deviceplug ( $ storecfg , $ conf , $ vmid , $ opt , $ drive ) ;
2012-02-02 10:41:08 +01:00
}
} ;
2012-02-03 10:23:50 +01:00
my $ vmconfig_update_net = sub {
my ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , $ opt , $ value ) = @ _ ;
2013-03-05 10:22:18 +01:00
if ( $ conf - > { $ opt } && PVE::QemuServer:: check_running ( $ vmid ) ) {
my $ oldnet = PVE::QemuServer:: parse_net ( $ conf - > { $ opt } ) ;
my $ newnet = PVE::QemuServer:: parse_net ( $ value ) ;
if ( $ oldnet - > { model } ne $ newnet - > { model } ) {
#if model change, we try to hot-unplug
die "error hot-unplug $opt for update" if ! PVE::QemuServer:: vm_deviceunplug ( $ vmid , $ conf , $ opt ) ;
} else {
2013-04-30 11:46:38 +02:00
2013-03-05 10:22:18 +01:00
if ( $ newnet - > { bridge } && $ oldnet - > { bridge } ) {
my $ iface = "tap" . $ vmid . "i" . $ 1 if $ opt =~ m/net(\d+)/ ;
2012-02-03 10:23:50 +01:00
2013-03-05 10:22:18 +01:00
if ( $ newnet - > { rate } ne $ oldnet - > { rate } ) {
PVE::Network:: tap_rate_limit ( $ iface , $ newnet - > { rate } ) ;
}
if ( ( $ newnet - > { bridge } ne $ oldnet - > { bridge } ) || ( $ newnet - > { tag } ne $ oldnet - > { tag } ) ) {
eval { PVE::Network:: tap_unplug ( $ iface , $ oldnet - > { bridge } , $ oldnet - > { tag } ) ; } ;
PVE::Network:: tap_plug ( $ iface , $ newnet - > { bridge } , $ newnet - > { tag } ) ;
}
} else {
#if bridge/nat mode change, we try to hot-unplug
die "error hot-unplug $opt for update" if ! PVE::QemuServer:: vm_deviceunplug ( $ vmid , $ conf , $ opt ) ;
}
}
2013-04-30 11:46:38 +02:00
2013-03-05 10:22:18 +01:00
}
2012-02-03 10:23:50 +01:00
$ conf - > { $ opt } = $ value ;
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
$ conf = PVE::QemuServer:: load_config ( $ vmid ) ; # update/reload
my $ net = PVE::QemuServer:: parse_net ( $ conf - > { $ opt } ) ;
die "error hotplug $opt" if ! PVE::QemuServer:: vm_deviceplug ( $ storecfg , $ conf , $ vmid , $ opt , $ net ) ;
} ;
2012-02-02 06:39:38 +01:00
my $ vm_config_perm_list = [
2013-04-30 11:46:38 +02:00
'VM.Config.Disk' ,
'VM.Config.CDROM' ,
'VM.Config.CPU' ,
'VM.Config.Memory' ,
'VM.Config.Network' ,
2012-02-02 06:39:38 +01:00
'VM.Config.HWType' ,
'VM.Config.Options' ,
] ;
2011-08-23 07:47:04 +02:00
__PACKAGE__ - > register_method ( {
2012-01-27 09:35:26 +01:00
name = > 'update_vm' ,
path = > '{vmid}/config' ,
2011-08-23 07:47:04 +02:00
method = > 'PUT' ,
protected = > 1 ,
proxyto = > 'node' ,
description = > "Set virtual machine options." ,
2012-02-02 06:39:38 +01:00
permissions = > {
check = > [ 'perm' , '/vms/{vmid}' , $ vm_config_perm_list , any = > 1 ] ,
} ,
2011-08-23 07:47:04 +02:00
parameters = > {
additionalProperties = > 0 ,
properties = > PVE::QemuServer:: json_config_properties (
{
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
2011-09-14 12:02:08 +02:00
skiplock = > get_standard_option ( 'skiplock' ) ,
2011-08-23 07:47:04 +02:00
delete = > {
type = > 'string' , format = > 'pve-configid-list' ,
description = > "A list of settings you want to delete." ,
optional = > 1 ,
} ,
force = > {
type = > 'boolean' ,
description = > $ opt_force_description ,
optional = > 1 ,
requires = > 'delete' ,
} ,
2011-09-07 11:41:34 +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 ,
2012-01-27 09:35:26 +01:00
optional = > 1 ,
2011-09-07 11:41:34 +02:00
}
2011-08-23 07:47:04 +02:00
} ) ,
} ,
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-08-23 07:47:04 +02:00
my $ node = extract_param ( $ param , 'node' ) ;
my $ vmid = extract_param ( $ param , 'vmid' ) ;
2011-10-10 13:17:40 +02:00
my $ digest = extract_param ( $ param , 'digest' ) ;
my @ paramarr = ( ) ; # used for log message
foreach my $ key ( keys %$ param ) {
push @ paramarr , "-$key" , $ param - > { $ key } ;
}
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-02-02 07:19:46 +01:00
my $ delete_str = extract_param ( $ param , 'delete' ) ;
2011-08-23 07:47:04 +02:00
my $ force = extract_param ( $ param , 'force' ) ;
2012-02-02 07:19:46 +01:00
die "no options specified\n" if ! $ delete_str && ! scalar ( keys %$ param ) ;
2012-02-02 08:35:11 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2012-12-28 14:06:46 +01:00
my $ defaults = PVE::QemuServer:: load_defaults ( ) ;
2012-02-02 08:35:11 +01:00
& $ resolve_cdrom_alias ( $ param ) ;
# now try to verify all parameters
2012-02-02 07:19:46 +01:00
my @ delete = ( ) ;
foreach my $ opt ( PVE::Tools:: split_list ( $ delete_str ) ) {
$ opt = 'ide2' if $ opt eq 'cdrom' ;
raise_param_exc ( { delete = > "you can't use '-$opt' and " .
"-delete $opt' at the same time" } )
if defined ( $ param - > { $ opt } ) ;
if ( ! PVE::QemuServer:: option_exists ( $ opt ) ) {
raise_param_exc ( { delete = > "unknown option '$opt'" } ) ;
}
2012-02-03 10:23:50 +01:00
2012-02-02 07:19:46 +01:00
push @ delete , $ opt ;
}
2011-08-23 07:47:04 +02:00
2012-02-02 08:35:11 +01:00
foreach my $ opt ( keys %$ param ) {
if ( PVE::QemuServer:: valid_drivename ( $ opt ) ) {
# cleanup drive path
my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ param - > { $ opt } ) ;
PVE::QemuServer:: cleanup_drive_path ( $ opt , $ storecfg , $ drive ) ;
$ param - > { $ opt } = PVE::QemuServer:: print_drive ( $ vmid , $ drive ) ;
2013-04-30 11:46:38 +02:00
} elsif ( $ opt =~ m/^net(\d+)$/ ) {
2012-02-02 08:35:11 +01:00
# add macaddr
my $ net = PVE::QemuServer:: parse_net ( $ param - > { $ opt } ) ;
$ param - > { $ opt } = PVE::QemuServer:: print_net ( $ net ) ;
}
}
2011-08-23 07:47:04 +02:00
2012-02-03 10:23:50 +01:00
& $ check_vm_modify_config_perm ( $ rpcenv , $ authuser , $ vmid , undef , [ @ delete ] ) ;
& $ check_vm_modify_config_perm ( $ rpcenv , $ authuser , $ vmid , undef , [ keys %$ param ] ) ;
2012-02-06 12:19:35 +01:00
& $ check_storage_access ( $ rpcenv , $ authuser , $ storecfg , $ vmid , $ param ) ;
2011-08-23 07:47:04 +02:00
2012-01-27 09:53:48 +01:00
my $ updatefn = sub {
2011-08-23 07:47:04 +02:00
2012-01-27 09:53:48 +01:00
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
2011-08-23 07:47:04 +02:00
2012-01-27 09:53:48 +01:00
die "checksum missmatch (file change by other user?)\n"
if $ digest && $ digest ne $ conf - > { digest } ;
2011-08-23 07:47:04 +02:00
2012-01-27 09:53:48 +01:00
PVE::QemuServer:: check_lock ( $ conf ) if ! $ skiplock ;
2011-08-23 07:47:04 +02:00
2012-12-28 14:06:46 +01:00
if ( $ param - > { memory } || defined ( $ param - > { balloon } ) ) {
my $ maxmem = $ param - > { memory } || $ conf - > { memory } || $ defaults - > { memory } ;
my $ balloon = defined ( $ param - > { balloon } ) ? $ param - > { balloon } : $ conf - > { balloon } ;
die "balloon value too large (must be smaller than assigned memory)\n"
if $ balloon > $ maxmem ;
}
2012-02-02 06:39:38 +01:00
PVE::Cluster:: log_msg ( 'info' , $ authuser , "update VM $vmid: " . join ( ' ' , @ paramarr ) ) ;
2012-01-26 19:43:48 +01:00
2012-02-02 10:41:08 +01:00
foreach my $ opt ( @ delete ) { # delete
2012-02-02 08:35:11 +01:00
$ conf = PVE::QemuServer:: load_config ( $ vmid ) ; # update/reload
2012-02-03 10:23:50 +01:00
& $ vmconfig_delete_option ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid , $ opt , $ force ) ;
2012-01-27 09:53:48 +01:00
}
2011-08-23 07:47:04 +02:00
2012-12-28 14:06:46 +01:00
my $ running = PVE::QemuServer:: check_running ( $ vmid ) ;
2012-02-02 10:41:08 +01:00
foreach my $ opt ( keys %$ param ) { # add/change
2011-08-23 07:47:04 +02:00
2012-02-02 08:35:11 +01:00
$ conf = PVE::QemuServer:: load_config ( $ vmid ) ; # update/reload
2011-08-23 07:47:04 +02:00
2012-02-02 10:41:08 +01:00
next if $ conf - > { $ opt } && ( $ param - > { $ opt } eq $ conf - > { $ opt } ) ; # skip if nothing changed
2012-02-03 10:23:50 +01:00
if ( PVE::QemuServer:: valid_drivename ( $ opt ) ) {
2012-02-02 10:41:08 +01:00
2013-04-30 11:46:38 +02:00
& $ vmconfig_update_disk ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid ,
2012-02-03 10:23:50 +01:00
$ opt , $ param - > { $ opt } , $ force ) ;
2013-04-30 11:46:38 +02:00
2012-02-03 10:23:50 +01:00
} elsif ( $ opt =~ m/^net(\d+)$/ ) { #nics
2012-02-02 14:01:08 +01:00
2013-04-30 11:46:38 +02:00
& $ vmconfig_update_net ( $ rpcenv , $ authuser , $ conf , $ storecfg , $ vmid ,
2012-02-03 10:23:50 +01:00
$ opt , $ param - > { $ opt } ) ;
2012-02-02 08:35:11 +01:00
} else {
2013-02-18 13:44:34 +01:00
if ( $ opt eq 'tablet' && $ param - > { $ opt } == 1 ) {
PVE::QemuServer:: vm_deviceplug ( undef , $ conf , $ vmid , $ opt ) ;
} elsif ( $ opt eq 'tablet' && $ param - > { $ opt } == 0 ) {
PVE::QemuServer:: vm_deviceunplug ( $ vmid , $ conf , $ opt ) ;
}
2012-02-02 14:01:08 +01:00
$ conf - > { $ opt } = $ param - > { $ opt } ;
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
2012-01-28 11:02:27 +01:00
}
2012-01-27 09:53:48 +01:00
}
2012-12-28 14:06:46 +01:00
# allow manual ballooning if shares is set to zero
2013-04-30 11:46:38 +02:00
if ( $ running && defined ( $ param - > { balloon } ) &&
2012-12-28 14:06:46 +01:00
defined ( $ conf - > { shares } ) && ( $ conf - > { shares } == 0 ) ) {
my $ balloon = $ param - > { 'balloon' } || $ conf - > { memory } || $ defaults - > { memory } ;
PVE::QemuServer:: vm_mon_cmd ( $ vmid , "balloon" , value = > $ balloon * 1024 * 1024 ) ;
}
2012-01-27 09:53:48 +01:00
} ;
PVE::QemuServer:: lock_config ( $ vmid , $ updatefn ) ;
2012-01-20 11:42:08 +01:00
2011-08-23 07:47:04 +02:00
return undef ;
} } ) ;
__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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
2012-01-27 09:35:26 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2011-08-23 07:47:04 +02:00
2013-04-30 11:46:38 +02:00
my $ delVMfromPoolFn = sub {
2012-02-03 13:44:12 +01:00
my $ usercfg = cfs_read_file ( "user.cfg" ) ;
2012-05-23 07:42:55 +02:00
if ( my $ pool = $ usercfg - > { vms } - > { $ vmid } ) {
if ( my $ data = $ usercfg - > { pools } - > { $ pool } ) {
delete $ data - > { vms } - > { $ vmid } ;
delete $ usercfg - > { vms } - > { $ vmid } ;
cfs_write_file ( "user.cfg" , $ usercfg ) ;
}
2012-02-03 13:44:12 +01:00
}
} ;
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
PVE::AccessControl:: lock_user_config ( $ delVMfromPoolFn , "pool cleanup failed" ) ;
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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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' ) ,
} ,
} ,
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 } ;
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 ;
my $ port = PVE::Tools:: next_vnc_port ( ) ;
my $ remip ;
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 ( ) ) {
2011-08-23 07:47:04 +02:00
$ remip = PVE::Cluster:: remote_node_ip ( $ node ) ;
}
2012-10-31 12:16:55 +01:00
# NOTE: kvm VNC traffic is already TLS encrypted
my $ remcmd = $ remip ? [ '/usr/bin/ssh' , '-T' , '-o' , 'BatchMode=yes' , $ remip ] : [] ;
2011-08-23 07:47:04 +02:00
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" ) ;
my $ qmcmd = [ @$ remcmd , "/usr/sbin/qm" , 'vncproxy' , $ vmid ] ;
my $ qmstr = join ( ' ' , @$ qmcmd ) ;
# also redirect stderr (else we get RFB protocol errors)
2011-10-05 10:16:20 +02:00
my $ cmd = [ '/bin/nc' , '-l' , '-p' , $ port , '-w' , $ timeout , '-c' , "$qmstr 2>/dev/null" ] ;
2011-08-23 07:47:04 +02:00
2011-10-05 10:16:20 +02:00
PVE::Tools:: run_command ( $ cmd ) ;
2011-08-23 07:47:04 +02:00
return ;
} ;
2012-02-02 06:39:38 +01:00
my $ upid = $ rpcenv - > fork_worker ( 'vncproxy' , $ vmid , $ authuser , $ realcmd ) ;
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
} ;
} } ) ;
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
my $ conf = PVE::QemuServer:: load_config ( $ param - > { vmid } ) ;
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 ;
} } ) ;
2012-03-27 10:37:39 +02:00
my $ vm_is_ha_managed = sub {
my ( $ vmid ) = @ _ ;
my $ cc = PVE::Cluster:: cfs_read_file ( 'cluster.conf' ) ;
if ( PVE::Cluster:: cluster_conf_lookup_pvevm ( $ cc , 0 , $ vmid , 1 ) ) {
return 1 ;
2013-04-30 11:46:38 +02:00
}
2012-03-27 10:37:39 +02:00
return 0 ;
} ;
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
my $ conf = PVE::QemuServer:: load_config ( $ param - > { vmid } ) ;
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
2012-03-27 10:37:39 +02:00
$ status - > { ha } = & $ vm_is_ha_managed ( $ param - > { vmid } ) ;
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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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 } ) ,
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' ) ;
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' ;
2012-01-27 09:35:26 +01:00
my $ storecfg = PVE::Storage:: config ( ) ;
2011-10-10 13:17:40 +02:00
2012-03-27 12:21:15 +02:00
if ( & $ vm_is_ha_managed ( $ vmid ) && ! $ stateuri &&
$ 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
2012-03-27 10:37:39 +02:00
my $ service = "pvevm:$vmid" ;
2011-10-10 13:17:40 +02:00
2012-03-27 10:37:39 +02:00
my $ cmd = [ 'clusvcadm' , '-e' , $ service , '-m' , $ node ] ;
print "Executing HA start for VM $vmid\n" ;
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" ) ;
2012-08-21 12:21:51 +02:00
PVE::QemuServer:: vm_start ( $ storecfg , $ vmid , $ stateuri , $ skiplock , $ migratedfrom ) ;
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' ,
description = > "Stop 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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
skiplock = > get_standard_option ( 'skiplock' ) ,
2012-08-23 10:28:41 +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 = > {
description = > "Do not decativate storage volumes." ,
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 ( ) ;
2012-03-27 10:55:59 +02:00
if ( & $ vm_is_ha_managed ( $ vmid ) && $ 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
2012-03-27 10:37:39 +02:00
my $ service = "pvevm:$vmid" ;
2011-10-11 11:58:34 +02:00
2012-03-27 10:37:39 +02:00
my $ cmd = [ 'clusvcadm' , '-d' , $ service ] ;
print "Executing HA stop for VM $vmid\n" ;
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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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' ,
description = > "Shutdown 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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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 = > {
description = > "Do not decativate storage volumes." ,
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 ( ) ;
2011-10-10 13:17:40 +02:00
my $ realcmd = sub {
my $ upid = shift ;
syslog ( 'info' , "shutdown VM $vmid: $upid\n" ) ;
2012-01-27 09:35:26 +01:00
PVE::QemuServer:: vm_stop ( $ storecfg , $ vmid , $ skiplock , 0 , $ param - > { timeout } ,
2012-01-17 11:56:56 +01:00
1 , $ param - > { forceStop } , $ keepActive ) ;
2011-10-11 11:58:34 +02:00
2011-10-10 13:17:40 +02:00
return ;
} ;
2012-02-02 06:39:38 +01:00
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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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-28 13:28:18 +01:00
die "VM $vmid not running\n" if ! PVE::QemuServer:: check_running ( $ vmid ) ;
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" ) ;
2011-08-23 07:47:04 +02:00
PVE::QemuServer:: vm_resume ( $ vmid , $ skiplock ) ;
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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
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' ,
enum = > [ 'snapshot' , 'clone' ] ,
} ,
snapname = > get_standard_option ( 'pve-snapshot-name' , {
optional = > 1 ,
} ) ,
} ,
} ,
returns = > {
type = > 'boolean'
} ,
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 ) ;
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
if ( $ snapname ) {
my $ snap = $ conf - > { snapshots } - > { $ snapname } ;
die "snapshot '$snapname' does not exist\n" if ! defined ( $ snap ) ;
$ conf = $ snap ;
}
my $ storecfg = PVE::Storage:: config ( ) ;
my $ hasfeature = PVE::QemuServer:: has_feature ( $ feature , $ conf , $ storecfg , $ snapname , $ running ) ;
my $ res = $ hasfeature ? 1 : 0 ;
return $ res ;
} } ) ;
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' ) ,
vmid = > get_standard_option ( 'pve-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' , {
requires = > 'full' ,
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' ) ;
# fixme: update pool after create
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-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
2013-04-29 09:30:15 +02:00
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
PVE::QemuServer:: check_lock ( $ conf ) ;
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
2013-04-29 09:30:15 +02:00
my $ conffile = PVE::QemuServer:: config_file ( $ newid ) ;
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 = { } ;
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' ;
# always change MAC! address
if ( $ opt =~ m/^net(\d+)$/ ) {
my $ net = PVE::QemuServer:: parse_net ( $ value ) ;
$ net - > { macaddr } = PVE::Tools:: random_ether_addr ( ) ;
$ newconf - > { $ opt } = PVE::QemuServer:: print_net ( $ net ) ;
} elsif ( my $ drive = PVE::QemuServer:: parse_drive ( $ opt , $ value ) ) {
if ( PVE::QemuServer:: drive_is_cdrom ( $ drive ) ) {
$ newconf - > { $ opt } = $ value ; # simply copy configuration
} else {
2013-05-02 07:17:15 +02:00
if ( $ param - > { full } || ! PVE::Storage:: volume_is_base ( $ storecfg , $ drive - > { file } ) ) {
2013-05-03 08:39:41 +02:00
die "Full clone feature is not available"
2013-05-02 07:17:15 +02:00
if ! PVE::Storage:: volume_has_feature ( $ storecfg , 'copy' , $ drive - > { file } , $ snapname , $ running ) ;
2013-05-03 08:39:41 +02:00
$ drive - > { full } = 1 ;
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 ;
}
}
delete $ newconf - > { template } ;
if ( $ param - > { name } ) {
$ newconf - > { name } = $ param - > { name } ;
} else {
$ newconf - > { name } = "Copy-of-$oldconf->{name}" ;
}
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 = [] ;
2013-04-29 09:30:15 +02:00
2013-04-29 10:42:35 +02:00
eval {
2013-05-02 07:10:46 +02:00
local $ SIG { INT } = $ SIG { TERM } = $ SIG { QUIT } = $ SIG { HUP } = sub { die "interrupted by signal\n" ; } ;
2013-04-30 11:46:38 +02:00
2013-04-29 09:30:15 +02:00
PVE::Storage:: activate_volumes ( $ storecfg , $ vollist ) ;
2013-05-02 07:10:46 +02:00
foreach my $ opt ( keys %$ drives ) {
my $ drive = $ drives - > { $ opt } ;
2013-05-03 08:39:41 +02:00
2013-05-02 07:10:46 +02:00
my $ newvolid ;
if ( ! $ drive - > { full } ) {
2013-05-02 11:42:22 +02:00
print "create linked clone of drive $opt ($drive->{file})\n" ;
2013-05-02 07:10:46 +02:00
$ newvolid = PVE::Storage:: vdisk_clone ( $ storecfg , $ drive - > { file } , $ newid ) ;
push @$ newvollist , $ newvolid ;
2013-05-02 06:43:37 +02:00
2013-05-02 07:10:46 +02:00
} else {
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ drive - > { file } ) ;
$ storeid = $ storage if $ storage ;
my $ fmt = undef ;
if ( $ format ) {
$ fmt = $ format ;
} else {
my $ defformat = PVE::Storage:: storage_default_format ( $ storecfg , $ storeid ) ;
$ fmt = $ drive - > { format } || $ defformat ;
2013-04-29 10:42:35 +02:00
}
2013-05-02 07:10:46 +02:00
my ( $ size ) = PVE::Storage:: volume_size_info ( $ storecfg , $ drive - > { file } , 3 ) ;
2013-05-03 08:39:41 +02:00
print "create full clone of drive $opt ($drive->{file})\n" ;
2013-05-02 07:10:46 +02:00
$ newvolid = PVE::Storage:: vdisk_alloc ( $ storecfg , $ storeid , $ newid , $ fmt , undef , ( $ size / 1024 ) ) ;
push @$ newvollist , $ newvolid ;
2013-04-29 09:30:15 +02:00
2013-05-02 18:18:04 +02:00
if ( ! $ running || $ snapname ) {
PVE::QemuServer:: qemu_img_convert ( $ drive - > { file } , $ newvolid , $ size , $ snapname ) ;
} else {
PVE::QemuServer:: qemu_drive_mirror ( $ vmid , $ opt , $ newvolid , $ newid ) ;
}
2013-04-29 10:42:35 +02:00
}
2013-05-02 07:10:46 +02:00
my ( $ size ) = PVE::Storage:: volume_size_info ( $ storecfg , $ newvolid , 3 ) ;
my $ disk = { file = > $ newvolid , size = > $ size } ;
$ newconf - > { $ opt } = PVE::QemuServer:: print_drive ( $ vmid , $ disk ) ;
2013-05-03 08:39:41 +02:00
2013-05-02 07:10:46 +02:00
PVE::QemuServer:: update_config_nolock ( $ newid , $ newconf , 1 ) ;
}
2013-04-29 10:42:35 +02:00
delete $ newconf - > { lock } ;
PVE::QemuServer:: update_config_nolock ( $ newid , $ newconf , 1 ) ;
2013-04-30 11:44:39 +02:00
if ( $ target ) {
my $ newconffile = PVE::QemuServer:: config_file ( $ newid , $ target ) ;
die "Failed to move config to node '$target' - rename failed: $!\n"
if ! rename ( $ conffile , $ newconffile ) ;
}
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 ;
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 ;
} ;
2013-05-02 11:42:22 +02:00
return $ rpcenv - > fork_worker ( 'qmclone' , $ vmid , $ authuser , $ realcmd ) ;
2013-04-29 09:30:15 +02:00
} ;
2013-04-30 07:40:43 +02:00
return PVE::QemuServer:: lock_config_mode ( $ vmid , 1 , $ shared_lock , sub {
2013-04-29 09:30:15 +02:00
# Aquire exclusive lock lock for $newid
2013-05-02 11:42:22 +02:00
return PVE::QemuServer:: lock_config_full ( $ newid , 1 , $ clonefn ) ;
2013-04-29 09:30:15 +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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
target = > get_standard_option ( 'pve-node' , { description = > "Target node." } ) ,
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 ,
} ,
} ,
} ,
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' ) ;
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
# test if VM exists
2011-11-25 13:25:32 +01:00
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
2011-09-14 12:02:08 +02:00
# try to detect errors early
2011-11-25 13:25:32 +01:00
PVE::QemuServer:: check_lock ( $ conf ) ;
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 ( ) ;
2012-04-07 08:26:51 +02:00
PVE::QemuServer:: check_storage_availability ( $ storecfg , $ conf , $ target ) ;
2012-03-30 09:13:31 +02:00
2012-03-27 10:55:59 +02:00
if ( & $ 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
2012-03-27 10:37:39 +02:00
my $ service = "pvevm:$vmid" ;
my $ cmd = [ 'clusvcadm' , '-M' , $ service , '-m' , $ target ] ;
print "Executing HA migrate for VM $vmid to node $target\n" ;
PVE::Tools:: run_command ( $ cmd ) ;
return ;
} ;
return $ rpcenv - > fork_worker ( 'hamigrate' , $ vmid , $ authuser , $ hacmd ) ;
} else {
my $ realcmd = sub {
my $ upid = shift ;
PVE::QemuMigrate - > migrate ( $ target , $ targetip , $ vmid , $ param ) ;
} ;
return $ rpcenv - > fork_worker ( 'qmigrate' , $ vmid , $ authuser , $ realcmd ) ;
}
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 = > {
check = > [ 'perm' , '/vms/{vmid}' , [ 'VM.Monitor' ] ] ,
} ,
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 ) = @ _ ;
my $ vmid = $ param - > { vmid } ;
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ; # check if VM exists
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 ;
} } ) ;
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' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
skiplock = > get_standard_option ( 'skiplock' ) ,
disk = > {
type = > 'string' ,
description = > "The disk you want to resize." ,
enum = > [ PVE::QemuServer:: disknames ( ) ] ,
} ,
size = > {
type = > 'string' ,
2012-08-08 09:25:54 +02:00
pattern = > '\+?\d+(\.\d+)?[KMGT]?' ,
2012-08-08 08:26:58 +02: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." ,
} ,
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 {
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
die "checksum missmatch (file change by other user?)\n"
if $ digest && $ digest ne $ conf - > { digest } ;
PVE::QemuServer:: check_lock ( $ conf ) if ! $ skiplock ;
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 } ) ;
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 ) ;
2013-04-30 11:46:38 +02:00
die "you can't online resize a virtio windows bootdisk\n"
2013-02-23 10:25:52 +01:00
if PVE::QemuServer:: check_running ( $ vmid ) && $ conf - > { bootdisk } eq $ disk && $ conf - > { ostype } =~ m/^w/ && $ disk =~ m/^virtio/ ;
2012-08-08 09:25:54 +02:00
my ( $ storeid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
$ rpcenv - > check ( $ authuser , "/storage/$storeid" , [ 'Datastore.AllocateSpace' ] ) ;
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 ) ;
die "unable to skrink disk size\n" if $ newsize < $ size ;
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 ) ;
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
} ;
2012-08-07 12:05:17 +02:00
PVE::QemuServer:: lock_config ( $ vmid , $ updatefn ) ;
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 = > {
vmid = > get_standard_option ( 'pve-vmid' ) ,
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 } ;
my $ conf = PVE::QemuServer:: 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' ) ,
vmid = > get_standard_option ( 'pve-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" ,
} ,
freezefs = > {
optional = > 1 ,
type = > 'boolean' ,
description = > "Freeze the filesystem" ,
} ,
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" ) ;
2013-04-30 11:46:38 +02:00
PVE::QemuServer:: snapshot_create ( $ vmid , $ snapname , $ param - > { vmstate } ,
2012-09-11 09:00:26 +02:00
$ param - > { freezefs } , $ 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 {
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
PVE::QemuServer:: check_lock ( $ conf ) ;
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 } ) ;
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
} ;
PVE::QemuServer:: lock_config ( $ vmid , $ updatefn ) ;
return undef ;
} } ) ;
__PACKAGE__ - > register_method ( {
name = > 'get_snapshot_config' ,
path = > '{vmid}/snapshot/{snapname}/config' ,
method = > 'GET' ,
proxyto = > 'node' ,
description = > "Get snapshot configuration" ,
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' ) ,
} ,
} ,
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' ) ;
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
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 = > {
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' ) ,
vmid = > get_standard_option ( 'pve-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" ) ;
PVE::QemuServer:: snapshot_rollback ( $ vmid , $ snapname ) ;
2012-09-10 07:32:33 +02:00
} ;
return $ rpcenv - > fork_worker ( 'qmrollback' , $ vmid , $ authuser , $ realcmd ) ;
} } ) ;
__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' ) ,
vmid = > get_standard_option ( 'pve-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" ) ;
2012-09-12 07:19:38 +02:00
PVE::QemuServer:: 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-04-22 07:02:10 +02:00
description = > "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}." ,
2013-04-30 11:46:38 +02:00
check = > [ 'or' ,
2013-04-19 08:37:32 +02:00
[ 'perm' , '/vms/{vmid}' , [ 'VM.Allocate' ] ] ,
[ 'perm' , '/pool/{pool}' , [ 'VM.Allocate' ] , require_param = > 'pool' ] ,
] ,
} ,
2013-02-14 11:58:49 +01:00
parameters = > {
additionalProperties = > 0 ,
properties = > {
node = > get_standard_option ( 'pve-node' ) ,
vmid = > get_standard_option ( 'pve-vmid' ) ,
disk = > {
optional = > 1 ,
type = > 'string' ,
description = > "If you want to convert only 1 disk to base image." ,
enum = > [ PVE::QemuServer:: disknames ( ) ] ,
} ,
} ,
} ,
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 {
my $ conf = PVE::QemuServer:: load_config ( $ vmid ) ;
PVE::QemuServer:: check_lock ( $ conf ) ;
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"
2013-02-15 08:45:42 +01:00
if PVE::QemuServer:: 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 ;
2013-02-14 11:58:49 +01:00
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
2013-04-22 07:08:51 +02:00
return $ rpcenv - > fork_worker ( 'qmtemplate' , $ vmid , $ authuser , $ realcmd ) ;
2013-02-14 11:58:49 +01:00
} ;
PVE::QemuServer:: lock_config ( $ vmid , $ updatefn ) ;
return undef ;
} } ) ;
2011-08-23 07:47:04 +02:00
1 ;