1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::RPCEnvironment
;
20 use PVE
::AccessControl
;
24 use PVE
::API2
::Firewall
::VM
;
25 use PVE
::HA
::Env
::PVE2
;
28 use Data
::Dumper
; # fixme: remove
30 use base
qw(PVE::RESTHandler);
32 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.";
34 my $resolve_cdrom_alias = sub {
37 if (my $value = $param->{cdrom
}) {
38 $value .= ",media=cdrom" if $value !~ m/media=/;
39 $param->{ide2
} = $value;
40 delete $param->{cdrom
};
44 my $check_storage_access = sub {
45 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
47 PVE
::QemuServer
::foreach_drive
($settings, sub {
48 my ($ds, $drive) = @_;
50 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
52 my $volid = $drive->{file
};
54 if (!$volid || $volid eq 'none') {
56 } elsif ($isCDROM && ($volid eq 'cdrom')) {
57 $rpcenv->check($authuser, "/", ['Sys.Console']);
58 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
59 my ($storeid, $size) = ($2 || $default_storage, $3);
60 die "no storage ID specified (and no default storage)\n" if !$storeid;
61 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
63 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
68 my $check_storage_access_clone = sub {
69 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
73 PVE
::QemuServer
::foreach_drive
($conf, sub {
74 my ($ds, $drive) = @_;
76 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
78 my $volid = $drive->{file
};
80 return if !$volid || $volid eq 'none';
83 if ($volid eq 'cdrom') {
84 $rpcenv->check($authuser, "/", ['Sys.Console']);
86 # we simply allow access
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
93 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
94 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
95 $sharedvm = 0 if !$scfg->{shared
};
97 $sid = $storage if $storage;
98 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
105 # Note: $pool is only needed when creating a VM, because pool permissions
106 # are automatically inherited if VM already exists inside a pool.
107 my $create_disks = sub {
108 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
113 PVE
::QemuServer
::foreach_drive
($settings, sub {
114 my ($ds, $disk) = @_;
116 my $volid = $disk->{file
};
118 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
119 delete $disk->{size
};
120 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
121 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
122 my ($storeid, $size) = ($2 || $default_storage, $3);
123 die "no storage ID specified (and no default storage)\n" if !$storeid;
124 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
125 my $fmt = $disk->{format
} || $defformat;
126 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
127 $fmt, undef, $size*1024*1024);
128 $disk->{file
} = $volid;
129 $disk->{size
} = $size*1024*1024*1024;
130 push @$vollist, $volid;
131 delete $disk->{format
}; # no longer needed
132 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
135 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
137 my $volid_is_new = 1;
140 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
141 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
148 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
150 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
152 die "volume $volid does not exists\n" if !$size;
154 $disk->{size
} = $size;
157 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
161 # free allocated images on error
163 syslog
('err', "VM $vmid creating disks failed");
164 foreach my $volid (@$vollist) {
165 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
171 # modify vm config if everything went well
172 foreach my $ds (keys %$res) {
173 $conf->{$ds} = $res->{$ds};
190 my $memoryoptions = {
196 my $hwtypeoptions = {
208 my $generaloptions = {
215 'migrate_downtime' => 1,
216 'migrate_speed' => 1,
228 my $vmpoweroptions = {
237 my $check_vm_modify_config_perm = sub {
238 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
240 return 1 if $authuser eq 'root@pam';
242 foreach my $opt (@$key_list) {
243 # disk checks need to be done somewhere else
244 next if PVE
::QemuServer
::is_valid_drivename
($opt);
245 next if $opt eq 'cdrom';
246 next if $opt =~ m/^unused\d+$/;
248 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
249 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
250 } elsif ($memoryoptions->{$opt}) {
251 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
252 } elsif ($hwtypeoptions->{$opt}) {
253 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
254 } elsif ($generaloptions->{$opt}) {
255 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
256 # special case for startup since it changes host behaviour
257 if ($opt eq 'startup') {
258 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
260 } elsif ($vmpoweroptions->{$opt}) {
261 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
262 } elsif ($diskoptions->{$opt}) {
263 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
264 } elsif ($opt =~ m/^net\d+$/) {
265 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
267 # catches usb\d+, hostpci\d+, args, lock, etc.
268 # new options will be checked here
269 die "only root can set '$opt' config\n";
276 __PACKAGE__-
>register_method({
280 description
=> "Virtual machine index (per node).",
282 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
286 protected
=> 1, # qemu pid files are only readable by root
288 additionalProperties
=> 0,
290 node
=> get_standard_option
('pve-node'),
294 description
=> "Determine the full status of active VMs.",
304 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
309 my $rpcenv = PVE
::RPCEnvironment
::get
();
310 my $authuser = $rpcenv->get_user();
312 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
315 foreach my $vmid (keys %$vmstatus) {
316 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
318 my $data = $vmstatus->{$vmid};
319 $data->{vmid
} = int($vmid);
328 __PACKAGE__-
>register_method({
332 description
=> "Create or restore a virtual machine.",
334 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
335 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
336 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
337 user
=> 'all', # check inside
342 additionalProperties
=> 0,
343 properties
=> PVE
::QemuServer
::json_config_properties
(
345 node
=> get_standard_option
('pve-node'),
346 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
348 description
=> "The backup file.",
352 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
354 storage
=> get_standard_option
('pve-storage-id', {
355 description
=> "Default storage.",
357 completion
=> \
&PVE
::QemuServer
::complete_storage
,
362 description
=> "Allow to overwrite existing VM.",
363 requires
=> 'archive',
368 description
=> "Assign a unique random ethernet address.",
369 requires
=> 'archive',
373 type
=> 'string', format
=> 'pve-poolid',
374 description
=> "Add the VM to the specified pool.",
384 my $rpcenv = PVE
::RPCEnvironment
::get
();
386 my $authuser = $rpcenv->get_user();
388 my $node = extract_param
($param, 'node');
390 my $vmid = extract_param
($param, 'vmid');
392 my $archive = extract_param
($param, 'archive');
394 my $storage = extract_param
($param, 'storage');
396 my $force = extract_param
($param, 'force');
398 my $unique = extract_param
($param, 'unique');
400 my $pool = extract_param
($param, 'pool');
402 my $filename = PVE
::QemuConfig-
>config_file($vmid);
404 my $storecfg = PVE
::Storage
::config
();
406 PVE
::Cluster
::check_cfs_quorum
();
408 if (defined($pool)) {
409 $rpcenv->check_pool_exist($pool);
412 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
413 if defined($storage);
415 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
417 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
419 } elsif ($archive && $force && (-f
$filename) &&
420 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
421 # OK: user has VM.Backup permissions, and want to restore an existing VM
427 &$resolve_cdrom_alias($param);
429 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
431 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
433 foreach my $opt (keys %$param) {
434 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
435 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
436 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
438 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
439 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
443 PVE
::QemuServer
::add_random_macs
($param);
445 my $keystr = join(' ', keys %$param);
446 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
448 if ($archive eq '-') {
449 die "pipe requires cli environment\n"
450 if $rpcenv->{type
} ne 'cli';
452 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
453 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
457 my $restorefn = sub {
458 my $vmlist = PVE
::Cluster
::get_vmlist
();
459 if ($vmlist->{ids
}->{$vmid}) {
460 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
461 if ($current_node eq $node) {
462 my $conf = PVE
::QemuConfig-
>load_config($vmid);
464 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
466 die "unable to restore vm $vmid - config file already exists\n"
469 die "unable to restore vm $vmid - vm is running\n"
470 if PVE
::QemuServer
::check_running
($vmid);
472 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
477 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
480 unique
=> $unique });
482 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
485 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
491 PVE
::Cluster
::check_vmid_unused
($vmid);
501 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
503 # try to be smart about bootdisk
504 my @disks = PVE
::QemuServer
::valid_drive_names
();
506 foreach my $ds (reverse @disks) {
507 next if !$conf->{$ds};
508 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
509 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
513 if (!$conf->{bootdisk
} && $firstdisk) {
514 $conf->{bootdisk
} = $firstdisk;
517 # auto generate uuid if user did not specify smbios1 option
518 if (!$conf->{smbios1
}) {
519 my ($uuid, $uuid_str);
520 UUID
::generate
($uuid);
521 UUID
::unparse
($uuid, $uuid_str);
522 $conf->{smbios1
} = "uuid=$uuid_str";
525 PVE
::QemuConfig-
>write_config($vmid, $conf);
531 foreach my $volid (@$vollist) {
532 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
535 die "create failed - $err";
538 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
541 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
544 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
547 __PACKAGE__-
>register_method({
552 description
=> "Directory index",
557 additionalProperties
=> 0,
559 node
=> get_standard_option
('pve-node'),
560 vmid
=> get_standard_option
('pve-vmid'),
568 subdir
=> { type
=> 'string' },
571 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
577 { subdir
=> 'config' },
578 { subdir
=> 'pending' },
579 { subdir
=> 'status' },
580 { subdir
=> 'unlink' },
581 { subdir
=> 'vncproxy' },
582 { subdir
=> 'migrate' },
583 { subdir
=> 'resize' },
584 { subdir
=> 'move' },
586 { subdir
=> 'rrddata' },
587 { subdir
=> 'monitor' },
588 { subdir
=> 'snapshot' },
589 { subdir
=> 'spiceproxy' },
590 { subdir
=> 'sendkey' },
591 { subdir
=> 'firewall' },
597 __PACKAGE__-
>register_method ({
598 subclass
=> "PVE::API2::Firewall::VM",
599 path
=> '{vmid}/firewall',
602 __PACKAGE__-
>register_method({
604 path
=> '{vmid}/rrd',
606 protected
=> 1, # fixme: can we avoid that?
608 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
610 description
=> "Read VM RRD statistics (returns PNG)",
612 additionalProperties
=> 0,
614 node
=> get_standard_option
('pve-node'),
615 vmid
=> get_standard_option
('pve-vmid'),
617 description
=> "Specify the time frame you are interested in.",
619 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
622 description
=> "The list of datasources you want to display.",
623 type
=> 'string', format
=> 'pve-configid-list',
626 description
=> "The RRD consolidation function",
628 enum
=> [ 'AVERAGE', 'MAX' ],
636 filename
=> { type
=> 'string' },
642 return PVE
::Cluster
::create_rrd_graph
(
643 "pve2-vm/$param->{vmid}", $param->{timeframe
},
644 $param->{ds
}, $param->{cf
});
648 __PACKAGE__-
>register_method({
650 path
=> '{vmid}/rrddata',
652 protected
=> 1, # fixme: can we avoid that?
654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
656 description
=> "Read VM RRD statistics",
658 additionalProperties
=> 0,
660 node
=> get_standard_option
('pve-node'),
661 vmid
=> get_standard_option
('pve-vmid'),
663 description
=> "Specify the time frame you are interested in.",
665 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
668 description
=> "The RRD consolidation function",
670 enum
=> [ 'AVERAGE', 'MAX' ],
685 return PVE
::Cluster
::create_rrd_data
(
686 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
690 __PACKAGE__-
>register_method({
692 path
=> '{vmid}/config',
695 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
700 additionalProperties
=> 0,
702 node
=> get_standard_option
('pve-node'),
703 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
705 description
=> "Get current values (instead of pending values).",
717 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
724 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
726 delete $conf->{snapshots
};
728 if (!$param->{current
}) {
729 foreach my $opt (keys %{$conf->{pending
}}) {
730 next if $opt eq 'delete';
731 my $value = $conf->{pending
}->{$opt};
732 next if ref($value); # just to be sure
733 $conf->{$opt} = $value;
735 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
736 foreach my $opt (keys %$pending_delete_hash) {
737 delete $conf->{$opt} if $conf->{$opt};
741 delete $conf->{pending
};
746 __PACKAGE__-
>register_method({
747 name
=> 'vm_pending',
748 path
=> '{vmid}/pending',
751 description
=> "Get virtual machine configuration, including pending changes.",
753 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
756 additionalProperties
=> 0,
758 node
=> get_standard_option
('pve-node'),
759 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
768 description
=> "Configuration option name.",
772 description
=> "Current value.",
777 description
=> "Pending value.",
782 description
=> "Indicates a pending delete request if present and not 0. " .
783 "The value 2 indicates a force-delete request.",
795 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
797 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
801 foreach my $opt (keys %$conf) {
802 next if ref($conf->{$opt});
803 my $item = { key
=> $opt };
804 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
805 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
806 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
810 foreach my $opt (keys %{$conf->{pending
}}) {
811 next if $opt eq 'delete';
812 next if ref($conf->{pending
}->{$opt}); # just to be sure
813 next if defined($conf->{$opt});
814 my $item = { key
=> $opt };
815 $item->{pending
} = $conf->{pending
}->{$opt};
819 while (my ($opt, $force) = each %$pending_delete_hash) {
820 next if $conf->{pending
}->{$opt}; # just to be sure
821 next if $conf->{$opt};
822 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
829 # POST/PUT {vmid}/config implementation
831 # The original API used PUT (idempotent) an we assumed that all operations
832 # are fast. But it turned out that almost any configuration change can
833 # involve hot-plug actions, or disk alloc/free. Such actions can take long
834 # time to complete and have side effects (not idempotent).
836 # The new implementation uses POST and forks a worker process. We added
837 # a new option 'background_delay'. If specified we wait up to
838 # 'background_delay' second for the worker task to complete. It returns null
839 # if the task is finished within that time, else we return the UPID.
841 my $update_vm_api = sub {
842 my ($param, $sync) = @_;
844 my $rpcenv = PVE
::RPCEnvironment
::get
();
846 my $authuser = $rpcenv->get_user();
848 my $node = extract_param
($param, 'node');
850 my $vmid = extract_param
($param, 'vmid');
852 my $digest = extract_param
($param, 'digest');
854 my $background_delay = extract_param
($param, 'background_delay');
856 my @paramarr = (); # used for log message
857 foreach my $key (keys %$param) {
858 push @paramarr, "-$key", $param->{$key};
861 my $skiplock = extract_param
($param, 'skiplock');
862 raise_param_exc
({ skiplock
=> "Only root may use this option." })
863 if $skiplock && $authuser ne 'root@pam';
865 my $delete_str = extract_param
($param, 'delete');
867 my $revert_str = extract_param
($param, 'revert');
869 my $force = extract_param
($param, 'force');
871 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
873 my $storecfg = PVE
::Storage
::config
();
875 my $defaults = PVE
::QemuServer
::load_defaults
();
877 &$resolve_cdrom_alias($param);
879 # now try to verify all parameters
882 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
883 if (!PVE
::QemuServer
::option_exists
($opt)) {
884 raise_param_exc
({ revert
=> "unknown option '$opt'" });
887 raise_param_exc
({ delete => "you can't use '-$opt' and " .
888 "-revert $opt' at the same time" })
889 if defined($param->{$opt});
895 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
896 $opt = 'ide2' if $opt eq 'cdrom';
898 raise_param_exc
({ delete => "you can't use '-$opt' and " .
899 "-delete $opt' at the same time" })
900 if defined($param->{$opt});
902 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
903 "-revert $opt' at the same time" })
906 if (!PVE
::QemuServer
::option_exists
($opt)) {
907 raise_param_exc
({ delete => "unknown option '$opt'" });
913 foreach my $opt (keys %$param) {
914 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
916 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
917 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
918 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
919 } elsif ($opt =~ m/^net(\d+)$/) {
921 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
922 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
926 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
928 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
930 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
934 my $conf = PVE
::QemuConfig-
>load_config($vmid);
936 die "checksum missmatch (file change by other user?)\n"
937 if $digest && $digest ne $conf->{digest
};
939 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
941 foreach my $opt (keys %$revert) {
942 if (defined($conf->{$opt})) {
943 $param->{$opt} = $conf->{$opt};
944 } elsif (defined($conf->{pending
}->{$opt})) {
949 if ($param->{memory
} || defined($param->{balloon
})) {
950 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
951 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
953 die "balloon value too large (must be smaller than assigned memory)\n"
954 if $balloon && $balloon > $maxmem;
957 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
961 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
963 # write updates to pending section
965 my $modified = {}; # record what $option we modify
967 foreach my $opt (@delete) {
968 $modified->{$opt} = 1;
969 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
970 if ($opt =~ m/^unused/) {
971 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
972 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
973 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
974 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
975 delete $conf->{$opt};
976 PVE
::QemuConfig-
>write_config($vmid, $conf);
978 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
979 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
980 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
981 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
982 if defined($conf->{pending
}->{$opt});
983 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
984 PVE
::QemuConfig-
>write_config($vmid, $conf);
986 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
987 PVE
::QemuConfig-
>write_config($vmid, $conf);
991 foreach my $opt (keys %$param) { # add/change
992 $modified->{$opt} = 1;
993 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
994 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
996 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
997 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
998 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
999 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1001 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1003 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1004 if defined($conf->{pending
}->{$opt});
1006 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1008 $conf->{pending
}->{$opt} = $param->{$opt};
1010 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1011 PVE
::QemuConfig-
>write_config($vmid, $conf);
1014 # remove pending changes when nothing changed
1015 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1016 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1017 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1019 return if !scalar(keys %{$conf->{pending
}});
1021 my $running = PVE
::QemuServer
::check_running
($vmid);
1023 # apply pending changes
1025 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1029 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1030 raise_param_exc
($errors) if scalar(keys %$errors);
1032 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1042 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1044 if ($background_delay) {
1046 # Note: It would be better to do that in the Event based HTTPServer
1047 # to avoid blocking call to sleep.
1049 my $end_time = time() + $background_delay;
1051 my $task = PVE
::Tools
::upid_decode
($upid);
1054 while (time() < $end_time) {
1055 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1057 sleep(1); # this gets interrupted when child process ends
1061 my $status = PVE
::Tools
::upid_read_status
($upid);
1062 return undef if $status eq 'OK';
1071 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1074 my $vm_config_perm_list = [
1079 'VM.Config.Network',
1081 'VM.Config.Options',
1084 __PACKAGE__-
>register_method({
1085 name
=> 'update_vm_async',
1086 path
=> '{vmid}/config',
1090 description
=> "Set virtual machine options (asynchrounous API).",
1092 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1095 additionalProperties
=> 0,
1096 properties
=> PVE
::QemuServer
::json_config_properties
(
1098 node
=> get_standard_option
('pve-node'),
1099 vmid
=> get_standard_option
('pve-vmid'),
1100 skiplock
=> get_standard_option
('skiplock'),
1102 type
=> 'string', format
=> 'pve-configid-list',
1103 description
=> "A list of settings you want to delete.",
1107 type
=> 'string', format
=> 'pve-configid-list',
1108 description
=> "Revert a pending change.",
1113 description
=> $opt_force_description,
1115 requires
=> 'delete',
1119 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1123 background_delay
=> {
1125 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1136 code
=> $update_vm_api,
1139 __PACKAGE__-
>register_method({
1140 name
=> 'update_vm',
1141 path
=> '{vmid}/config',
1145 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1147 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1150 additionalProperties
=> 0,
1151 properties
=> PVE
::QemuServer
::json_config_properties
(
1153 node
=> get_standard_option
('pve-node'),
1154 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1155 skiplock
=> get_standard_option
('skiplock'),
1157 type
=> 'string', format
=> 'pve-configid-list',
1158 description
=> "A list of settings you want to delete.",
1162 type
=> 'string', format
=> 'pve-configid-list',
1163 description
=> "Revert a pending change.",
1168 description
=> $opt_force_description,
1170 requires
=> 'delete',
1174 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1180 returns
=> { type
=> 'null' },
1183 &$update_vm_api($param, 1);
1189 __PACKAGE__-
>register_method({
1190 name
=> 'destroy_vm',
1195 description
=> "Destroy the vm (also delete all used/owned volumes).",
1197 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1200 additionalProperties
=> 0,
1202 node
=> get_standard_option
('pve-node'),
1203 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1204 skiplock
=> get_standard_option
('skiplock'),
1213 my $rpcenv = PVE
::RPCEnvironment
::get
();
1215 my $authuser = $rpcenv->get_user();
1217 my $vmid = $param->{vmid
};
1219 my $skiplock = $param->{skiplock
};
1220 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1221 if $skiplock && $authuser ne 'root@pam';
1224 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1226 my $storecfg = PVE
::Storage
::config
();
1228 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1230 die "unable to remove VM $vmid - used in HA resources\n"
1231 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1233 # early tests (repeat after locking)
1234 die "VM $vmid is running - destroy failed\n"
1235 if PVE
::QemuServer
::check_running
($vmid);
1240 syslog
('info', "destroy VM $vmid: $upid\n");
1242 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1244 PVE
::AccessControl
::remove_vm_access
($vmid);
1246 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1249 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1252 __PACKAGE__-
>register_method({
1254 path
=> '{vmid}/unlink',
1258 description
=> "Unlink/delete disk images.",
1260 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1263 additionalProperties
=> 0,
1265 node
=> get_standard_option
('pve-node'),
1266 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1268 type
=> 'string', format
=> 'pve-configid-list',
1269 description
=> "A list of disk IDs you want to delete.",
1273 description
=> $opt_force_description,
1278 returns
=> { type
=> 'null'},
1282 $param->{delete} = extract_param
($param, 'idlist');
1284 __PACKAGE__-
>update_vm($param);
1291 __PACKAGE__-
>register_method({
1293 path
=> '{vmid}/vncproxy',
1297 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1299 description
=> "Creates a TCP VNC proxy connections.",
1301 additionalProperties
=> 0,
1303 node
=> get_standard_option
('pve-node'),
1304 vmid
=> get_standard_option
('pve-vmid'),
1308 description
=> "starts websockify instead of vncproxy",
1313 additionalProperties
=> 0,
1315 user
=> { type
=> 'string' },
1316 ticket
=> { type
=> 'string' },
1317 cert
=> { type
=> 'string' },
1318 port
=> { type
=> 'integer' },
1319 upid
=> { type
=> 'string' },
1325 my $rpcenv = PVE
::RPCEnvironment
::get
();
1327 my $authuser = $rpcenv->get_user();
1329 my $vmid = $param->{vmid
};
1330 my $node = $param->{node
};
1331 my $websocket = $param->{websocket
};
1333 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1335 my $authpath = "/vms/$vmid";
1337 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1339 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1342 my ($remip, $family);
1345 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1346 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1347 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1348 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1350 $family = PVE
::Tools
::get_host_address_family
($node);
1353 my $port = PVE
::Tools
::next_vnc_port
($family);
1360 syslog
('info', "starting vnc proxy $upid\n");
1364 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1366 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1368 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1369 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1370 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1371 '-timeout', $timeout, '-authpath', $authpath,
1372 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1375 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1377 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1379 my $qmstr = join(' ', @$qmcmd);
1381 # also redirect stderr (else we get RFB protocol errors)
1382 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1385 PVE
::Tools
::run_command
($cmd);
1390 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1392 PVE
::Tools
::wait_for_vnc_port
($port);
1403 __PACKAGE__-
>register_method({
1404 name
=> 'vncwebsocket',
1405 path
=> '{vmid}/vncwebsocket',
1408 description
=> "You also need to pass a valid ticket (vncticket).",
1409 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1411 description
=> "Opens a weksocket for VNC traffic.",
1413 additionalProperties
=> 0,
1415 node
=> get_standard_option
('pve-node'),
1416 vmid
=> get_standard_option
('pve-vmid'),
1418 description
=> "Ticket from previous call to vncproxy.",
1423 description
=> "Port number returned by previous vncproxy call.",
1433 port
=> { type
=> 'string' },
1439 my $rpcenv = PVE
::RPCEnvironment
::get
();
1441 my $authuser = $rpcenv->get_user();
1443 my $vmid = $param->{vmid
};
1444 my $node = $param->{node
};
1446 my $authpath = "/vms/$vmid";
1448 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1450 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1452 # Note: VNC ports are acessible from outside, so we do not gain any
1453 # security if we verify that $param->{port} belongs to VM $vmid. This
1454 # check is done by verifying the VNC ticket (inside VNC protocol).
1456 my $port = $param->{port
};
1458 return { port
=> $port };
1461 __PACKAGE__-
>register_method({
1462 name
=> 'spiceproxy',
1463 path
=> '{vmid}/spiceproxy',
1468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1470 description
=> "Returns a SPICE configuration to connect to the VM.",
1472 additionalProperties
=> 0,
1474 node
=> get_standard_option
('pve-node'),
1475 vmid
=> get_standard_option
('pve-vmid'),
1476 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1479 returns
=> get_standard_option
('remote-viewer-config'),
1483 my $rpcenv = PVE
::RPCEnvironment
::get
();
1485 my $authuser = $rpcenv->get_user();
1487 my $vmid = $param->{vmid
};
1488 my $node = $param->{node
};
1489 my $proxy = $param->{proxy
};
1491 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1492 my $title = "VM $vmid";
1493 $title .= " - ". $conf->{name
} if $conf->{name
};
1495 my $port = PVE
::QemuServer
::spice_port
($vmid);
1497 my ($ticket, undef, $remote_viewer_config) =
1498 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1500 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1501 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1503 return $remote_viewer_config;
1506 __PACKAGE__-
>register_method({
1508 path
=> '{vmid}/status',
1511 description
=> "Directory index",
1516 additionalProperties
=> 0,
1518 node
=> get_standard_option
('pve-node'),
1519 vmid
=> get_standard_option
('pve-vmid'),
1527 subdir
=> { type
=> 'string' },
1530 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1536 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1539 { subdir
=> 'current' },
1540 { subdir
=> 'start' },
1541 { subdir
=> 'stop' },
1547 __PACKAGE__-
>register_method({
1548 name
=> 'vm_status',
1549 path
=> '{vmid}/status/current',
1552 protected
=> 1, # qemu pid files are only readable by root
1553 description
=> "Get virtual machine status.",
1555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1558 additionalProperties
=> 0,
1560 node
=> get_standard_option
('pve-node'),
1561 vmid
=> get_standard_option
('pve-vmid'),
1564 returns
=> { type
=> 'object' },
1569 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1571 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1572 my $status = $vmstatus->{$param->{vmid
}};
1574 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1576 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1581 __PACKAGE__-
>register_method({
1583 path
=> '{vmid}/status/start',
1587 description
=> "Start virtual machine.",
1589 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1592 additionalProperties
=> 0,
1594 node
=> get_standard_option
('pve-node'),
1595 vmid
=> get_standard_option
('pve-vmid',
1596 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1597 skiplock
=> get_standard_option
('skiplock'),
1598 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1599 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1600 machine
=> get_standard_option
('pve-qm-machine'),
1609 my $rpcenv = PVE
::RPCEnvironment
::get
();
1611 my $authuser = $rpcenv->get_user();
1613 my $node = extract_param
($param, 'node');
1615 my $vmid = extract_param
($param, 'vmid');
1617 my $machine = extract_param
($param, 'machine');
1619 my $stateuri = extract_param
($param, 'stateuri');
1620 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1621 if $stateuri && $authuser ne 'root@pam';
1623 my $skiplock = extract_param
($param, 'skiplock');
1624 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1625 if $skiplock && $authuser ne 'root@pam';
1627 my $migratedfrom = extract_param
($param, 'migratedfrom');
1628 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1629 if $migratedfrom && $authuser ne 'root@pam';
1631 # read spice ticket from STDIN
1633 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1634 if (defined(my $line = <>)) {
1636 $spice_ticket = $line;
1640 PVE
::Cluster
::check_cfs_quorum
();
1642 my $storecfg = PVE
::Storage
::config
();
1644 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1645 $rpcenv->{type
} ne 'ha') {
1650 my $service = "vm:$vmid";
1652 my $cmd = ['ha-manager', 'enable', $service];
1654 print "Executing HA start for VM $vmid\n";
1656 PVE
::Tools
::run_command
($cmd);
1661 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1668 syslog
('info', "start VM $vmid: $upid\n");
1670 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1671 $machine, $spice_ticket);
1676 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1680 __PACKAGE__-
>register_method({
1682 path
=> '{vmid}/status/stop',
1686 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1687 "is akin to pulling the power plug of a running computer and may damage the VM data",
1689 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1692 additionalProperties
=> 0,
1694 node
=> get_standard_option
('pve-node'),
1695 vmid
=> get_standard_option
('pve-vmid',
1696 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1697 skiplock
=> get_standard_option
('skiplock'),
1698 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1700 description
=> "Wait maximal timeout seconds.",
1706 description
=> "Do not decativate storage volumes.",
1719 my $rpcenv = PVE
::RPCEnvironment
::get
();
1721 my $authuser = $rpcenv->get_user();
1723 my $node = extract_param
($param, 'node');
1725 my $vmid = extract_param
($param, 'vmid');
1727 my $skiplock = extract_param
($param, 'skiplock');
1728 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1729 if $skiplock && $authuser ne 'root@pam';
1731 my $keepActive = extract_param
($param, 'keepActive');
1732 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1733 if $keepActive && $authuser ne 'root@pam';
1735 my $migratedfrom = extract_param
($param, 'migratedfrom');
1736 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1737 if $migratedfrom && $authuser ne 'root@pam';
1740 my $storecfg = PVE
::Storage
::config
();
1742 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1747 my $service = "vm:$vmid";
1749 my $cmd = ['ha-manager', 'disable', $service];
1751 print "Executing HA stop for VM $vmid\n";
1753 PVE
::Tools
::run_command
($cmd);
1758 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1764 syslog
('info', "stop VM $vmid: $upid\n");
1766 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1767 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1772 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1776 __PACKAGE__-
>register_method({
1778 path
=> '{vmid}/status/reset',
1782 description
=> "Reset virtual machine.",
1784 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1787 additionalProperties
=> 0,
1789 node
=> get_standard_option
('pve-node'),
1790 vmid
=> get_standard_option
('pve-vmid',
1791 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1792 skiplock
=> get_standard_option
('skiplock'),
1801 my $rpcenv = PVE
::RPCEnvironment
::get
();
1803 my $authuser = $rpcenv->get_user();
1805 my $node = extract_param
($param, 'node');
1807 my $vmid = extract_param
($param, 'vmid');
1809 my $skiplock = extract_param
($param, 'skiplock');
1810 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1811 if $skiplock && $authuser ne 'root@pam';
1813 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1818 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1823 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1826 __PACKAGE__-
>register_method({
1827 name
=> 'vm_shutdown',
1828 path
=> '{vmid}/status/shutdown',
1832 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1833 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1835 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1838 additionalProperties
=> 0,
1840 node
=> get_standard_option
('pve-node'),
1841 vmid
=> get_standard_option
('pve-vmid',
1842 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1843 skiplock
=> get_standard_option
('skiplock'),
1845 description
=> "Wait maximal timeout seconds.",
1851 description
=> "Make sure the VM stops.",
1857 description
=> "Do not decativate storage volumes.",
1870 my $rpcenv = PVE
::RPCEnvironment
::get
();
1872 my $authuser = $rpcenv->get_user();
1874 my $node = extract_param
($param, 'node');
1876 my $vmid = extract_param
($param, 'vmid');
1878 my $skiplock = extract_param
($param, 'skiplock');
1879 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1880 if $skiplock && $authuser ne 'root@pam';
1882 my $keepActive = extract_param
($param, 'keepActive');
1883 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1884 if $keepActive && $authuser ne 'root@pam';
1886 my $storecfg = PVE
::Storage
::config
();
1890 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1891 # otherwise, we will infer a shutdown command, but run into the timeout,
1892 # then when the vm is resumed, it will instantly shutdown
1894 # checking the qmp status here to get feedback to the gui/cli/api
1895 # and the status query should not take too long
1898 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1902 if (!$err && $qmpstatus->{status
} eq "paused") {
1903 if ($param->{forceStop
}) {
1904 warn "VM is paused - stop instead of shutdown\n";
1907 die "VM is paused - cannot shutdown\n";
1914 syslog
('info', "shutdown VM $vmid: $upid\n");
1916 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1917 $shutdown, $param->{forceStop
}, $keepActive);
1922 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1925 __PACKAGE__-
>register_method({
1926 name
=> 'vm_suspend',
1927 path
=> '{vmid}/status/suspend',
1931 description
=> "Suspend virtual machine.",
1933 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1936 additionalProperties
=> 0,
1938 node
=> get_standard_option
('pve-node'),
1939 vmid
=> get_standard_option
('pve-vmid',
1940 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1941 skiplock
=> get_standard_option
('skiplock'),
1950 my $rpcenv = PVE
::RPCEnvironment
::get
();
1952 my $authuser = $rpcenv->get_user();
1954 my $node = extract_param
($param, 'node');
1956 my $vmid = extract_param
($param, 'vmid');
1958 my $skiplock = extract_param
($param, 'skiplock');
1959 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1960 if $skiplock && $authuser ne 'root@pam';
1962 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1967 syslog
('info', "suspend VM $vmid: $upid\n");
1969 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1974 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1977 __PACKAGE__-
>register_method({
1978 name
=> 'vm_resume',
1979 path
=> '{vmid}/status/resume',
1983 description
=> "Resume virtual machine.",
1985 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1988 additionalProperties
=> 0,
1990 node
=> get_standard_option
('pve-node'),
1991 vmid
=> get_standard_option
('pve-vmid',
1992 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1993 skiplock
=> get_standard_option
('skiplock'),
1994 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2004 my $rpcenv = PVE
::RPCEnvironment
::get
();
2006 my $authuser = $rpcenv->get_user();
2008 my $node = extract_param
($param, 'node');
2010 my $vmid = extract_param
($param, 'vmid');
2012 my $skiplock = extract_param
($param, 'skiplock');
2013 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2014 if $skiplock && $authuser ne 'root@pam';
2016 my $nocheck = extract_param
($param, 'nocheck');
2018 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2023 syslog
('info', "resume VM $vmid: $upid\n");
2025 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2030 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2033 __PACKAGE__-
>register_method({
2034 name
=> 'vm_sendkey',
2035 path
=> '{vmid}/sendkey',
2039 description
=> "Send key event to virtual machine.",
2041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2044 additionalProperties
=> 0,
2046 node
=> get_standard_option
('pve-node'),
2047 vmid
=> get_standard_option
('pve-vmid',
2048 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2049 skiplock
=> get_standard_option
('skiplock'),
2051 description
=> "The key (qemu monitor encoding).",
2056 returns
=> { type
=> 'null'},
2060 my $rpcenv = PVE
::RPCEnvironment
::get
();
2062 my $authuser = $rpcenv->get_user();
2064 my $node = extract_param
($param, 'node');
2066 my $vmid = extract_param
($param, 'vmid');
2068 my $skiplock = extract_param
($param, 'skiplock');
2069 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2070 if $skiplock && $authuser ne 'root@pam';
2072 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2077 __PACKAGE__-
>register_method({
2078 name
=> 'vm_feature',
2079 path
=> '{vmid}/feature',
2083 description
=> "Check if feature for virtual machine is available.",
2085 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2088 additionalProperties
=> 0,
2090 node
=> get_standard_option
('pve-node'),
2091 vmid
=> get_standard_option
('pve-vmid'),
2093 description
=> "Feature to check.",
2095 enum
=> [ 'snapshot', 'clone', 'copy' ],
2097 snapname
=> get_standard_option
('pve-snapshot-name', {
2105 hasFeature
=> { type
=> 'boolean' },
2108 items
=> { type
=> 'string' },
2115 my $node = extract_param
($param, 'node');
2117 my $vmid = extract_param
($param, 'vmid');
2119 my $snapname = extract_param
($param, 'snapname');
2121 my $feature = extract_param
($param, 'feature');
2123 my $running = PVE
::QemuServer
::check_running
($vmid);
2125 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2128 my $snap = $conf->{snapshots
}->{$snapname};
2129 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2132 my $storecfg = PVE
::Storage
::config
();
2134 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2135 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2138 hasFeature
=> $hasFeature,
2139 nodes
=> [ keys %$nodelist ],
2143 __PACKAGE__-
>register_method({
2145 path
=> '{vmid}/clone',
2149 description
=> "Create a copy of virtual machine/template.",
2151 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2152 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2153 "'Datastore.AllocateSpace' on any used storage.",
2156 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2158 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2159 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2164 additionalProperties
=> 0,
2166 node
=> get_standard_option
('pve-node'),
2167 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2168 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2171 type
=> 'string', format
=> 'dns-name',
2172 description
=> "Set a name for the new VM.",
2177 description
=> "Description for the new VM.",
2181 type
=> 'string', format
=> 'pve-poolid',
2182 description
=> "Add the new VM to the specified pool.",
2184 snapname
=> get_standard_option
('pve-snapshot-name', {
2187 storage
=> get_standard_option
('pve-storage-id', {
2188 description
=> "Target storage for full clone.",
2193 description
=> "Target format for file storage.",
2197 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2202 description
=> "Create a full copy of all disk. This is always done when " .
2203 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2206 target
=> get_standard_option
('pve-node', {
2207 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2218 my $rpcenv = PVE
::RPCEnvironment
::get
();
2220 my $authuser = $rpcenv->get_user();
2222 my $node = extract_param
($param, 'node');
2224 my $vmid = extract_param
($param, 'vmid');
2226 my $newid = extract_param
($param, 'newid');
2228 my $pool = extract_param
($param, 'pool');
2230 if (defined($pool)) {
2231 $rpcenv->check_pool_exist($pool);
2234 my $snapname = extract_param
($param, 'snapname');
2236 my $storage = extract_param
($param, 'storage');
2238 my $format = extract_param
($param, 'format');
2240 my $target = extract_param
($param, 'target');
2242 my $localnode = PVE
::INotify
::nodename
();
2244 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2246 PVE
::Cluster
::check_node_exists
($target) if $target;
2248 my $storecfg = PVE
::Storage
::config
();
2251 # check if storage is enabled on local node
2252 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2254 # check if storage is available on target node
2255 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2256 # clone only works if target storage is shared
2257 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2258 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2262 PVE
::Cluster
::check_cfs_quorum
();
2264 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2266 # exclusive lock if VM is running - else shared lock is enough;
2267 my $shared_lock = $running ?
0 : 1;
2271 # do all tests after lock
2272 # we also try to do all tests before we fork the worker
2274 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2276 PVE
::QemuConfig-
>check_lock($conf);
2278 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2280 die "unexpected state change\n" if $verify_running != $running;
2282 die "snapshot '$snapname' does not exist\n"
2283 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2285 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2287 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2289 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2291 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2293 die "unable to create VM $newid: config file already exists\n"
2296 my $newconf = { lock => 'clone' };
2301 foreach my $opt (keys %$oldconf) {
2302 my $value = $oldconf->{$opt};
2304 # do not copy snapshot related info
2305 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2306 $opt eq 'vmstate' || $opt eq 'snapstate';
2308 # no need to copy unused images, because VMID(owner) changes anyways
2309 next if $opt =~ m/^unused\d+$/;
2311 # always change MAC! address
2312 if ($opt =~ m/^net(\d+)$/) {
2313 my $net = PVE
::QemuServer
::parse_net
($value);
2314 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2315 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2316 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2317 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2318 die "unable to parse drive options for '$opt'\n" if !$drive;
2319 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2320 $newconf->{$opt} = $value; # simply copy configuration
2322 if ($param->{full
}) {
2323 die "Full clone feature is not available"
2324 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2325 $fullclone->{$opt} = 1;
2327 # not full means clone instead of copy
2328 die "Linked clone feature is not available"
2329 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2331 $drives->{$opt} = $drive;
2332 push @$vollist, $drive->{file
};
2335 # copy everything else
2336 $newconf->{$opt} = $value;
2340 # auto generate a new uuid
2341 my ($uuid, $uuid_str);
2342 UUID
::generate
($uuid);
2343 UUID
::unparse
($uuid, $uuid_str);
2344 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2345 $smbios1->{uuid
} = $uuid_str;
2346 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2348 delete $newconf->{template
};
2350 if ($param->{name
}) {
2351 $newconf->{name
} = $param->{name
};
2353 if ($oldconf->{name
}) {
2354 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2356 $newconf->{name
} = "Copy-of-VM-$vmid";
2360 if ($param->{description
}) {
2361 $newconf->{description
} = $param->{description
};
2364 # create empty/temp config - this fails if VM already exists on other node
2365 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2370 my $newvollist = [];
2373 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2375 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2377 foreach my $opt (keys %$drives) {
2378 my $drive = $drives->{$opt};
2380 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2381 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2383 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2385 PVE
::QemuConfig-
>write_config($newid, $newconf);
2388 delete $newconf->{lock};
2389 PVE
::QemuConfig-
>write_config($newid, $newconf);
2392 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2393 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2395 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2396 die "Failed to move config to node '$target' - rename failed: $!\n"
2397 if !rename($conffile, $newconffile);
2400 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2405 sleep 1; # some storage like rbd need to wait before release volume - really?
2407 foreach my $volid (@$newvollist) {
2408 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2411 die "clone failed: $err";
2417 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2419 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2422 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2423 # Aquire exclusive lock lock for $newid
2424 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2429 __PACKAGE__-
>register_method({
2430 name
=> 'move_vm_disk',
2431 path
=> '{vmid}/move_disk',
2435 description
=> "Move volume to different storage.",
2437 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2438 "and 'Datastore.AllocateSpace' permissions on the storage.",
2441 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2442 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2446 additionalProperties
=> 0,
2448 node
=> get_standard_option
('pve-node'),
2449 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2452 description
=> "The disk you want to move.",
2453 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2455 storage
=> get_standard_option
('pve-storage-id', {
2456 description
=> "Target storage.",
2457 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2461 description
=> "Target Format.",
2462 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2467 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2473 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2481 description
=> "the task ID.",
2486 my $rpcenv = PVE
::RPCEnvironment
::get
();
2488 my $authuser = $rpcenv->get_user();
2490 my $node = extract_param
($param, 'node');
2492 my $vmid = extract_param
($param, 'vmid');
2494 my $digest = extract_param
($param, 'digest');
2496 my $disk = extract_param
($param, 'disk');
2498 my $storeid = extract_param
($param, 'storage');
2500 my $format = extract_param
($param, 'format');
2502 my $storecfg = PVE
::Storage
::config
();
2504 my $updatefn = sub {
2506 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2508 die "checksum missmatch (file change by other user?)\n"
2509 if $digest && $digest ne $conf->{digest
};
2511 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2513 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2515 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2517 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2520 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2521 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2525 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2526 (!$format || !$oldfmt || $oldfmt eq $format);
2528 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2530 my $running = PVE
::QemuServer
::check_running
($vmid);
2532 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2536 my $newvollist = [];
2539 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2541 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2542 $vmid, $storeid, $format, 1, $newvollist);
2544 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2546 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2548 PVE
::QemuConfig-
>write_config($vmid, $conf);
2551 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2552 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2559 foreach my $volid (@$newvollist) {
2560 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2563 die "storage migration failed: $err";
2566 if ($param->{delete}) {
2567 if (PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, undef, $old_volid)) {
2568 warn "volume $old_volid still has snapshots, can't delete it\n";
2569 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid);
2570 PVE
::QemuConfig-
>write_config($vmid, $conf);
2573 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2574 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2581 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2584 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2587 __PACKAGE__-
>register_method({
2588 name
=> 'migrate_vm',
2589 path
=> '{vmid}/migrate',
2593 description
=> "Migrate virtual machine. Creates a new migration task.",
2595 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2598 additionalProperties
=> 0,
2600 node
=> get_standard_option
('pve-node'),
2601 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2602 target
=> get_standard_option
('pve-node', {
2603 description
=> "Target node.",
2604 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2608 description
=> "Use online/live migration.",
2613 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2620 description
=> "the task ID.",
2625 my $rpcenv = PVE
::RPCEnvironment
::get
();
2627 my $authuser = $rpcenv->get_user();
2629 my $target = extract_param
($param, 'target');
2631 my $localnode = PVE
::INotify
::nodename
();
2632 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2634 PVE
::Cluster
::check_cfs_quorum
();
2636 PVE
::Cluster
::check_node_exists
($target);
2638 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2640 my $vmid = extract_param
($param, 'vmid');
2642 raise_param_exc
({ force
=> "Only root may use this option." })
2643 if $param->{force
} && $authuser ne 'root@pam';
2646 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2648 # try to detect errors early
2650 PVE
::QemuConfig-
>check_lock($conf);
2652 if (PVE
::QemuServer
::check_running
($vmid)) {
2653 die "cant migrate running VM without --online\n"
2654 if !$param->{online
};
2657 my $storecfg = PVE
::Storage
::config
();
2658 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2660 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2665 my $service = "vm:$vmid";
2667 my $cmd = ['ha-manager', 'migrate', $service, $target];
2669 print "Executing HA migrate for VM $vmid to node $target\n";
2671 PVE
::Tools
::run_command
($cmd);
2676 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2683 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2686 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2691 __PACKAGE__-
>register_method({
2693 path
=> '{vmid}/monitor',
2697 description
=> "Execute Qemu monitor commands.",
2699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2702 additionalProperties
=> 0,
2704 node
=> get_standard_option
('pve-node'),
2705 vmid
=> get_standard_option
('pve-vmid'),
2708 description
=> "The monitor command.",
2712 returns
=> { type
=> 'string'},
2716 my $vmid = $param->{vmid
};
2718 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2722 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2724 $res = "ERROR: $@" if $@;
2729 __PACKAGE__-
>register_method({
2730 name
=> 'resize_vm',
2731 path
=> '{vmid}/resize',
2735 description
=> "Extend volume size.",
2737 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2740 additionalProperties
=> 0,
2742 node
=> get_standard_option
('pve-node'),
2743 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2744 skiplock
=> get_standard_option
('skiplock'),
2747 description
=> "The disk you want to resize.",
2748 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2752 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2753 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.",
2757 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2763 returns
=> { type
=> 'null'},
2767 my $rpcenv = PVE
::RPCEnvironment
::get
();
2769 my $authuser = $rpcenv->get_user();
2771 my $node = extract_param
($param, 'node');
2773 my $vmid = extract_param
($param, 'vmid');
2775 my $digest = extract_param
($param, 'digest');
2777 my $disk = extract_param
($param, 'disk');
2779 my $sizestr = extract_param
($param, 'size');
2781 my $skiplock = extract_param
($param, 'skiplock');
2782 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2783 if $skiplock && $authuser ne 'root@pam';
2785 my $storecfg = PVE
::Storage
::config
();
2787 my $updatefn = sub {
2789 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2791 die "checksum missmatch (file change by other user?)\n"
2792 if $digest && $digest ne $conf->{digest
};
2793 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2795 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2797 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2799 my (undef, undef, undef, undef, undef, undef, $format) =
2800 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2802 die "can't resize volume: $disk if snapshot exists\n"
2803 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2805 my $volid = $drive->{file
};
2807 die "disk '$disk' has no associated volume\n" if !$volid;
2809 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2811 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2813 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2815 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2816 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2818 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2819 my ($ext, $newsize, $unit) = ($1, $2, $4);
2822 $newsize = $newsize * 1024;
2823 } elsif ($unit eq 'M') {
2824 $newsize = $newsize * 1024 * 1024;
2825 } elsif ($unit eq 'G') {
2826 $newsize = $newsize * 1024 * 1024 * 1024;
2827 } elsif ($unit eq 'T') {
2828 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2831 $newsize += $size if $ext;
2832 $newsize = int($newsize);
2834 die "unable to skrink disk size\n" if $newsize < $size;
2836 return if $size == $newsize;
2838 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2840 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2842 $drive->{size
} = $newsize;
2843 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2845 PVE
::QemuConfig-
>write_config($vmid, $conf);
2848 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2852 __PACKAGE__-
>register_method({
2853 name
=> 'snapshot_list',
2854 path
=> '{vmid}/snapshot',
2856 description
=> "List all snapshots.",
2858 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2861 protected
=> 1, # qemu pid files are only readable by root
2863 additionalProperties
=> 0,
2865 vmid
=> get_standard_option
('pve-vmid'),
2866 node
=> get_standard_option
('pve-node'),
2875 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2880 my $vmid = $param->{vmid
};
2882 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2883 my $snaphash = $conf->{snapshots
} || {};
2887 foreach my $name (keys %$snaphash) {
2888 my $d = $snaphash->{$name};
2891 snaptime
=> $d->{snaptime
} || 0,
2892 vmstate
=> $d->{vmstate
} ?
1 : 0,
2893 description
=> $d->{description
} || '',
2895 $item->{parent
} = $d->{parent
} if $d->{parent
};
2896 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2900 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2901 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2902 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2904 push @$res, $current;
2909 __PACKAGE__-
>register_method({
2911 path
=> '{vmid}/snapshot',
2915 description
=> "Snapshot a VM.",
2917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2920 additionalProperties
=> 0,
2922 node
=> get_standard_option
('pve-node'),
2923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2924 snapname
=> get_standard_option
('pve-snapshot-name'),
2928 description
=> "Save the vmstate",
2933 description
=> "A textual description or comment.",
2939 description
=> "the task ID.",
2944 my $rpcenv = PVE
::RPCEnvironment
::get
();
2946 my $authuser = $rpcenv->get_user();
2948 my $node = extract_param
($param, 'node');
2950 my $vmid = extract_param
($param, 'vmid');
2952 my $snapname = extract_param
($param, 'snapname');
2954 die "unable to use snapshot name 'current' (reserved name)\n"
2955 if $snapname eq 'current';
2958 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2959 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2960 $param->{description
});
2963 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2966 __PACKAGE__-
>register_method({
2967 name
=> 'snapshot_cmd_idx',
2968 path
=> '{vmid}/snapshot/{snapname}',
2975 additionalProperties
=> 0,
2977 vmid
=> get_standard_option
('pve-vmid'),
2978 node
=> get_standard_option
('pve-node'),
2979 snapname
=> get_standard_option
('pve-snapshot-name'),
2988 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2995 push @$res, { cmd
=> 'rollback' };
2996 push @$res, { cmd
=> 'config' };
3001 __PACKAGE__-
>register_method({
3002 name
=> 'update_snapshot_config',
3003 path
=> '{vmid}/snapshot/{snapname}/config',
3007 description
=> "Update snapshot metadata.",
3009 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3012 additionalProperties
=> 0,
3014 node
=> get_standard_option
('pve-node'),
3015 vmid
=> get_standard_option
('pve-vmid'),
3016 snapname
=> get_standard_option
('pve-snapshot-name'),
3020 description
=> "A textual description or comment.",
3024 returns
=> { type
=> 'null' },
3028 my $rpcenv = PVE
::RPCEnvironment
::get
();
3030 my $authuser = $rpcenv->get_user();
3032 my $vmid = extract_param
($param, 'vmid');
3034 my $snapname = extract_param
($param, 'snapname');
3036 return undef if !defined($param->{description
});
3038 my $updatefn = sub {
3040 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3042 PVE
::QemuConfig-
>check_lock($conf);
3044 my $snap = $conf->{snapshots
}->{$snapname};
3046 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3048 $snap->{description
} = $param->{description
} if defined($param->{description
});
3050 PVE
::QemuConfig-
>write_config($vmid, $conf);
3053 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3058 __PACKAGE__-
>register_method({
3059 name
=> 'get_snapshot_config',
3060 path
=> '{vmid}/snapshot/{snapname}/config',
3063 description
=> "Get snapshot configuration",
3065 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3068 additionalProperties
=> 0,
3070 node
=> get_standard_option
('pve-node'),
3071 vmid
=> get_standard_option
('pve-vmid'),
3072 snapname
=> get_standard_option
('pve-snapshot-name'),
3075 returns
=> { type
=> "object" },
3079 my $rpcenv = PVE
::RPCEnvironment
::get
();
3081 my $authuser = $rpcenv->get_user();
3083 my $vmid = extract_param
($param, 'vmid');
3085 my $snapname = extract_param
($param, 'snapname');
3087 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3089 my $snap = $conf->{snapshots
}->{$snapname};
3091 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3096 __PACKAGE__-
>register_method({
3098 path
=> '{vmid}/snapshot/{snapname}/rollback',
3102 description
=> "Rollback VM state to specified snapshot.",
3104 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3107 additionalProperties
=> 0,
3109 node
=> get_standard_option
('pve-node'),
3110 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3111 snapname
=> get_standard_option
('pve-snapshot-name'),
3116 description
=> "the task ID.",
3121 my $rpcenv = PVE
::RPCEnvironment
::get
();
3123 my $authuser = $rpcenv->get_user();
3125 my $node = extract_param
($param, 'node');
3127 my $vmid = extract_param
($param, 'vmid');
3129 my $snapname = extract_param
($param, 'snapname');
3132 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3133 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3136 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3139 __PACKAGE__-
>register_method({
3140 name
=> 'delsnapshot',
3141 path
=> '{vmid}/snapshot/{snapname}',
3145 description
=> "Delete a VM snapshot.",
3147 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3150 additionalProperties
=> 0,
3152 node
=> get_standard_option
('pve-node'),
3153 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3154 snapname
=> get_standard_option
('pve-snapshot-name'),
3158 description
=> "For removal from config file, even if removing disk snapshots fails.",
3164 description
=> "the task ID.",
3169 my $rpcenv = PVE
::RPCEnvironment
::get
();
3171 my $authuser = $rpcenv->get_user();
3173 my $node = extract_param
($param, 'node');
3175 my $vmid = extract_param
($param, 'vmid');
3177 my $snapname = extract_param
($param, 'snapname');
3180 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3181 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3184 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3187 __PACKAGE__-
>register_method({
3189 path
=> '{vmid}/template',
3193 description
=> "Create a Template.",
3195 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3196 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3199 additionalProperties
=> 0,
3201 node
=> get_standard_option
('pve-node'),
3202 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3206 description
=> "If you want to convert only 1 disk to base image.",
3207 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3212 returns
=> { type
=> 'null'},
3216 my $rpcenv = PVE
::RPCEnvironment
::get
();
3218 my $authuser = $rpcenv->get_user();
3220 my $node = extract_param
($param, 'node');
3222 my $vmid = extract_param
($param, 'vmid');
3224 my $disk = extract_param
($param, 'disk');
3226 my $updatefn = sub {
3228 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3230 PVE
::QemuConfig-
>check_lock($conf);
3232 die "unable to create template, because VM contains snapshots\n"
3233 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3235 die "you can't convert a template to a template\n"
3236 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3238 die "you can't convert a VM to template if VM is running\n"
3239 if PVE
::QemuServer
::check_running
($vmid);
3242 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3245 $conf->{template
} = 1;
3246 PVE
::QemuConfig-
>write_config($vmid, $conf);
3248 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3251 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);