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
;
22 use PVE
::API2
::Firewall
::VM
;
24 use Data
::Dumper
; # fixme: remove
26 use base
qw(PVE::RESTHandler);
28 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.";
30 my $resolve_cdrom_alias = sub {
33 if (my $value = $param->{cdrom
}) {
34 $value .= ",media=cdrom" if $value !~ m/media=/;
35 $param->{ide2
} = $value;
36 delete $param->{cdrom
};
40 my $test_deallocate_drive = sub {
41 my ($storecfg, $vmid, $key, $drive, $force) = @_;
43 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
44 my $volid = $drive->{file
};
45 if ( PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
46 if ($force || $key =~ m/^unused/) {
47 my $sid = PVE
::Storage
::parse_volume_id
($volid);
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
75 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
80 my $check_storage_access_clone = sub {
81 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
85 PVE
::QemuServer
::foreach_drive
($conf, sub {
86 my ($ds, $drive) = @_;
88 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
90 my $volid = $drive->{file
};
92 return if !$volid || $volid eq 'none';
95 if ($volid eq 'cdrom') {
96 $rpcenv->check($authuser, "/", ['Sys.Console']);
98 # we simply allow access
99 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
100 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
101 $sharedvm = 0 if !$scfg->{shared
};
105 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
106 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared
};
109 $sid = $storage if $storage;
110 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
117 # Note: $pool is only needed when creating a VM, because pool permissions
118 # are automatically inherited if VM already exists inside a pool.
119 my $create_disks = sub {
120 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
125 PVE
::QemuServer
::foreach_drive
($settings, sub {
126 my ($ds, $disk) = @_;
128 my $volid = $disk->{file
};
130 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
131 delete $disk->{size
};
132 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
134 my ($storeid, $size) = ($2 || $default_storage, $3);
135 die "no storage ID specified (and no default storage)\n" if !$storeid;
136 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
137 my $fmt = $disk->{format
} || $defformat;
138 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
139 $fmt, undef, $size*1024*1024);
140 $disk->{file
} = $volid;
141 $disk->{size
} = $size*1024*1024*1024;
142 push @$vollist, $volid;
143 delete $disk->{format
}; # no longer needed
144 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
149 my $volid_is_new = 1;
152 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
153 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
158 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
160 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
162 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
164 die "volume $volid does not exists\n" if !$size;
166 $disk->{size
} = $size;
169 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
173 # free allocated images on error
175 syslog
('err', "VM $vmid creating disks failed");
176 foreach my $volid (@$vollist) {
177 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
183 # modify vm config if everything went well
184 foreach my $ds (keys %$res) {
185 $conf->{$ds} = $res->{$ds};
191 my $delete_drive = sub {
192 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
194 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
195 my $volid = $drive->{file
};
197 if (PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
198 if ($force || $key =~ m/^unused/) {
200 # check if the disk is really unused
201 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
202 my $path = PVE
::Storage
::path
($storecfg, $volid);
204 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
205 if $used_paths->{$path};
207 PVE
::Storage
::vdisk_free
($storecfg, $volid);
211 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
216 delete $conf->{$key};
219 my $check_vm_modify_config_perm = sub {
220 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
222 return 1 if $authuser eq 'root@pam';
224 foreach my $opt (@$key_list) {
225 # disk checks need to be done somewhere else
226 next if PVE
::QemuServer
::valid_drivename
($opt);
228 if ($opt eq 'sockets' || $opt eq 'cores' ||
229 $opt eq 'cpu' || $opt eq 'smp' ||
230 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
231 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
232 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
233 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
234 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
235 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
236 } elsif ($opt eq 'args' || $opt eq 'lock') {
237 die "only root can set '$opt' config\n";
238 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
239 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
240 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
241 } elsif ($opt =~ m/^net\d+$/) {
242 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
244 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
251 __PACKAGE__-
>register_method({
255 description
=> "Virtual machine index (per node).",
257 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
261 protected
=> 1, # qemu pid files are only readable by root
263 additionalProperties
=> 0,
265 node
=> get_standard_option
('pve-node'),
274 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
279 my $rpcenv = PVE
::RPCEnvironment
::get
();
280 my $authuser = $rpcenv->get_user();
282 my $vmstatus = PVE
::QemuServer
::vmstatus
();
285 foreach my $vmid (keys %$vmstatus) {
286 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
288 my $data = $vmstatus->{$vmid};
289 $data->{vmid
} = int($vmid);
298 __PACKAGE__-
>register_method({
302 description
=> "Create or restore a virtual machine.",
304 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
305 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
306 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
307 user
=> 'all', # check inside
312 additionalProperties
=> 0,
313 properties
=> PVE
::QemuServer
::json_config_properties
(
315 node
=> get_standard_option
('pve-node'),
316 vmid
=> get_standard_option
('pve-vmid'),
318 description
=> "The backup file.",
323 storage
=> get_standard_option
('pve-storage-id', {
324 description
=> "Default storage.",
330 description
=> "Allow to overwrite existing VM.",
331 requires
=> 'archive',
336 description
=> "Assign a unique random ethernet address.",
337 requires
=> 'archive',
341 type
=> 'string', format
=> 'pve-poolid',
342 description
=> "Add the VM to the specified pool.",
352 my $rpcenv = PVE
::RPCEnvironment
::get
();
354 my $authuser = $rpcenv->get_user();
356 my $node = extract_param
($param, 'node');
358 my $vmid = extract_param
($param, 'vmid');
360 my $archive = extract_param
($param, 'archive');
362 my $storage = extract_param
($param, 'storage');
364 my $force = extract_param
($param, 'force');
366 my $unique = extract_param
($param, 'unique');
368 my $pool = extract_param
($param, 'pool');
370 my $filename = PVE
::QemuServer
::config_file
($vmid);
372 my $storecfg = PVE
::Storage
::config
();
374 PVE
::Cluster
::check_cfs_quorum
();
376 if (defined($pool)) {
377 $rpcenv->check_pool_exist($pool);
380 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
381 if defined($storage);
383 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
385 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
387 } elsif ($archive && $force && (-f
$filename) &&
388 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
389 # OK: user has VM.Backup permissions, and want to restore an existing VM
395 &$resolve_cdrom_alias($param);
397 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
399 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
401 foreach my $opt (keys %$param) {
402 if (PVE
::QemuServer
::valid_drivename
($opt)) {
403 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
404 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
406 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
407 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
411 PVE
::QemuServer
::add_random_macs
($param);
413 my $keystr = join(' ', keys %$param);
414 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
416 if ($archive eq '-') {
417 die "pipe requires cli environment\n"
418 if $rpcenv->{type
} ne 'cli';
420 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
421 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
425 my $restorefn = sub {
427 # fixme: this test does not work if VM exists on other node!
429 die "unable to restore vm $vmid: config file already exists\n"
432 die "unable to restore vm $vmid: vm is running\n"
433 if PVE
::QemuServer
::check_running
($vmid);
437 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
440 unique
=> $unique });
442 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
445 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
451 die "unable to create vm $vmid: config file already exists\n"
462 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
464 # try to be smart about bootdisk
465 my @disks = PVE
::QemuServer
::disknames
();
467 foreach my $ds (reverse @disks) {
468 next if !$conf->{$ds};
469 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
470 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
474 if (!$conf->{bootdisk
} && $firstdisk) {
475 $conf->{bootdisk
} = $firstdisk;
478 # auto generate uuid if user did not specify smbios1 option
479 if (!$conf->{smbios1
}) {
480 my ($uuid, $uuid_str);
481 UUID
::generate
($uuid);
482 UUID
::unparse
($uuid, $uuid_str);
483 $conf->{smbios1
} = "uuid=$uuid_str";
486 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
492 foreach my $volid (@$vollist) {
493 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
496 die "create failed - $err";
499 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
502 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
505 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
508 __PACKAGE__-
>register_method({
513 description
=> "Directory index",
518 additionalProperties
=> 0,
520 node
=> get_standard_option
('pve-node'),
521 vmid
=> get_standard_option
('pve-vmid'),
529 subdir
=> { type
=> 'string' },
532 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
538 { subdir
=> 'config' },
539 { subdir
=> 'pending' },
540 { subdir
=> 'status' },
541 { subdir
=> 'unlink' },
542 { subdir
=> 'vncproxy' },
543 { subdir
=> 'migrate' },
544 { subdir
=> 'resize' },
545 { subdir
=> 'move' },
547 { subdir
=> 'rrddata' },
548 { subdir
=> 'monitor' },
549 { subdir
=> 'snapshot' },
550 { subdir
=> 'spiceproxy' },
551 { subdir
=> 'sendkey' },
552 { subdir
=> 'firewall' },
558 __PACKAGE__-
>register_method ({
559 subclass
=> "PVE::API2::Firewall::VM",
560 path
=> '{vmid}/firewall',
563 __PACKAGE__-
>register_method({
565 path
=> '{vmid}/rrd',
567 protected
=> 1, # fixme: can we avoid that?
569 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
571 description
=> "Read VM RRD statistics (returns PNG)",
573 additionalProperties
=> 0,
575 node
=> get_standard_option
('pve-node'),
576 vmid
=> get_standard_option
('pve-vmid'),
578 description
=> "Specify the time frame you are interested in.",
580 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
583 description
=> "The list of datasources you want to display.",
584 type
=> 'string', format
=> 'pve-configid-list',
587 description
=> "The RRD consolidation function",
589 enum
=> [ 'AVERAGE', 'MAX' ],
597 filename
=> { type
=> 'string' },
603 return PVE
::Cluster
::create_rrd_graph
(
604 "pve2-vm/$param->{vmid}", $param->{timeframe
},
605 $param->{ds
}, $param->{cf
});
609 __PACKAGE__-
>register_method({
611 path
=> '{vmid}/rrddata',
613 protected
=> 1, # fixme: can we avoid that?
615 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
617 description
=> "Read VM RRD statistics",
619 additionalProperties
=> 0,
621 node
=> get_standard_option
('pve-node'),
622 vmid
=> get_standard_option
('pve-vmid'),
624 description
=> "Specify the time frame you are interested in.",
626 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
629 description
=> "The RRD consolidation function",
631 enum
=> [ 'AVERAGE', 'MAX' ],
646 return PVE
::Cluster
::create_rrd_data
(
647 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
651 __PACKAGE__-
>register_method({
653 path
=> '{vmid}/config',
656 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
658 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
661 additionalProperties
=> 0,
663 node
=> get_standard_option
('pve-node'),
664 vmid
=> get_standard_option
('pve-vmid'),
672 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
679 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
681 delete $conf->{snapshots
};
682 delete $conf->{pending
};
687 __PACKAGE__-
>register_method({
688 name
=> 'vm_pending',
689 path
=> '{vmid}/pending',
692 description
=> "Get virtual machine configuration, including pending changes.",
694 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
697 additionalProperties
=> 0,
699 node
=> get_standard_option
('pve-node'),
700 vmid
=> get_standard_option
('pve-vmid'),
709 description
=> "Configuration option name.",
713 description
=> "Current value.",
718 description
=> "Pending value.",
723 description
=> "Indicated a pending delete request.",
733 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
735 my $pending_delete_hash = {};
736 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
737 $pending_delete_hash->{$opt} = 1;
742 foreach my $opt (keys $conf) {
743 next if ref($conf->{$opt});
744 my $item = { key
=> $opt };
745 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
746 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
747 $item->{delete} = 1 if $pending_delete_hash->{$opt};
751 foreach my $opt (keys $conf->{pending
}) {
752 next if $opt eq 'delete';
753 next if ref($conf->{pending
}->{$opt}); # just to be sure
754 next if $conf->{$opt};
755 my $item = { key
=> $opt };
756 $item->{pending
} = $conf->{pending
}->{$opt};
760 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
761 next if $conf->{pending
}->{$opt}; # just to be sure
762 next if $conf->{$opt};
763 my $item = { key
=> $opt, delete => 1};
770 # POST/PUT {vmid}/config implementation
772 # The original API used PUT (idempotent) an we assumed that all operations
773 # are fast. But it turned out that almost any configuration change can
774 # involve hot-plug actions, or disk alloc/free. Such actions can take long
775 # time to complete and have side effects (not idempotent).
777 # The new implementation uses POST and forks a worker process. We added
778 # a new option 'background_delay'. If specified we wait up to
779 # 'background_delay' second for the worker task to complete. It returns null
780 # if the task is finished within that time, else we return the UPID.
782 my $update_vm_api = sub {
783 my ($param, $sync) = @_;
785 my $rpcenv = PVE
::RPCEnvironment
::get
();
787 my $authuser = $rpcenv->get_user();
789 my $node = extract_param
($param, 'node');
791 my $vmid = extract_param
($param, 'vmid');
793 my $digest = extract_param
($param, 'digest');
795 my $background_delay = extract_param
($param, 'background_delay');
797 my @paramarr = (); # used for log message
798 foreach my $key (keys %$param) {
799 push @paramarr, "-$key", $param->{$key};
802 my $skiplock = extract_param
($param, 'skiplock');
803 raise_param_exc
({ skiplock
=> "Only root may use this option." })
804 if $skiplock && $authuser ne 'root@pam';
806 my $delete_str = extract_param
($param, 'delete');
808 my $force = extract_param
($param, 'force');
810 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
812 my $storecfg = PVE
::Storage
::config
();
814 my $defaults = PVE
::QemuServer
::load_defaults
();
816 &$resolve_cdrom_alias($param);
818 # now try to verify all parameters
821 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
822 $opt = 'ide2' if $opt eq 'cdrom';
823 raise_param_exc
({ delete => "you can't use '-$opt' and " .
824 "-delete $opt' at the same time" })
825 if defined($param->{$opt});
827 if (!PVE
::QemuServer
::option_exists
($opt)) {
828 raise_param_exc
({ delete => "unknown option '$opt'" });
834 foreach my $opt (keys %$param) {
835 if (PVE
::QemuServer
::valid_drivename
($opt)) {
837 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
838 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
839 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
840 } elsif ($opt =~ m/^net(\d+)$/) {
842 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
843 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
847 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
849 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
851 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
855 my $conf = PVE
::QemuServer
::load_config
($vmid);
857 die "checksum missmatch (file change by other user?)\n"
858 if $digest && $digest ne $conf->{digest
};
860 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
862 if ($param->{memory
} || defined($param->{balloon
})) {
863 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
864 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
866 die "balloon value too large (must be smaller than assigned memory)\n"
867 if $balloon && $balloon > $maxmem;
870 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
874 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
876 # write updates to pending section
878 my $modified = {}; # record what $option we modify
880 foreach my $opt (@delete) {
881 $modified->{$opt} = 1;
882 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
883 if ($opt =~ m/^unused/) {
884 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
885 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
886 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
887 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
888 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
889 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
891 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
892 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
893 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
894 if defined($conf->{pending
}->{$opt});
895 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
896 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
898 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
899 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
903 foreach my $opt (keys %$param) { # add/change
904 $modified->{$opt} = 1;
905 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
906 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
908 if (PVE
::QemuServer
::valid_drivename
($opt)) {
909 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
910 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
911 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
913 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
915 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
916 if defined($conf->{pending
}->{$opt});
918 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
920 $conf->{pending
}->{$opt} = $param->{$opt};
922 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
923 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
926 # remove pending changes when nothing changed
927 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
928 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
929 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
931 return if !scalar(keys %{$conf->{pending
}});
933 my $running = PVE
::QemuServer
::check_running
($vmid);
935 # apply pending changes
937 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
941 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
942 raise_param_exc
($errors) if scalar(keys %$errors);
944 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
954 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
956 if ($background_delay) {
958 # Note: It would be better to do that in the Event based HTTPServer
959 # to avoid blocking call to sleep.
961 my $end_time = time() + $background_delay;
963 my $task = PVE
::Tools
::upid_decode
($upid);
966 while (time() < $end_time) {
967 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
969 sleep(1); # this gets interrupted when child process ends
973 my $status = PVE
::Tools
::upid_read_status
($upid);
974 return undef if $status eq 'OK';
983 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
986 my $vm_config_perm_list = [
996 __PACKAGE__-
>register_method({
997 name
=> 'update_vm_async',
998 path
=> '{vmid}/config',
1002 description
=> "Set virtual machine options (asynchrounous API).",
1004 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1007 additionalProperties
=> 0,
1008 properties
=> PVE
::QemuServer
::json_config_properties
(
1010 node
=> get_standard_option
('pve-node'),
1011 vmid
=> get_standard_option
('pve-vmid'),
1012 skiplock
=> get_standard_option
('skiplock'),
1014 type
=> 'string', format
=> 'pve-configid-list',
1015 description
=> "A list of settings you want to delete.",
1020 description
=> $opt_force_description,
1022 requires
=> 'delete',
1026 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1030 background_delay
=> {
1032 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1043 code
=> $update_vm_api,
1046 __PACKAGE__-
>register_method({
1047 name
=> 'update_vm',
1048 path
=> '{vmid}/config',
1052 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1054 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1057 additionalProperties
=> 0,
1058 properties
=> PVE
::QemuServer
::json_config_properties
(
1060 node
=> get_standard_option
('pve-node'),
1061 vmid
=> get_standard_option
('pve-vmid'),
1062 skiplock
=> get_standard_option
('skiplock'),
1064 type
=> 'string', format
=> 'pve-configid-list',
1065 description
=> "A list of settings you want to delete.",
1070 description
=> $opt_force_description,
1072 requires
=> 'delete',
1076 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1082 returns
=> { type
=> 'null' },
1085 &$update_vm_api($param, 1);
1091 __PACKAGE__-
>register_method({
1092 name
=> 'destroy_vm',
1097 description
=> "Destroy the vm (also delete all used/owned volumes).",
1099 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1102 additionalProperties
=> 0,
1104 node
=> get_standard_option
('pve-node'),
1105 vmid
=> get_standard_option
('pve-vmid'),
1106 skiplock
=> get_standard_option
('skiplock'),
1115 my $rpcenv = PVE
::RPCEnvironment
::get
();
1117 my $authuser = $rpcenv->get_user();
1119 my $vmid = $param->{vmid
};
1121 my $skiplock = $param->{skiplock
};
1122 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1123 if $skiplock && $authuser ne 'root@pam';
1126 my $conf = PVE
::QemuServer
::load_config
($vmid);
1128 my $storecfg = PVE
::Storage
::config
();
1130 my $delVMfromPoolFn = sub {
1131 my $usercfg = cfs_read_file
("user.cfg");
1132 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1133 if (my $data = $usercfg->{pools
}->{$pool}) {
1134 delete $data->{vms
}->{$vmid};
1135 delete $usercfg->{vms
}->{$vmid};
1136 cfs_write_file
("user.cfg", $usercfg);
1144 syslog
('info', "destroy VM $vmid: $upid\n");
1146 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1148 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1151 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1154 __PACKAGE__-
>register_method({
1156 path
=> '{vmid}/unlink',
1160 description
=> "Unlink/delete disk images.",
1162 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1165 additionalProperties
=> 0,
1167 node
=> get_standard_option
('pve-node'),
1168 vmid
=> get_standard_option
('pve-vmid'),
1170 type
=> 'string', format
=> 'pve-configid-list',
1171 description
=> "A list of disk IDs you want to delete.",
1175 description
=> $opt_force_description,
1180 returns
=> { type
=> 'null'},
1184 $param->{delete} = extract_param
($param, 'idlist');
1186 __PACKAGE__-
>update_vm($param);
1193 __PACKAGE__-
>register_method({
1195 path
=> '{vmid}/vncproxy',
1199 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1201 description
=> "Creates a TCP VNC proxy connections.",
1203 additionalProperties
=> 0,
1205 node
=> get_standard_option
('pve-node'),
1206 vmid
=> get_standard_option
('pve-vmid'),
1210 description
=> "starts websockify instead of vncproxy",
1215 additionalProperties
=> 0,
1217 user
=> { type
=> 'string' },
1218 ticket
=> { type
=> 'string' },
1219 cert
=> { type
=> 'string' },
1220 port
=> { type
=> 'integer' },
1221 upid
=> { type
=> 'string' },
1227 my $rpcenv = PVE
::RPCEnvironment
::get
();
1229 my $authuser = $rpcenv->get_user();
1231 my $vmid = $param->{vmid
};
1232 my $node = $param->{node
};
1233 my $websocket = $param->{websocket
};
1235 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1237 my $authpath = "/vms/$vmid";
1239 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1241 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1244 my $port = PVE
::Tools
::next_vnc_port
();
1249 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1250 $remip = PVE
::Cluster
::remote_node_ip
($node);
1251 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1252 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1260 syslog
('info', "starting vnc proxy $upid\n");
1264 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1266 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1268 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1269 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1270 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1271 '-timeout', $timeout, '-authpath', $authpath,
1272 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1275 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1277 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1279 my $qmstr = join(' ', @$qmcmd);
1281 # also redirect stderr (else we get RFB protocol errors)
1282 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1285 PVE
::Tools
::run_command
($cmd);
1290 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1292 PVE
::Tools
::wait_for_vnc_port
($port);
1303 __PACKAGE__-
>register_method({
1304 name
=> 'vncwebsocket',
1305 path
=> '{vmid}/vncwebsocket',
1308 description
=> "You also need to pass a valid ticket (vncticket).",
1309 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1311 description
=> "Opens a weksocket for VNC traffic.",
1313 additionalProperties
=> 0,
1315 node
=> get_standard_option
('pve-node'),
1316 vmid
=> get_standard_option
('pve-vmid'),
1318 description
=> "Ticket from previous call to vncproxy.",
1323 description
=> "Port number returned by previous vncproxy call.",
1333 port
=> { type
=> 'string' },
1339 my $rpcenv = PVE
::RPCEnvironment
::get
();
1341 my $authuser = $rpcenv->get_user();
1343 my $vmid = $param->{vmid
};
1344 my $node = $param->{node
};
1346 my $authpath = "/vms/$vmid";
1348 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1350 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1352 # Note: VNC ports are acessible from outside, so we do not gain any
1353 # security if we verify that $param->{port} belongs to VM $vmid. This
1354 # check is done by verifying the VNC ticket (inside VNC protocol).
1356 my $port = $param->{port
};
1358 return { port
=> $port };
1361 __PACKAGE__-
>register_method({
1362 name
=> 'spiceproxy',
1363 path
=> '{vmid}/spiceproxy',
1368 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1370 description
=> "Returns a SPICE configuration to connect to the VM.",
1372 additionalProperties
=> 0,
1374 node
=> get_standard_option
('pve-node'),
1375 vmid
=> get_standard_option
('pve-vmid'),
1376 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1379 returns
=> get_standard_option
('remote-viewer-config'),
1383 my $rpcenv = PVE
::RPCEnvironment
::get
();
1385 my $authuser = $rpcenv->get_user();
1387 my $vmid = $param->{vmid
};
1388 my $node = $param->{node
};
1389 my $proxy = $param->{proxy
};
1391 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1392 my $title = "VM $vmid - $conf->{'name'}",
1394 my $port = PVE
::QemuServer
::spice_port
($vmid);
1396 my ($ticket, undef, $remote_viewer_config) =
1397 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1399 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1400 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1402 return $remote_viewer_config;
1405 __PACKAGE__-
>register_method({
1407 path
=> '{vmid}/status',
1410 description
=> "Directory index",
1415 additionalProperties
=> 0,
1417 node
=> get_standard_option
('pve-node'),
1418 vmid
=> get_standard_option
('pve-vmid'),
1426 subdir
=> { type
=> 'string' },
1429 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1435 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1438 { subdir
=> 'current' },
1439 { subdir
=> 'start' },
1440 { subdir
=> 'stop' },
1446 my $vm_is_ha_managed = sub {
1449 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1450 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1456 __PACKAGE__-
>register_method({
1457 name
=> 'vm_status',
1458 path
=> '{vmid}/status/current',
1461 protected
=> 1, # qemu pid files are only readable by root
1462 description
=> "Get virtual machine status.",
1464 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1467 additionalProperties
=> 0,
1469 node
=> get_standard_option
('pve-node'),
1470 vmid
=> get_standard_option
('pve-vmid'),
1473 returns
=> { type
=> 'object' },
1478 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1480 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1481 my $status = $vmstatus->{$param->{vmid
}};
1483 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1485 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1490 __PACKAGE__-
>register_method({
1492 path
=> '{vmid}/status/start',
1496 description
=> "Start virtual machine.",
1498 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1501 additionalProperties
=> 0,
1503 node
=> get_standard_option
('pve-node'),
1504 vmid
=> get_standard_option
('pve-vmid'),
1505 skiplock
=> get_standard_option
('skiplock'),
1506 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1507 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1508 machine
=> get_standard_option
('pve-qm-machine'),
1517 my $rpcenv = PVE
::RPCEnvironment
::get
();
1519 my $authuser = $rpcenv->get_user();
1521 my $node = extract_param
($param, 'node');
1523 my $vmid = extract_param
($param, 'vmid');
1525 my $machine = extract_param
($param, 'machine');
1527 my $stateuri = extract_param
($param, 'stateuri');
1528 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1529 if $stateuri && $authuser ne 'root@pam';
1531 my $skiplock = extract_param
($param, 'skiplock');
1532 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1533 if $skiplock && $authuser ne 'root@pam';
1535 my $migratedfrom = extract_param
($param, 'migratedfrom');
1536 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1537 if $migratedfrom && $authuser ne 'root@pam';
1539 # read spice ticket from STDIN
1541 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1542 if (defined(my $line = <>)) {
1544 $spice_ticket = $line;
1548 my $storecfg = PVE
::Storage
::config
();
1550 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1551 $rpcenv->{type
} ne 'ha') {
1556 my $service = "pvevm:$vmid";
1558 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1560 print "Executing HA start for VM $vmid\n";
1562 PVE
::Tools
::run_command
($cmd);
1567 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1574 syslog
('info', "start VM $vmid: $upid\n");
1576 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1577 $machine, $spice_ticket);
1582 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1586 __PACKAGE__-
>register_method({
1588 path
=> '{vmid}/status/stop',
1592 description
=> "Stop virtual machine.",
1594 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1597 additionalProperties
=> 0,
1599 node
=> get_standard_option
('pve-node'),
1600 vmid
=> get_standard_option
('pve-vmid'),
1601 skiplock
=> get_standard_option
('skiplock'),
1602 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1604 description
=> "Wait maximal timeout seconds.",
1610 description
=> "Do not decativate storage volumes.",
1623 my $rpcenv = PVE
::RPCEnvironment
::get
();
1625 my $authuser = $rpcenv->get_user();
1627 my $node = extract_param
($param, 'node');
1629 my $vmid = extract_param
($param, 'vmid');
1631 my $skiplock = extract_param
($param, 'skiplock');
1632 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1633 if $skiplock && $authuser ne 'root@pam';
1635 my $keepActive = extract_param
($param, 'keepActive');
1636 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1637 if $keepActive && $authuser ne 'root@pam';
1639 my $migratedfrom = extract_param
($param, 'migratedfrom');
1640 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1641 if $migratedfrom && $authuser ne 'root@pam';
1644 my $storecfg = PVE
::Storage
::config
();
1646 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1651 my $service = "pvevm:$vmid";
1653 my $cmd = ['clusvcadm', '-d', $service];
1655 print "Executing HA stop for VM $vmid\n";
1657 PVE
::Tools
::run_command
($cmd);
1662 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1668 syslog
('info', "stop VM $vmid: $upid\n");
1670 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1671 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1676 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1680 __PACKAGE__-
>register_method({
1682 path
=> '{vmid}/status/reset',
1686 description
=> "Reset virtual machine.",
1688 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1691 additionalProperties
=> 0,
1693 node
=> get_standard_option
('pve-node'),
1694 vmid
=> get_standard_option
('pve-vmid'),
1695 skiplock
=> get_standard_option
('skiplock'),
1704 my $rpcenv = PVE
::RPCEnvironment
::get
();
1706 my $authuser = $rpcenv->get_user();
1708 my $node = extract_param
($param, 'node');
1710 my $vmid = extract_param
($param, 'vmid');
1712 my $skiplock = extract_param
($param, 'skiplock');
1713 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1714 if $skiplock && $authuser ne 'root@pam';
1716 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1721 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1726 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1729 __PACKAGE__-
>register_method({
1730 name
=> 'vm_shutdown',
1731 path
=> '{vmid}/status/shutdown',
1735 description
=> "Shutdown virtual machine.",
1737 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1740 additionalProperties
=> 0,
1742 node
=> get_standard_option
('pve-node'),
1743 vmid
=> get_standard_option
('pve-vmid'),
1744 skiplock
=> get_standard_option
('skiplock'),
1746 description
=> "Wait maximal timeout seconds.",
1752 description
=> "Make sure the VM stops.",
1758 description
=> "Do not decativate storage volumes.",
1771 my $rpcenv = PVE
::RPCEnvironment
::get
();
1773 my $authuser = $rpcenv->get_user();
1775 my $node = extract_param
($param, 'node');
1777 my $vmid = extract_param
($param, 'vmid');
1779 my $skiplock = extract_param
($param, 'skiplock');
1780 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1781 if $skiplock && $authuser ne 'root@pam';
1783 my $keepActive = extract_param
($param, 'keepActive');
1784 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1785 if $keepActive && $authuser ne 'root@pam';
1787 my $storecfg = PVE
::Storage
::config
();
1792 syslog
('info', "shutdown VM $vmid: $upid\n");
1794 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1795 1, $param->{forceStop
}, $keepActive);
1800 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1803 __PACKAGE__-
>register_method({
1804 name
=> 'vm_suspend',
1805 path
=> '{vmid}/status/suspend',
1809 description
=> "Suspend virtual machine.",
1811 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1814 additionalProperties
=> 0,
1816 node
=> get_standard_option
('pve-node'),
1817 vmid
=> get_standard_option
('pve-vmid'),
1818 skiplock
=> get_standard_option
('skiplock'),
1827 my $rpcenv = PVE
::RPCEnvironment
::get
();
1829 my $authuser = $rpcenv->get_user();
1831 my $node = extract_param
($param, 'node');
1833 my $vmid = extract_param
($param, 'vmid');
1835 my $skiplock = extract_param
($param, 'skiplock');
1836 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1837 if $skiplock && $authuser ne 'root@pam';
1839 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1844 syslog
('info', "suspend VM $vmid: $upid\n");
1846 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1851 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1854 __PACKAGE__-
>register_method({
1855 name
=> 'vm_resume',
1856 path
=> '{vmid}/status/resume',
1860 description
=> "Resume virtual machine.",
1862 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1865 additionalProperties
=> 0,
1867 node
=> get_standard_option
('pve-node'),
1868 vmid
=> get_standard_option
('pve-vmid'),
1869 skiplock
=> get_standard_option
('skiplock'),
1878 my $rpcenv = PVE
::RPCEnvironment
::get
();
1880 my $authuser = $rpcenv->get_user();
1882 my $node = extract_param
($param, 'node');
1884 my $vmid = extract_param
($param, 'vmid');
1886 my $skiplock = extract_param
($param, 'skiplock');
1887 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1888 if $skiplock && $authuser ne 'root@pam';
1890 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1895 syslog
('info', "resume VM $vmid: $upid\n");
1897 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1902 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1905 __PACKAGE__-
>register_method({
1906 name
=> 'vm_sendkey',
1907 path
=> '{vmid}/sendkey',
1911 description
=> "Send key event to virtual machine.",
1913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1916 additionalProperties
=> 0,
1918 node
=> get_standard_option
('pve-node'),
1919 vmid
=> get_standard_option
('pve-vmid'),
1920 skiplock
=> get_standard_option
('skiplock'),
1922 description
=> "The key (qemu monitor encoding).",
1927 returns
=> { type
=> 'null'},
1931 my $rpcenv = PVE
::RPCEnvironment
::get
();
1933 my $authuser = $rpcenv->get_user();
1935 my $node = extract_param
($param, 'node');
1937 my $vmid = extract_param
($param, 'vmid');
1939 my $skiplock = extract_param
($param, 'skiplock');
1940 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1941 if $skiplock && $authuser ne 'root@pam';
1943 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1948 __PACKAGE__-
>register_method({
1949 name
=> 'vm_feature',
1950 path
=> '{vmid}/feature',
1954 description
=> "Check if feature for virtual machine is available.",
1956 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1959 additionalProperties
=> 0,
1961 node
=> get_standard_option
('pve-node'),
1962 vmid
=> get_standard_option
('pve-vmid'),
1964 description
=> "Feature to check.",
1966 enum
=> [ 'snapshot', 'clone', 'copy' ],
1968 snapname
=> get_standard_option
('pve-snapshot-name', {
1976 hasFeature
=> { type
=> 'boolean' },
1979 items
=> { type
=> 'string' },
1986 my $node = extract_param
($param, 'node');
1988 my $vmid = extract_param
($param, 'vmid');
1990 my $snapname = extract_param
($param, 'snapname');
1992 my $feature = extract_param
($param, 'feature');
1994 my $running = PVE
::QemuServer
::check_running
($vmid);
1996 my $conf = PVE
::QemuServer
::load_config
($vmid);
1999 my $snap = $conf->{snapshots
}->{$snapname};
2000 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2003 my $storecfg = PVE
::Storage
::config
();
2005 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2006 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2009 hasFeature
=> $hasFeature,
2010 nodes
=> [ keys %$nodelist ],
2014 __PACKAGE__-
>register_method({
2016 path
=> '{vmid}/clone',
2020 description
=> "Create a copy of virtual machine/template.",
2022 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2023 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2024 "'Datastore.AllocateSpace' on any used storage.",
2027 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2029 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2030 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2035 additionalProperties
=> 0,
2037 node
=> get_standard_option
('pve-node'),
2038 vmid
=> get_standard_option
('pve-vmid'),
2039 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2042 type
=> 'string', format
=> 'dns-name',
2043 description
=> "Set a name for the new VM.",
2048 description
=> "Description for the new VM.",
2052 type
=> 'string', format
=> 'pve-poolid',
2053 description
=> "Add the new VM to the specified pool.",
2055 snapname
=> get_standard_option
('pve-snapshot-name', {
2058 storage
=> get_standard_option
('pve-storage-id', {
2059 description
=> "Target storage for full clone.",
2064 description
=> "Target format for file storage.",
2068 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2073 description
=> "Create a full copy of all disk. This is always done when " .
2074 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2077 target
=> get_standard_option
('pve-node', {
2078 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2089 my $rpcenv = PVE
::RPCEnvironment
::get
();
2091 my $authuser = $rpcenv->get_user();
2093 my $node = extract_param
($param, 'node');
2095 my $vmid = extract_param
($param, 'vmid');
2097 my $newid = extract_param
($param, 'newid');
2099 my $pool = extract_param
($param, 'pool');
2101 if (defined($pool)) {
2102 $rpcenv->check_pool_exist($pool);
2105 my $snapname = extract_param
($param, 'snapname');
2107 my $storage = extract_param
($param, 'storage');
2109 my $format = extract_param
($param, 'format');
2111 my $target = extract_param
($param, 'target');
2113 my $localnode = PVE
::INotify
::nodename
();
2115 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2117 PVE
::Cluster
::check_node_exists
($target) if $target;
2119 my $storecfg = PVE
::Storage
::config
();
2122 # check if storage is enabled on local node
2123 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2125 # check if storage is available on target node
2126 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2127 # clone only works if target storage is shared
2128 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2129 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2133 PVE
::Cluster
::check_cfs_quorum
();
2135 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2137 # exclusive lock if VM is running - else shared lock is enough;
2138 my $shared_lock = $running ?
0 : 1;
2142 # do all tests after lock
2143 # we also try to do all tests before we fork the worker
2145 my $conf = PVE
::QemuServer
::load_config
($vmid);
2147 PVE
::QemuServer
::check_lock
($conf);
2149 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2151 die "unexpected state change\n" if $verify_running != $running;
2153 die "snapshot '$snapname' does not exist\n"
2154 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2156 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2158 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2160 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2162 my $conffile = PVE
::QemuServer
::config_file
($newid);
2164 die "unable to create VM $newid: config file already exists\n"
2167 my $newconf = { lock => 'clone' };
2171 foreach my $opt (keys %$oldconf) {
2172 my $value = $oldconf->{$opt};
2174 # do not copy snapshot related info
2175 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2176 $opt eq 'vmstate' || $opt eq 'snapstate';
2178 # always change MAC! address
2179 if ($opt =~ m/^net(\d+)$/) {
2180 my $net = PVE
::QemuServer
::parse_net
($value);
2181 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2182 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2183 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2184 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2185 die "unable to parse drive options for '$opt'\n" if !$drive;
2186 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2187 $newconf->{$opt} = $value; # simply copy configuration
2189 if ($param->{full
}) {
2190 die "Full clone feature is not available"
2191 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2194 # not full means clone instead of copy
2195 die "Linked clone feature is not available"
2196 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2198 $drives->{$opt} = $drive;
2199 push @$vollist, $drive->{file
};
2202 # copy everything else
2203 $newconf->{$opt} = $value;
2207 # auto generate a new uuid
2208 my ($uuid, $uuid_str);
2209 UUID
::generate
($uuid);
2210 UUID
::unparse
($uuid, $uuid_str);
2211 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2212 $smbios1->{uuid
} = $uuid_str;
2213 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2215 delete $newconf->{template
};
2217 if ($param->{name
}) {
2218 $newconf->{name
} = $param->{name
};
2220 if ($oldconf->{name
}) {
2221 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2223 $newconf->{name
} = "Copy-of-VM-$vmid";
2227 if ($param->{description
}) {
2228 $newconf->{description
} = $param->{description
};
2231 # create empty/temp config - this fails if VM already exists on other node
2232 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2237 my $newvollist = [];
2240 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2242 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2244 foreach my $opt (keys %$drives) {
2245 my $drive = $drives->{$opt};
2247 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2248 $newid, $storage, $format, $drive->{full
}, $newvollist);
2250 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2252 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2255 delete $newconf->{lock};
2256 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2259 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2260 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2262 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2263 die "Failed to move config to node '$target' - rename failed: $!\n"
2264 if !rename($conffile, $newconffile);
2267 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2272 sleep 1; # some storage like rbd need to wait before release volume - really?
2274 foreach my $volid (@$newvollist) {
2275 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2278 die "clone failed: $err";
2284 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2287 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2288 # Aquire exclusive lock lock for $newid
2289 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2294 __PACKAGE__-
>register_method({
2295 name
=> 'move_vm_disk',
2296 path
=> '{vmid}/move_disk',
2300 description
=> "Move volume to different storage.",
2302 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2303 "and 'Datastore.AllocateSpace' permissions on the storage.",
2306 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2307 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2311 additionalProperties
=> 0,
2313 node
=> get_standard_option
('pve-node'),
2314 vmid
=> get_standard_option
('pve-vmid'),
2317 description
=> "The disk you want to move.",
2318 enum
=> [ PVE
::QemuServer
::disknames
() ],
2320 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2323 description
=> "Target Format.",
2324 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2329 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2335 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2343 description
=> "the task ID.",
2348 my $rpcenv = PVE
::RPCEnvironment
::get
();
2350 my $authuser = $rpcenv->get_user();
2352 my $node = extract_param
($param, 'node');
2354 my $vmid = extract_param
($param, 'vmid');
2356 my $digest = extract_param
($param, 'digest');
2358 my $disk = extract_param
($param, 'disk');
2360 my $storeid = extract_param
($param, 'storage');
2362 my $format = extract_param
($param, 'format');
2364 my $storecfg = PVE
::Storage
::config
();
2366 my $updatefn = sub {
2368 my $conf = PVE
::QemuServer
::load_config
($vmid);
2370 die "checksum missmatch (file change by other user?)\n"
2371 if $digest && $digest ne $conf->{digest
};
2373 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2375 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2377 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2379 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2382 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2383 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2387 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2388 (!$format || !$oldfmt || $oldfmt eq $format);
2390 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2392 my $running = PVE
::QemuServer
::check_running
($vmid);
2394 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2398 my $newvollist = [];
2401 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2403 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2404 $vmid, $storeid, $format, 1, $newvollist);
2406 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2408 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2410 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2413 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2414 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2421 foreach my $volid (@$newvollist) {
2422 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2425 die "storage migration failed: $err";
2428 if ($param->{delete}) {
2429 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2430 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2431 if ($used_paths->{$path}){
2432 warn "volume $old_volid have snapshots. Can't delete it\n";
2433 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2434 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2436 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2442 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2445 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2448 __PACKAGE__-
>register_method({
2449 name
=> 'migrate_vm',
2450 path
=> '{vmid}/migrate',
2454 description
=> "Migrate virtual machine. Creates a new migration task.",
2456 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2459 additionalProperties
=> 0,
2461 node
=> get_standard_option
('pve-node'),
2462 vmid
=> get_standard_option
('pve-vmid'),
2463 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2466 description
=> "Use online/live migration.",
2471 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2478 description
=> "the task ID.",
2483 my $rpcenv = PVE
::RPCEnvironment
::get
();
2485 my $authuser = $rpcenv->get_user();
2487 my $target = extract_param
($param, 'target');
2489 my $localnode = PVE
::INotify
::nodename
();
2490 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2492 PVE
::Cluster
::check_cfs_quorum
();
2494 PVE
::Cluster
::check_node_exists
($target);
2496 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2498 my $vmid = extract_param
($param, 'vmid');
2500 raise_param_exc
({ force
=> "Only root may use this option." })
2501 if $param->{force
} && $authuser ne 'root@pam';
2504 my $conf = PVE
::QemuServer
::load_config
($vmid);
2506 # try to detect errors early
2508 PVE
::QemuServer
::check_lock
($conf);
2510 if (PVE
::QemuServer
::check_running
($vmid)) {
2511 die "cant migrate running VM without --online\n"
2512 if !$param->{online
};
2515 my $storecfg = PVE
::Storage
::config
();
2516 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2518 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2523 my $service = "pvevm:$vmid";
2525 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2527 print "Executing HA migrate for VM $vmid to node $target\n";
2529 PVE
::Tools
::run_command
($cmd);
2534 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2541 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2544 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2549 __PACKAGE__-
>register_method({
2551 path
=> '{vmid}/monitor',
2555 description
=> "Execute Qemu monitor commands.",
2557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2560 additionalProperties
=> 0,
2562 node
=> get_standard_option
('pve-node'),
2563 vmid
=> get_standard_option
('pve-vmid'),
2566 description
=> "The monitor command.",
2570 returns
=> { type
=> 'string'},
2574 my $vmid = $param->{vmid
};
2576 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2580 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2582 $res = "ERROR: $@" if $@;
2587 __PACKAGE__-
>register_method({
2588 name
=> 'resize_vm',
2589 path
=> '{vmid}/resize',
2593 description
=> "Extend volume size.",
2595 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2598 additionalProperties
=> 0,
2600 node
=> get_standard_option
('pve-node'),
2601 vmid
=> get_standard_option
('pve-vmid'),
2602 skiplock
=> get_standard_option
('skiplock'),
2605 description
=> "The disk you want to resize.",
2606 enum
=> [PVE
::QemuServer
::disknames
()],
2610 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2611 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.",
2615 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2621 returns
=> { type
=> 'null'},
2625 my $rpcenv = PVE
::RPCEnvironment
::get
();
2627 my $authuser = $rpcenv->get_user();
2629 my $node = extract_param
($param, 'node');
2631 my $vmid = extract_param
($param, 'vmid');
2633 my $digest = extract_param
($param, 'digest');
2635 my $disk = extract_param
($param, 'disk');
2637 my $sizestr = extract_param
($param, 'size');
2639 my $skiplock = extract_param
($param, 'skiplock');
2640 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2641 if $skiplock && $authuser ne 'root@pam';
2643 my $storecfg = PVE
::Storage
::config
();
2645 my $updatefn = sub {
2647 my $conf = PVE
::QemuServer
::load_config
($vmid);
2649 die "checksum missmatch (file change by other user?)\n"
2650 if $digest && $digest ne $conf->{digest
};
2651 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2653 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2655 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2657 my $volid = $drive->{file
};
2659 die "disk '$disk' has no associated volume\n" if !$volid;
2661 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2663 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2665 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2667 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2669 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2670 my ($ext, $newsize, $unit) = ($1, $2, $4);
2673 $newsize = $newsize * 1024;
2674 } elsif ($unit eq 'M') {
2675 $newsize = $newsize * 1024 * 1024;
2676 } elsif ($unit eq 'G') {
2677 $newsize = $newsize * 1024 * 1024 * 1024;
2678 } elsif ($unit eq 'T') {
2679 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2682 $newsize += $size if $ext;
2683 $newsize = int($newsize);
2685 die "unable to skrink disk size\n" if $newsize < $size;
2687 return if $size == $newsize;
2689 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2691 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2693 $drive->{size
} = $newsize;
2694 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2696 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2699 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2703 __PACKAGE__-
>register_method({
2704 name
=> 'snapshot_list',
2705 path
=> '{vmid}/snapshot',
2707 description
=> "List all snapshots.",
2709 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2712 protected
=> 1, # qemu pid files are only readable by root
2714 additionalProperties
=> 0,
2716 vmid
=> get_standard_option
('pve-vmid'),
2717 node
=> get_standard_option
('pve-node'),
2726 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2731 my $vmid = $param->{vmid
};
2733 my $conf = PVE
::QemuServer
::load_config
($vmid);
2734 my $snaphash = $conf->{snapshots
} || {};
2738 foreach my $name (keys %$snaphash) {
2739 my $d = $snaphash->{$name};
2742 snaptime
=> $d->{snaptime
} || 0,
2743 vmstate
=> $d->{vmstate
} ?
1 : 0,
2744 description
=> $d->{description
} || '',
2746 $item->{parent
} = $d->{parent
} if $d->{parent
};
2747 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2751 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2752 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2753 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2755 push @$res, $current;
2760 __PACKAGE__-
>register_method({
2762 path
=> '{vmid}/snapshot',
2766 description
=> "Snapshot a VM.",
2768 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2771 additionalProperties
=> 0,
2773 node
=> get_standard_option
('pve-node'),
2774 vmid
=> get_standard_option
('pve-vmid'),
2775 snapname
=> get_standard_option
('pve-snapshot-name'),
2779 description
=> "Save the vmstate",
2784 description
=> "A textual description or comment.",
2790 description
=> "the task ID.",
2795 my $rpcenv = PVE
::RPCEnvironment
::get
();
2797 my $authuser = $rpcenv->get_user();
2799 my $node = extract_param
($param, 'node');
2801 my $vmid = extract_param
($param, 'vmid');
2803 my $snapname = extract_param
($param, 'snapname');
2805 die "unable to use snapshot name 'current' (reserved name)\n"
2806 if $snapname eq 'current';
2809 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2810 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2811 $param->{description
});
2814 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2817 __PACKAGE__-
>register_method({
2818 name
=> 'snapshot_cmd_idx',
2819 path
=> '{vmid}/snapshot/{snapname}',
2826 additionalProperties
=> 0,
2828 vmid
=> get_standard_option
('pve-vmid'),
2829 node
=> get_standard_option
('pve-node'),
2830 snapname
=> get_standard_option
('pve-snapshot-name'),
2839 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2846 push @$res, { cmd
=> 'rollback' };
2847 push @$res, { cmd
=> 'config' };
2852 __PACKAGE__-
>register_method({
2853 name
=> 'update_snapshot_config',
2854 path
=> '{vmid}/snapshot/{snapname}/config',
2858 description
=> "Update snapshot metadata.",
2860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2863 additionalProperties
=> 0,
2865 node
=> get_standard_option
('pve-node'),
2866 vmid
=> get_standard_option
('pve-vmid'),
2867 snapname
=> get_standard_option
('pve-snapshot-name'),
2871 description
=> "A textual description or comment.",
2875 returns
=> { type
=> 'null' },
2879 my $rpcenv = PVE
::RPCEnvironment
::get
();
2881 my $authuser = $rpcenv->get_user();
2883 my $vmid = extract_param
($param, 'vmid');
2885 my $snapname = extract_param
($param, 'snapname');
2887 return undef if !defined($param->{description
});
2889 my $updatefn = sub {
2891 my $conf = PVE
::QemuServer
::load_config
($vmid);
2893 PVE
::QemuServer
::check_lock
($conf);
2895 my $snap = $conf->{snapshots
}->{$snapname};
2897 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2899 $snap->{description
} = $param->{description
} if defined($param->{description
});
2901 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2904 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2909 __PACKAGE__-
>register_method({
2910 name
=> 'get_snapshot_config',
2911 path
=> '{vmid}/snapshot/{snapname}/config',
2914 description
=> "Get snapshot configuration",
2916 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2919 additionalProperties
=> 0,
2921 node
=> get_standard_option
('pve-node'),
2922 vmid
=> get_standard_option
('pve-vmid'),
2923 snapname
=> get_standard_option
('pve-snapshot-name'),
2926 returns
=> { type
=> "object" },
2930 my $rpcenv = PVE
::RPCEnvironment
::get
();
2932 my $authuser = $rpcenv->get_user();
2934 my $vmid = extract_param
($param, 'vmid');
2936 my $snapname = extract_param
($param, 'snapname');
2938 my $conf = PVE
::QemuServer
::load_config
($vmid);
2940 my $snap = $conf->{snapshots
}->{$snapname};
2942 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2947 __PACKAGE__-
>register_method({
2949 path
=> '{vmid}/snapshot/{snapname}/rollback',
2953 description
=> "Rollback VM state to specified snapshot.",
2955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2958 additionalProperties
=> 0,
2960 node
=> get_standard_option
('pve-node'),
2961 vmid
=> get_standard_option
('pve-vmid'),
2962 snapname
=> get_standard_option
('pve-snapshot-name'),
2967 description
=> "the task ID.",
2972 my $rpcenv = PVE
::RPCEnvironment
::get
();
2974 my $authuser = $rpcenv->get_user();
2976 my $node = extract_param
($param, 'node');
2978 my $vmid = extract_param
($param, 'vmid');
2980 my $snapname = extract_param
($param, 'snapname');
2983 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2984 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2987 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2990 __PACKAGE__-
>register_method({
2991 name
=> 'delsnapshot',
2992 path
=> '{vmid}/snapshot/{snapname}',
2996 description
=> "Delete a VM snapshot.",
2998 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3001 additionalProperties
=> 0,
3003 node
=> get_standard_option
('pve-node'),
3004 vmid
=> get_standard_option
('pve-vmid'),
3005 snapname
=> get_standard_option
('pve-snapshot-name'),
3009 description
=> "For removal from config file, even if removing disk snapshots fails.",
3015 description
=> "the task ID.",
3020 my $rpcenv = PVE
::RPCEnvironment
::get
();
3022 my $authuser = $rpcenv->get_user();
3024 my $node = extract_param
($param, 'node');
3026 my $vmid = extract_param
($param, 'vmid');
3028 my $snapname = extract_param
($param, 'snapname');
3031 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3032 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3035 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3038 __PACKAGE__-
>register_method({
3040 path
=> '{vmid}/template',
3044 description
=> "Create a Template.",
3046 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3047 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3050 additionalProperties
=> 0,
3052 node
=> get_standard_option
('pve-node'),
3053 vmid
=> get_standard_option
('pve-vmid'),
3057 description
=> "If you want to convert only 1 disk to base image.",
3058 enum
=> [PVE
::QemuServer
::disknames
()],
3063 returns
=> { type
=> 'null'},
3067 my $rpcenv = PVE
::RPCEnvironment
::get
();
3069 my $authuser = $rpcenv->get_user();
3071 my $node = extract_param
($param, 'node');
3073 my $vmid = extract_param
($param, 'vmid');
3075 my $disk = extract_param
($param, 'disk');
3077 my $updatefn = sub {
3079 my $conf = PVE
::QemuServer
::load_config
($vmid);
3081 PVE
::QemuServer
::check_lock
($conf);
3083 die "unable to create template, because VM contains snapshots\n"
3084 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3086 die "you can't convert a template to a template\n"
3087 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3089 die "you can't convert a VM to template if VM is running\n"
3090 if PVE
::QemuServer
::check_running
($vmid);
3093 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3096 $conf->{template
} = 1;
3097 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3099 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3102 PVE
::QemuServer
::lock_config
($vmid, $updatefn);