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 # no need to copy unused images, because VMID(owner) changes anyways
2177 next if $opt =~ m/^unused\d+$/;
2179 # always change MAC! address
2180 if ($opt =~ m/^net(\d+)$/) {
2181 my $net = PVE
::QemuServer
::parse_net
($value);
2182 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2183 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2184 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2185 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2186 die "unable to parse drive options for '$opt'\n" if !$drive;
2187 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2188 $newconf->{$opt} = $value; # simply copy configuration
2190 if ($param->{full
}) {
2191 die "Full clone feature is not available"
2192 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2195 # not full means clone instead of copy
2196 die "Linked clone feature is not available"
2197 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2199 $drives->{$opt} = $drive;
2200 push @$vollist, $drive->{file
};
2203 # copy everything else
2204 $newconf->{$opt} = $value;
2208 # auto generate a new uuid
2209 my ($uuid, $uuid_str);
2210 UUID
::generate
($uuid);
2211 UUID
::unparse
($uuid, $uuid_str);
2212 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2213 $smbios1->{uuid
} = $uuid_str;
2214 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2216 delete $newconf->{template
};
2218 if ($param->{name
}) {
2219 $newconf->{name
} = $param->{name
};
2221 if ($oldconf->{name
}) {
2222 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2224 $newconf->{name
} = "Copy-of-VM-$vmid";
2228 if ($param->{description
}) {
2229 $newconf->{description
} = $param->{description
};
2232 # create empty/temp config - this fails if VM already exists on other node
2233 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2238 my $newvollist = [];
2241 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2243 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2245 foreach my $opt (keys %$drives) {
2246 my $drive = $drives->{$opt};
2248 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2249 $newid, $storage, $format, $drive->{full
}, $newvollist);
2251 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2253 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2256 delete $newconf->{lock};
2257 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2260 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2261 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2263 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2264 die "Failed to move config to node '$target' - rename failed: $!\n"
2265 if !rename($conffile, $newconffile);
2268 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2273 sleep 1; # some storage like rbd need to wait before release volume - really?
2275 foreach my $volid (@$newvollist) {
2276 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2279 die "clone failed: $err";
2285 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2288 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2289 # Aquire exclusive lock lock for $newid
2290 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2295 __PACKAGE__-
>register_method({
2296 name
=> 'move_vm_disk',
2297 path
=> '{vmid}/move_disk',
2301 description
=> "Move volume to different storage.",
2303 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2304 "and 'Datastore.AllocateSpace' permissions on the storage.",
2307 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2308 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2312 additionalProperties
=> 0,
2314 node
=> get_standard_option
('pve-node'),
2315 vmid
=> get_standard_option
('pve-vmid'),
2318 description
=> "The disk you want to move.",
2319 enum
=> [ PVE
::QemuServer
::disknames
() ],
2321 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2324 description
=> "Target Format.",
2325 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2330 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2336 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2344 description
=> "the task ID.",
2349 my $rpcenv = PVE
::RPCEnvironment
::get
();
2351 my $authuser = $rpcenv->get_user();
2353 my $node = extract_param
($param, 'node');
2355 my $vmid = extract_param
($param, 'vmid');
2357 my $digest = extract_param
($param, 'digest');
2359 my $disk = extract_param
($param, 'disk');
2361 my $storeid = extract_param
($param, 'storage');
2363 my $format = extract_param
($param, 'format');
2365 my $storecfg = PVE
::Storage
::config
();
2367 my $updatefn = sub {
2369 my $conf = PVE
::QemuServer
::load_config
($vmid);
2371 die "checksum missmatch (file change by other user?)\n"
2372 if $digest && $digest ne $conf->{digest
};
2374 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2376 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2378 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2380 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2383 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2384 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2388 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2389 (!$format || !$oldfmt || $oldfmt eq $format);
2391 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2393 my $running = PVE
::QemuServer
::check_running
($vmid);
2395 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2399 my $newvollist = [];
2402 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2404 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2405 $vmid, $storeid, $format, 1, $newvollist);
2407 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2409 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2411 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2414 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2415 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2422 foreach my $volid (@$newvollist) {
2423 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2426 die "storage migration failed: $err";
2429 if ($param->{delete}) {
2430 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2431 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2432 if ($used_paths->{$path}){
2433 warn "volume $old_volid have snapshots. Can't delete it\n";
2434 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2435 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2437 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2443 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2446 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2449 __PACKAGE__-
>register_method({
2450 name
=> 'migrate_vm',
2451 path
=> '{vmid}/migrate',
2455 description
=> "Migrate virtual machine. Creates a new migration task.",
2457 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2460 additionalProperties
=> 0,
2462 node
=> get_standard_option
('pve-node'),
2463 vmid
=> get_standard_option
('pve-vmid'),
2464 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2467 description
=> "Use online/live migration.",
2472 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2479 description
=> "the task ID.",
2484 my $rpcenv = PVE
::RPCEnvironment
::get
();
2486 my $authuser = $rpcenv->get_user();
2488 my $target = extract_param
($param, 'target');
2490 my $localnode = PVE
::INotify
::nodename
();
2491 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2493 PVE
::Cluster
::check_cfs_quorum
();
2495 PVE
::Cluster
::check_node_exists
($target);
2497 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2499 my $vmid = extract_param
($param, 'vmid');
2501 raise_param_exc
({ force
=> "Only root may use this option." })
2502 if $param->{force
} && $authuser ne 'root@pam';
2505 my $conf = PVE
::QemuServer
::load_config
($vmid);
2507 # try to detect errors early
2509 PVE
::QemuServer
::check_lock
($conf);
2511 if (PVE
::QemuServer
::check_running
($vmid)) {
2512 die "cant migrate running VM without --online\n"
2513 if !$param->{online
};
2516 my $storecfg = PVE
::Storage
::config
();
2517 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2519 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2524 my $service = "vm:$vmid";
2526 my $cmd = ['ha-manager', 'migrate', $service, $target];
2528 print "Executing HA migrate for VM $vmid to node $target\n";
2530 PVE
::Tools
::run_command
($cmd);
2535 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2542 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2545 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2550 __PACKAGE__-
>register_method({
2552 path
=> '{vmid}/monitor',
2556 description
=> "Execute Qemu monitor commands.",
2558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2561 additionalProperties
=> 0,
2563 node
=> get_standard_option
('pve-node'),
2564 vmid
=> get_standard_option
('pve-vmid'),
2567 description
=> "The monitor command.",
2571 returns
=> { type
=> 'string'},
2575 my $vmid = $param->{vmid
};
2577 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2581 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2583 $res = "ERROR: $@" if $@;
2588 __PACKAGE__-
>register_method({
2589 name
=> 'resize_vm',
2590 path
=> '{vmid}/resize',
2594 description
=> "Extend volume size.",
2596 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2599 additionalProperties
=> 0,
2601 node
=> get_standard_option
('pve-node'),
2602 vmid
=> get_standard_option
('pve-vmid'),
2603 skiplock
=> get_standard_option
('skiplock'),
2606 description
=> "The disk you want to resize.",
2607 enum
=> [PVE
::QemuServer
::disknames
()],
2611 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2612 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.",
2616 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2622 returns
=> { type
=> 'null'},
2626 my $rpcenv = PVE
::RPCEnvironment
::get
();
2628 my $authuser = $rpcenv->get_user();
2630 my $node = extract_param
($param, 'node');
2632 my $vmid = extract_param
($param, 'vmid');
2634 my $digest = extract_param
($param, 'digest');
2636 my $disk = extract_param
($param, 'disk');
2638 my $sizestr = extract_param
($param, 'size');
2640 my $skiplock = extract_param
($param, 'skiplock');
2641 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2642 if $skiplock && $authuser ne 'root@pam';
2644 my $storecfg = PVE
::Storage
::config
();
2646 my $updatefn = sub {
2648 my $conf = PVE
::QemuServer
::load_config
($vmid);
2650 die "checksum missmatch (file change by other user?)\n"
2651 if $digest && $digest ne $conf->{digest
};
2652 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2654 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2656 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2658 my (undef, undef, undef, undef, undef, undef, $format) =
2659 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2661 die "can't resize volume: $disk if snapshot exists\n"
2662 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2664 my $volid = $drive->{file
};
2666 die "disk '$disk' has no associated volume\n" if !$volid;
2668 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2670 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2672 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2674 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2676 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2677 my ($ext, $newsize, $unit) = ($1, $2, $4);
2680 $newsize = $newsize * 1024;
2681 } elsif ($unit eq 'M') {
2682 $newsize = $newsize * 1024 * 1024;
2683 } elsif ($unit eq 'G') {
2684 $newsize = $newsize * 1024 * 1024 * 1024;
2685 } elsif ($unit eq 'T') {
2686 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2689 $newsize += $size if $ext;
2690 $newsize = int($newsize);
2692 die "unable to skrink disk size\n" if $newsize < $size;
2694 return if $size == $newsize;
2696 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2698 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2700 $drive->{size
} = $newsize;
2701 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2703 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2706 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2710 __PACKAGE__-
>register_method({
2711 name
=> 'snapshot_list',
2712 path
=> '{vmid}/snapshot',
2714 description
=> "List all snapshots.",
2716 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2719 protected
=> 1, # qemu pid files are only readable by root
2721 additionalProperties
=> 0,
2723 vmid
=> get_standard_option
('pve-vmid'),
2724 node
=> get_standard_option
('pve-node'),
2733 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2738 my $vmid = $param->{vmid
};
2740 my $conf = PVE
::QemuServer
::load_config
($vmid);
2741 my $snaphash = $conf->{snapshots
} || {};
2745 foreach my $name (keys %$snaphash) {
2746 my $d = $snaphash->{$name};
2749 snaptime
=> $d->{snaptime
} || 0,
2750 vmstate
=> $d->{vmstate
} ?
1 : 0,
2751 description
=> $d->{description
} || '',
2753 $item->{parent
} = $d->{parent
} if $d->{parent
};
2754 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2758 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2759 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2760 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2762 push @$res, $current;
2767 __PACKAGE__-
>register_method({
2769 path
=> '{vmid}/snapshot',
2773 description
=> "Snapshot a VM.",
2775 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2778 additionalProperties
=> 0,
2780 node
=> get_standard_option
('pve-node'),
2781 vmid
=> get_standard_option
('pve-vmid'),
2782 snapname
=> get_standard_option
('pve-snapshot-name'),
2786 description
=> "Save the vmstate",
2791 description
=> "A textual description or comment.",
2797 description
=> "the task ID.",
2802 my $rpcenv = PVE
::RPCEnvironment
::get
();
2804 my $authuser = $rpcenv->get_user();
2806 my $node = extract_param
($param, 'node');
2808 my $vmid = extract_param
($param, 'vmid');
2810 my $snapname = extract_param
($param, 'snapname');
2812 die "unable to use snapshot name 'current' (reserved name)\n"
2813 if $snapname eq 'current';
2816 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2817 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2818 $param->{description
});
2821 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2824 __PACKAGE__-
>register_method({
2825 name
=> 'snapshot_cmd_idx',
2826 path
=> '{vmid}/snapshot/{snapname}',
2833 additionalProperties
=> 0,
2835 vmid
=> get_standard_option
('pve-vmid'),
2836 node
=> get_standard_option
('pve-node'),
2837 snapname
=> get_standard_option
('pve-snapshot-name'),
2846 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2853 push @$res, { cmd
=> 'rollback' };
2854 push @$res, { cmd
=> 'config' };
2859 __PACKAGE__-
>register_method({
2860 name
=> 'update_snapshot_config',
2861 path
=> '{vmid}/snapshot/{snapname}/config',
2865 description
=> "Update snapshot metadata.",
2867 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2870 additionalProperties
=> 0,
2872 node
=> get_standard_option
('pve-node'),
2873 vmid
=> get_standard_option
('pve-vmid'),
2874 snapname
=> get_standard_option
('pve-snapshot-name'),
2878 description
=> "A textual description or comment.",
2882 returns
=> { type
=> 'null' },
2886 my $rpcenv = PVE
::RPCEnvironment
::get
();
2888 my $authuser = $rpcenv->get_user();
2890 my $vmid = extract_param
($param, 'vmid');
2892 my $snapname = extract_param
($param, 'snapname');
2894 return undef if !defined($param->{description
});
2896 my $updatefn = sub {
2898 my $conf = PVE
::QemuServer
::load_config
($vmid);
2900 PVE
::QemuServer
::check_lock
($conf);
2902 my $snap = $conf->{snapshots
}->{$snapname};
2904 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2906 $snap->{description
} = $param->{description
} if defined($param->{description
});
2908 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2911 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2916 __PACKAGE__-
>register_method({
2917 name
=> 'get_snapshot_config',
2918 path
=> '{vmid}/snapshot/{snapname}/config',
2921 description
=> "Get snapshot configuration",
2923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2926 additionalProperties
=> 0,
2928 node
=> get_standard_option
('pve-node'),
2929 vmid
=> get_standard_option
('pve-vmid'),
2930 snapname
=> get_standard_option
('pve-snapshot-name'),
2933 returns
=> { type
=> "object" },
2937 my $rpcenv = PVE
::RPCEnvironment
::get
();
2939 my $authuser = $rpcenv->get_user();
2941 my $vmid = extract_param
($param, 'vmid');
2943 my $snapname = extract_param
($param, 'snapname');
2945 my $conf = PVE
::QemuServer
::load_config
($vmid);
2947 my $snap = $conf->{snapshots
}->{$snapname};
2949 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2954 __PACKAGE__-
>register_method({
2956 path
=> '{vmid}/snapshot/{snapname}/rollback',
2960 description
=> "Rollback VM state to specified snapshot.",
2962 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2965 additionalProperties
=> 0,
2967 node
=> get_standard_option
('pve-node'),
2968 vmid
=> get_standard_option
('pve-vmid'),
2969 snapname
=> get_standard_option
('pve-snapshot-name'),
2974 description
=> "the task ID.",
2979 my $rpcenv = PVE
::RPCEnvironment
::get
();
2981 my $authuser = $rpcenv->get_user();
2983 my $node = extract_param
($param, 'node');
2985 my $vmid = extract_param
($param, 'vmid');
2987 my $snapname = extract_param
($param, 'snapname');
2990 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2991 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2994 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2997 __PACKAGE__-
>register_method({
2998 name
=> 'delsnapshot',
2999 path
=> '{vmid}/snapshot/{snapname}',
3003 description
=> "Delete a VM snapshot.",
3005 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3008 additionalProperties
=> 0,
3010 node
=> get_standard_option
('pve-node'),
3011 vmid
=> get_standard_option
('pve-vmid'),
3012 snapname
=> get_standard_option
('pve-snapshot-name'),
3016 description
=> "For removal from config file, even if removing disk snapshots fails.",
3022 description
=> "the task ID.",
3027 my $rpcenv = PVE
::RPCEnvironment
::get
();
3029 my $authuser = $rpcenv->get_user();
3031 my $node = extract_param
($param, 'node');
3033 my $vmid = extract_param
($param, 'vmid');
3035 my $snapname = extract_param
($param, 'snapname');
3038 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3039 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3042 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3045 __PACKAGE__-
>register_method({
3047 path
=> '{vmid}/template',
3051 description
=> "Create a Template.",
3053 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3054 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3057 additionalProperties
=> 0,
3059 node
=> get_standard_option
('pve-node'),
3060 vmid
=> get_standard_option
('pve-vmid'),
3064 description
=> "If you want to convert only 1 disk to base image.",
3065 enum
=> [PVE
::QemuServer
::disknames
()],
3070 returns
=> { type
=> 'null'},
3074 my $rpcenv = PVE
::RPCEnvironment
::get
();
3076 my $authuser = $rpcenv->get_user();
3078 my $node = extract_param
($param, 'node');
3080 my $vmid = extract_param
($param, 'vmid');
3082 my $disk = extract_param
($param, 'disk');
3084 my $updatefn = sub {
3086 my $conf = PVE
::QemuServer
::load_config
($vmid);
3088 PVE
::QemuServer
::check_lock
($conf);
3090 die "unable to create template, because VM contains snapshots\n"
3091 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3093 die "you can't convert a template to a template\n"
3094 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3096 die "you can't convert a VM to template if VM is running\n"
3097 if PVE
::QemuServer
::check_running
($vmid);
3100 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3103 $conf->{template
} = 1;
3104 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3106 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3109 PVE
::QemuServer
::lock_config
($vmid, $updatefn);