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 # this only checks snapshots because $disk is passed!
2529 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2530 die "you can't move a disk with snapshots and delete the source\n"
2531 if $snapshotted && $param->{delete};
2533 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2535 my $running = PVE
::QemuServer
::check_running
($vmid);
2537 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2541 my $newvollist = [];
2544 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2546 warn "moving disk with snapshots, snapshots will not be moved!\n"
2549 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2550 $vmid, $storeid, $format, 1, $newvollist);
2552 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2554 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2556 PVE
::QemuConfig-
>write_config($vmid, $conf);
2559 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2560 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2567 foreach my $volid (@$newvollist) {
2568 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2571 die "storage migration failed: $err";
2574 if ($param->{delete}) {
2576 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2577 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2583 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2586 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2589 __PACKAGE__-
>register_method({
2590 name
=> 'migrate_vm',
2591 path
=> '{vmid}/migrate',
2595 description
=> "Migrate virtual machine. Creates a new migration task.",
2597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2600 additionalProperties
=> 0,
2602 node
=> get_standard_option
('pve-node'),
2603 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2604 target
=> get_standard_option
('pve-node', {
2605 description
=> "Target node.",
2606 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2610 description
=> "Use online/live migration.",
2615 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2622 description
=> "the task ID.",
2627 my $rpcenv = PVE
::RPCEnvironment
::get
();
2629 my $authuser = $rpcenv->get_user();
2631 my $target = extract_param
($param, 'target');
2633 my $localnode = PVE
::INotify
::nodename
();
2634 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2636 PVE
::Cluster
::check_cfs_quorum
();
2638 PVE
::Cluster
::check_node_exists
($target);
2640 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2642 my $vmid = extract_param
($param, 'vmid');
2644 raise_param_exc
({ force
=> "Only root may use this option." })
2645 if $param->{force
} && $authuser ne 'root@pam';
2648 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2650 # try to detect errors early
2652 PVE
::QemuConfig-
>check_lock($conf);
2654 if (PVE
::QemuServer
::check_running
($vmid)) {
2655 die "cant migrate running VM without --online\n"
2656 if !$param->{online
};
2659 my $storecfg = PVE
::Storage
::config
();
2660 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2662 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2667 my $service = "vm:$vmid";
2669 my $cmd = ['ha-manager', 'migrate', $service, $target];
2671 print "Executing HA migrate for VM $vmid to node $target\n";
2673 PVE
::Tools
::run_command
($cmd);
2678 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2685 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2688 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2693 __PACKAGE__-
>register_method({
2695 path
=> '{vmid}/monitor',
2699 description
=> "Execute Qemu monitor commands.",
2701 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2704 additionalProperties
=> 0,
2706 node
=> get_standard_option
('pve-node'),
2707 vmid
=> get_standard_option
('pve-vmid'),
2710 description
=> "The monitor command.",
2714 returns
=> { type
=> 'string'},
2718 my $vmid = $param->{vmid
};
2720 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2724 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2726 $res = "ERROR: $@" if $@;
2731 __PACKAGE__-
>register_method({
2732 name
=> 'resize_vm',
2733 path
=> '{vmid}/resize',
2737 description
=> "Extend volume size.",
2739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2742 additionalProperties
=> 0,
2744 node
=> get_standard_option
('pve-node'),
2745 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2746 skiplock
=> get_standard_option
('skiplock'),
2749 description
=> "The disk you want to resize.",
2750 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2754 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2755 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.",
2759 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2765 returns
=> { type
=> 'null'},
2769 my $rpcenv = PVE
::RPCEnvironment
::get
();
2771 my $authuser = $rpcenv->get_user();
2773 my $node = extract_param
($param, 'node');
2775 my $vmid = extract_param
($param, 'vmid');
2777 my $digest = extract_param
($param, 'digest');
2779 my $disk = extract_param
($param, 'disk');
2781 my $sizestr = extract_param
($param, 'size');
2783 my $skiplock = extract_param
($param, 'skiplock');
2784 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2785 if $skiplock && $authuser ne 'root@pam';
2787 my $storecfg = PVE
::Storage
::config
();
2789 my $updatefn = sub {
2791 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2793 die "checksum missmatch (file change by other user?)\n"
2794 if $digest && $digest ne $conf->{digest
};
2795 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2797 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2799 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2801 my (undef, undef, undef, undef, undef, undef, $format) =
2802 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2804 die "can't resize volume: $disk if snapshot exists\n"
2805 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2807 my $volid = $drive->{file
};
2809 die "disk '$disk' has no associated volume\n" if !$volid;
2811 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2813 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2815 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2817 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2818 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2820 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2821 my ($ext, $newsize, $unit) = ($1, $2, $4);
2824 $newsize = $newsize * 1024;
2825 } elsif ($unit eq 'M') {
2826 $newsize = $newsize * 1024 * 1024;
2827 } elsif ($unit eq 'G') {
2828 $newsize = $newsize * 1024 * 1024 * 1024;
2829 } elsif ($unit eq 'T') {
2830 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2833 $newsize += $size if $ext;
2834 $newsize = int($newsize);
2836 die "unable to skrink disk size\n" if $newsize < $size;
2838 return if $size == $newsize;
2840 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2842 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2844 $drive->{size
} = $newsize;
2845 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2847 PVE
::QemuConfig-
>write_config($vmid, $conf);
2850 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2854 __PACKAGE__-
>register_method({
2855 name
=> 'snapshot_list',
2856 path
=> '{vmid}/snapshot',
2858 description
=> "List all snapshots.",
2860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2863 protected
=> 1, # qemu pid files are only readable by root
2865 additionalProperties
=> 0,
2867 vmid
=> get_standard_option
('pve-vmid'),
2868 node
=> get_standard_option
('pve-node'),
2877 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2882 my $vmid = $param->{vmid
};
2884 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2885 my $snaphash = $conf->{snapshots
} || {};
2889 foreach my $name (keys %$snaphash) {
2890 my $d = $snaphash->{$name};
2893 snaptime
=> $d->{snaptime
} || 0,
2894 vmstate
=> $d->{vmstate
} ?
1 : 0,
2895 description
=> $d->{description
} || '',
2897 $item->{parent
} = $d->{parent
} if $d->{parent
};
2898 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2902 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2903 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2904 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2906 push @$res, $current;
2911 __PACKAGE__-
>register_method({
2913 path
=> '{vmid}/snapshot',
2917 description
=> "Snapshot a VM.",
2919 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2922 additionalProperties
=> 0,
2924 node
=> get_standard_option
('pve-node'),
2925 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2926 snapname
=> get_standard_option
('pve-snapshot-name'),
2930 description
=> "Save the vmstate",
2935 description
=> "A textual description or comment.",
2941 description
=> "the task ID.",
2946 my $rpcenv = PVE
::RPCEnvironment
::get
();
2948 my $authuser = $rpcenv->get_user();
2950 my $node = extract_param
($param, 'node');
2952 my $vmid = extract_param
($param, 'vmid');
2954 my $snapname = extract_param
($param, 'snapname');
2956 die "unable to use snapshot name 'current' (reserved name)\n"
2957 if $snapname eq 'current';
2960 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2961 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2962 $param->{description
});
2965 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2968 __PACKAGE__-
>register_method({
2969 name
=> 'snapshot_cmd_idx',
2970 path
=> '{vmid}/snapshot/{snapname}',
2977 additionalProperties
=> 0,
2979 vmid
=> get_standard_option
('pve-vmid'),
2980 node
=> get_standard_option
('pve-node'),
2981 snapname
=> get_standard_option
('pve-snapshot-name'),
2990 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2997 push @$res, { cmd
=> 'rollback' };
2998 push @$res, { cmd
=> 'config' };
3003 __PACKAGE__-
>register_method({
3004 name
=> 'update_snapshot_config',
3005 path
=> '{vmid}/snapshot/{snapname}/config',
3009 description
=> "Update snapshot metadata.",
3011 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3014 additionalProperties
=> 0,
3016 node
=> get_standard_option
('pve-node'),
3017 vmid
=> get_standard_option
('pve-vmid'),
3018 snapname
=> get_standard_option
('pve-snapshot-name'),
3022 description
=> "A textual description or comment.",
3026 returns
=> { type
=> 'null' },
3030 my $rpcenv = PVE
::RPCEnvironment
::get
();
3032 my $authuser = $rpcenv->get_user();
3034 my $vmid = extract_param
($param, 'vmid');
3036 my $snapname = extract_param
($param, 'snapname');
3038 return undef if !defined($param->{description
});
3040 my $updatefn = sub {
3042 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3044 PVE
::QemuConfig-
>check_lock($conf);
3046 my $snap = $conf->{snapshots
}->{$snapname};
3048 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3050 $snap->{description
} = $param->{description
} if defined($param->{description
});
3052 PVE
::QemuConfig-
>write_config($vmid, $conf);
3055 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3060 __PACKAGE__-
>register_method({
3061 name
=> 'get_snapshot_config',
3062 path
=> '{vmid}/snapshot/{snapname}/config',
3065 description
=> "Get snapshot configuration",
3067 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3070 additionalProperties
=> 0,
3072 node
=> get_standard_option
('pve-node'),
3073 vmid
=> get_standard_option
('pve-vmid'),
3074 snapname
=> get_standard_option
('pve-snapshot-name'),
3077 returns
=> { type
=> "object" },
3081 my $rpcenv = PVE
::RPCEnvironment
::get
();
3083 my $authuser = $rpcenv->get_user();
3085 my $vmid = extract_param
($param, 'vmid');
3087 my $snapname = extract_param
($param, 'snapname');
3089 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3091 my $snap = $conf->{snapshots
}->{$snapname};
3093 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3098 __PACKAGE__-
>register_method({
3100 path
=> '{vmid}/snapshot/{snapname}/rollback',
3104 description
=> "Rollback VM state to specified snapshot.",
3106 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3109 additionalProperties
=> 0,
3111 node
=> get_standard_option
('pve-node'),
3112 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3113 snapname
=> get_standard_option
('pve-snapshot-name'),
3118 description
=> "the task ID.",
3123 my $rpcenv = PVE
::RPCEnvironment
::get
();
3125 my $authuser = $rpcenv->get_user();
3127 my $node = extract_param
($param, 'node');
3129 my $vmid = extract_param
($param, 'vmid');
3131 my $snapname = extract_param
($param, 'snapname');
3134 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3135 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3138 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3141 __PACKAGE__-
>register_method({
3142 name
=> 'delsnapshot',
3143 path
=> '{vmid}/snapshot/{snapname}',
3147 description
=> "Delete a VM snapshot.",
3149 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3152 additionalProperties
=> 0,
3154 node
=> get_standard_option
('pve-node'),
3155 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3156 snapname
=> get_standard_option
('pve-snapshot-name'),
3160 description
=> "For removal from config file, even if removing disk snapshots fails.",
3166 description
=> "the task ID.",
3171 my $rpcenv = PVE
::RPCEnvironment
::get
();
3173 my $authuser = $rpcenv->get_user();
3175 my $node = extract_param
($param, 'node');
3177 my $vmid = extract_param
($param, 'vmid');
3179 my $snapname = extract_param
($param, 'snapname');
3182 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3183 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3186 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3189 __PACKAGE__-
>register_method({
3191 path
=> '{vmid}/template',
3195 description
=> "Create a Template.",
3197 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3198 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3201 additionalProperties
=> 0,
3203 node
=> get_standard_option
('pve-node'),
3204 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3208 description
=> "If you want to convert only 1 disk to base image.",
3209 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3214 returns
=> { type
=> 'null'},
3218 my $rpcenv = PVE
::RPCEnvironment
::get
();
3220 my $authuser = $rpcenv->get_user();
3222 my $node = extract_param
($param, 'node');
3224 my $vmid = extract_param
($param, 'vmid');
3226 my $disk = extract_param
($param, 'disk');
3228 my $updatefn = sub {
3230 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3232 PVE
::QemuConfig-
>check_lock($conf);
3234 die "unable to create template, because VM contains snapshots\n"
3235 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3237 die "you can't convert a template to a template\n"
3238 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3240 die "you can't convert a VM to template if VM is running\n"
3241 if PVE
::QemuServer
::check_running
($vmid);
3244 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3247 $conf->{template
} = 1;
3248 PVE
::QemuConfig-
>write_config($vmid, $conf);
3250 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3253 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);