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 - $conf->{'name'}",
1402 my $port = PVE
::QemuServer
::spice_port
($vmid);
1404 my ($ticket, undef, $remote_viewer_config) =
1405 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1407 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1408 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1410 return $remote_viewer_config;
1413 __PACKAGE__-
>register_method({
1415 path
=> '{vmid}/status',
1418 description
=> "Directory index",
1423 additionalProperties
=> 0,
1425 node
=> get_standard_option
('pve-node'),
1426 vmid
=> get_standard_option
('pve-vmid'),
1434 subdir
=> { type
=> 'string' },
1437 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1443 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1446 { subdir
=> 'current' },
1447 { subdir
=> 'start' },
1448 { subdir
=> 'stop' },
1454 __PACKAGE__-
>register_method({
1455 name
=> 'vm_status',
1456 path
=> '{vmid}/status/current',
1459 protected
=> 1, # qemu pid files are only readable by root
1460 description
=> "Get virtual machine status.",
1462 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1465 additionalProperties
=> 0,
1467 node
=> get_standard_option
('pve-node'),
1468 vmid
=> get_standard_option
('pve-vmid'),
1471 returns
=> { type
=> 'object' },
1476 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1478 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1479 my $status = $vmstatus->{$param->{vmid
}};
1481 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1483 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1488 __PACKAGE__-
>register_method({
1490 path
=> '{vmid}/status/start',
1494 description
=> "Start virtual machine.",
1496 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1499 additionalProperties
=> 0,
1501 node
=> get_standard_option
('pve-node'),
1502 vmid
=> get_standard_option
('pve-vmid'),
1503 skiplock
=> get_standard_option
('skiplock'),
1504 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1505 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1506 machine
=> get_standard_option
('pve-qm-machine'),
1515 my $rpcenv = PVE
::RPCEnvironment
::get
();
1517 my $authuser = $rpcenv->get_user();
1519 my $node = extract_param
($param, 'node');
1521 my $vmid = extract_param
($param, 'vmid');
1523 my $machine = extract_param
($param, 'machine');
1525 my $stateuri = extract_param
($param, 'stateuri');
1526 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1527 if $stateuri && $authuser ne 'root@pam';
1529 my $skiplock = extract_param
($param, 'skiplock');
1530 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1531 if $skiplock && $authuser ne 'root@pam';
1533 my $migratedfrom = extract_param
($param, 'migratedfrom');
1534 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1535 if $migratedfrom && $authuser ne 'root@pam';
1537 # read spice ticket from STDIN
1539 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1540 if (defined(my $line = <>)) {
1542 $spice_ticket = $line;
1546 my $storecfg = PVE
::Storage
::config
();
1548 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1549 $rpcenv->{type
} ne 'ha') {
1554 my $service = "vm:$vmid";
1556 my $cmd = ['ha-manager', 'enable', $service];
1558 print "Executing HA start for VM $vmid\n";
1560 PVE
::Tools
::run_command
($cmd);
1565 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1572 syslog
('info', "start VM $vmid: $upid\n");
1574 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1575 $machine, $spice_ticket);
1580 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1584 __PACKAGE__-
>register_method({
1586 path
=> '{vmid}/status/stop',
1590 description
=> "Stop virtual machine.",
1592 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1595 additionalProperties
=> 0,
1597 node
=> get_standard_option
('pve-node'),
1598 vmid
=> get_standard_option
('pve-vmid'),
1599 skiplock
=> get_standard_option
('skiplock'),
1600 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1602 description
=> "Wait maximal timeout seconds.",
1608 description
=> "Do not decativate storage volumes.",
1621 my $rpcenv = PVE
::RPCEnvironment
::get
();
1623 my $authuser = $rpcenv->get_user();
1625 my $node = extract_param
($param, 'node');
1627 my $vmid = extract_param
($param, 'vmid');
1629 my $skiplock = extract_param
($param, 'skiplock');
1630 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1631 if $skiplock && $authuser ne 'root@pam';
1633 my $keepActive = extract_param
($param, 'keepActive');
1634 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1635 if $keepActive && $authuser ne 'root@pam';
1637 my $migratedfrom = extract_param
($param, 'migratedfrom');
1638 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1639 if $migratedfrom && $authuser ne 'root@pam';
1642 my $storecfg = PVE
::Storage
::config
();
1644 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1649 my $service = "vm:$vmid";
1651 my $cmd = ['ha-manager', 'disable', $service];
1653 print "Executing HA stop for VM $vmid\n";
1655 PVE
::Tools
::run_command
($cmd);
1660 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1666 syslog
('info', "stop VM $vmid: $upid\n");
1668 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1669 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1674 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1678 __PACKAGE__-
>register_method({
1680 path
=> '{vmid}/status/reset',
1684 description
=> "Reset virtual machine.",
1686 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1689 additionalProperties
=> 0,
1691 node
=> get_standard_option
('pve-node'),
1692 vmid
=> get_standard_option
('pve-vmid'),
1693 skiplock
=> get_standard_option
('skiplock'),
1702 my $rpcenv = PVE
::RPCEnvironment
::get
();
1704 my $authuser = $rpcenv->get_user();
1706 my $node = extract_param
($param, 'node');
1708 my $vmid = extract_param
($param, 'vmid');
1710 my $skiplock = extract_param
($param, 'skiplock');
1711 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1712 if $skiplock && $authuser ne 'root@pam';
1714 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1719 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1724 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1727 __PACKAGE__-
>register_method({
1728 name
=> 'vm_shutdown',
1729 path
=> '{vmid}/status/shutdown',
1733 description
=> "Shutdown virtual machine.",
1735 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1738 additionalProperties
=> 0,
1740 node
=> get_standard_option
('pve-node'),
1741 vmid
=> get_standard_option
('pve-vmid'),
1742 skiplock
=> get_standard_option
('skiplock'),
1744 description
=> "Wait maximal timeout seconds.",
1750 description
=> "Make sure the VM stops.",
1756 description
=> "Do not decativate storage volumes.",
1769 my $rpcenv = PVE
::RPCEnvironment
::get
();
1771 my $authuser = $rpcenv->get_user();
1773 my $node = extract_param
($param, 'node');
1775 my $vmid = extract_param
($param, 'vmid');
1777 my $skiplock = extract_param
($param, 'skiplock');
1778 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1779 if $skiplock && $authuser ne 'root@pam';
1781 my $keepActive = extract_param
($param, 'keepActive');
1782 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1783 if $keepActive && $authuser ne 'root@pam';
1785 my $storecfg = PVE
::Storage
::config
();
1790 syslog
('info', "shutdown VM $vmid: $upid\n");
1792 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1793 1, $param->{forceStop
}, $keepActive);
1798 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1801 __PACKAGE__-
>register_method({
1802 name
=> 'vm_suspend',
1803 path
=> '{vmid}/status/suspend',
1807 description
=> "Suspend virtual machine.",
1809 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1812 additionalProperties
=> 0,
1814 node
=> get_standard_option
('pve-node'),
1815 vmid
=> get_standard_option
('pve-vmid'),
1816 skiplock
=> get_standard_option
('skiplock'),
1825 my $rpcenv = PVE
::RPCEnvironment
::get
();
1827 my $authuser = $rpcenv->get_user();
1829 my $node = extract_param
($param, 'node');
1831 my $vmid = extract_param
($param, 'vmid');
1833 my $skiplock = extract_param
($param, 'skiplock');
1834 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1835 if $skiplock && $authuser ne 'root@pam';
1837 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1842 syslog
('info', "suspend VM $vmid: $upid\n");
1844 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1849 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1852 __PACKAGE__-
>register_method({
1853 name
=> 'vm_resume',
1854 path
=> '{vmid}/status/resume',
1858 description
=> "Resume virtual machine.",
1860 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1863 additionalProperties
=> 0,
1865 node
=> get_standard_option
('pve-node'),
1866 vmid
=> get_standard_option
('pve-vmid'),
1867 skiplock
=> get_standard_option
('skiplock'),
1876 my $rpcenv = PVE
::RPCEnvironment
::get
();
1878 my $authuser = $rpcenv->get_user();
1880 my $node = extract_param
($param, 'node');
1882 my $vmid = extract_param
($param, 'vmid');
1884 my $skiplock = extract_param
($param, 'skiplock');
1885 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1886 if $skiplock && $authuser ne 'root@pam';
1888 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1893 syslog
('info', "resume VM $vmid: $upid\n");
1895 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1900 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1903 __PACKAGE__-
>register_method({
1904 name
=> 'vm_sendkey',
1905 path
=> '{vmid}/sendkey',
1909 description
=> "Send key event to virtual machine.",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid'),
1918 skiplock
=> get_standard_option
('skiplock'),
1920 description
=> "The key (qemu monitor encoding).",
1925 returns
=> { type
=> 'null'},
1929 my $rpcenv = PVE
::RPCEnvironment
::get
();
1931 my $authuser = $rpcenv->get_user();
1933 my $node = extract_param
($param, 'node');
1935 my $vmid = extract_param
($param, 'vmid');
1937 my $skiplock = extract_param
($param, 'skiplock');
1938 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1939 if $skiplock && $authuser ne 'root@pam';
1941 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1946 __PACKAGE__-
>register_method({
1947 name
=> 'vm_feature',
1948 path
=> '{vmid}/feature',
1952 description
=> "Check if feature for virtual machine is available.",
1954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1957 additionalProperties
=> 0,
1959 node
=> get_standard_option
('pve-node'),
1960 vmid
=> get_standard_option
('pve-vmid'),
1962 description
=> "Feature to check.",
1964 enum
=> [ 'snapshot', 'clone', 'copy' ],
1966 snapname
=> get_standard_option
('pve-snapshot-name', {
1974 hasFeature
=> { type
=> 'boolean' },
1977 items
=> { type
=> 'string' },
1984 my $node = extract_param
($param, 'node');
1986 my $vmid = extract_param
($param, 'vmid');
1988 my $snapname = extract_param
($param, 'snapname');
1990 my $feature = extract_param
($param, 'feature');
1992 my $running = PVE
::QemuServer
::check_running
($vmid);
1994 my $conf = PVE
::QemuServer
::load_config
($vmid);
1997 my $snap = $conf->{snapshots
}->{$snapname};
1998 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2001 my $storecfg = PVE
::Storage
::config
();
2003 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2004 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2007 hasFeature
=> $hasFeature,
2008 nodes
=> [ keys %$nodelist ],
2012 __PACKAGE__-
>register_method({
2014 path
=> '{vmid}/clone',
2018 description
=> "Create a copy of virtual machine/template.",
2020 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2021 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2022 "'Datastore.AllocateSpace' on any used storage.",
2025 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2027 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2028 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2033 additionalProperties
=> 0,
2035 node
=> get_standard_option
('pve-node'),
2036 vmid
=> get_standard_option
('pve-vmid'),
2037 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2040 type
=> 'string', format
=> 'dns-name',
2041 description
=> "Set a name for the new VM.",
2046 description
=> "Description for the new VM.",
2050 type
=> 'string', format
=> 'pve-poolid',
2051 description
=> "Add the new VM to the specified pool.",
2053 snapname
=> get_standard_option
('pve-snapshot-name', {
2056 storage
=> get_standard_option
('pve-storage-id', {
2057 description
=> "Target storage for full clone.",
2062 description
=> "Target format for file storage.",
2066 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2071 description
=> "Create a full copy of all disk. This is always done when " .
2072 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2075 target
=> get_standard_option
('pve-node', {
2076 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2087 my $rpcenv = PVE
::RPCEnvironment
::get
();
2089 my $authuser = $rpcenv->get_user();
2091 my $node = extract_param
($param, 'node');
2093 my $vmid = extract_param
($param, 'vmid');
2095 my $newid = extract_param
($param, 'newid');
2097 my $pool = extract_param
($param, 'pool');
2099 if (defined($pool)) {
2100 $rpcenv->check_pool_exist($pool);
2103 my $snapname = extract_param
($param, 'snapname');
2105 my $storage = extract_param
($param, 'storage');
2107 my $format = extract_param
($param, 'format');
2109 my $target = extract_param
($param, 'target');
2111 my $localnode = PVE
::INotify
::nodename
();
2113 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2115 PVE
::Cluster
::check_node_exists
($target) if $target;
2117 my $storecfg = PVE
::Storage
::config
();
2120 # check if storage is enabled on local node
2121 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2123 # check if storage is available on target node
2124 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2125 # clone only works if target storage is shared
2126 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2127 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2131 PVE
::Cluster
::check_cfs_quorum
();
2133 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2135 # exclusive lock if VM is running - else shared lock is enough;
2136 my $shared_lock = $running ?
0 : 1;
2140 # do all tests after lock
2141 # we also try to do all tests before we fork the worker
2143 my $conf = PVE
::QemuServer
::load_config
($vmid);
2145 PVE
::QemuServer
::check_lock
($conf);
2147 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2149 die "unexpected state change\n" if $verify_running != $running;
2151 die "snapshot '$snapname' does not exist\n"
2152 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2154 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2156 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2158 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2160 my $conffile = PVE
::QemuServer
::config_file
($newid);
2162 die "unable to create VM $newid: config file already exists\n"
2165 my $newconf = { lock => 'clone' };
2169 foreach my $opt (keys %$oldconf) {
2170 my $value = $oldconf->{$opt};
2172 # do not copy snapshot related info
2173 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2174 $opt eq 'vmstate' || $opt eq 'snapstate';
2176 # always change MAC! address
2177 if ($opt =~ m/^net(\d+)$/) {
2178 my $net = PVE
::QemuServer
::parse_net
($value);
2179 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2180 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2181 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2182 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2183 die "unable to parse drive options for '$opt'\n" if !$drive;
2184 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2185 $newconf->{$opt} = $value; # simply copy configuration
2187 if ($param->{full
}) {
2188 die "Full clone feature is not available"
2189 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2192 # not full means clone instead of copy
2193 die "Linked clone feature is not available"
2194 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2196 $drives->{$opt} = $drive;
2197 push @$vollist, $drive->{file
};
2200 # copy everything else
2201 $newconf->{$opt} = $value;
2205 # auto generate a new uuid
2206 my ($uuid, $uuid_str);
2207 UUID
::generate
($uuid);
2208 UUID
::unparse
($uuid, $uuid_str);
2209 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2210 $smbios1->{uuid
} = $uuid_str;
2211 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2213 delete $newconf->{template
};
2215 if ($param->{name
}) {
2216 $newconf->{name
} = $param->{name
};
2218 if ($oldconf->{name
}) {
2219 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2221 $newconf->{name
} = "Copy-of-VM-$vmid";
2225 if ($param->{description
}) {
2226 $newconf->{description
} = $param->{description
};
2229 # create empty/temp config - this fails if VM already exists on other node
2230 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2235 my $newvollist = [];
2238 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2240 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2242 foreach my $opt (keys %$drives) {
2243 my $drive = $drives->{$opt};
2245 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2246 $newid, $storage, $format, $drive->{full
}, $newvollist);
2248 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2250 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2253 delete $newconf->{lock};
2254 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2257 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2258 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2260 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2261 die "Failed to move config to node '$target' - rename failed: $!\n"
2262 if !rename($conffile, $newconffile);
2265 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2270 sleep 1; # some storage like rbd need to wait before release volume - really?
2272 foreach my $volid (@$newvollist) {
2273 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2276 die "clone failed: $err";
2282 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2285 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2286 # Aquire exclusive lock lock for $newid
2287 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2292 __PACKAGE__-
>register_method({
2293 name
=> 'move_vm_disk',
2294 path
=> '{vmid}/move_disk',
2298 description
=> "Move volume to different storage.",
2300 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2301 "and 'Datastore.AllocateSpace' permissions on the storage.",
2304 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2305 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2309 additionalProperties
=> 0,
2311 node
=> get_standard_option
('pve-node'),
2312 vmid
=> get_standard_option
('pve-vmid'),
2315 description
=> "The disk you want to move.",
2316 enum
=> [ PVE
::QemuServer
::disknames
() ],
2318 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2321 description
=> "Target Format.",
2322 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2327 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2333 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2341 description
=> "the task ID.",
2346 my $rpcenv = PVE
::RPCEnvironment
::get
();
2348 my $authuser = $rpcenv->get_user();
2350 my $node = extract_param
($param, 'node');
2352 my $vmid = extract_param
($param, 'vmid');
2354 my $digest = extract_param
($param, 'digest');
2356 my $disk = extract_param
($param, 'disk');
2358 my $storeid = extract_param
($param, 'storage');
2360 my $format = extract_param
($param, 'format');
2362 my $storecfg = PVE
::Storage
::config
();
2364 my $updatefn = sub {
2366 my $conf = PVE
::QemuServer
::load_config
($vmid);
2368 die "checksum missmatch (file change by other user?)\n"
2369 if $digest && $digest ne $conf->{digest
};
2371 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2373 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2375 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2377 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2380 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2381 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2385 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2386 (!$format || !$oldfmt || $oldfmt eq $format);
2388 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2390 my $running = PVE
::QemuServer
::check_running
($vmid);
2392 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2396 my $newvollist = [];
2399 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2401 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2402 $vmid, $storeid, $format, 1, $newvollist);
2404 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2406 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2408 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2411 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2412 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2419 foreach my $volid (@$newvollist) {
2420 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2423 die "storage migration failed: $err";
2426 if ($param->{delete}) {
2427 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2428 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2429 if ($used_paths->{$path}){
2430 warn "volume $old_volid have snapshots. Can't delete it\n";
2431 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2432 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2434 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2440 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2443 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2446 __PACKAGE__-
>register_method({
2447 name
=> 'migrate_vm',
2448 path
=> '{vmid}/migrate',
2452 description
=> "Migrate virtual machine. Creates a new migration task.",
2454 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2457 additionalProperties
=> 0,
2459 node
=> get_standard_option
('pve-node'),
2460 vmid
=> get_standard_option
('pve-vmid'),
2461 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2464 description
=> "Use online/live migration.",
2469 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2476 description
=> "the task ID.",
2481 my $rpcenv = PVE
::RPCEnvironment
::get
();
2483 my $authuser = $rpcenv->get_user();
2485 my $target = extract_param
($param, 'target');
2487 my $localnode = PVE
::INotify
::nodename
();
2488 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2490 PVE
::Cluster
::check_cfs_quorum
();
2492 PVE
::Cluster
::check_node_exists
($target);
2494 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2496 my $vmid = extract_param
($param, 'vmid');
2498 raise_param_exc
({ force
=> "Only root may use this option." })
2499 if $param->{force
} && $authuser ne 'root@pam';
2502 my $conf = PVE
::QemuServer
::load_config
($vmid);
2504 # try to detect errors early
2506 PVE
::QemuServer
::check_lock
($conf);
2508 if (PVE
::QemuServer
::check_running
($vmid)) {
2509 die "cant migrate running VM without --online\n"
2510 if !$param->{online
};
2513 my $storecfg = PVE
::Storage
::config
();
2514 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2516 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2521 my $service = "vm:$vmid";
2523 my $cmd = ['ha-manager', 'migrate', $service, $target];
2525 print "Executing HA migrate for VM $vmid to node $target\n";
2527 PVE
::Tools
::run_command
($cmd);
2532 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2539 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2542 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2547 __PACKAGE__-
>register_method({
2549 path
=> '{vmid}/monitor',
2553 description
=> "Execute Qemu monitor commands.",
2555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2558 additionalProperties
=> 0,
2560 node
=> get_standard_option
('pve-node'),
2561 vmid
=> get_standard_option
('pve-vmid'),
2564 description
=> "The monitor command.",
2568 returns
=> { type
=> 'string'},
2572 my $vmid = $param->{vmid
};
2574 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2578 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2580 $res = "ERROR: $@" if $@;
2585 __PACKAGE__-
>register_method({
2586 name
=> 'resize_vm',
2587 path
=> '{vmid}/resize',
2591 description
=> "Extend volume size.",
2593 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2596 additionalProperties
=> 0,
2598 node
=> get_standard_option
('pve-node'),
2599 vmid
=> get_standard_option
('pve-vmid'),
2600 skiplock
=> get_standard_option
('skiplock'),
2603 description
=> "The disk you want to resize.",
2604 enum
=> [PVE
::QemuServer
::disknames
()],
2608 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2609 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.",
2613 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2619 returns
=> { type
=> 'null'},
2623 my $rpcenv = PVE
::RPCEnvironment
::get
();
2625 my $authuser = $rpcenv->get_user();
2627 my $node = extract_param
($param, 'node');
2629 my $vmid = extract_param
($param, 'vmid');
2631 my $digest = extract_param
($param, 'digest');
2633 my $disk = extract_param
($param, 'disk');
2635 my $sizestr = extract_param
($param, 'size');
2637 my $skiplock = extract_param
($param, 'skiplock');
2638 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2639 if $skiplock && $authuser ne 'root@pam';
2641 my $storecfg = PVE
::Storage
::config
();
2643 my $updatefn = sub {
2645 my $conf = PVE
::QemuServer
::load_config
($vmid);
2647 die "checksum missmatch (file change by other user?)\n"
2648 if $digest && $digest ne $conf->{digest
};
2649 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2651 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2653 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2655 my (undef, undef, undef, undef, undef, undef, $format) =
2656 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2658 die "can't resize volume: $disk if snapshot exists\n"
2659 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2661 my $volid = $drive->{file
};
2663 die "disk '$disk' has no associated volume\n" if !$volid;
2665 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2667 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2669 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2671 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2673 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2674 my ($ext, $newsize, $unit) = ($1, $2, $4);
2677 $newsize = $newsize * 1024;
2678 } elsif ($unit eq 'M') {
2679 $newsize = $newsize * 1024 * 1024;
2680 } elsif ($unit eq 'G') {
2681 $newsize = $newsize * 1024 * 1024 * 1024;
2682 } elsif ($unit eq 'T') {
2683 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2686 $newsize += $size if $ext;
2687 $newsize = int($newsize);
2689 die "unable to skrink disk size\n" if $newsize < $size;
2691 return if $size == $newsize;
2693 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2695 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2697 $drive->{size
} = $newsize;
2698 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2700 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2703 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2707 __PACKAGE__-
>register_method({
2708 name
=> 'snapshot_list',
2709 path
=> '{vmid}/snapshot',
2711 description
=> "List all snapshots.",
2713 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2716 protected
=> 1, # qemu pid files are only readable by root
2718 additionalProperties
=> 0,
2720 vmid
=> get_standard_option
('pve-vmid'),
2721 node
=> get_standard_option
('pve-node'),
2730 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2735 my $vmid = $param->{vmid
};
2737 my $conf = PVE
::QemuServer
::load_config
($vmid);
2738 my $snaphash = $conf->{snapshots
} || {};
2742 foreach my $name (keys %$snaphash) {
2743 my $d = $snaphash->{$name};
2746 snaptime
=> $d->{snaptime
} || 0,
2747 vmstate
=> $d->{vmstate
} ?
1 : 0,
2748 description
=> $d->{description
} || '',
2750 $item->{parent
} = $d->{parent
} if $d->{parent
};
2751 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2755 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2756 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2757 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2759 push @$res, $current;
2764 __PACKAGE__-
>register_method({
2766 path
=> '{vmid}/snapshot',
2770 description
=> "Snapshot a VM.",
2772 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2775 additionalProperties
=> 0,
2777 node
=> get_standard_option
('pve-node'),
2778 vmid
=> get_standard_option
('pve-vmid'),
2779 snapname
=> get_standard_option
('pve-snapshot-name'),
2783 description
=> "Save the vmstate",
2788 description
=> "A textual description or comment.",
2794 description
=> "the task ID.",
2799 my $rpcenv = PVE
::RPCEnvironment
::get
();
2801 my $authuser = $rpcenv->get_user();
2803 my $node = extract_param
($param, 'node');
2805 my $vmid = extract_param
($param, 'vmid');
2807 my $snapname = extract_param
($param, 'snapname');
2809 die "unable to use snapshot name 'current' (reserved name)\n"
2810 if $snapname eq 'current';
2813 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2814 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2815 $param->{description
});
2818 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2821 __PACKAGE__-
>register_method({
2822 name
=> 'snapshot_cmd_idx',
2823 path
=> '{vmid}/snapshot/{snapname}',
2830 additionalProperties
=> 0,
2832 vmid
=> get_standard_option
('pve-vmid'),
2833 node
=> get_standard_option
('pve-node'),
2834 snapname
=> get_standard_option
('pve-snapshot-name'),
2843 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2850 push @$res, { cmd
=> 'rollback' };
2851 push @$res, { cmd
=> 'config' };
2856 __PACKAGE__-
>register_method({
2857 name
=> 'update_snapshot_config',
2858 path
=> '{vmid}/snapshot/{snapname}/config',
2862 description
=> "Update snapshot metadata.",
2864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2867 additionalProperties
=> 0,
2869 node
=> get_standard_option
('pve-node'),
2870 vmid
=> get_standard_option
('pve-vmid'),
2871 snapname
=> get_standard_option
('pve-snapshot-name'),
2875 description
=> "A textual description or comment.",
2879 returns
=> { type
=> 'null' },
2883 my $rpcenv = PVE
::RPCEnvironment
::get
();
2885 my $authuser = $rpcenv->get_user();
2887 my $vmid = extract_param
($param, 'vmid');
2889 my $snapname = extract_param
($param, 'snapname');
2891 return undef if !defined($param->{description
});
2893 my $updatefn = sub {
2895 my $conf = PVE
::QemuServer
::load_config
($vmid);
2897 PVE
::QemuServer
::check_lock
($conf);
2899 my $snap = $conf->{snapshots
}->{$snapname};
2901 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2903 $snap->{description
} = $param->{description
} if defined($param->{description
});
2905 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2908 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2913 __PACKAGE__-
>register_method({
2914 name
=> 'get_snapshot_config',
2915 path
=> '{vmid}/snapshot/{snapname}/config',
2918 description
=> "Get snapshot configuration",
2920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2923 additionalProperties
=> 0,
2925 node
=> get_standard_option
('pve-node'),
2926 vmid
=> get_standard_option
('pve-vmid'),
2927 snapname
=> get_standard_option
('pve-snapshot-name'),
2930 returns
=> { type
=> "object" },
2934 my $rpcenv = PVE
::RPCEnvironment
::get
();
2936 my $authuser = $rpcenv->get_user();
2938 my $vmid = extract_param
($param, 'vmid');
2940 my $snapname = extract_param
($param, 'snapname');
2942 my $conf = PVE
::QemuServer
::load_config
($vmid);
2944 my $snap = $conf->{snapshots
}->{$snapname};
2946 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2951 __PACKAGE__-
>register_method({
2953 path
=> '{vmid}/snapshot/{snapname}/rollback',
2957 description
=> "Rollback VM state to specified snapshot.",
2959 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2962 additionalProperties
=> 0,
2964 node
=> get_standard_option
('pve-node'),
2965 vmid
=> get_standard_option
('pve-vmid'),
2966 snapname
=> get_standard_option
('pve-snapshot-name'),
2971 description
=> "the task ID.",
2976 my $rpcenv = PVE
::RPCEnvironment
::get
();
2978 my $authuser = $rpcenv->get_user();
2980 my $node = extract_param
($param, 'node');
2982 my $vmid = extract_param
($param, 'vmid');
2984 my $snapname = extract_param
($param, 'snapname');
2987 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2988 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2991 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2994 __PACKAGE__-
>register_method({
2995 name
=> 'delsnapshot',
2996 path
=> '{vmid}/snapshot/{snapname}',
3000 description
=> "Delete a VM snapshot.",
3002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3005 additionalProperties
=> 0,
3007 node
=> get_standard_option
('pve-node'),
3008 vmid
=> get_standard_option
('pve-vmid'),
3009 snapname
=> get_standard_option
('pve-snapshot-name'),
3013 description
=> "For removal from config file, even if removing disk snapshots fails.",
3019 description
=> "the task ID.",
3024 my $rpcenv = PVE
::RPCEnvironment
::get
();
3026 my $authuser = $rpcenv->get_user();
3028 my $node = extract_param
($param, 'node');
3030 my $vmid = extract_param
($param, 'vmid');
3032 my $snapname = extract_param
($param, 'snapname');
3035 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3036 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3039 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3042 __PACKAGE__-
>register_method({
3044 path
=> '{vmid}/template',
3048 description
=> "Create a Template.",
3050 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3051 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3054 additionalProperties
=> 0,
3056 node
=> get_standard_option
('pve-node'),
3057 vmid
=> get_standard_option
('pve-vmid'),
3061 description
=> "If you want to convert only 1 disk to base image.",
3062 enum
=> [PVE
::QemuServer
::disknames
()],
3067 returns
=> { type
=> 'null'},
3071 my $rpcenv = PVE
::RPCEnvironment
::get
();
3073 my $authuser = $rpcenv->get_user();
3075 my $node = extract_param
($param, 'node');
3077 my $vmid = extract_param
($param, 'vmid');
3079 my $disk = extract_param
($param, 'disk');
3081 my $updatefn = sub {
3083 my $conf = PVE
::QemuServer
::load_config
($vmid);
3085 PVE
::QemuServer
::check_lock
($conf);
3087 die "unable to create template, because VM contains snapshots\n"
3088 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3090 die "you can't convert a template to a template\n"
3091 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3093 die "you can't convert a VM to template if VM is running\n"
3094 if PVE
::QemuServer
::check_running
($vmid);
3097 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3100 $conf->{template
} = 1;
3101 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3103 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3106 PVE
::QemuServer
::lock_config
($vmid, $updatefn);