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 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2287 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2290 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2291 # Aquire exclusive lock lock for $newid
2292 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2297 __PACKAGE__-
>register_method({
2298 name
=> 'move_vm_disk',
2299 path
=> '{vmid}/move_disk',
2303 description
=> "Move volume to different storage.",
2305 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2306 "and 'Datastore.AllocateSpace' permissions on the storage.",
2309 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2310 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2314 additionalProperties
=> 0,
2316 node
=> get_standard_option
('pve-node'),
2317 vmid
=> get_standard_option
('pve-vmid'),
2320 description
=> "The disk you want to move.",
2321 enum
=> [ PVE
::QemuServer
::disknames
() ],
2323 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2326 description
=> "Target Format.",
2327 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2332 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2338 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2346 description
=> "the task ID.",
2351 my $rpcenv = PVE
::RPCEnvironment
::get
();
2353 my $authuser = $rpcenv->get_user();
2355 my $node = extract_param
($param, 'node');
2357 my $vmid = extract_param
($param, 'vmid');
2359 my $digest = extract_param
($param, 'digest');
2361 my $disk = extract_param
($param, 'disk');
2363 my $storeid = extract_param
($param, 'storage');
2365 my $format = extract_param
($param, 'format');
2367 my $storecfg = PVE
::Storage
::config
();
2369 my $updatefn = sub {
2371 my $conf = PVE
::QemuServer
::load_config
($vmid);
2373 die "checksum missmatch (file change by other user?)\n"
2374 if $digest && $digest ne $conf->{digest
};
2376 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2378 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2380 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2382 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2385 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2386 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2390 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2391 (!$format || !$oldfmt || $oldfmt eq $format);
2393 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2395 my $running = PVE
::QemuServer
::check_running
($vmid);
2397 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2401 my $newvollist = [];
2404 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2406 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2407 $vmid, $storeid, $format, 1, $newvollist);
2409 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2411 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2413 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2416 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2417 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2424 foreach my $volid (@$newvollist) {
2425 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2428 die "storage migration failed: $err";
2431 if ($param->{delete}) {
2432 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2433 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2434 if ($used_paths->{$path}){
2435 warn "volume $old_volid have snapshots. Can't delete it\n";
2436 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2437 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2439 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2445 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2448 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2451 __PACKAGE__-
>register_method({
2452 name
=> 'migrate_vm',
2453 path
=> '{vmid}/migrate',
2457 description
=> "Migrate virtual machine. Creates a new migration task.",
2459 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2462 additionalProperties
=> 0,
2464 node
=> get_standard_option
('pve-node'),
2465 vmid
=> get_standard_option
('pve-vmid'),
2466 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2469 description
=> "Use online/live migration.",
2474 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2481 description
=> "the task ID.",
2486 my $rpcenv = PVE
::RPCEnvironment
::get
();
2488 my $authuser = $rpcenv->get_user();
2490 my $target = extract_param
($param, 'target');
2492 my $localnode = PVE
::INotify
::nodename
();
2493 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2495 PVE
::Cluster
::check_cfs_quorum
();
2497 PVE
::Cluster
::check_node_exists
($target);
2499 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2501 my $vmid = extract_param
($param, 'vmid');
2503 raise_param_exc
({ force
=> "Only root may use this option." })
2504 if $param->{force
} && $authuser ne 'root@pam';
2507 my $conf = PVE
::QemuServer
::load_config
($vmid);
2509 # try to detect errors early
2511 PVE
::QemuServer
::check_lock
($conf);
2513 if (PVE
::QemuServer
::check_running
($vmid)) {
2514 die "cant migrate running VM without --online\n"
2515 if !$param->{online
};
2518 my $storecfg = PVE
::Storage
::config
();
2519 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2521 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2526 my $service = "vm:$vmid";
2528 my $cmd = ['ha-manager', 'migrate', $service, $target];
2530 print "Executing HA migrate for VM $vmid to node $target\n";
2532 PVE
::Tools
::run_command
($cmd);
2537 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2544 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2547 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2552 __PACKAGE__-
>register_method({
2554 path
=> '{vmid}/monitor',
2558 description
=> "Execute Qemu monitor commands.",
2560 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2563 additionalProperties
=> 0,
2565 node
=> get_standard_option
('pve-node'),
2566 vmid
=> get_standard_option
('pve-vmid'),
2569 description
=> "The monitor command.",
2573 returns
=> { type
=> 'string'},
2577 my $vmid = $param->{vmid
};
2579 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2583 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2585 $res = "ERROR: $@" if $@;
2590 __PACKAGE__-
>register_method({
2591 name
=> 'resize_vm',
2592 path
=> '{vmid}/resize',
2596 description
=> "Extend volume size.",
2598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2601 additionalProperties
=> 0,
2603 node
=> get_standard_option
('pve-node'),
2604 vmid
=> get_standard_option
('pve-vmid'),
2605 skiplock
=> get_standard_option
('skiplock'),
2608 description
=> "The disk you want to resize.",
2609 enum
=> [PVE
::QemuServer
::disknames
()],
2613 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2614 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.",
2618 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2624 returns
=> { type
=> 'null'},
2628 my $rpcenv = PVE
::RPCEnvironment
::get
();
2630 my $authuser = $rpcenv->get_user();
2632 my $node = extract_param
($param, 'node');
2634 my $vmid = extract_param
($param, 'vmid');
2636 my $digest = extract_param
($param, 'digest');
2638 my $disk = extract_param
($param, 'disk');
2640 my $sizestr = extract_param
($param, 'size');
2642 my $skiplock = extract_param
($param, 'skiplock');
2643 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2644 if $skiplock && $authuser ne 'root@pam';
2646 my $storecfg = PVE
::Storage
::config
();
2648 my $updatefn = sub {
2650 my $conf = PVE
::QemuServer
::load_config
($vmid);
2652 die "checksum missmatch (file change by other user?)\n"
2653 if $digest && $digest ne $conf->{digest
};
2654 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2656 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2658 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2660 my (undef, undef, undef, undef, undef, undef, $format) =
2661 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2663 die "can't resize volume: $disk if snapshot exists\n"
2664 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2666 my $volid = $drive->{file
};
2668 die "disk '$disk' has no associated volume\n" if !$volid;
2670 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2672 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2674 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2676 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2678 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2679 my ($ext, $newsize, $unit) = ($1, $2, $4);
2682 $newsize = $newsize * 1024;
2683 } elsif ($unit eq 'M') {
2684 $newsize = $newsize * 1024 * 1024;
2685 } elsif ($unit eq 'G') {
2686 $newsize = $newsize * 1024 * 1024 * 1024;
2687 } elsif ($unit eq 'T') {
2688 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2691 $newsize += $size if $ext;
2692 $newsize = int($newsize);
2694 die "unable to skrink disk size\n" if $newsize < $size;
2696 return if $size == $newsize;
2698 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2700 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2702 $drive->{size
} = $newsize;
2703 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2705 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2708 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2712 __PACKAGE__-
>register_method({
2713 name
=> 'snapshot_list',
2714 path
=> '{vmid}/snapshot',
2716 description
=> "List all snapshots.",
2718 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2721 protected
=> 1, # qemu pid files are only readable by root
2723 additionalProperties
=> 0,
2725 vmid
=> get_standard_option
('pve-vmid'),
2726 node
=> get_standard_option
('pve-node'),
2735 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2740 my $vmid = $param->{vmid
};
2742 my $conf = PVE
::QemuServer
::load_config
($vmid);
2743 my $snaphash = $conf->{snapshots
} || {};
2747 foreach my $name (keys %$snaphash) {
2748 my $d = $snaphash->{$name};
2751 snaptime
=> $d->{snaptime
} || 0,
2752 vmstate
=> $d->{vmstate
} ?
1 : 0,
2753 description
=> $d->{description
} || '',
2755 $item->{parent
} = $d->{parent
} if $d->{parent
};
2756 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2760 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2761 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2762 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2764 push @$res, $current;
2769 __PACKAGE__-
>register_method({
2771 path
=> '{vmid}/snapshot',
2775 description
=> "Snapshot a VM.",
2777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2780 additionalProperties
=> 0,
2782 node
=> get_standard_option
('pve-node'),
2783 vmid
=> get_standard_option
('pve-vmid'),
2784 snapname
=> get_standard_option
('pve-snapshot-name'),
2788 description
=> "Save the vmstate",
2793 description
=> "A textual description or comment.",
2799 description
=> "the task ID.",
2804 my $rpcenv = PVE
::RPCEnvironment
::get
();
2806 my $authuser = $rpcenv->get_user();
2808 my $node = extract_param
($param, 'node');
2810 my $vmid = extract_param
($param, 'vmid');
2812 my $snapname = extract_param
($param, 'snapname');
2814 die "unable to use snapshot name 'current' (reserved name)\n"
2815 if $snapname eq 'current';
2818 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2819 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2820 $param->{description
});
2823 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2826 __PACKAGE__-
>register_method({
2827 name
=> 'snapshot_cmd_idx',
2828 path
=> '{vmid}/snapshot/{snapname}',
2835 additionalProperties
=> 0,
2837 vmid
=> get_standard_option
('pve-vmid'),
2838 node
=> get_standard_option
('pve-node'),
2839 snapname
=> get_standard_option
('pve-snapshot-name'),
2848 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2855 push @$res, { cmd
=> 'rollback' };
2856 push @$res, { cmd
=> 'config' };
2861 __PACKAGE__-
>register_method({
2862 name
=> 'update_snapshot_config',
2863 path
=> '{vmid}/snapshot/{snapname}/config',
2867 description
=> "Update snapshot metadata.",
2869 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2872 additionalProperties
=> 0,
2874 node
=> get_standard_option
('pve-node'),
2875 vmid
=> get_standard_option
('pve-vmid'),
2876 snapname
=> get_standard_option
('pve-snapshot-name'),
2880 description
=> "A textual description or comment.",
2884 returns
=> { type
=> 'null' },
2888 my $rpcenv = PVE
::RPCEnvironment
::get
();
2890 my $authuser = $rpcenv->get_user();
2892 my $vmid = extract_param
($param, 'vmid');
2894 my $snapname = extract_param
($param, 'snapname');
2896 return undef if !defined($param->{description
});
2898 my $updatefn = sub {
2900 my $conf = PVE
::QemuServer
::load_config
($vmid);
2902 PVE
::QemuServer
::check_lock
($conf);
2904 my $snap = $conf->{snapshots
}->{$snapname};
2906 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2908 $snap->{description
} = $param->{description
} if defined($param->{description
});
2910 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2913 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2918 __PACKAGE__-
>register_method({
2919 name
=> 'get_snapshot_config',
2920 path
=> '{vmid}/snapshot/{snapname}/config',
2923 description
=> "Get snapshot configuration",
2925 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2928 additionalProperties
=> 0,
2930 node
=> get_standard_option
('pve-node'),
2931 vmid
=> get_standard_option
('pve-vmid'),
2932 snapname
=> get_standard_option
('pve-snapshot-name'),
2935 returns
=> { type
=> "object" },
2939 my $rpcenv = PVE
::RPCEnvironment
::get
();
2941 my $authuser = $rpcenv->get_user();
2943 my $vmid = extract_param
($param, 'vmid');
2945 my $snapname = extract_param
($param, 'snapname');
2947 my $conf = PVE
::QemuServer
::load_config
($vmid);
2949 my $snap = $conf->{snapshots
}->{$snapname};
2951 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2956 __PACKAGE__-
>register_method({
2958 path
=> '{vmid}/snapshot/{snapname}/rollback',
2962 description
=> "Rollback VM state to specified snapshot.",
2964 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2967 additionalProperties
=> 0,
2969 node
=> get_standard_option
('pve-node'),
2970 vmid
=> get_standard_option
('pve-vmid'),
2971 snapname
=> get_standard_option
('pve-snapshot-name'),
2976 description
=> "the task ID.",
2981 my $rpcenv = PVE
::RPCEnvironment
::get
();
2983 my $authuser = $rpcenv->get_user();
2985 my $node = extract_param
($param, 'node');
2987 my $vmid = extract_param
($param, 'vmid');
2989 my $snapname = extract_param
($param, 'snapname');
2992 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2993 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2996 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2999 __PACKAGE__-
>register_method({
3000 name
=> 'delsnapshot',
3001 path
=> '{vmid}/snapshot/{snapname}',
3005 description
=> "Delete a VM snapshot.",
3007 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3010 additionalProperties
=> 0,
3012 node
=> get_standard_option
('pve-node'),
3013 vmid
=> get_standard_option
('pve-vmid'),
3014 snapname
=> get_standard_option
('pve-snapshot-name'),
3018 description
=> "For removal from config file, even if removing disk snapshots fails.",
3024 description
=> "the task ID.",
3029 my $rpcenv = PVE
::RPCEnvironment
::get
();
3031 my $authuser = $rpcenv->get_user();
3033 my $node = extract_param
($param, 'node');
3035 my $vmid = extract_param
($param, 'vmid');
3037 my $snapname = extract_param
($param, 'snapname');
3040 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3041 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3044 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3047 __PACKAGE__-
>register_method({
3049 path
=> '{vmid}/template',
3053 description
=> "Create a Template.",
3055 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3056 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3059 additionalProperties
=> 0,
3061 node
=> get_standard_option
('pve-node'),
3062 vmid
=> get_standard_option
('pve-vmid'),
3066 description
=> "If you want to convert only 1 disk to base image.",
3067 enum
=> [PVE
::QemuServer
::disknames
()],
3072 returns
=> { type
=> 'null'},
3076 my $rpcenv = PVE
::RPCEnvironment
::get
();
3078 my $authuser = $rpcenv->get_user();
3080 my $node = extract_param
($param, 'node');
3082 my $vmid = extract_param
($param, 'vmid');
3084 my $disk = extract_param
($param, 'disk');
3086 my $updatefn = sub {
3088 my $conf = PVE
::QemuServer
::load_config
($vmid);
3090 PVE
::QemuServer
::check_lock
($conf);
3092 die "unable to create template, because VM contains snapshots\n"
3093 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3095 die "you can't convert a template to a template\n"
3096 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3098 die "you can't convert a VM to template if VM is running\n"
3099 if PVE
::QemuServer
::check_running
($vmid);
3102 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3105 $conf->{template
} = 1;
3106 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3108 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3111 PVE
::QemuServer
::lock_config
($vmid, $updatefn);