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 $remainingoptions = {
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';
247 if ($cpuoptions->{$opt}) {
248 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
249 } elsif ($memoryoptions->{$opt}) {
250 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
251 } elsif ($hwtypeoptions->{$opt}) {
252 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
253 } elsif ($remainingoptions->{$opt} || $opt =~ m/^(numa|parallell|serial)\d+$/) {
254 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
255 # special case for startup since it changes host behaviour
256 if ($opt eq 'startup') {
257 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
259 } elsif ($vmpoweroptions->{$opt}) {
260 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
261 } elsif ($diskoptions->{$opt}) {
262 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
263 } elsif ($opt =~ m/^net\d+$/) {
264 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
266 # catches usb\d+, hostpci\d+, args, lock, etc.
267 # new options will be checked here
268 die "only root can set '$opt' config\n";
275 __PACKAGE__-
>register_method({
279 description
=> "Virtual machine index (per node).",
281 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
285 protected
=> 1, # qemu pid files are only readable by root
287 additionalProperties
=> 0,
289 node
=> get_standard_option
('pve-node'),
293 description
=> "Determine the full status of active VMs.",
303 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
308 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
314 foreach my $vmid (keys %$vmstatus) {
315 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
317 my $data = $vmstatus->{$vmid};
318 $data->{vmid
} = int($vmid);
327 __PACKAGE__-
>register_method({
331 description
=> "Create or restore a virtual machine.",
333 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
334 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
335 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
336 user
=> 'all', # check inside
341 additionalProperties
=> 0,
342 properties
=> PVE
::QemuServer
::json_config_properties
(
344 node
=> get_standard_option
('pve-node'),
345 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
347 description
=> "The backup file.",
351 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
353 storage
=> get_standard_option
('pve-storage-id', {
354 description
=> "Default storage.",
356 completion
=> \
&PVE
::QemuServer
::complete_storage
,
361 description
=> "Allow to overwrite existing VM.",
362 requires
=> 'archive',
367 description
=> "Assign a unique random ethernet address.",
368 requires
=> 'archive',
372 type
=> 'string', format
=> 'pve-poolid',
373 description
=> "Add the VM to the specified pool.",
383 my $rpcenv = PVE
::RPCEnvironment
::get
();
385 my $authuser = $rpcenv->get_user();
387 my $node = extract_param
($param, 'node');
389 my $vmid = extract_param
($param, 'vmid');
391 my $archive = extract_param
($param, 'archive');
393 my $storage = extract_param
($param, 'storage');
395 my $force = extract_param
($param, 'force');
397 my $unique = extract_param
($param, 'unique');
399 my $pool = extract_param
($param, 'pool');
401 my $filename = PVE
::QemuConfig-
>config_file($vmid);
403 my $storecfg = PVE
::Storage
::config
();
405 PVE
::Cluster
::check_cfs_quorum
();
407 if (defined($pool)) {
408 $rpcenv->check_pool_exist($pool);
411 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
412 if defined($storage);
414 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
416 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
418 } elsif ($archive && $force && (-f
$filename) &&
419 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
420 # OK: user has VM.Backup permissions, and want to restore an existing VM
426 &$resolve_cdrom_alias($param);
428 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
430 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
432 foreach my $opt (keys %$param) {
433 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
434 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
435 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
437 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
438 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
442 PVE
::QemuServer
::add_random_macs
($param);
444 my $keystr = join(' ', keys %$param);
445 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
447 if ($archive eq '-') {
448 die "pipe requires cli environment\n"
449 if $rpcenv->{type
} ne 'cli';
451 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
452 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
456 my $restorefn = sub {
457 my $vmlist = PVE
::Cluster
::get_vmlist
();
458 if ($vmlist->{ids
}->{$vmid}) {
459 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
460 if ($current_node eq $node) {
461 my $conf = PVE
::QemuConfig-
>load_config($vmid);
463 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
465 die "unable to restore vm $vmid - config file already exists\n"
468 die "unable to restore vm $vmid - vm is running\n"
469 if PVE
::QemuServer
::check_running
($vmid);
471 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
476 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
479 unique
=> $unique });
481 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
484 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
490 PVE
::Cluster
::check_vmid_unused
($vmid);
500 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
502 # try to be smart about bootdisk
503 my @disks = PVE
::QemuServer
::valid_drive_names
();
505 foreach my $ds (reverse @disks) {
506 next if !$conf->{$ds};
507 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
508 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
512 if (!$conf->{bootdisk
} && $firstdisk) {
513 $conf->{bootdisk
} = $firstdisk;
516 # auto generate uuid if user did not specify smbios1 option
517 if (!$conf->{smbios1
}) {
518 my ($uuid, $uuid_str);
519 UUID
::generate
($uuid);
520 UUID
::unparse
($uuid, $uuid_str);
521 $conf->{smbios1
} = "uuid=$uuid_str";
524 PVE
::QemuConfig-
>write_config($vmid, $conf);
530 foreach my $volid (@$vollist) {
531 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
534 die "create failed - $err";
537 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
540 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
543 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
546 __PACKAGE__-
>register_method({
551 description
=> "Directory index",
556 additionalProperties
=> 0,
558 node
=> get_standard_option
('pve-node'),
559 vmid
=> get_standard_option
('pve-vmid'),
567 subdir
=> { type
=> 'string' },
570 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
576 { subdir
=> 'config' },
577 { subdir
=> 'pending' },
578 { subdir
=> 'status' },
579 { subdir
=> 'unlink' },
580 { subdir
=> 'vncproxy' },
581 { subdir
=> 'migrate' },
582 { subdir
=> 'resize' },
583 { subdir
=> 'move' },
585 { subdir
=> 'rrddata' },
586 { subdir
=> 'monitor' },
587 { subdir
=> 'snapshot' },
588 { subdir
=> 'spiceproxy' },
589 { subdir
=> 'sendkey' },
590 { subdir
=> 'firewall' },
596 __PACKAGE__-
>register_method ({
597 subclass
=> "PVE::API2::Firewall::VM",
598 path
=> '{vmid}/firewall',
601 __PACKAGE__-
>register_method({
603 path
=> '{vmid}/rrd',
605 protected
=> 1, # fixme: can we avoid that?
607 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
609 description
=> "Read VM RRD statistics (returns PNG)",
611 additionalProperties
=> 0,
613 node
=> get_standard_option
('pve-node'),
614 vmid
=> get_standard_option
('pve-vmid'),
616 description
=> "Specify the time frame you are interested in.",
618 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
621 description
=> "The list of datasources you want to display.",
622 type
=> 'string', format
=> 'pve-configid-list',
625 description
=> "The RRD consolidation function",
627 enum
=> [ 'AVERAGE', 'MAX' ],
635 filename
=> { type
=> 'string' },
641 return PVE
::Cluster
::create_rrd_graph
(
642 "pve2-vm/$param->{vmid}", $param->{timeframe
},
643 $param->{ds
}, $param->{cf
});
647 __PACKAGE__-
>register_method({
649 path
=> '{vmid}/rrddata',
651 protected
=> 1, # fixme: can we avoid that?
653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
655 description
=> "Read VM RRD statistics",
657 additionalProperties
=> 0,
659 node
=> get_standard_option
('pve-node'),
660 vmid
=> get_standard_option
('pve-vmid'),
662 description
=> "Specify the time frame you are interested in.",
664 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
667 description
=> "The RRD consolidation function",
669 enum
=> [ 'AVERAGE', 'MAX' ],
684 return PVE
::Cluster
::create_rrd_data
(
685 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
689 __PACKAGE__-
>register_method({
691 path
=> '{vmid}/config',
694 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
696 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
699 additionalProperties
=> 0,
701 node
=> get_standard_option
('pve-node'),
702 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
704 description
=> "Get current values (instead of pending values).",
716 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
723 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
725 delete $conf->{snapshots
};
727 if (!$param->{current
}) {
728 foreach my $opt (keys %{$conf->{pending
}}) {
729 next if $opt eq 'delete';
730 my $value = $conf->{pending
}->{$opt};
731 next if ref($value); # just to be sure
732 $conf->{$opt} = $value;
734 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
735 foreach my $opt (keys %$pending_delete_hash) {
736 delete $conf->{$opt} if $conf->{$opt};
740 delete $conf->{pending
};
745 __PACKAGE__-
>register_method({
746 name
=> 'vm_pending',
747 path
=> '{vmid}/pending',
750 description
=> "Get virtual machine configuration, including pending changes.",
752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
755 additionalProperties
=> 0,
757 node
=> get_standard_option
('pve-node'),
758 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
767 description
=> "Configuration option name.",
771 description
=> "Current value.",
776 description
=> "Pending value.",
781 description
=> "Indicates a pending delete request if present and not 0. " .
782 "The value 2 indicates a force-delete request.",
794 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
796 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
800 foreach my $opt (keys %$conf) {
801 next if ref($conf->{$opt});
802 my $item = { key
=> $opt };
803 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
804 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
805 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
809 foreach my $opt (keys %{$conf->{pending
}}) {
810 next if $opt eq 'delete';
811 next if ref($conf->{pending
}->{$opt}); # just to be sure
812 next if defined($conf->{$opt});
813 my $item = { key
=> $opt };
814 $item->{pending
} = $conf->{pending
}->{$opt};
818 while (my ($opt, $force) = each %$pending_delete_hash) {
819 next if $conf->{pending
}->{$opt}; # just to be sure
820 next if $conf->{$opt};
821 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
828 # POST/PUT {vmid}/config implementation
830 # The original API used PUT (idempotent) an we assumed that all operations
831 # are fast. But it turned out that almost any configuration change can
832 # involve hot-plug actions, or disk alloc/free. Such actions can take long
833 # time to complete and have side effects (not idempotent).
835 # The new implementation uses POST and forks a worker process. We added
836 # a new option 'background_delay'. If specified we wait up to
837 # 'background_delay' second for the worker task to complete. It returns null
838 # if the task is finished within that time, else we return the UPID.
840 my $update_vm_api = sub {
841 my ($param, $sync) = @_;
843 my $rpcenv = PVE
::RPCEnvironment
::get
();
845 my $authuser = $rpcenv->get_user();
847 my $node = extract_param
($param, 'node');
849 my $vmid = extract_param
($param, 'vmid');
851 my $digest = extract_param
($param, 'digest');
853 my $background_delay = extract_param
($param, 'background_delay');
855 my @paramarr = (); # used for log message
856 foreach my $key (keys %$param) {
857 push @paramarr, "-$key", $param->{$key};
860 my $skiplock = extract_param
($param, 'skiplock');
861 raise_param_exc
({ skiplock
=> "Only root may use this option." })
862 if $skiplock && $authuser ne 'root@pam';
864 my $delete_str = extract_param
($param, 'delete');
866 my $revert_str = extract_param
($param, 'revert');
868 my $force = extract_param
($param, 'force');
870 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
872 my $storecfg = PVE
::Storage
::config
();
874 my $defaults = PVE
::QemuServer
::load_defaults
();
876 &$resolve_cdrom_alias($param);
878 # now try to verify all parameters
881 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
882 if (!PVE
::QemuServer
::option_exists
($opt)) {
883 raise_param_exc
({ revert
=> "unknown option '$opt'" });
886 raise_param_exc
({ delete => "you can't use '-$opt' and " .
887 "-revert $opt' at the same time" })
888 if defined($param->{$opt});
894 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
895 $opt = 'ide2' if $opt eq 'cdrom';
897 raise_param_exc
({ delete => "you can't use '-$opt' and " .
898 "-delete $opt' at the same time" })
899 if defined($param->{$opt});
901 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
902 "-revert $opt' at the same time" })
905 if (!PVE
::QemuServer
::option_exists
($opt)) {
906 raise_param_exc
({ delete => "unknown option '$opt'" });
912 foreach my $opt (keys %$param) {
913 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
915 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
916 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
917 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
918 } elsif ($opt =~ m/^net(\d+)$/) {
920 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
921 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
925 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
927 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
929 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
933 my $conf = PVE
::QemuConfig-
>load_config($vmid);
935 die "checksum missmatch (file change by other user?)\n"
936 if $digest && $digest ne $conf->{digest
};
938 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
940 foreach my $opt (keys %$revert) {
941 if (defined($conf->{$opt})) {
942 $param->{$opt} = $conf->{$opt};
943 } elsif (defined($conf->{pending
}->{$opt})) {
948 if ($param->{memory
} || defined($param->{balloon
})) {
949 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
950 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
952 die "balloon value too large (must be smaller than assigned memory)\n"
953 if $balloon && $balloon > $maxmem;
956 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
960 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
962 # write updates to pending section
964 my $modified = {}; # record what $option we modify
966 foreach my $opt (@delete) {
967 $modified->{$opt} = 1;
968 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
969 if ($opt =~ m/^unused/) {
970 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
971 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
972 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
973 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
974 delete $conf->{$opt};
975 PVE
::QemuConfig-
>write_config($vmid, $conf);
977 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
978 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
979 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
980 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
981 if defined($conf->{pending
}->{$opt});
982 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
983 PVE
::QemuConfig-
>write_config($vmid, $conf);
985 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
986 PVE
::QemuConfig-
>write_config($vmid, $conf);
990 foreach my $opt (keys %$param) { # add/change
991 $modified->{$opt} = 1;
992 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
993 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
995 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
996 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
997 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
998 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1000 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1002 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1003 if defined($conf->{pending
}->{$opt});
1005 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1007 $conf->{pending
}->{$opt} = $param->{$opt};
1009 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1010 PVE
::QemuConfig-
>write_config($vmid, $conf);
1013 # remove pending changes when nothing changed
1014 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1015 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1016 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1018 return if !scalar(keys %{$conf->{pending
}});
1020 my $running = PVE
::QemuServer
::check_running
($vmid);
1022 # apply pending changes
1024 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1028 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1029 raise_param_exc
($errors) if scalar(keys %$errors);
1031 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1041 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1043 if ($background_delay) {
1045 # Note: It would be better to do that in the Event based HTTPServer
1046 # to avoid blocking call to sleep.
1048 my $end_time = time() + $background_delay;
1050 my $task = PVE
::Tools
::upid_decode
($upid);
1053 while (time() < $end_time) {
1054 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1056 sleep(1); # this gets interrupted when child process ends
1060 my $status = PVE
::Tools
::upid_read_status
($upid);
1061 return undef if $status eq 'OK';
1070 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1073 my $vm_config_perm_list = [
1078 'VM.Config.Network',
1080 'VM.Config.Options',
1083 __PACKAGE__-
>register_method({
1084 name
=> 'update_vm_async',
1085 path
=> '{vmid}/config',
1089 description
=> "Set virtual machine options (asynchrounous API).",
1091 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1094 additionalProperties
=> 0,
1095 properties
=> PVE
::QemuServer
::json_config_properties
(
1097 node
=> get_standard_option
('pve-node'),
1098 vmid
=> get_standard_option
('pve-vmid'),
1099 skiplock
=> get_standard_option
('skiplock'),
1101 type
=> 'string', format
=> 'pve-configid-list',
1102 description
=> "A list of settings you want to delete.",
1106 type
=> 'string', format
=> 'pve-configid-list',
1107 description
=> "Revert a pending change.",
1112 description
=> $opt_force_description,
1114 requires
=> 'delete',
1118 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1122 background_delay
=> {
1124 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1135 code
=> $update_vm_api,
1138 __PACKAGE__-
>register_method({
1139 name
=> 'update_vm',
1140 path
=> '{vmid}/config',
1144 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1146 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1149 additionalProperties
=> 0,
1150 properties
=> PVE
::QemuServer
::json_config_properties
(
1152 node
=> get_standard_option
('pve-node'),
1153 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1154 skiplock
=> get_standard_option
('skiplock'),
1156 type
=> 'string', format
=> 'pve-configid-list',
1157 description
=> "A list of settings you want to delete.",
1161 type
=> 'string', format
=> 'pve-configid-list',
1162 description
=> "Revert a pending change.",
1167 description
=> $opt_force_description,
1169 requires
=> 'delete',
1173 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1179 returns
=> { type
=> 'null' },
1182 &$update_vm_api($param, 1);
1188 __PACKAGE__-
>register_method({
1189 name
=> 'destroy_vm',
1194 description
=> "Destroy the vm (also delete all used/owned volumes).",
1196 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1199 additionalProperties
=> 0,
1201 node
=> get_standard_option
('pve-node'),
1202 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1203 skiplock
=> get_standard_option
('skiplock'),
1212 my $rpcenv = PVE
::RPCEnvironment
::get
();
1214 my $authuser = $rpcenv->get_user();
1216 my $vmid = $param->{vmid
};
1218 my $skiplock = $param->{skiplock
};
1219 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1220 if $skiplock && $authuser ne 'root@pam';
1223 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1225 my $storecfg = PVE
::Storage
::config
();
1227 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1229 die "unable to remove VM $vmid - used in HA resources\n"
1230 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1232 # early tests (repeat after locking)
1233 die "VM $vmid is running - destroy failed\n"
1234 if PVE
::QemuServer
::check_running
($vmid);
1239 syslog
('info', "destroy VM $vmid: $upid\n");
1241 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1243 PVE
::AccessControl
::remove_vm_access
($vmid);
1245 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1248 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1251 __PACKAGE__-
>register_method({
1253 path
=> '{vmid}/unlink',
1257 description
=> "Unlink/delete disk images.",
1259 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1262 additionalProperties
=> 0,
1264 node
=> get_standard_option
('pve-node'),
1265 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1267 type
=> 'string', format
=> 'pve-configid-list',
1268 description
=> "A list of disk IDs you want to delete.",
1272 description
=> $opt_force_description,
1277 returns
=> { type
=> 'null'},
1281 $param->{delete} = extract_param
($param, 'idlist');
1283 __PACKAGE__-
>update_vm($param);
1290 __PACKAGE__-
>register_method({
1292 path
=> '{vmid}/vncproxy',
1296 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1298 description
=> "Creates a TCP VNC proxy connections.",
1300 additionalProperties
=> 0,
1302 node
=> get_standard_option
('pve-node'),
1303 vmid
=> get_standard_option
('pve-vmid'),
1307 description
=> "starts websockify instead of vncproxy",
1312 additionalProperties
=> 0,
1314 user
=> { type
=> 'string' },
1315 ticket
=> { type
=> 'string' },
1316 cert
=> { type
=> 'string' },
1317 port
=> { type
=> 'integer' },
1318 upid
=> { type
=> 'string' },
1324 my $rpcenv = PVE
::RPCEnvironment
::get
();
1326 my $authuser = $rpcenv->get_user();
1328 my $vmid = $param->{vmid
};
1329 my $node = $param->{node
};
1330 my $websocket = $param->{websocket
};
1332 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1334 my $authpath = "/vms/$vmid";
1336 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1338 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1341 my ($remip, $family);
1344 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1345 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1346 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1347 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1349 $family = PVE
::Tools
::get_host_address_family
($node);
1352 my $port = PVE
::Tools
::next_vnc_port
($family);
1359 syslog
('info', "starting vnc proxy $upid\n");
1363 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1365 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1367 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1368 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1369 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1370 '-timeout', $timeout, '-authpath', $authpath,
1371 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1374 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1376 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1378 my $qmstr = join(' ', @$qmcmd);
1380 # also redirect stderr (else we get RFB protocol errors)
1381 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1384 PVE
::Tools
::run_command
($cmd);
1389 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1391 PVE
::Tools
::wait_for_vnc_port
($port);
1402 __PACKAGE__-
>register_method({
1403 name
=> 'vncwebsocket',
1404 path
=> '{vmid}/vncwebsocket',
1407 description
=> "You also need to pass a valid ticket (vncticket).",
1408 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1410 description
=> "Opens a weksocket for VNC traffic.",
1412 additionalProperties
=> 0,
1414 node
=> get_standard_option
('pve-node'),
1415 vmid
=> get_standard_option
('pve-vmid'),
1417 description
=> "Ticket from previous call to vncproxy.",
1422 description
=> "Port number returned by previous vncproxy call.",
1432 port
=> { type
=> 'string' },
1438 my $rpcenv = PVE
::RPCEnvironment
::get
();
1440 my $authuser = $rpcenv->get_user();
1442 my $vmid = $param->{vmid
};
1443 my $node = $param->{node
};
1445 my $authpath = "/vms/$vmid";
1447 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1449 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1451 # Note: VNC ports are acessible from outside, so we do not gain any
1452 # security if we verify that $param->{port} belongs to VM $vmid. This
1453 # check is done by verifying the VNC ticket (inside VNC protocol).
1455 my $port = $param->{port
};
1457 return { port
=> $port };
1460 __PACKAGE__-
>register_method({
1461 name
=> 'spiceproxy',
1462 path
=> '{vmid}/spiceproxy',
1467 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1469 description
=> "Returns a SPICE configuration to connect to the VM.",
1471 additionalProperties
=> 0,
1473 node
=> get_standard_option
('pve-node'),
1474 vmid
=> get_standard_option
('pve-vmid'),
1475 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1478 returns
=> get_standard_option
('remote-viewer-config'),
1482 my $rpcenv = PVE
::RPCEnvironment
::get
();
1484 my $authuser = $rpcenv->get_user();
1486 my $vmid = $param->{vmid
};
1487 my $node = $param->{node
};
1488 my $proxy = $param->{proxy
};
1490 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1491 my $title = "VM $vmid";
1492 $title .= " - ". $conf->{name
} if $conf->{name
};
1494 my $port = PVE
::QemuServer
::spice_port
($vmid);
1496 my ($ticket, undef, $remote_viewer_config) =
1497 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1499 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1500 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1502 return $remote_viewer_config;
1505 __PACKAGE__-
>register_method({
1507 path
=> '{vmid}/status',
1510 description
=> "Directory index",
1515 additionalProperties
=> 0,
1517 node
=> get_standard_option
('pve-node'),
1518 vmid
=> get_standard_option
('pve-vmid'),
1526 subdir
=> { type
=> 'string' },
1529 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1535 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1538 { subdir
=> 'current' },
1539 { subdir
=> 'start' },
1540 { subdir
=> 'stop' },
1546 __PACKAGE__-
>register_method({
1547 name
=> 'vm_status',
1548 path
=> '{vmid}/status/current',
1551 protected
=> 1, # qemu pid files are only readable by root
1552 description
=> "Get virtual machine status.",
1554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1557 additionalProperties
=> 0,
1559 node
=> get_standard_option
('pve-node'),
1560 vmid
=> get_standard_option
('pve-vmid'),
1563 returns
=> { type
=> 'object' },
1568 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1570 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1571 my $status = $vmstatus->{$param->{vmid
}};
1573 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1575 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1580 __PACKAGE__-
>register_method({
1582 path
=> '{vmid}/status/start',
1586 description
=> "Start virtual machine.",
1588 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1591 additionalProperties
=> 0,
1593 node
=> get_standard_option
('pve-node'),
1594 vmid
=> get_standard_option
('pve-vmid',
1595 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1596 skiplock
=> get_standard_option
('skiplock'),
1597 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1598 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1599 machine
=> get_standard_option
('pve-qm-machine'),
1608 my $rpcenv = PVE
::RPCEnvironment
::get
();
1610 my $authuser = $rpcenv->get_user();
1612 my $node = extract_param
($param, 'node');
1614 my $vmid = extract_param
($param, 'vmid');
1616 my $machine = extract_param
($param, 'machine');
1618 my $stateuri = extract_param
($param, 'stateuri');
1619 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1620 if $stateuri && $authuser ne 'root@pam';
1622 my $skiplock = extract_param
($param, 'skiplock');
1623 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1624 if $skiplock && $authuser ne 'root@pam';
1626 my $migratedfrom = extract_param
($param, 'migratedfrom');
1627 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1628 if $migratedfrom && $authuser ne 'root@pam';
1630 # read spice ticket from STDIN
1632 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1633 if (defined(my $line = <>)) {
1635 $spice_ticket = $line;
1639 PVE
::Cluster
::check_cfs_quorum
();
1641 my $storecfg = PVE
::Storage
::config
();
1643 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1644 $rpcenv->{type
} ne 'ha') {
1649 my $service = "vm:$vmid";
1651 my $cmd = ['ha-manager', 'enable', $service];
1653 print "Executing HA start for VM $vmid\n";
1655 PVE
::Tools
::run_command
($cmd);
1660 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1667 syslog
('info', "start VM $vmid: $upid\n");
1669 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1670 $machine, $spice_ticket);
1675 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1679 __PACKAGE__-
>register_method({
1681 path
=> '{vmid}/status/stop',
1685 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1686 "is akin to pulling the power plug of a running computer and may damage the VM data",
1688 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1691 additionalProperties
=> 0,
1693 node
=> get_standard_option
('pve-node'),
1694 vmid
=> get_standard_option
('pve-vmid',
1695 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1696 skiplock
=> get_standard_option
('skiplock'),
1697 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1699 description
=> "Wait maximal timeout seconds.",
1705 description
=> "Do not decativate storage volumes.",
1718 my $rpcenv = PVE
::RPCEnvironment
::get
();
1720 my $authuser = $rpcenv->get_user();
1722 my $node = extract_param
($param, 'node');
1724 my $vmid = extract_param
($param, 'vmid');
1726 my $skiplock = extract_param
($param, 'skiplock');
1727 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1728 if $skiplock && $authuser ne 'root@pam';
1730 my $keepActive = extract_param
($param, 'keepActive');
1731 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1732 if $keepActive && $authuser ne 'root@pam';
1734 my $migratedfrom = extract_param
($param, 'migratedfrom');
1735 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1736 if $migratedfrom && $authuser ne 'root@pam';
1739 my $storecfg = PVE
::Storage
::config
();
1741 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1746 my $service = "vm:$vmid";
1748 my $cmd = ['ha-manager', 'disable', $service];
1750 print "Executing HA stop for VM $vmid\n";
1752 PVE
::Tools
::run_command
($cmd);
1757 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1763 syslog
('info', "stop VM $vmid: $upid\n");
1765 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1766 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1771 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1775 __PACKAGE__-
>register_method({
1777 path
=> '{vmid}/status/reset',
1781 description
=> "Reset virtual machine.",
1783 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1786 additionalProperties
=> 0,
1788 node
=> get_standard_option
('pve-node'),
1789 vmid
=> get_standard_option
('pve-vmid',
1790 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1791 skiplock
=> get_standard_option
('skiplock'),
1800 my $rpcenv = PVE
::RPCEnvironment
::get
();
1802 my $authuser = $rpcenv->get_user();
1804 my $node = extract_param
($param, 'node');
1806 my $vmid = extract_param
($param, 'vmid');
1808 my $skiplock = extract_param
($param, 'skiplock');
1809 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1810 if $skiplock && $authuser ne 'root@pam';
1812 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1817 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1822 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1825 __PACKAGE__-
>register_method({
1826 name
=> 'vm_shutdown',
1827 path
=> '{vmid}/status/shutdown',
1831 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1832 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1834 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1837 additionalProperties
=> 0,
1839 node
=> get_standard_option
('pve-node'),
1840 vmid
=> get_standard_option
('pve-vmid',
1841 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1842 skiplock
=> get_standard_option
('skiplock'),
1844 description
=> "Wait maximal timeout seconds.",
1850 description
=> "Make sure the VM stops.",
1856 description
=> "Do not decativate storage volumes.",
1869 my $rpcenv = PVE
::RPCEnvironment
::get
();
1871 my $authuser = $rpcenv->get_user();
1873 my $node = extract_param
($param, 'node');
1875 my $vmid = extract_param
($param, 'vmid');
1877 my $skiplock = extract_param
($param, 'skiplock');
1878 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1879 if $skiplock && $authuser ne 'root@pam';
1881 my $keepActive = extract_param
($param, 'keepActive');
1882 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1883 if $keepActive && $authuser ne 'root@pam';
1885 my $storecfg = PVE
::Storage
::config
();
1889 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1890 # otherwise, we will infer a shutdown command, but run into the timeout,
1891 # then when the vm is resumed, it will instantly shutdown
1893 # checking the qmp status here to get feedback to the gui/cli/api
1894 # and the status query should not take too long
1897 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1901 if (!$err && $qmpstatus->{status
} eq "paused") {
1902 if ($param->{forceStop
}) {
1903 warn "VM is paused - stop instead of shutdown\n";
1906 die "VM is paused - cannot shutdown\n";
1913 syslog
('info', "shutdown VM $vmid: $upid\n");
1915 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1916 $shutdown, $param->{forceStop
}, $keepActive);
1921 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1924 __PACKAGE__-
>register_method({
1925 name
=> 'vm_suspend',
1926 path
=> '{vmid}/status/suspend',
1930 description
=> "Suspend virtual machine.",
1932 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1935 additionalProperties
=> 0,
1937 node
=> get_standard_option
('pve-node'),
1938 vmid
=> get_standard_option
('pve-vmid',
1939 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1940 skiplock
=> get_standard_option
('skiplock'),
1949 my $rpcenv = PVE
::RPCEnvironment
::get
();
1951 my $authuser = $rpcenv->get_user();
1953 my $node = extract_param
($param, 'node');
1955 my $vmid = extract_param
($param, 'vmid');
1957 my $skiplock = extract_param
($param, 'skiplock');
1958 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1959 if $skiplock && $authuser ne 'root@pam';
1961 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1966 syslog
('info', "suspend VM $vmid: $upid\n");
1968 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1973 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1976 __PACKAGE__-
>register_method({
1977 name
=> 'vm_resume',
1978 path
=> '{vmid}/status/resume',
1982 description
=> "Resume virtual machine.",
1984 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1987 additionalProperties
=> 0,
1989 node
=> get_standard_option
('pve-node'),
1990 vmid
=> get_standard_option
('pve-vmid',
1991 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1992 skiplock
=> get_standard_option
('skiplock'),
1993 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2003 my $rpcenv = PVE
::RPCEnvironment
::get
();
2005 my $authuser = $rpcenv->get_user();
2007 my $node = extract_param
($param, 'node');
2009 my $vmid = extract_param
($param, 'vmid');
2011 my $skiplock = extract_param
($param, 'skiplock');
2012 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2013 if $skiplock && $authuser ne 'root@pam';
2015 my $nocheck = extract_param
($param, 'nocheck');
2017 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2022 syslog
('info', "resume VM $vmid: $upid\n");
2024 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2029 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2032 __PACKAGE__-
>register_method({
2033 name
=> 'vm_sendkey',
2034 path
=> '{vmid}/sendkey',
2038 description
=> "Send key event to virtual machine.",
2040 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2043 additionalProperties
=> 0,
2045 node
=> get_standard_option
('pve-node'),
2046 vmid
=> get_standard_option
('pve-vmid',
2047 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2048 skiplock
=> get_standard_option
('skiplock'),
2050 description
=> "The key (qemu monitor encoding).",
2055 returns
=> { type
=> 'null'},
2059 my $rpcenv = PVE
::RPCEnvironment
::get
();
2061 my $authuser = $rpcenv->get_user();
2063 my $node = extract_param
($param, 'node');
2065 my $vmid = extract_param
($param, 'vmid');
2067 my $skiplock = extract_param
($param, 'skiplock');
2068 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2069 if $skiplock && $authuser ne 'root@pam';
2071 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2076 __PACKAGE__-
>register_method({
2077 name
=> 'vm_feature',
2078 path
=> '{vmid}/feature',
2082 description
=> "Check if feature for virtual machine is available.",
2084 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2087 additionalProperties
=> 0,
2089 node
=> get_standard_option
('pve-node'),
2090 vmid
=> get_standard_option
('pve-vmid'),
2092 description
=> "Feature to check.",
2094 enum
=> [ 'snapshot', 'clone', 'copy' ],
2096 snapname
=> get_standard_option
('pve-snapshot-name', {
2104 hasFeature
=> { type
=> 'boolean' },
2107 items
=> { type
=> 'string' },
2114 my $node = extract_param
($param, 'node');
2116 my $vmid = extract_param
($param, 'vmid');
2118 my $snapname = extract_param
($param, 'snapname');
2120 my $feature = extract_param
($param, 'feature');
2122 my $running = PVE
::QemuServer
::check_running
($vmid);
2124 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2127 my $snap = $conf->{snapshots
}->{$snapname};
2128 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2131 my $storecfg = PVE
::Storage
::config
();
2133 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2134 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2137 hasFeature
=> $hasFeature,
2138 nodes
=> [ keys %$nodelist ],
2142 __PACKAGE__-
>register_method({
2144 path
=> '{vmid}/clone',
2148 description
=> "Create a copy of virtual machine/template.",
2150 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2151 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2152 "'Datastore.AllocateSpace' on any used storage.",
2155 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2157 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2158 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2163 additionalProperties
=> 0,
2165 node
=> get_standard_option
('pve-node'),
2166 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2167 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2170 type
=> 'string', format
=> 'dns-name',
2171 description
=> "Set a name for the new VM.",
2176 description
=> "Description for the new VM.",
2180 type
=> 'string', format
=> 'pve-poolid',
2181 description
=> "Add the new VM to the specified pool.",
2183 snapname
=> get_standard_option
('pve-snapshot-name', {
2186 storage
=> get_standard_option
('pve-storage-id', {
2187 description
=> "Target storage for full clone.",
2192 description
=> "Target format for file storage.",
2196 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2201 description
=> "Create a full copy of all disk. This is always done when " .
2202 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2205 target
=> get_standard_option
('pve-node', {
2206 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2217 my $rpcenv = PVE
::RPCEnvironment
::get
();
2219 my $authuser = $rpcenv->get_user();
2221 my $node = extract_param
($param, 'node');
2223 my $vmid = extract_param
($param, 'vmid');
2225 my $newid = extract_param
($param, 'newid');
2227 my $pool = extract_param
($param, 'pool');
2229 if (defined($pool)) {
2230 $rpcenv->check_pool_exist($pool);
2233 my $snapname = extract_param
($param, 'snapname');
2235 my $storage = extract_param
($param, 'storage');
2237 my $format = extract_param
($param, 'format');
2239 my $target = extract_param
($param, 'target');
2241 my $localnode = PVE
::INotify
::nodename
();
2243 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2245 PVE
::Cluster
::check_node_exists
($target) if $target;
2247 my $storecfg = PVE
::Storage
::config
();
2250 # check if storage is enabled on local node
2251 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2253 # check if storage is available on target node
2254 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2255 # clone only works if target storage is shared
2256 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2257 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2261 PVE
::Cluster
::check_cfs_quorum
();
2263 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2265 # exclusive lock if VM is running - else shared lock is enough;
2266 my $shared_lock = $running ?
0 : 1;
2270 # do all tests after lock
2271 # we also try to do all tests before we fork the worker
2273 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2275 PVE
::QemuConfig-
>check_lock($conf);
2277 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2279 die "unexpected state change\n" if $verify_running != $running;
2281 die "snapshot '$snapname' does not exist\n"
2282 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2284 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2286 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2288 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2290 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2292 die "unable to create VM $newid: config file already exists\n"
2295 my $newconf = { lock => 'clone' };
2300 foreach my $opt (keys %$oldconf) {
2301 my $value = $oldconf->{$opt};
2303 # do not copy snapshot related info
2304 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2305 $opt eq 'vmstate' || $opt eq 'snapstate';
2307 # no need to copy unused images, because VMID(owner) changes anyways
2308 next if $opt =~ m/^unused\d+$/;
2310 # always change MAC! address
2311 if ($opt =~ m/^net(\d+)$/) {
2312 my $net = PVE
::QemuServer
::parse_net
($value);
2313 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2314 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2315 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2316 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2317 die "unable to parse drive options for '$opt'\n" if !$drive;
2318 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2319 $newconf->{$opt} = $value; # simply copy configuration
2321 if ($param->{full
}) {
2322 die "Full clone feature is not available"
2323 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2324 $fullclone->{$opt} = 1;
2326 # not full means clone instead of copy
2327 die "Linked clone feature is not available"
2328 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2330 $drives->{$opt} = $drive;
2331 push @$vollist, $drive->{file
};
2334 # copy everything else
2335 $newconf->{$opt} = $value;
2339 # auto generate a new uuid
2340 my ($uuid, $uuid_str);
2341 UUID
::generate
($uuid);
2342 UUID
::unparse
($uuid, $uuid_str);
2343 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2344 $smbios1->{uuid
} = $uuid_str;
2345 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2347 delete $newconf->{template
};
2349 if ($param->{name
}) {
2350 $newconf->{name
} = $param->{name
};
2352 if ($oldconf->{name
}) {
2353 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2355 $newconf->{name
} = "Copy-of-VM-$vmid";
2359 if ($param->{description
}) {
2360 $newconf->{description
} = $param->{description
};
2363 # create empty/temp config - this fails if VM already exists on other node
2364 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2369 my $newvollist = [];
2372 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2374 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2376 foreach my $opt (keys %$drives) {
2377 my $drive = $drives->{$opt};
2379 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2380 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2382 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2384 PVE
::QemuConfig-
>write_config($newid, $newconf);
2387 delete $newconf->{lock};
2388 PVE
::QemuConfig-
>write_config($newid, $newconf);
2391 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2392 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2394 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2395 die "Failed to move config to node '$target' - rename failed: $!\n"
2396 if !rename($conffile, $newconffile);
2399 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2404 sleep 1; # some storage like rbd need to wait before release volume - really?
2406 foreach my $volid (@$newvollist) {
2407 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2410 die "clone failed: $err";
2416 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2418 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2421 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2422 # Aquire exclusive lock lock for $newid
2423 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2428 __PACKAGE__-
>register_method({
2429 name
=> 'move_vm_disk',
2430 path
=> '{vmid}/move_disk',
2434 description
=> "Move volume to different storage.",
2436 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2437 "and 'Datastore.AllocateSpace' permissions on the storage.",
2440 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2441 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2445 additionalProperties
=> 0,
2447 node
=> get_standard_option
('pve-node'),
2448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2451 description
=> "The disk you want to move.",
2452 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2454 storage
=> get_standard_option
('pve-storage-id', {
2455 description
=> "Target storage.",
2456 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2460 description
=> "Target Format.",
2461 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2466 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2472 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2480 description
=> "the task ID.",
2485 my $rpcenv = PVE
::RPCEnvironment
::get
();
2487 my $authuser = $rpcenv->get_user();
2489 my $node = extract_param
($param, 'node');
2491 my $vmid = extract_param
($param, 'vmid');
2493 my $digest = extract_param
($param, 'digest');
2495 my $disk = extract_param
($param, 'disk');
2497 my $storeid = extract_param
($param, 'storage');
2499 my $format = extract_param
($param, 'format');
2501 my $storecfg = PVE
::Storage
::config
();
2503 my $updatefn = sub {
2505 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2507 die "checksum missmatch (file change by other user?)\n"
2508 if $digest && $digest ne $conf->{digest
};
2510 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2512 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2514 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2516 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2519 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2520 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2524 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2525 (!$format || !$oldfmt || $oldfmt eq $format);
2527 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2529 my $running = PVE
::QemuServer
::check_running
($vmid);
2531 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2535 my $newvollist = [];
2538 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2540 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2541 $vmid, $storeid, $format, 1, $newvollist);
2543 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2545 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2547 PVE
::QemuConfig-
>write_config($vmid, $conf);
2550 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2551 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2558 foreach my $volid (@$newvollist) {
2559 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2562 die "storage migration failed: $err";
2565 if ($param->{delete}) {
2566 if (PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, undef, $old_volid)) {
2567 warn "volume $old_volid still has snapshots, can't delete it\n";
2568 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid);
2569 PVE
::QemuConfig-
>write_config($vmid, $conf);
2572 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2573 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2580 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2583 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2586 __PACKAGE__-
>register_method({
2587 name
=> 'migrate_vm',
2588 path
=> '{vmid}/migrate',
2592 description
=> "Migrate virtual machine. Creates a new migration task.",
2594 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2597 additionalProperties
=> 0,
2599 node
=> get_standard_option
('pve-node'),
2600 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2601 target
=> get_standard_option
('pve-node', {
2602 description
=> "Target node.",
2603 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2607 description
=> "Use online/live migration.",
2612 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2619 description
=> "the task ID.",
2624 my $rpcenv = PVE
::RPCEnvironment
::get
();
2626 my $authuser = $rpcenv->get_user();
2628 my $target = extract_param
($param, 'target');
2630 my $localnode = PVE
::INotify
::nodename
();
2631 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2633 PVE
::Cluster
::check_cfs_quorum
();
2635 PVE
::Cluster
::check_node_exists
($target);
2637 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2639 my $vmid = extract_param
($param, 'vmid');
2641 raise_param_exc
({ force
=> "Only root may use this option." })
2642 if $param->{force
} && $authuser ne 'root@pam';
2645 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2647 # try to detect errors early
2649 PVE
::QemuConfig-
>check_lock($conf);
2651 if (PVE
::QemuServer
::check_running
($vmid)) {
2652 die "cant migrate running VM without --online\n"
2653 if !$param->{online
};
2656 my $storecfg = PVE
::Storage
::config
();
2657 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2659 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2664 my $service = "vm:$vmid";
2666 my $cmd = ['ha-manager', 'migrate', $service, $target];
2668 print "Executing HA migrate for VM $vmid to node $target\n";
2670 PVE
::Tools
::run_command
($cmd);
2675 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2682 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2685 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2690 __PACKAGE__-
>register_method({
2692 path
=> '{vmid}/monitor',
2696 description
=> "Execute Qemu monitor commands.",
2698 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2701 additionalProperties
=> 0,
2703 node
=> get_standard_option
('pve-node'),
2704 vmid
=> get_standard_option
('pve-vmid'),
2707 description
=> "The monitor command.",
2711 returns
=> { type
=> 'string'},
2715 my $vmid = $param->{vmid
};
2717 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2721 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2723 $res = "ERROR: $@" if $@;
2728 __PACKAGE__-
>register_method({
2729 name
=> 'resize_vm',
2730 path
=> '{vmid}/resize',
2734 description
=> "Extend volume size.",
2736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2739 additionalProperties
=> 0,
2741 node
=> get_standard_option
('pve-node'),
2742 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2743 skiplock
=> get_standard_option
('skiplock'),
2746 description
=> "The disk you want to resize.",
2747 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2751 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2752 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.",
2756 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2762 returns
=> { type
=> 'null'},
2766 my $rpcenv = PVE
::RPCEnvironment
::get
();
2768 my $authuser = $rpcenv->get_user();
2770 my $node = extract_param
($param, 'node');
2772 my $vmid = extract_param
($param, 'vmid');
2774 my $digest = extract_param
($param, 'digest');
2776 my $disk = extract_param
($param, 'disk');
2778 my $sizestr = extract_param
($param, 'size');
2780 my $skiplock = extract_param
($param, 'skiplock');
2781 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2782 if $skiplock && $authuser ne 'root@pam';
2784 my $storecfg = PVE
::Storage
::config
();
2786 my $updatefn = sub {
2788 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2790 die "checksum missmatch (file change by other user?)\n"
2791 if $digest && $digest ne $conf->{digest
};
2792 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2794 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2796 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2798 my (undef, undef, undef, undef, undef, undef, $format) =
2799 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2801 die "can't resize volume: $disk if snapshot exists\n"
2802 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2804 my $volid = $drive->{file
};
2806 die "disk '$disk' has no associated volume\n" if !$volid;
2808 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2810 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2812 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2814 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2815 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2817 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2818 my ($ext, $newsize, $unit) = ($1, $2, $4);
2821 $newsize = $newsize * 1024;
2822 } elsif ($unit eq 'M') {
2823 $newsize = $newsize * 1024 * 1024;
2824 } elsif ($unit eq 'G') {
2825 $newsize = $newsize * 1024 * 1024 * 1024;
2826 } elsif ($unit eq 'T') {
2827 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2830 $newsize += $size if $ext;
2831 $newsize = int($newsize);
2833 die "unable to skrink disk size\n" if $newsize < $size;
2835 return if $size == $newsize;
2837 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2839 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2841 $drive->{size
} = $newsize;
2842 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2844 PVE
::QemuConfig-
>write_config($vmid, $conf);
2847 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2851 __PACKAGE__-
>register_method({
2852 name
=> 'snapshot_list',
2853 path
=> '{vmid}/snapshot',
2855 description
=> "List all snapshots.",
2857 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2860 protected
=> 1, # qemu pid files are only readable by root
2862 additionalProperties
=> 0,
2864 vmid
=> get_standard_option
('pve-vmid'),
2865 node
=> get_standard_option
('pve-node'),
2874 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2879 my $vmid = $param->{vmid
};
2881 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2882 my $snaphash = $conf->{snapshots
} || {};
2886 foreach my $name (keys %$snaphash) {
2887 my $d = $snaphash->{$name};
2890 snaptime
=> $d->{snaptime
} || 0,
2891 vmstate
=> $d->{vmstate
} ?
1 : 0,
2892 description
=> $d->{description
} || '',
2894 $item->{parent
} = $d->{parent
} if $d->{parent
};
2895 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2899 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2900 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2901 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2903 push @$res, $current;
2908 __PACKAGE__-
>register_method({
2910 path
=> '{vmid}/snapshot',
2914 description
=> "Snapshot a VM.",
2916 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2919 additionalProperties
=> 0,
2921 node
=> get_standard_option
('pve-node'),
2922 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2923 snapname
=> get_standard_option
('pve-snapshot-name'),
2927 description
=> "Save the vmstate",
2932 description
=> "A textual description or comment.",
2938 description
=> "the task ID.",
2943 my $rpcenv = PVE
::RPCEnvironment
::get
();
2945 my $authuser = $rpcenv->get_user();
2947 my $node = extract_param
($param, 'node');
2949 my $vmid = extract_param
($param, 'vmid');
2951 my $snapname = extract_param
($param, 'snapname');
2953 die "unable to use snapshot name 'current' (reserved name)\n"
2954 if $snapname eq 'current';
2957 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2958 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2959 $param->{description
});
2962 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2965 __PACKAGE__-
>register_method({
2966 name
=> 'snapshot_cmd_idx',
2967 path
=> '{vmid}/snapshot/{snapname}',
2974 additionalProperties
=> 0,
2976 vmid
=> get_standard_option
('pve-vmid'),
2977 node
=> get_standard_option
('pve-node'),
2978 snapname
=> get_standard_option
('pve-snapshot-name'),
2987 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2994 push @$res, { cmd
=> 'rollback' };
2995 push @$res, { cmd
=> 'config' };
3000 __PACKAGE__-
>register_method({
3001 name
=> 'update_snapshot_config',
3002 path
=> '{vmid}/snapshot/{snapname}/config',
3006 description
=> "Update snapshot metadata.",
3008 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3011 additionalProperties
=> 0,
3013 node
=> get_standard_option
('pve-node'),
3014 vmid
=> get_standard_option
('pve-vmid'),
3015 snapname
=> get_standard_option
('pve-snapshot-name'),
3019 description
=> "A textual description or comment.",
3023 returns
=> { type
=> 'null' },
3027 my $rpcenv = PVE
::RPCEnvironment
::get
();
3029 my $authuser = $rpcenv->get_user();
3031 my $vmid = extract_param
($param, 'vmid');
3033 my $snapname = extract_param
($param, 'snapname');
3035 return undef if !defined($param->{description
});
3037 my $updatefn = sub {
3039 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3041 PVE
::QemuConfig-
>check_lock($conf);
3043 my $snap = $conf->{snapshots
}->{$snapname};
3045 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3047 $snap->{description
} = $param->{description
} if defined($param->{description
});
3049 PVE
::QemuConfig-
>write_config($vmid, $conf);
3052 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3057 __PACKAGE__-
>register_method({
3058 name
=> 'get_snapshot_config',
3059 path
=> '{vmid}/snapshot/{snapname}/config',
3062 description
=> "Get snapshot configuration",
3064 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3067 additionalProperties
=> 0,
3069 node
=> get_standard_option
('pve-node'),
3070 vmid
=> get_standard_option
('pve-vmid'),
3071 snapname
=> get_standard_option
('pve-snapshot-name'),
3074 returns
=> { type
=> "object" },
3078 my $rpcenv = PVE
::RPCEnvironment
::get
();
3080 my $authuser = $rpcenv->get_user();
3082 my $vmid = extract_param
($param, 'vmid');
3084 my $snapname = extract_param
($param, 'snapname');
3086 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3088 my $snap = $conf->{snapshots
}->{$snapname};
3090 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3095 __PACKAGE__-
>register_method({
3097 path
=> '{vmid}/snapshot/{snapname}/rollback',
3101 description
=> "Rollback VM state to specified snapshot.",
3103 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3106 additionalProperties
=> 0,
3108 node
=> get_standard_option
('pve-node'),
3109 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3110 snapname
=> get_standard_option
('pve-snapshot-name'),
3115 description
=> "the task ID.",
3120 my $rpcenv = PVE
::RPCEnvironment
::get
();
3122 my $authuser = $rpcenv->get_user();
3124 my $node = extract_param
($param, 'node');
3126 my $vmid = extract_param
($param, 'vmid');
3128 my $snapname = extract_param
($param, 'snapname');
3131 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3132 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3135 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3138 __PACKAGE__-
>register_method({
3139 name
=> 'delsnapshot',
3140 path
=> '{vmid}/snapshot/{snapname}',
3144 description
=> "Delete a VM snapshot.",
3146 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3149 additionalProperties
=> 0,
3151 node
=> get_standard_option
('pve-node'),
3152 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3153 snapname
=> get_standard_option
('pve-snapshot-name'),
3157 description
=> "For removal from config file, even if removing disk snapshots fails.",
3163 description
=> "the task ID.",
3168 my $rpcenv = PVE
::RPCEnvironment
::get
();
3170 my $authuser = $rpcenv->get_user();
3172 my $node = extract_param
($param, 'node');
3174 my $vmid = extract_param
($param, 'vmid');
3176 my $snapname = extract_param
($param, 'snapname');
3179 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3180 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3183 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3186 __PACKAGE__-
>register_method({
3188 path
=> '{vmid}/template',
3192 description
=> "Create a Template.",
3194 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3195 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3198 additionalProperties
=> 0,
3200 node
=> get_standard_option
('pve-node'),
3201 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3205 description
=> "If you want to convert only 1 disk to base image.",
3206 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3211 returns
=> { type
=> 'null'},
3215 my $rpcenv = PVE
::RPCEnvironment
::get
();
3217 my $authuser = $rpcenv->get_user();
3219 my $node = extract_param
($param, 'node');
3221 my $vmid = extract_param
($param, 'vmid');
3223 my $disk = extract_param
($param, 'disk');
3225 my $updatefn = sub {
3227 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3229 PVE
::QemuConfig-
>check_lock($conf);
3231 die "unable to create template, because VM contains snapshots\n"
3232 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3234 die "you can't convert a template to a template\n"
3235 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3237 die "you can't convert a VM to template if VM is running\n"
3238 if PVE
::QemuServer
::check_running
($vmid);
3241 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3244 $conf->{template
} = 1;
3245 PVE
::QemuConfig-
>write_config($vmid, $conf);
3247 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3250 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);