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);
18 use PVE
::RPCEnvironment
;
19 use PVE
::AccessControl
;
23 use PVE
::API2
::Firewall
::VM
;
26 use Data
::Dumper
; # fixme: remove
28 use base
qw(PVE::RESTHandler);
30 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.";
32 my $resolve_cdrom_alias = sub {
35 if (my $value = $param->{cdrom
}) {
36 $value .= ",media=cdrom" if $value !~ m/media=/;
37 $param->{ide2
} = $value;
38 delete $param->{cdrom
};
42 my $check_storage_access = sub {
43 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
45 PVE
::QemuServer
::foreach_drive
($settings, sub {
46 my ($ds, $drive) = @_;
48 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
50 my $volid = $drive->{file
};
52 if (!$volid || $volid eq 'none') {
54 } elsif ($isCDROM && ($volid eq 'cdrom')) {
55 $rpcenv->check($authuser, "/", ['Sys.Console']);
56 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
57 my ($storeid, $size) = ($2 || $default_storage, $3);
58 die "no storage ID specified (and no default storage)\n" if !$storeid;
59 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
61 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
66 my $check_storage_access_clone = sub {
67 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
71 PVE
::QemuServer
::foreach_drive
($conf, sub {
72 my ($ds, $drive) = @_;
74 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
76 my $volid = $drive->{file
};
78 return if !$volid || $volid eq 'none';
81 if ($volid eq 'cdrom') {
82 $rpcenv->check($authuser, "/", ['Sys.Console']);
84 # we simply allow access
85 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
86 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
87 $sharedvm = 0 if !$scfg->{shared
};
91 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
92 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
93 $sharedvm = 0 if !$scfg->{shared
};
95 $sid = $storage if $storage;
96 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
103 # Note: $pool is only needed when creating a VM, because pool permissions
104 # are automatically inherited if VM already exists inside a pool.
105 my $create_disks = sub {
106 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
111 PVE
::QemuServer
::foreach_drive
($settings, sub {
112 my ($ds, $disk) = @_;
114 my $volid = $disk->{file
};
116 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
117 delete $disk->{size
};
118 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
119 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
120 my ($storeid, $size) = ($2 || $default_storage, $3);
121 die "no storage ID specified (and no default storage)\n" if !$storeid;
122 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
123 my $fmt = $disk->{format
} || $defformat;
124 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
125 $fmt, undef, $size*1024*1024);
126 $disk->{file
} = $volid;
127 $disk->{size
} = $size*1024*1024*1024;
128 push @$vollist, $volid;
129 delete $disk->{format
}; # no longer needed
130 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
135 my $volid_is_new = 1;
138 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
139 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
144 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
146 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
148 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
150 die "volume $volid does not exists\n" if !$size;
152 $disk->{size
} = $size;
155 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
159 # free allocated images on error
161 syslog
('err', "VM $vmid creating disks failed");
162 foreach my $volid (@$vollist) {
163 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
169 # modify vm config if everything went well
170 foreach my $ds (keys %$res) {
171 $conf->{$ds} = $res->{$ds};
177 my $check_vm_modify_config_perm = sub {
178 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
180 return 1 if $authuser eq 'root@pam';
182 foreach my $opt (@$key_list) {
183 # disk checks need to be done somewhere else
184 next if PVE
::QemuServer
::valid_drivename
($opt);
186 if ($opt eq 'sockets' || $opt eq 'cores' ||
187 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
188 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
190 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
192 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
193 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
194 } elsif ($opt eq 'args' || $opt eq 'lock') {
195 die "only root can set '$opt' config\n";
196 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
197 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
199 } elsif ($opt =~ m/^net\d+$/) {
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
202 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
209 __PACKAGE__-
>register_method({
213 description
=> "Virtual machine index (per node).",
215 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
219 protected
=> 1, # qemu pid files are only readable by root
221 additionalProperties
=> 0,
223 node
=> get_standard_option
('pve-node'),
232 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
237 my $rpcenv = PVE
::RPCEnvironment
::get
();
238 my $authuser = $rpcenv->get_user();
240 my $vmstatus = PVE
::QemuServer
::vmstatus
();
243 foreach my $vmid (keys %$vmstatus) {
244 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
246 my $data = $vmstatus->{$vmid};
247 $data->{vmid
} = int($vmid);
256 __PACKAGE__-
>register_method({
260 description
=> "Create or restore a virtual machine.",
262 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
263 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
264 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
265 user
=> 'all', # check inside
270 additionalProperties
=> 0,
271 properties
=> PVE
::QemuServer
::json_config_properties
(
273 node
=> get_standard_option
('pve-node'),
274 vmid
=> get_standard_option
('pve-vmid'),
276 description
=> "The backup file.",
281 storage
=> get_standard_option
('pve-storage-id', {
282 description
=> "Default storage.",
288 description
=> "Allow to overwrite existing VM.",
289 requires
=> 'archive',
294 description
=> "Assign a unique random ethernet address.",
295 requires
=> 'archive',
299 type
=> 'string', format
=> 'pve-poolid',
300 description
=> "Add the VM to the specified pool.",
310 my $rpcenv = PVE
::RPCEnvironment
::get
();
312 my $authuser = $rpcenv->get_user();
314 my $node = extract_param
($param, 'node');
316 my $vmid = extract_param
($param, 'vmid');
318 my $archive = extract_param
($param, 'archive');
320 my $storage = extract_param
($param, 'storage');
322 my $force = extract_param
($param, 'force');
324 my $unique = extract_param
($param, 'unique');
326 my $pool = extract_param
($param, 'pool');
328 my $filename = PVE
::QemuServer
::config_file
($vmid);
330 my $storecfg = PVE
::Storage
::config
();
332 PVE
::Cluster
::check_cfs_quorum
();
334 if (defined($pool)) {
335 $rpcenv->check_pool_exist($pool);
338 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
339 if defined($storage);
341 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
343 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
345 } elsif ($archive && $force && (-f
$filename) &&
346 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
347 # OK: user has VM.Backup permissions, and want to restore an existing VM
353 &$resolve_cdrom_alias($param);
355 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
357 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
359 foreach my $opt (keys %$param) {
360 if (PVE
::QemuServer
::valid_drivename
($opt)) {
361 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
362 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
364 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
365 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
369 PVE
::QemuServer
::add_random_macs
($param);
371 my $keystr = join(' ', keys %$param);
372 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
374 if ($archive eq '-') {
375 die "pipe requires cli environment\n"
376 if $rpcenv->{type
} ne 'cli';
378 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
379 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
383 my $restorefn = sub {
385 # fixme: this test does not work if VM exists on other node!
387 die "unable to restore vm $vmid: config file already exists\n"
390 die "unable to restore vm $vmid: vm is running\n"
391 if PVE
::QemuServer
::check_running
($vmid);
395 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
398 unique
=> $unique });
400 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
403 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
409 die "unable to create vm $vmid: config file already exists\n"
420 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
422 # try to be smart about bootdisk
423 my @disks = PVE
::QemuServer
::disknames
();
425 foreach my $ds (reverse @disks) {
426 next if !$conf->{$ds};
427 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
428 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
432 if (!$conf->{bootdisk
} && $firstdisk) {
433 $conf->{bootdisk
} = $firstdisk;
436 # auto generate uuid if user did not specify smbios1 option
437 if (!$conf->{smbios1
}) {
438 my ($uuid, $uuid_str);
439 UUID
::generate
($uuid);
440 UUID
::unparse
($uuid, $uuid_str);
441 $conf->{smbios1
} = "uuid=$uuid_str";
444 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
450 foreach my $volid (@$vollist) {
451 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
454 die "create failed - $err";
457 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
460 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
463 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
466 __PACKAGE__-
>register_method({
471 description
=> "Directory index",
476 additionalProperties
=> 0,
478 node
=> get_standard_option
('pve-node'),
479 vmid
=> get_standard_option
('pve-vmid'),
487 subdir
=> { type
=> 'string' },
490 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
496 { subdir
=> 'config' },
497 { subdir
=> 'pending' },
498 { subdir
=> 'status' },
499 { subdir
=> 'unlink' },
500 { subdir
=> 'vncproxy' },
501 { subdir
=> 'migrate' },
502 { subdir
=> 'resize' },
503 { subdir
=> 'move' },
505 { subdir
=> 'rrddata' },
506 { subdir
=> 'monitor' },
507 { subdir
=> 'snapshot' },
508 { subdir
=> 'spiceproxy' },
509 { subdir
=> 'sendkey' },
510 { subdir
=> 'firewall' },
516 __PACKAGE__-
>register_method ({
517 subclass
=> "PVE::API2::Firewall::VM",
518 path
=> '{vmid}/firewall',
521 __PACKAGE__-
>register_method({
523 path
=> '{vmid}/rrd',
525 protected
=> 1, # fixme: can we avoid that?
527 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
529 description
=> "Read VM RRD statistics (returns PNG)",
531 additionalProperties
=> 0,
533 node
=> get_standard_option
('pve-node'),
534 vmid
=> get_standard_option
('pve-vmid'),
536 description
=> "Specify the time frame you are interested in.",
538 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
541 description
=> "The list of datasources you want to display.",
542 type
=> 'string', format
=> 'pve-configid-list',
545 description
=> "The RRD consolidation function",
547 enum
=> [ 'AVERAGE', 'MAX' ],
555 filename
=> { type
=> 'string' },
561 return PVE
::Cluster
::create_rrd_graph
(
562 "pve2-vm/$param->{vmid}", $param->{timeframe
},
563 $param->{ds
}, $param->{cf
});
567 __PACKAGE__-
>register_method({
569 path
=> '{vmid}/rrddata',
571 protected
=> 1, # fixme: can we avoid that?
573 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
575 description
=> "Read VM RRD statistics",
577 additionalProperties
=> 0,
579 node
=> get_standard_option
('pve-node'),
580 vmid
=> get_standard_option
('pve-vmid'),
582 description
=> "Specify the time frame you are interested in.",
584 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
587 description
=> "The RRD consolidation function",
589 enum
=> [ 'AVERAGE', 'MAX' ],
604 return PVE
::Cluster
::create_rrd_data
(
605 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
609 __PACKAGE__-
>register_method({
611 path
=> '{vmid}/config',
614 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
619 additionalProperties
=> 0,
621 node
=> get_standard_option
('pve-node'),
622 vmid
=> get_standard_option
('pve-vmid'),
624 description
=> "Get current values (instead of pending values).",
636 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
643 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
645 delete $conf->{snapshots
};
647 if (!$param->{current
}) {
648 foreach my $opt (keys %{$conf->{pending
}}) {
649 next if $opt eq 'delete';
650 my $value = $conf->{pending
}->{$opt};
651 next if ref($value); # just to be sure
652 $conf->{$opt} = $value;
654 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
655 foreach my $opt (keys %$pending_delete_hash) {
656 delete $conf->{$opt} if $conf->{$opt};
660 delete $conf->{pending
};
665 __PACKAGE__-
>register_method({
666 name
=> 'vm_pending',
667 path
=> '{vmid}/pending',
670 description
=> "Get virtual machine configuration, including pending changes.",
672 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
675 additionalProperties
=> 0,
677 node
=> get_standard_option
('pve-node'),
678 vmid
=> get_standard_option
('pve-vmid'),
687 description
=> "Configuration option name.",
691 description
=> "Current value.",
696 description
=> "Pending value.",
701 description
=> "Indicates a pending delete request if present and not 0. " .
702 "The value 2 indicates a force-delete request.",
714 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
716 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
720 foreach my $opt (keys %$conf) {
721 next if ref($conf->{$opt});
722 my $item = { key
=> $opt };
723 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
724 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
725 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
729 foreach my $opt (keys %{$conf->{pending
}}) {
730 next if $opt eq 'delete';
731 next if ref($conf->{pending
}->{$opt}); # just to be sure
732 next if defined($conf->{$opt});
733 my $item = { key
=> $opt };
734 $item->{pending
} = $conf->{pending
}->{$opt};
738 while (my ($opt, $force) = each %$pending_delete_hash) {
739 next if $conf->{pending
}->{$opt}; # just to be sure
740 next if $conf->{$opt};
741 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
748 # POST/PUT {vmid}/config implementation
750 # The original API used PUT (idempotent) an we assumed that all operations
751 # are fast. But it turned out that almost any configuration change can
752 # involve hot-plug actions, or disk alloc/free. Such actions can take long
753 # time to complete and have side effects (not idempotent).
755 # The new implementation uses POST and forks a worker process. We added
756 # a new option 'background_delay'. If specified we wait up to
757 # 'background_delay' second for the worker task to complete. It returns null
758 # if the task is finished within that time, else we return the UPID.
760 my $update_vm_api = sub {
761 my ($param, $sync) = @_;
763 my $rpcenv = PVE
::RPCEnvironment
::get
();
765 my $authuser = $rpcenv->get_user();
767 my $node = extract_param
($param, 'node');
769 my $vmid = extract_param
($param, 'vmid');
771 my $digest = extract_param
($param, 'digest');
773 my $background_delay = extract_param
($param, 'background_delay');
775 my @paramarr = (); # used for log message
776 foreach my $key (keys %$param) {
777 push @paramarr, "-$key", $param->{$key};
780 my $skiplock = extract_param
($param, 'skiplock');
781 raise_param_exc
({ skiplock
=> "Only root may use this option." })
782 if $skiplock && $authuser ne 'root@pam';
784 my $delete_str = extract_param
($param, 'delete');
786 my $revert_str = extract_param
($param, 'revert');
788 my $force = extract_param
($param, 'force');
790 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
792 my $storecfg = PVE
::Storage
::config
();
794 my $defaults = PVE
::QemuServer
::load_defaults
();
796 &$resolve_cdrom_alias($param);
798 # now try to verify all parameters
801 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
802 if (!PVE
::QemuServer
::option_exists
($opt)) {
803 raise_param_exc
({ revert
=> "unknown option '$opt'" });
806 raise_param_exc
({ delete => "you can't use '-$opt' and " .
807 "-revert $opt' at the same time" })
808 if defined($param->{$opt});
814 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
815 $opt = 'ide2' if $opt eq 'cdrom';
817 raise_param_exc
({ delete => "you can't use '-$opt' and " .
818 "-delete $opt' at the same time" })
819 if defined($param->{$opt});
821 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
822 "-revert $opt' at the same time" })
825 if (!PVE
::QemuServer
::option_exists
($opt)) {
826 raise_param_exc
({ delete => "unknown option '$opt'" });
832 foreach my $opt (keys %$param) {
833 if (PVE
::QemuServer
::valid_drivename
($opt)) {
835 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
836 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
837 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
838 } elsif ($opt =~ m/^net(\d+)$/) {
840 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
841 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
845 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
847 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
849 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
853 my $conf = PVE
::QemuServer
::load_config
($vmid);
855 die "checksum missmatch (file change by other user?)\n"
856 if $digest && $digest ne $conf->{digest
};
858 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
860 foreach my $opt (keys %$revert) {
861 if (defined($conf->{$opt})) {
862 $param->{$opt} = $conf->{$opt};
863 } elsif (defined($conf->{pending
}->{$opt})) {
868 if ($param->{memory
} || defined($param->{balloon
})) {
869 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
870 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
872 die "balloon value too large (must be smaller than assigned memory)\n"
873 if $balloon && $balloon > $maxmem;
876 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
880 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
882 # write updates to pending section
884 my $modified = {}; # record what $option we modify
886 foreach my $opt (@delete) {
887 $modified->{$opt} = 1;
888 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
889 if ($opt =~ m/^unused/) {
890 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
891 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
892 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
893 delete $conf->{$opt};
894 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
896 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
897 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
898 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
899 if defined($conf->{pending
}->{$opt});
900 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
901 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
903 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
904 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
908 foreach my $opt (keys %$param) { # add/change
909 $modified->{$opt} = 1;
910 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
911 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
913 if (PVE
::QemuServer
::valid_drivename
($opt)) {
914 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
915 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
916 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
918 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
920 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
921 if defined($conf->{pending
}->{$opt});
923 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
925 $conf->{pending
}->{$opt} = $param->{$opt};
927 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
928 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
931 # remove pending changes when nothing changed
932 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
933 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
934 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
936 return if !scalar(keys %{$conf->{pending
}});
938 my $running = PVE
::QemuServer
::check_running
($vmid);
940 # apply pending changes
942 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
946 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
947 raise_param_exc
($errors) if scalar(keys %$errors);
949 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
959 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
961 if ($background_delay) {
963 # Note: It would be better to do that in the Event based HTTPServer
964 # to avoid blocking call to sleep.
966 my $end_time = time() + $background_delay;
968 my $task = PVE
::Tools
::upid_decode
($upid);
971 while (time() < $end_time) {
972 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
974 sleep(1); # this gets interrupted when child process ends
978 my $status = PVE
::Tools
::upid_read_status
($upid);
979 return undef if $status eq 'OK';
988 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
991 my $vm_config_perm_list = [
1001 __PACKAGE__-
>register_method({
1002 name
=> 'update_vm_async',
1003 path
=> '{vmid}/config',
1007 description
=> "Set virtual machine options (asynchrounous API).",
1009 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1012 additionalProperties
=> 0,
1013 properties
=> PVE
::QemuServer
::json_config_properties
(
1015 node
=> get_standard_option
('pve-node'),
1016 vmid
=> get_standard_option
('pve-vmid'),
1017 skiplock
=> get_standard_option
('skiplock'),
1019 type
=> 'string', format
=> 'pve-configid-list',
1020 description
=> "A list of settings you want to delete.",
1024 type
=> 'string', format
=> 'pve-configid-list',
1025 description
=> "Revert a pending change.",
1030 description
=> $opt_force_description,
1032 requires
=> 'delete',
1036 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1040 background_delay
=> {
1042 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1053 code
=> $update_vm_api,
1056 __PACKAGE__-
>register_method({
1057 name
=> 'update_vm',
1058 path
=> '{vmid}/config',
1062 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1064 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1067 additionalProperties
=> 0,
1068 properties
=> PVE
::QemuServer
::json_config_properties
(
1070 node
=> get_standard_option
('pve-node'),
1071 vmid
=> get_standard_option
('pve-vmid'),
1072 skiplock
=> get_standard_option
('skiplock'),
1074 type
=> 'string', format
=> 'pve-configid-list',
1075 description
=> "A list of settings you want to delete.",
1079 type
=> 'string', format
=> 'pve-configid-list',
1080 description
=> "Revert a pending change.",
1085 description
=> $opt_force_description,
1087 requires
=> 'delete',
1091 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1097 returns
=> { type
=> 'null' },
1100 &$update_vm_api($param, 1);
1106 __PACKAGE__-
>register_method({
1107 name
=> 'destroy_vm',
1112 description
=> "Destroy the vm (also delete all used/owned volumes).",
1114 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1117 additionalProperties
=> 0,
1119 node
=> get_standard_option
('pve-node'),
1120 vmid
=> get_standard_option
('pve-vmid'),
1121 skiplock
=> get_standard_option
('skiplock'),
1130 my $rpcenv = PVE
::RPCEnvironment
::get
();
1132 my $authuser = $rpcenv->get_user();
1134 my $vmid = $param->{vmid
};
1136 my $skiplock = $param->{skiplock
};
1137 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1138 if $skiplock && $authuser ne 'root@pam';
1141 my $conf = PVE
::QemuServer
::load_config
($vmid);
1143 my $storecfg = PVE
::Storage
::config
();
1148 syslog
('info', "destroy VM $vmid: $upid\n");
1150 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1152 PVE
::AccessControl
::remove_vm_access
($vmid);
1154 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1157 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1160 __PACKAGE__-
>register_method({
1162 path
=> '{vmid}/unlink',
1166 description
=> "Unlink/delete disk images.",
1168 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1171 additionalProperties
=> 0,
1173 node
=> get_standard_option
('pve-node'),
1174 vmid
=> get_standard_option
('pve-vmid'),
1176 type
=> 'string', format
=> 'pve-configid-list',
1177 description
=> "A list of disk IDs you want to delete.",
1181 description
=> $opt_force_description,
1186 returns
=> { type
=> 'null'},
1190 $param->{delete} = extract_param
($param, 'idlist');
1192 __PACKAGE__-
>update_vm($param);
1199 __PACKAGE__-
>register_method({
1201 path
=> '{vmid}/vncproxy',
1205 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1207 description
=> "Creates a TCP VNC proxy connections.",
1209 additionalProperties
=> 0,
1211 node
=> get_standard_option
('pve-node'),
1212 vmid
=> get_standard_option
('pve-vmid'),
1216 description
=> "starts websockify instead of vncproxy",
1221 additionalProperties
=> 0,
1223 user
=> { type
=> 'string' },
1224 ticket
=> { type
=> 'string' },
1225 cert
=> { type
=> 'string' },
1226 port
=> { type
=> 'integer' },
1227 upid
=> { type
=> 'string' },
1233 my $rpcenv = PVE
::RPCEnvironment
::get
();
1235 my $authuser = $rpcenv->get_user();
1237 my $vmid = $param->{vmid
};
1238 my $node = $param->{node
};
1239 my $websocket = $param->{websocket
};
1241 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1243 my $authpath = "/vms/$vmid";
1245 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1247 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1250 my ($remip, $family);
1253 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1254 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1255 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1256 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1258 $family = PVE
::Tools
::get_host_address_family
($node);
1261 my $port = PVE
::Tools
::next_vnc_port
($family);
1268 syslog
('info', "starting vnc proxy $upid\n");
1272 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1274 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1276 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1277 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1278 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1279 '-timeout', $timeout, '-authpath', $authpath,
1280 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1283 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1285 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1287 my $qmstr = join(' ', @$qmcmd);
1289 # also redirect stderr (else we get RFB protocol errors)
1290 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1293 PVE
::Tools
::run_command
($cmd);
1298 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1300 PVE
::Tools
::wait_for_vnc_port
($port);
1311 __PACKAGE__-
>register_method({
1312 name
=> 'vncwebsocket',
1313 path
=> '{vmid}/vncwebsocket',
1316 description
=> "You also need to pass a valid ticket (vncticket).",
1317 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1319 description
=> "Opens a weksocket for VNC traffic.",
1321 additionalProperties
=> 0,
1323 node
=> get_standard_option
('pve-node'),
1324 vmid
=> get_standard_option
('pve-vmid'),
1326 description
=> "Ticket from previous call to vncproxy.",
1331 description
=> "Port number returned by previous vncproxy call.",
1341 port
=> { type
=> 'string' },
1347 my $rpcenv = PVE
::RPCEnvironment
::get
();
1349 my $authuser = $rpcenv->get_user();
1351 my $vmid = $param->{vmid
};
1352 my $node = $param->{node
};
1354 my $authpath = "/vms/$vmid";
1356 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1358 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1360 # Note: VNC ports are acessible from outside, so we do not gain any
1361 # security if we verify that $param->{port} belongs to VM $vmid. This
1362 # check is done by verifying the VNC ticket (inside VNC protocol).
1364 my $port = $param->{port
};
1366 return { port
=> $port };
1369 __PACKAGE__-
>register_method({
1370 name
=> 'spiceproxy',
1371 path
=> '{vmid}/spiceproxy',
1376 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1378 description
=> "Returns a SPICE configuration to connect to the VM.",
1380 additionalProperties
=> 0,
1382 node
=> get_standard_option
('pve-node'),
1383 vmid
=> get_standard_option
('pve-vmid'),
1384 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1387 returns
=> get_standard_option
('remote-viewer-config'),
1391 my $rpcenv = PVE
::RPCEnvironment
::get
();
1393 my $authuser = $rpcenv->get_user();
1395 my $vmid = $param->{vmid
};
1396 my $node = $param->{node
};
1397 my $proxy = $param->{proxy
};
1399 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1400 my $title = "VM $vmid";
1401 $title .= " - ". $conf->{name
} if $conf->{name
};
1403 my $port = PVE
::QemuServer
::spice_port
($vmid);
1405 my ($ticket, undef, $remote_viewer_config) =
1406 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1408 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1409 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1411 return $remote_viewer_config;
1414 __PACKAGE__-
>register_method({
1416 path
=> '{vmid}/status',
1419 description
=> "Directory index",
1424 additionalProperties
=> 0,
1426 node
=> get_standard_option
('pve-node'),
1427 vmid
=> get_standard_option
('pve-vmid'),
1435 subdir
=> { type
=> 'string' },
1438 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1444 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1447 { subdir
=> 'current' },
1448 { subdir
=> 'start' },
1449 { subdir
=> 'stop' },
1455 __PACKAGE__-
>register_method({
1456 name
=> 'vm_status',
1457 path
=> '{vmid}/status/current',
1460 protected
=> 1, # qemu pid files are only readable by root
1461 description
=> "Get virtual machine status.",
1463 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1466 additionalProperties
=> 0,
1468 node
=> get_standard_option
('pve-node'),
1469 vmid
=> get_standard_option
('pve-vmid'),
1472 returns
=> { type
=> 'object' },
1477 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1479 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1480 my $status = $vmstatus->{$param->{vmid
}};
1482 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1484 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1489 __PACKAGE__-
>register_method({
1491 path
=> '{vmid}/status/start',
1495 description
=> "Start virtual machine.",
1497 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1500 additionalProperties
=> 0,
1502 node
=> get_standard_option
('pve-node'),
1503 vmid
=> get_standard_option
('pve-vmid'),
1504 skiplock
=> get_standard_option
('skiplock'),
1505 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1506 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1507 machine
=> get_standard_option
('pve-qm-machine'),
1516 my $rpcenv = PVE
::RPCEnvironment
::get
();
1518 my $authuser = $rpcenv->get_user();
1520 my $node = extract_param
($param, 'node');
1522 my $vmid = extract_param
($param, 'vmid');
1524 my $machine = extract_param
($param, 'machine');
1526 my $stateuri = extract_param
($param, 'stateuri');
1527 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1528 if $stateuri && $authuser ne 'root@pam';
1530 my $skiplock = extract_param
($param, 'skiplock');
1531 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1532 if $skiplock && $authuser ne 'root@pam';
1534 my $migratedfrom = extract_param
($param, 'migratedfrom');
1535 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1536 if $migratedfrom && $authuser ne 'root@pam';
1538 # read spice ticket from STDIN
1540 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1541 if (defined(my $line = <>)) {
1543 $spice_ticket = $line;
1547 my $storecfg = PVE
::Storage
::config
();
1549 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1550 $rpcenv->{type
} ne 'ha') {
1555 my $service = "vm:$vmid";
1557 my $cmd = ['ha-manager', 'enable', $service];
1559 print "Executing HA start for VM $vmid\n";
1561 PVE
::Tools
::run_command
($cmd);
1566 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1573 syslog
('info', "start VM $vmid: $upid\n");
1575 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1576 $machine, $spice_ticket);
1581 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1585 __PACKAGE__-
>register_method({
1587 path
=> '{vmid}/status/stop',
1591 description
=> "Stop virtual machine.",
1593 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1596 additionalProperties
=> 0,
1598 node
=> get_standard_option
('pve-node'),
1599 vmid
=> get_standard_option
('pve-vmid'),
1600 skiplock
=> get_standard_option
('skiplock'),
1601 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1603 description
=> "Wait maximal timeout seconds.",
1609 description
=> "Do not decativate storage volumes.",
1622 my $rpcenv = PVE
::RPCEnvironment
::get
();
1624 my $authuser = $rpcenv->get_user();
1626 my $node = extract_param
($param, 'node');
1628 my $vmid = extract_param
($param, 'vmid');
1630 my $skiplock = extract_param
($param, 'skiplock');
1631 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1632 if $skiplock && $authuser ne 'root@pam';
1634 my $keepActive = extract_param
($param, 'keepActive');
1635 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1636 if $keepActive && $authuser ne 'root@pam';
1638 my $migratedfrom = extract_param
($param, 'migratedfrom');
1639 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1640 if $migratedfrom && $authuser ne 'root@pam';
1643 my $storecfg = PVE
::Storage
::config
();
1645 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1650 my $service = "vm:$vmid";
1652 my $cmd = ['ha-manager', 'disable', $service];
1654 print "Executing HA stop for VM $vmid\n";
1656 PVE
::Tools
::run_command
($cmd);
1661 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1667 syslog
('info', "stop VM $vmid: $upid\n");
1669 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1670 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1675 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1679 __PACKAGE__-
>register_method({
1681 path
=> '{vmid}/status/reset',
1685 description
=> "Reset virtual machine.",
1687 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1690 additionalProperties
=> 0,
1692 node
=> get_standard_option
('pve-node'),
1693 vmid
=> get_standard_option
('pve-vmid'),
1694 skiplock
=> get_standard_option
('skiplock'),
1703 my $rpcenv = PVE
::RPCEnvironment
::get
();
1705 my $authuser = $rpcenv->get_user();
1707 my $node = extract_param
($param, 'node');
1709 my $vmid = extract_param
($param, 'vmid');
1711 my $skiplock = extract_param
($param, 'skiplock');
1712 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1713 if $skiplock && $authuser ne 'root@pam';
1715 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1720 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1725 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1728 __PACKAGE__-
>register_method({
1729 name
=> 'vm_shutdown',
1730 path
=> '{vmid}/status/shutdown',
1734 description
=> "Shutdown virtual machine.",
1736 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1739 additionalProperties
=> 0,
1741 node
=> get_standard_option
('pve-node'),
1742 vmid
=> get_standard_option
('pve-vmid'),
1743 skiplock
=> get_standard_option
('skiplock'),
1745 description
=> "Wait maximal timeout seconds.",
1751 description
=> "Make sure the VM stops.",
1757 description
=> "Do not decativate storage volumes.",
1770 my $rpcenv = PVE
::RPCEnvironment
::get
();
1772 my $authuser = $rpcenv->get_user();
1774 my $node = extract_param
($param, 'node');
1776 my $vmid = extract_param
($param, 'vmid');
1778 my $skiplock = extract_param
($param, 'skiplock');
1779 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1780 if $skiplock && $authuser ne 'root@pam';
1782 my $keepActive = extract_param
($param, 'keepActive');
1783 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1784 if $keepActive && $authuser ne 'root@pam';
1786 my $storecfg = PVE
::Storage
::config
();
1791 syslog
('info', "shutdown VM $vmid: $upid\n");
1793 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1794 1, $param->{forceStop
}, $keepActive);
1799 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1802 __PACKAGE__-
>register_method({
1803 name
=> 'vm_suspend',
1804 path
=> '{vmid}/status/suspend',
1808 description
=> "Suspend virtual machine.",
1810 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1813 additionalProperties
=> 0,
1815 node
=> get_standard_option
('pve-node'),
1816 vmid
=> get_standard_option
('pve-vmid'),
1817 skiplock
=> get_standard_option
('skiplock'),
1826 my $rpcenv = PVE
::RPCEnvironment
::get
();
1828 my $authuser = $rpcenv->get_user();
1830 my $node = extract_param
($param, 'node');
1832 my $vmid = extract_param
($param, 'vmid');
1834 my $skiplock = extract_param
($param, 'skiplock');
1835 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1836 if $skiplock && $authuser ne 'root@pam';
1838 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1843 syslog
('info', "suspend VM $vmid: $upid\n");
1845 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1850 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1853 __PACKAGE__-
>register_method({
1854 name
=> 'vm_resume',
1855 path
=> '{vmid}/status/resume',
1859 description
=> "Resume virtual machine.",
1861 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1864 additionalProperties
=> 0,
1866 node
=> get_standard_option
('pve-node'),
1867 vmid
=> get_standard_option
('pve-vmid'),
1868 skiplock
=> get_standard_option
('skiplock'),
1877 my $rpcenv = PVE
::RPCEnvironment
::get
();
1879 my $authuser = $rpcenv->get_user();
1881 my $node = extract_param
($param, 'node');
1883 my $vmid = extract_param
($param, 'vmid');
1885 my $skiplock = extract_param
($param, 'skiplock');
1886 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1887 if $skiplock && $authuser ne 'root@pam';
1889 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1894 syslog
('info', "resume VM $vmid: $upid\n");
1896 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1901 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1904 __PACKAGE__-
>register_method({
1905 name
=> 'vm_sendkey',
1906 path
=> '{vmid}/sendkey',
1910 description
=> "Send key event to virtual machine.",
1912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1915 additionalProperties
=> 0,
1917 node
=> get_standard_option
('pve-node'),
1918 vmid
=> get_standard_option
('pve-vmid'),
1919 skiplock
=> get_standard_option
('skiplock'),
1921 description
=> "The key (qemu monitor encoding).",
1926 returns
=> { type
=> 'null'},
1930 my $rpcenv = PVE
::RPCEnvironment
::get
();
1932 my $authuser = $rpcenv->get_user();
1934 my $node = extract_param
($param, 'node');
1936 my $vmid = extract_param
($param, 'vmid');
1938 my $skiplock = extract_param
($param, 'skiplock');
1939 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1940 if $skiplock && $authuser ne 'root@pam';
1942 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1947 __PACKAGE__-
>register_method({
1948 name
=> 'vm_feature',
1949 path
=> '{vmid}/feature',
1953 description
=> "Check if feature for virtual machine is available.",
1955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1958 additionalProperties
=> 0,
1960 node
=> get_standard_option
('pve-node'),
1961 vmid
=> get_standard_option
('pve-vmid'),
1963 description
=> "Feature to check.",
1965 enum
=> [ 'snapshot', 'clone', 'copy' ],
1967 snapname
=> get_standard_option
('pve-snapshot-name', {
1975 hasFeature
=> { type
=> 'boolean' },
1978 items
=> { type
=> 'string' },
1985 my $node = extract_param
($param, 'node');
1987 my $vmid = extract_param
($param, 'vmid');
1989 my $snapname = extract_param
($param, 'snapname');
1991 my $feature = extract_param
($param, 'feature');
1993 my $running = PVE
::QemuServer
::check_running
($vmid);
1995 my $conf = PVE
::QemuServer
::load_config
($vmid);
1998 my $snap = $conf->{snapshots
}->{$snapname};
1999 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2002 my $storecfg = PVE
::Storage
::config
();
2004 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2005 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2008 hasFeature
=> $hasFeature,
2009 nodes
=> [ keys %$nodelist ],
2013 __PACKAGE__-
>register_method({
2015 path
=> '{vmid}/clone',
2019 description
=> "Create a copy of virtual machine/template.",
2021 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2022 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2023 "'Datastore.AllocateSpace' on any used storage.",
2026 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2028 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2029 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2034 additionalProperties
=> 0,
2036 node
=> get_standard_option
('pve-node'),
2037 vmid
=> get_standard_option
('pve-vmid'),
2038 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2041 type
=> 'string', format
=> 'dns-name',
2042 description
=> "Set a name for the new VM.",
2047 description
=> "Description for the new VM.",
2051 type
=> 'string', format
=> 'pve-poolid',
2052 description
=> "Add the new VM to the specified pool.",
2054 snapname
=> get_standard_option
('pve-snapshot-name', {
2057 storage
=> get_standard_option
('pve-storage-id', {
2058 description
=> "Target storage for full clone.",
2063 description
=> "Target format for file storage.",
2067 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2072 description
=> "Create a full copy of all disk. This is always done when " .
2073 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2076 target
=> get_standard_option
('pve-node', {
2077 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2088 my $rpcenv = PVE
::RPCEnvironment
::get
();
2090 my $authuser = $rpcenv->get_user();
2092 my $node = extract_param
($param, 'node');
2094 my $vmid = extract_param
($param, 'vmid');
2096 my $newid = extract_param
($param, 'newid');
2098 my $pool = extract_param
($param, 'pool');
2100 if (defined($pool)) {
2101 $rpcenv->check_pool_exist($pool);
2104 my $snapname = extract_param
($param, 'snapname');
2106 my $storage = extract_param
($param, 'storage');
2108 my $format = extract_param
($param, 'format');
2110 my $target = extract_param
($param, 'target');
2112 my $localnode = PVE
::INotify
::nodename
();
2114 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2116 PVE
::Cluster
::check_node_exists
($target) if $target;
2118 my $storecfg = PVE
::Storage
::config
();
2121 # check if storage is enabled on local node
2122 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2124 # check if storage is available on target node
2125 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2126 # clone only works if target storage is shared
2127 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2128 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2132 PVE
::Cluster
::check_cfs_quorum
();
2134 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2136 # exclusive lock if VM is running - else shared lock is enough;
2137 my $shared_lock = $running ?
0 : 1;
2141 # do all tests after lock
2142 # we also try to do all tests before we fork the worker
2144 my $conf = PVE
::QemuServer
::load_config
($vmid);
2146 PVE
::QemuServer
::check_lock
($conf);
2148 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2150 die "unexpected state change\n" if $verify_running != $running;
2152 die "snapshot '$snapname' does not exist\n"
2153 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2155 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2157 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2159 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2161 my $conffile = PVE
::QemuServer
::config_file
($newid);
2163 die "unable to create VM $newid: config file already exists\n"
2166 my $newconf = { lock => 'clone' };
2170 foreach my $opt (keys %$oldconf) {
2171 my $value = $oldconf->{$opt};
2173 # do not copy snapshot related info
2174 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2175 $opt eq 'vmstate' || $opt eq 'snapstate';
2177 # no need to copy unused images, because VMID(owner) changes anyways
2178 next if $opt =~ m/^unused\d+$/;
2180 # always change MAC! address
2181 if ($opt =~ m/^net(\d+)$/) {
2182 my $net = PVE
::QemuServer
::parse_net
($value);
2183 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2184 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2185 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2186 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2187 die "unable to parse drive options for '$opt'\n" if !$drive;
2188 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2189 $newconf->{$opt} = $value; # simply copy configuration
2191 if ($param->{full
}) {
2192 die "Full clone feature is not available"
2193 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2196 # not full means clone instead of copy
2197 die "Linked clone feature is not available"
2198 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2200 $drives->{$opt} = $drive;
2201 push @$vollist, $drive->{file
};
2204 # copy everything else
2205 $newconf->{$opt} = $value;
2209 # auto generate a new uuid
2210 my ($uuid, $uuid_str);
2211 UUID
::generate
($uuid);
2212 UUID
::unparse
($uuid, $uuid_str);
2213 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2214 $smbios1->{uuid
} = $uuid_str;
2215 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2217 delete $newconf->{template
};
2219 if ($param->{name
}) {
2220 $newconf->{name
} = $param->{name
};
2222 if ($oldconf->{name
}) {
2223 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2225 $newconf->{name
} = "Copy-of-VM-$vmid";
2229 if ($param->{description
}) {
2230 $newconf->{description
} = $param->{description
};
2233 # create empty/temp config - this fails if VM already exists on other node
2234 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2239 my $newvollist = [];
2242 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2244 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2246 foreach my $opt (keys %$drives) {
2247 my $drive = $drives->{$opt};
2249 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2250 $newid, $storage, $format, $drive->{full
}, $newvollist);
2252 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2254 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2257 delete $newconf->{lock};
2258 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2261 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2262 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2264 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2265 die "Failed to move config to node '$target' - rename failed: $!\n"
2266 if !rename($conffile, $newconffile);
2269 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2274 sleep 1; # some storage like rbd need to wait before release volume - really?
2276 foreach my $volid (@$newvollist) {
2277 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2280 die "clone failed: $err";
2286 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2288 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2291 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2292 # Aquire exclusive lock lock for $newid
2293 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2298 __PACKAGE__-
>register_method({
2299 name
=> 'move_vm_disk',
2300 path
=> '{vmid}/move_disk',
2304 description
=> "Move volume to different storage.",
2306 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2307 "and 'Datastore.AllocateSpace' permissions on the storage.",
2310 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2311 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2315 additionalProperties
=> 0,
2317 node
=> get_standard_option
('pve-node'),
2318 vmid
=> get_standard_option
('pve-vmid'),
2321 description
=> "The disk you want to move.",
2322 enum
=> [ PVE
::QemuServer
::disknames
() ],
2324 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2327 description
=> "Target Format.",
2328 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2333 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2339 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2347 description
=> "the task ID.",
2352 my $rpcenv = PVE
::RPCEnvironment
::get
();
2354 my $authuser = $rpcenv->get_user();
2356 my $node = extract_param
($param, 'node');
2358 my $vmid = extract_param
($param, 'vmid');
2360 my $digest = extract_param
($param, 'digest');
2362 my $disk = extract_param
($param, 'disk');
2364 my $storeid = extract_param
($param, 'storage');
2366 my $format = extract_param
($param, 'format');
2368 my $storecfg = PVE
::Storage
::config
();
2370 my $updatefn = sub {
2372 my $conf = PVE
::QemuServer
::load_config
($vmid);
2374 die "checksum missmatch (file change by other user?)\n"
2375 if $digest && $digest ne $conf->{digest
};
2377 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2379 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2381 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2383 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2386 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2387 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2391 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2392 (!$format || !$oldfmt || $oldfmt eq $format);
2394 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2396 my $running = PVE
::QemuServer
::check_running
($vmid);
2398 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2402 my $newvollist = [];
2405 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2407 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2408 $vmid, $storeid, $format, 1, $newvollist);
2410 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2412 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2414 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2417 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2418 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2425 foreach my $volid (@$newvollist) {
2426 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2429 die "storage migration failed: $err";
2432 if ($param->{delete}) {
2433 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2434 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2435 if ($used_paths->{$path}){
2436 warn "volume $old_volid have snapshots. Can't delete it\n";
2437 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2438 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2440 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2446 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2449 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2452 __PACKAGE__-
>register_method({
2453 name
=> 'migrate_vm',
2454 path
=> '{vmid}/migrate',
2458 description
=> "Migrate virtual machine. Creates a new migration task.",
2460 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2463 additionalProperties
=> 0,
2465 node
=> get_standard_option
('pve-node'),
2466 vmid
=> get_standard_option
('pve-vmid'),
2467 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2470 description
=> "Use online/live migration.",
2475 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2482 description
=> "the task ID.",
2487 my $rpcenv = PVE
::RPCEnvironment
::get
();
2489 my $authuser = $rpcenv->get_user();
2491 my $target = extract_param
($param, 'target');
2493 my $localnode = PVE
::INotify
::nodename
();
2494 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2496 PVE
::Cluster
::check_cfs_quorum
();
2498 PVE
::Cluster
::check_node_exists
($target);
2500 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2502 my $vmid = extract_param
($param, 'vmid');
2504 raise_param_exc
({ force
=> "Only root may use this option." })
2505 if $param->{force
} && $authuser ne 'root@pam';
2508 my $conf = PVE
::QemuServer
::load_config
($vmid);
2510 # try to detect errors early
2512 PVE
::QemuServer
::check_lock
($conf);
2514 if (PVE
::QemuServer
::check_running
($vmid)) {
2515 die "cant migrate running VM without --online\n"
2516 if !$param->{online
};
2519 my $storecfg = PVE
::Storage
::config
();
2520 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2522 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2527 my $service = "vm:$vmid";
2529 my $cmd = ['ha-manager', 'migrate', $service, $target];
2531 print "Executing HA migrate for VM $vmid to node $target\n";
2533 PVE
::Tools
::run_command
($cmd);
2538 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2545 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2548 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2553 __PACKAGE__-
>register_method({
2555 path
=> '{vmid}/monitor',
2559 description
=> "Execute Qemu monitor commands.",
2561 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2564 additionalProperties
=> 0,
2566 node
=> get_standard_option
('pve-node'),
2567 vmid
=> get_standard_option
('pve-vmid'),
2570 description
=> "The monitor command.",
2574 returns
=> { type
=> 'string'},
2578 my $vmid = $param->{vmid
};
2580 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2584 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2586 $res = "ERROR: $@" if $@;
2591 __PACKAGE__-
>register_method({
2592 name
=> 'resize_vm',
2593 path
=> '{vmid}/resize',
2597 description
=> "Extend volume size.",
2599 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2602 additionalProperties
=> 0,
2604 node
=> get_standard_option
('pve-node'),
2605 vmid
=> get_standard_option
('pve-vmid'),
2606 skiplock
=> get_standard_option
('skiplock'),
2609 description
=> "The disk you want to resize.",
2610 enum
=> [PVE
::QemuServer
::disknames
()],
2614 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2615 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.",
2619 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2625 returns
=> { type
=> 'null'},
2629 my $rpcenv = PVE
::RPCEnvironment
::get
();
2631 my $authuser = $rpcenv->get_user();
2633 my $node = extract_param
($param, 'node');
2635 my $vmid = extract_param
($param, 'vmid');
2637 my $digest = extract_param
($param, 'digest');
2639 my $disk = extract_param
($param, 'disk');
2641 my $sizestr = extract_param
($param, 'size');
2643 my $skiplock = extract_param
($param, 'skiplock');
2644 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2645 if $skiplock && $authuser ne 'root@pam';
2647 my $storecfg = PVE
::Storage
::config
();
2649 my $updatefn = sub {
2651 my $conf = PVE
::QemuServer
::load_config
($vmid);
2653 die "checksum missmatch (file change by other user?)\n"
2654 if $digest && $digest ne $conf->{digest
};
2655 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2657 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2659 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2661 my (undef, undef, undef, undef, undef, undef, $format) =
2662 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2664 die "can't resize volume: $disk if snapshot exists\n"
2665 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2667 my $volid = $drive->{file
};
2669 die "disk '$disk' has no associated volume\n" if !$volid;
2671 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2673 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2675 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2677 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2679 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2680 my ($ext, $newsize, $unit) = ($1, $2, $4);
2683 $newsize = $newsize * 1024;
2684 } elsif ($unit eq 'M') {
2685 $newsize = $newsize * 1024 * 1024;
2686 } elsif ($unit eq 'G') {
2687 $newsize = $newsize * 1024 * 1024 * 1024;
2688 } elsif ($unit eq 'T') {
2689 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2692 $newsize += $size if $ext;
2693 $newsize = int($newsize);
2695 die "unable to skrink disk size\n" if $newsize < $size;
2697 return if $size == $newsize;
2699 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2701 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2703 $drive->{size
} = $newsize;
2704 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2706 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2709 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2713 __PACKAGE__-
>register_method({
2714 name
=> 'snapshot_list',
2715 path
=> '{vmid}/snapshot',
2717 description
=> "List all snapshots.",
2719 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2722 protected
=> 1, # qemu pid files are only readable by root
2724 additionalProperties
=> 0,
2726 vmid
=> get_standard_option
('pve-vmid'),
2727 node
=> get_standard_option
('pve-node'),
2736 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2741 my $vmid = $param->{vmid
};
2743 my $conf = PVE
::QemuServer
::load_config
($vmid);
2744 my $snaphash = $conf->{snapshots
} || {};
2748 foreach my $name (keys %$snaphash) {
2749 my $d = $snaphash->{$name};
2752 snaptime
=> $d->{snaptime
} || 0,
2753 vmstate
=> $d->{vmstate
} ?
1 : 0,
2754 description
=> $d->{description
} || '',
2756 $item->{parent
} = $d->{parent
} if $d->{parent
};
2757 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2761 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2762 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2763 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2765 push @$res, $current;
2770 __PACKAGE__-
>register_method({
2772 path
=> '{vmid}/snapshot',
2776 description
=> "Snapshot a VM.",
2778 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2781 additionalProperties
=> 0,
2783 node
=> get_standard_option
('pve-node'),
2784 vmid
=> get_standard_option
('pve-vmid'),
2785 snapname
=> get_standard_option
('pve-snapshot-name'),
2789 description
=> "Save the vmstate",
2794 description
=> "A textual description or comment.",
2800 description
=> "the task ID.",
2805 my $rpcenv = PVE
::RPCEnvironment
::get
();
2807 my $authuser = $rpcenv->get_user();
2809 my $node = extract_param
($param, 'node');
2811 my $vmid = extract_param
($param, 'vmid');
2813 my $snapname = extract_param
($param, 'snapname');
2815 die "unable to use snapshot name 'current' (reserved name)\n"
2816 if $snapname eq 'current';
2819 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2820 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2821 $param->{description
});
2824 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2827 __PACKAGE__-
>register_method({
2828 name
=> 'snapshot_cmd_idx',
2829 path
=> '{vmid}/snapshot/{snapname}',
2836 additionalProperties
=> 0,
2838 vmid
=> get_standard_option
('pve-vmid'),
2839 node
=> get_standard_option
('pve-node'),
2840 snapname
=> get_standard_option
('pve-snapshot-name'),
2849 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2856 push @$res, { cmd
=> 'rollback' };
2857 push @$res, { cmd
=> 'config' };
2862 __PACKAGE__-
>register_method({
2863 name
=> 'update_snapshot_config',
2864 path
=> '{vmid}/snapshot/{snapname}/config',
2868 description
=> "Update snapshot metadata.",
2870 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2873 additionalProperties
=> 0,
2875 node
=> get_standard_option
('pve-node'),
2876 vmid
=> get_standard_option
('pve-vmid'),
2877 snapname
=> get_standard_option
('pve-snapshot-name'),
2881 description
=> "A textual description or comment.",
2885 returns
=> { type
=> 'null' },
2889 my $rpcenv = PVE
::RPCEnvironment
::get
();
2891 my $authuser = $rpcenv->get_user();
2893 my $vmid = extract_param
($param, 'vmid');
2895 my $snapname = extract_param
($param, 'snapname');
2897 return undef if !defined($param->{description
});
2899 my $updatefn = sub {
2901 my $conf = PVE
::QemuServer
::load_config
($vmid);
2903 PVE
::QemuServer
::check_lock
($conf);
2905 my $snap = $conf->{snapshots
}->{$snapname};
2907 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2909 $snap->{description
} = $param->{description
} if defined($param->{description
});
2911 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2914 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2919 __PACKAGE__-
>register_method({
2920 name
=> 'get_snapshot_config',
2921 path
=> '{vmid}/snapshot/{snapname}/config',
2924 description
=> "Get snapshot configuration",
2926 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2929 additionalProperties
=> 0,
2931 node
=> get_standard_option
('pve-node'),
2932 vmid
=> get_standard_option
('pve-vmid'),
2933 snapname
=> get_standard_option
('pve-snapshot-name'),
2936 returns
=> { type
=> "object" },
2940 my $rpcenv = PVE
::RPCEnvironment
::get
();
2942 my $authuser = $rpcenv->get_user();
2944 my $vmid = extract_param
($param, 'vmid');
2946 my $snapname = extract_param
($param, 'snapname');
2948 my $conf = PVE
::QemuServer
::load_config
($vmid);
2950 my $snap = $conf->{snapshots
}->{$snapname};
2952 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2957 __PACKAGE__-
>register_method({
2959 path
=> '{vmid}/snapshot/{snapname}/rollback',
2963 description
=> "Rollback VM state to specified snapshot.",
2965 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2968 additionalProperties
=> 0,
2970 node
=> get_standard_option
('pve-node'),
2971 vmid
=> get_standard_option
('pve-vmid'),
2972 snapname
=> get_standard_option
('pve-snapshot-name'),
2977 description
=> "the task ID.",
2982 my $rpcenv = PVE
::RPCEnvironment
::get
();
2984 my $authuser = $rpcenv->get_user();
2986 my $node = extract_param
($param, 'node');
2988 my $vmid = extract_param
($param, 'vmid');
2990 my $snapname = extract_param
($param, 'snapname');
2993 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2994 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2997 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3000 __PACKAGE__-
>register_method({
3001 name
=> 'delsnapshot',
3002 path
=> '{vmid}/snapshot/{snapname}',
3006 description
=> "Delete a VM snapshot.",
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
=> "For removal from config file, even if removing disk snapshots fails.",
3025 description
=> "the task ID.",
3030 my $rpcenv = PVE
::RPCEnvironment
::get
();
3032 my $authuser = $rpcenv->get_user();
3034 my $node = extract_param
($param, 'node');
3036 my $vmid = extract_param
($param, 'vmid');
3038 my $snapname = extract_param
($param, 'snapname');
3041 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3042 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3045 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3048 __PACKAGE__-
>register_method({
3050 path
=> '{vmid}/template',
3054 description
=> "Create a Template.",
3056 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3057 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3060 additionalProperties
=> 0,
3062 node
=> get_standard_option
('pve-node'),
3063 vmid
=> get_standard_option
('pve-vmid'),
3067 description
=> "If you want to convert only 1 disk to base image.",
3068 enum
=> [PVE
::QemuServer
::disknames
()],
3073 returns
=> { type
=> 'null'},
3077 my $rpcenv = PVE
::RPCEnvironment
::get
();
3079 my $authuser = $rpcenv->get_user();
3081 my $node = extract_param
($param, 'node');
3083 my $vmid = extract_param
($param, 'vmid');
3085 my $disk = extract_param
($param, 'disk');
3087 my $updatefn = sub {
3089 my $conf = PVE
::QemuServer
::load_config
($vmid);
3091 PVE
::QemuServer
::check_lock
($conf);
3093 die "unable to create template, because VM contains snapshots\n"
3094 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3096 die "you can't convert a template to a template\n"
3097 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3099 die "you can't convert a VM to template if VM is running\n"
3100 if PVE
::QemuServer
::check_running
($vmid);
3103 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3106 $conf->{template
} = 1;
3107 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3109 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3112 PVE
::QemuServer
::lock_config
($vmid, $updatefn);