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
();
1145 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid)) {
1146 die "unable to delete VM $vmid - used in HA resources\n";
1152 syslog
('info', "destroy VM $vmid: $upid\n");
1154 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1156 PVE
::AccessControl
::remove_vm_access
($vmid);
1158 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1161 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1164 __PACKAGE__-
>register_method({
1166 path
=> '{vmid}/unlink',
1170 description
=> "Unlink/delete disk images.",
1172 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1175 additionalProperties
=> 0,
1177 node
=> get_standard_option
('pve-node'),
1178 vmid
=> get_standard_option
('pve-vmid'),
1180 type
=> 'string', format
=> 'pve-configid-list',
1181 description
=> "A list of disk IDs you want to delete.",
1185 description
=> $opt_force_description,
1190 returns
=> { type
=> 'null'},
1194 $param->{delete} = extract_param
($param, 'idlist');
1196 __PACKAGE__-
>update_vm($param);
1203 __PACKAGE__-
>register_method({
1205 path
=> '{vmid}/vncproxy',
1209 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1211 description
=> "Creates a TCP VNC proxy connections.",
1213 additionalProperties
=> 0,
1215 node
=> get_standard_option
('pve-node'),
1216 vmid
=> get_standard_option
('pve-vmid'),
1220 description
=> "starts websockify instead of vncproxy",
1225 additionalProperties
=> 0,
1227 user
=> { type
=> 'string' },
1228 ticket
=> { type
=> 'string' },
1229 cert
=> { type
=> 'string' },
1230 port
=> { type
=> 'integer' },
1231 upid
=> { type
=> 'string' },
1237 my $rpcenv = PVE
::RPCEnvironment
::get
();
1239 my $authuser = $rpcenv->get_user();
1241 my $vmid = $param->{vmid
};
1242 my $node = $param->{node
};
1243 my $websocket = $param->{websocket
};
1245 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1247 my $authpath = "/vms/$vmid";
1249 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1251 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1254 my ($remip, $family);
1257 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1258 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1259 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1260 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1262 $family = PVE
::Tools
::get_host_address_family
($node);
1265 my $port = PVE
::Tools
::next_vnc_port
($family);
1272 syslog
('info', "starting vnc proxy $upid\n");
1276 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1278 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1280 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1281 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1282 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1283 '-timeout', $timeout, '-authpath', $authpath,
1284 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1287 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1289 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1291 my $qmstr = join(' ', @$qmcmd);
1293 # also redirect stderr (else we get RFB protocol errors)
1294 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1297 PVE
::Tools
::run_command
($cmd);
1302 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1304 PVE
::Tools
::wait_for_vnc_port
($port);
1315 __PACKAGE__-
>register_method({
1316 name
=> 'vncwebsocket',
1317 path
=> '{vmid}/vncwebsocket',
1320 description
=> "You also need to pass a valid ticket (vncticket).",
1321 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1323 description
=> "Opens a weksocket for VNC traffic.",
1325 additionalProperties
=> 0,
1327 node
=> get_standard_option
('pve-node'),
1328 vmid
=> get_standard_option
('pve-vmid'),
1330 description
=> "Ticket from previous call to vncproxy.",
1335 description
=> "Port number returned by previous vncproxy call.",
1345 port
=> { type
=> 'string' },
1351 my $rpcenv = PVE
::RPCEnvironment
::get
();
1353 my $authuser = $rpcenv->get_user();
1355 my $vmid = $param->{vmid
};
1356 my $node = $param->{node
};
1358 my $authpath = "/vms/$vmid";
1360 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1362 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1364 # Note: VNC ports are acessible from outside, so we do not gain any
1365 # security if we verify that $param->{port} belongs to VM $vmid. This
1366 # check is done by verifying the VNC ticket (inside VNC protocol).
1368 my $port = $param->{port
};
1370 return { port
=> $port };
1373 __PACKAGE__-
>register_method({
1374 name
=> 'spiceproxy',
1375 path
=> '{vmid}/spiceproxy',
1380 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1382 description
=> "Returns a SPICE configuration to connect to the VM.",
1384 additionalProperties
=> 0,
1386 node
=> get_standard_option
('pve-node'),
1387 vmid
=> get_standard_option
('pve-vmid'),
1388 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1391 returns
=> get_standard_option
('remote-viewer-config'),
1395 my $rpcenv = PVE
::RPCEnvironment
::get
();
1397 my $authuser = $rpcenv->get_user();
1399 my $vmid = $param->{vmid
};
1400 my $node = $param->{node
};
1401 my $proxy = $param->{proxy
};
1403 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1404 my $title = "VM $vmid";
1405 $title .= " - ". $conf->{name
} if $conf->{name
};
1407 my $port = PVE
::QemuServer
::spice_port
($vmid);
1409 my ($ticket, undef, $remote_viewer_config) =
1410 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1412 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1413 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1415 return $remote_viewer_config;
1418 __PACKAGE__-
>register_method({
1420 path
=> '{vmid}/status',
1423 description
=> "Directory index",
1428 additionalProperties
=> 0,
1430 node
=> get_standard_option
('pve-node'),
1431 vmid
=> get_standard_option
('pve-vmid'),
1439 subdir
=> { type
=> 'string' },
1442 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1448 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1451 { subdir
=> 'current' },
1452 { subdir
=> 'start' },
1453 { subdir
=> 'stop' },
1459 __PACKAGE__-
>register_method({
1460 name
=> 'vm_status',
1461 path
=> '{vmid}/status/current',
1464 protected
=> 1, # qemu pid files are only readable by root
1465 description
=> "Get virtual machine status.",
1467 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1470 additionalProperties
=> 0,
1472 node
=> get_standard_option
('pve-node'),
1473 vmid
=> get_standard_option
('pve-vmid'),
1476 returns
=> { type
=> 'object' },
1481 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1483 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1484 my $status = $vmstatus->{$param->{vmid
}};
1486 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1488 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1493 __PACKAGE__-
>register_method({
1495 path
=> '{vmid}/status/start',
1499 description
=> "Start virtual machine.",
1501 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1504 additionalProperties
=> 0,
1506 node
=> get_standard_option
('pve-node'),
1507 vmid
=> get_standard_option
('pve-vmid'),
1508 skiplock
=> get_standard_option
('skiplock'),
1509 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1510 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1511 machine
=> get_standard_option
('pve-qm-machine'),
1520 my $rpcenv = PVE
::RPCEnvironment
::get
();
1522 my $authuser = $rpcenv->get_user();
1524 my $node = extract_param
($param, 'node');
1526 my $vmid = extract_param
($param, 'vmid');
1528 my $machine = extract_param
($param, 'machine');
1530 my $stateuri = extract_param
($param, 'stateuri');
1531 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1532 if $stateuri && $authuser ne 'root@pam';
1534 my $skiplock = extract_param
($param, 'skiplock');
1535 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1536 if $skiplock && $authuser ne 'root@pam';
1538 my $migratedfrom = extract_param
($param, 'migratedfrom');
1539 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1540 if $migratedfrom && $authuser ne 'root@pam';
1542 # read spice ticket from STDIN
1544 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1545 if (defined(my $line = <>)) {
1547 $spice_ticket = $line;
1551 my $storecfg = PVE
::Storage
::config
();
1553 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1554 $rpcenv->{type
} ne 'ha') {
1559 my $service = "vm:$vmid";
1561 my $cmd = ['ha-manager', 'enable', $service];
1563 print "Executing HA start for VM $vmid\n";
1565 PVE
::Tools
::run_command
($cmd);
1570 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1577 syslog
('info', "start VM $vmid: $upid\n");
1579 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1580 $machine, $spice_ticket);
1585 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1589 __PACKAGE__-
>register_method({
1591 path
=> '{vmid}/status/stop',
1595 description
=> "Stop virtual machine.",
1597 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1600 additionalProperties
=> 0,
1602 node
=> get_standard_option
('pve-node'),
1603 vmid
=> get_standard_option
('pve-vmid'),
1604 skiplock
=> get_standard_option
('skiplock'),
1605 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1607 description
=> "Wait maximal timeout seconds.",
1613 description
=> "Do not decativate storage volumes.",
1626 my $rpcenv = PVE
::RPCEnvironment
::get
();
1628 my $authuser = $rpcenv->get_user();
1630 my $node = extract_param
($param, 'node');
1632 my $vmid = extract_param
($param, 'vmid');
1634 my $skiplock = extract_param
($param, 'skiplock');
1635 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1636 if $skiplock && $authuser ne 'root@pam';
1638 my $keepActive = extract_param
($param, 'keepActive');
1639 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1640 if $keepActive && $authuser ne 'root@pam';
1642 my $migratedfrom = extract_param
($param, 'migratedfrom');
1643 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1644 if $migratedfrom && $authuser ne 'root@pam';
1647 my $storecfg = PVE
::Storage
::config
();
1649 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1654 my $service = "vm:$vmid";
1656 my $cmd = ['ha-manager', 'disable', $service];
1658 print "Executing HA stop for VM $vmid\n";
1660 PVE
::Tools
::run_command
($cmd);
1665 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1671 syslog
('info', "stop VM $vmid: $upid\n");
1673 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1674 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1679 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1683 __PACKAGE__-
>register_method({
1685 path
=> '{vmid}/status/reset',
1689 description
=> "Reset virtual machine.",
1691 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1694 additionalProperties
=> 0,
1696 node
=> get_standard_option
('pve-node'),
1697 vmid
=> get_standard_option
('pve-vmid'),
1698 skiplock
=> get_standard_option
('skiplock'),
1707 my $rpcenv = PVE
::RPCEnvironment
::get
();
1709 my $authuser = $rpcenv->get_user();
1711 my $node = extract_param
($param, 'node');
1713 my $vmid = extract_param
($param, 'vmid');
1715 my $skiplock = extract_param
($param, 'skiplock');
1716 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1717 if $skiplock && $authuser ne 'root@pam';
1719 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1724 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1729 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1732 __PACKAGE__-
>register_method({
1733 name
=> 'vm_shutdown',
1734 path
=> '{vmid}/status/shutdown',
1738 description
=> "Shutdown virtual machine.",
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid'),
1747 skiplock
=> get_standard_option
('skiplock'),
1749 description
=> "Wait maximal timeout seconds.",
1755 description
=> "Make sure the VM stops.",
1761 description
=> "Do not decativate storage volumes.",
1774 my $rpcenv = PVE
::RPCEnvironment
::get
();
1776 my $authuser = $rpcenv->get_user();
1778 my $node = extract_param
($param, 'node');
1780 my $vmid = extract_param
($param, 'vmid');
1782 my $skiplock = extract_param
($param, 'skiplock');
1783 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1784 if $skiplock && $authuser ne 'root@pam';
1786 my $keepActive = extract_param
($param, 'keepActive');
1787 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1788 if $keepActive && $authuser ne 'root@pam';
1790 my $storecfg = PVE
::Storage
::config
();
1795 syslog
('info', "shutdown VM $vmid: $upid\n");
1797 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1798 1, $param->{forceStop
}, $keepActive);
1803 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1806 __PACKAGE__-
>register_method({
1807 name
=> 'vm_suspend',
1808 path
=> '{vmid}/status/suspend',
1812 description
=> "Suspend virtual machine.",
1814 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1817 additionalProperties
=> 0,
1819 node
=> get_standard_option
('pve-node'),
1820 vmid
=> get_standard_option
('pve-vmid'),
1821 skiplock
=> get_standard_option
('skiplock'),
1830 my $rpcenv = PVE
::RPCEnvironment
::get
();
1832 my $authuser = $rpcenv->get_user();
1834 my $node = extract_param
($param, 'node');
1836 my $vmid = extract_param
($param, 'vmid');
1838 my $skiplock = extract_param
($param, 'skiplock');
1839 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1840 if $skiplock && $authuser ne 'root@pam';
1842 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1847 syslog
('info', "suspend VM $vmid: $upid\n");
1849 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1854 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1857 __PACKAGE__-
>register_method({
1858 name
=> 'vm_resume',
1859 path
=> '{vmid}/status/resume',
1863 description
=> "Resume virtual machine.",
1865 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1868 additionalProperties
=> 0,
1870 node
=> get_standard_option
('pve-node'),
1871 vmid
=> get_standard_option
('pve-vmid'),
1872 skiplock
=> get_standard_option
('skiplock'),
1881 my $rpcenv = PVE
::RPCEnvironment
::get
();
1883 my $authuser = $rpcenv->get_user();
1885 my $node = extract_param
($param, 'node');
1887 my $vmid = extract_param
($param, 'vmid');
1889 my $skiplock = extract_param
($param, 'skiplock');
1890 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1891 if $skiplock && $authuser ne 'root@pam';
1893 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1898 syslog
('info', "resume VM $vmid: $upid\n");
1900 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1905 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1908 __PACKAGE__-
>register_method({
1909 name
=> 'vm_sendkey',
1910 path
=> '{vmid}/sendkey',
1914 description
=> "Send key event to virtual machine.",
1916 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1919 additionalProperties
=> 0,
1921 node
=> get_standard_option
('pve-node'),
1922 vmid
=> get_standard_option
('pve-vmid'),
1923 skiplock
=> get_standard_option
('skiplock'),
1925 description
=> "The key (qemu monitor encoding).",
1930 returns
=> { type
=> 'null'},
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $node = extract_param
($param, 'node');
1940 my $vmid = extract_param
($param, 'vmid');
1942 my $skiplock = extract_param
($param, 'skiplock');
1943 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1944 if $skiplock && $authuser ne 'root@pam';
1946 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1951 __PACKAGE__-
>register_method({
1952 name
=> 'vm_feature',
1953 path
=> '{vmid}/feature',
1957 description
=> "Check if feature for virtual machine is available.",
1959 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1962 additionalProperties
=> 0,
1964 node
=> get_standard_option
('pve-node'),
1965 vmid
=> get_standard_option
('pve-vmid'),
1967 description
=> "Feature to check.",
1969 enum
=> [ 'snapshot', 'clone', 'copy' ],
1971 snapname
=> get_standard_option
('pve-snapshot-name', {
1979 hasFeature
=> { type
=> 'boolean' },
1982 items
=> { type
=> 'string' },
1989 my $node = extract_param
($param, 'node');
1991 my $vmid = extract_param
($param, 'vmid');
1993 my $snapname = extract_param
($param, 'snapname');
1995 my $feature = extract_param
($param, 'feature');
1997 my $running = PVE
::QemuServer
::check_running
($vmid);
1999 my $conf = PVE
::QemuServer
::load_config
($vmid);
2002 my $snap = $conf->{snapshots
}->{$snapname};
2003 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2006 my $storecfg = PVE
::Storage
::config
();
2008 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2009 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2012 hasFeature
=> $hasFeature,
2013 nodes
=> [ keys %$nodelist ],
2017 __PACKAGE__-
>register_method({
2019 path
=> '{vmid}/clone',
2023 description
=> "Create a copy of virtual machine/template.",
2025 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2026 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2027 "'Datastore.AllocateSpace' on any used storage.",
2030 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2032 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2033 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2038 additionalProperties
=> 0,
2040 node
=> get_standard_option
('pve-node'),
2041 vmid
=> get_standard_option
('pve-vmid'),
2042 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2045 type
=> 'string', format
=> 'dns-name',
2046 description
=> "Set a name for the new VM.",
2051 description
=> "Description for the new VM.",
2055 type
=> 'string', format
=> 'pve-poolid',
2056 description
=> "Add the new VM to the specified pool.",
2058 snapname
=> get_standard_option
('pve-snapshot-name', {
2061 storage
=> get_standard_option
('pve-storage-id', {
2062 description
=> "Target storage for full clone.",
2067 description
=> "Target format for file storage.",
2071 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2076 description
=> "Create a full copy of all disk. This is always done when " .
2077 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2080 target
=> get_standard_option
('pve-node', {
2081 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2092 my $rpcenv = PVE
::RPCEnvironment
::get
();
2094 my $authuser = $rpcenv->get_user();
2096 my $node = extract_param
($param, 'node');
2098 my $vmid = extract_param
($param, 'vmid');
2100 my $newid = extract_param
($param, 'newid');
2102 my $pool = extract_param
($param, 'pool');
2104 if (defined($pool)) {
2105 $rpcenv->check_pool_exist($pool);
2108 my $snapname = extract_param
($param, 'snapname');
2110 my $storage = extract_param
($param, 'storage');
2112 my $format = extract_param
($param, 'format');
2114 my $target = extract_param
($param, 'target');
2116 my $localnode = PVE
::INotify
::nodename
();
2118 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2120 PVE
::Cluster
::check_node_exists
($target) if $target;
2122 my $storecfg = PVE
::Storage
::config
();
2125 # check if storage is enabled on local node
2126 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2128 # check if storage is available on target node
2129 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2130 # clone only works if target storage is shared
2131 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2132 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2136 PVE
::Cluster
::check_cfs_quorum
();
2138 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2140 # exclusive lock if VM is running - else shared lock is enough;
2141 my $shared_lock = $running ?
0 : 1;
2145 # do all tests after lock
2146 # we also try to do all tests before we fork the worker
2148 my $conf = PVE
::QemuServer
::load_config
($vmid);
2150 PVE
::QemuServer
::check_lock
($conf);
2152 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2154 die "unexpected state change\n" if $verify_running != $running;
2156 die "snapshot '$snapname' does not exist\n"
2157 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2159 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2161 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2163 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2165 my $conffile = PVE
::QemuServer
::config_file
($newid);
2167 die "unable to create VM $newid: config file already exists\n"
2170 my $newconf = { lock => 'clone' };
2174 foreach my $opt (keys %$oldconf) {
2175 my $value = $oldconf->{$opt};
2177 # do not copy snapshot related info
2178 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2179 $opt eq 'vmstate' || $opt eq 'snapstate';
2181 # no need to copy unused images, because VMID(owner) changes anyways
2182 next if $opt =~ m/^unused\d+$/;
2184 # always change MAC! address
2185 if ($opt =~ m/^net(\d+)$/) {
2186 my $net = PVE
::QemuServer
::parse_net
($value);
2187 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2188 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2189 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2190 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2191 die "unable to parse drive options for '$opt'\n" if !$drive;
2192 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2193 $newconf->{$opt} = $value; # simply copy configuration
2195 if ($param->{full
}) {
2196 die "Full clone feature is not available"
2197 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2200 # not full means clone instead of copy
2201 die "Linked clone feature is not available"
2202 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2204 $drives->{$opt} = $drive;
2205 push @$vollist, $drive->{file
};
2208 # copy everything else
2209 $newconf->{$opt} = $value;
2213 # auto generate a new uuid
2214 my ($uuid, $uuid_str);
2215 UUID
::generate
($uuid);
2216 UUID
::unparse
($uuid, $uuid_str);
2217 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2218 $smbios1->{uuid
} = $uuid_str;
2219 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2221 delete $newconf->{template
};
2223 if ($param->{name
}) {
2224 $newconf->{name
} = $param->{name
};
2226 if ($oldconf->{name
}) {
2227 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2229 $newconf->{name
} = "Copy-of-VM-$vmid";
2233 if ($param->{description
}) {
2234 $newconf->{description
} = $param->{description
};
2237 # create empty/temp config - this fails if VM already exists on other node
2238 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2243 my $newvollist = [];
2246 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2248 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2250 foreach my $opt (keys %$drives) {
2251 my $drive = $drives->{$opt};
2253 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2254 $newid, $storage, $format, $drive->{full
}, $newvollist);
2256 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2258 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2261 delete $newconf->{lock};
2262 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2265 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2266 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2268 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2269 die "Failed to move config to node '$target' - rename failed: $!\n"
2270 if !rename($conffile, $newconffile);
2273 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2278 sleep 1; # some storage like rbd need to wait before release volume - really?
2280 foreach my $volid (@$newvollist) {
2281 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2284 die "clone failed: $err";
2290 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2292 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2295 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2296 # Aquire exclusive lock lock for $newid
2297 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2302 __PACKAGE__-
>register_method({
2303 name
=> 'move_vm_disk',
2304 path
=> '{vmid}/move_disk',
2308 description
=> "Move volume to different storage.",
2310 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2311 "and 'Datastore.AllocateSpace' permissions on the storage.",
2314 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2315 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2319 additionalProperties
=> 0,
2321 node
=> get_standard_option
('pve-node'),
2322 vmid
=> get_standard_option
('pve-vmid'),
2325 description
=> "The disk you want to move.",
2326 enum
=> [ PVE
::QemuServer
::disknames
() ],
2328 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2331 description
=> "Target Format.",
2332 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2337 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2343 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2351 description
=> "the task ID.",
2356 my $rpcenv = PVE
::RPCEnvironment
::get
();
2358 my $authuser = $rpcenv->get_user();
2360 my $node = extract_param
($param, 'node');
2362 my $vmid = extract_param
($param, 'vmid');
2364 my $digest = extract_param
($param, 'digest');
2366 my $disk = extract_param
($param, 'disk');
2368 my $storeid = extract_param
($param, 'storage');
2370 my $format = extract_param
($param, 'format');
2372 my $storecfg = PVE
::Storage
::config
();
2374 my $updatefn = sub {
2376 my $conf = PVE
::QemuServer
::load_config
($vmid);
2378 die "checksum missmatch (file change by other user?)\n"
2379 if $digest && $digest ne $conf->{digest
};
2381 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2383 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2385 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2387 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2390 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2391 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2395 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2396 (!$format || !$oldfmt || $oldfmt eq $format);
2398 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2400 my $running = PVE
::QemuServer
::check_running
($vmid);
2402 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2406 my $newvollist = [];
2409 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2411 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2412 $vmid, $storeid, $format, 1, $newvollist);
2414 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2416 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2418 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2421 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2422 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2429 foreach my $volid (@$newvollist) {
2430 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2433 die "storage migration failed: $err";
2436 if ($param->{delete}) {
2437 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2438 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2439 if ($used_paths->{$path}){
2440 warn "volume $old_volid have snapshots. Can't delete it\n";
2441 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2442 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2444 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2450 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2453 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2456 __PACKAGE__-
>register_method({
2457 name
=> 'migrate_vm',
2458 path
=> '{vmid}/migrate',
2462 description
=> "Migrate virtual machine. Creates a new migration task.",
2464 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2467 additionalProperties
=> 0,
2469 node
=> get_standard_option
('pve-node'),
2470 vmid
=> get_standard_option
('pve-vmid'),
2471 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2474 description
=> "Use online/live migration.",
2479 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2486 description
=> "the task ID.",
2491 my $rpcenv = PVE
::RPCEnvironment
::get
();
2493 my $authuser = $rpcenv->get_user();
2495 my $target = extract_param
($param, 'target');
2497 my $localnode = PVE
::INotify
::nodename
();
2498 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2500 PVE
::Cluster
::check_cfs_quorum
();
2502 PVE
::Cluster
::check_node_exists
($target);
2504 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2506 my $vmid = extract_param
($param, 'vmid');
2508 raise_param_exc
({ force
=> "Only root may use this option." })
2509 if $param->{force
} && $authuser ne 'root@pam';
2512 my $conf = PVE
::QemuServer
::load_config
($vmid);
2514 # try to detect errors early
2516 PVE
::QemuServer
::check_lock
($conf);
2518 if (PVE
::QemuServer
::check_running
($vmid)) {
2519 die "cant migrate running VM without --online\n"
2520 if !$param->{online
};
2523 my $storecfg = PVE
::Storage
::config
();
2524 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2526 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2531 my $service = "vm:$vmid";
2533 my $cmd = ['ha-manager', 'migrate', $service, $target];
2535 print "Executing HA migrate for VM $vmid to node $target\n";
2537 PVE
::Tools
::run_command
($cmd);
2542 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2549 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2552 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2557 __PACKAGE__-
>register_method({
2559 path
=> '{vmid}/monitor',
2563 description
=> "Execute Qemu monitor commands.",
2565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2568 additionalProperties
=> 0,
2570 node
=> get_standard_option
('pve-node'),
2571 vmid
=> get_standard_option
('pve-vmid'),
2574 description
=> "The monitor command.",
2578 returns
=> { type
=> 'string'},
2582 my $vmid = $param->{vmid
};
2584 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2588 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2590 $res = "ERROR: $@" if $@;
2595 __PACKAGE__-
>register_method({
2596 name
=> 'resize_vm',
2597 path
=> '{vmid}/resize',
2601 description
=> "Extend volume size.",
2603 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2606 additionalProperties
=> 0,
2608 node
=> get_standard_option
('pve-node'),
2609 vmid
=> get_standard_option
('pve-vmid'),
2610 skiplock
=> get_standard_option
('skiplock'),
2613 description
=> "The disk you want to resize.",
2614 enum
=> [PVE
::QemuServer
::disknames
()],
2618 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2619 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.",
2623 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2629 returns
=> { type
=> 'null'},
2633 my $rpcenv = PVE
::RPCEnvironment
::get
();
2635 my $authuser = $rpcenv->get_user();
2637 my $node = extract_param
($param, 'node');
2639 my $vmid = extract_param
($param, 'vmid');
2641 my $digest = extract_param
($param, 'digest');
2643 my $disk = extract_param
($param, 'disk');
2645 my $sizestr = extract_param
($param, 'size');
2647 my $skiplock = extract_param
($param, 'skiplock');
2648 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2649 if $skiplock && $authuser ne 'root@pam';
2651 my $storecfg = PVE
::Storage
::config
();
2653 my $updatefn = sub {
2655 my $conf = PVE
::QemuServer
::load_config
($vmid);
2657 die "checksum missmatch (file change by other user?)\n"
2658 if $digest && $digest ne $conf->{digest
};
2659 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2661 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2663 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2665 my (undef, undef, undef, undef, undef, undef, $format) =
2666 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2668 die "can't resize volume: $disk if snapshot exists\n"
2669 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2671 my $volid = $drive->{file
};
2673 die "disk '$disk' has no associated volume\n" if !$volid;
2675 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2677 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2679 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2681 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2683 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2684 my ($ext, $newsize, $unit) = ($1, $2, $4);
2687 $newsize = $newsize * 1024;
2688 } elsif ($unit eq 'M') {
2689 $newsize = $newsize * 1024 * 1024;
2690 } elsif ($unit eq 'G') {
2691 $newsize = $newsize * 1024 * 1024 * 1024;
2692 } elsif ($unit eq 'T') {
2693 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2696 $newsize += $size if $ext;
2697 $newsize = int($newsize);
2699 die "unable to skrink disk size\n" if $newsize < $size;
2701 return if $size == $newsize;
2703 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2705 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2707 $drive->{size
} = $newsize;
2708 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2710 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2713 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2717 __PACKAGE__-
>register_method({
2718 name
=> 'snapshot_list',
2719 path
=> '{vmid}/snapshot',
2721 description
=> "List all snapshots.",
2723 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2726 protected
=> 1, # qemu pid files are only readable by root
2728 additionalProperties
=> 0,
2730 vmid
=> get_standard_option
('pve-vmid'),
2731 node
=> get_standard_option
('pve-node'),
2740 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2745 my $vmid = $param->{vmid
};
2747 my $conf = PVE
::QemuServer
::load_config
($vmid);
2748 my $snaphash = $conf->{snapshots
} || {};
2752 foreach my $name (keys %$snaphash) {
2753 my $d = $snaphash->{$name};
2756 snaptime
=> $d->{snaptime
} || 0,
2757 vmstate
=> $d->{vmstate
} ?
1 : 0,
2758 description
=> $d->{description
} || '',
2760 $item->{parent
} = $d->{parent
} if $d->{parent
};
2761 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2765 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2766 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2767 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2769 push @$res, $current;
2774 __PACKAGE__-
>register_method({
2776 path
=> '{vmid}/snapshot',
2780 description
=> "Snapshot a VM.",
2782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2785 additionalProperties
=> 0,
2787 node
=> get_standard_option
('pve-node'),
2788 vmid
=> get_standard_option
('pve-vmid'),
2789 snapname
=> get_standard_option
('pve-snapshot-name'),
2793 description
=> "Save the vmstate",
2798 description
=> "A textual description or comment.",
2804 description
=> "the task ID.",
2809 my $rpcenv = PVE
::RPCEnvironment
::get
();
2811 my $authuser = $rpcenv->get_user();
2813 my $node = extract_param
($param, 'node');
2815 my $vmid = extract_param
($param, 'vmid');
2817 my $snapname = extract_param
($param, 'snapname');
2819 die "unable to use snapshot name 'current' (reserved name)\n"
2820 if $snapname eq 'current';
2823 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2824 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2825 $param->{description
});
2828 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2831 __PACKAGE__-
>register_method({
2832 name
=> 'snapshot_cmd_idx',
2833 path
=> '{vmid}/snapshot/{snapname}',
2840 additionalProperties
=> 0,
2842 vmid
=> get_standard_option
('pve-vmid'),
2843 node
=> get_standard_option
('pve-node'),
2844 snapname
=> get_standard_option
('pve-snapshot-name'),
2853 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2860 push @$res, { cmd
=> 'rollback' };
2861 push @$res, { cmd
=> 'config' };
2866 __PACKAGE__-
>register_method({
2867 name
=> 'update_snapshot_config',
2868 path
=> '{vmid}/snapshot/{snapname}/config',
2872 description
=> "Update snapshot metadata.",
2874 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2877 additionalProperties
=> 0,
2879 node
=> get_standard_option
('pve-node'),
2880 vmid
=> get_standard_option
('pve-vmid'),
2881 snapname
=> get_standard_option
('pve-snapshot-name'),
2885 description
=> "A textual description or comment.",
2889 returns
=> { type
=> 'null' },
2893 my $rpcenv = PVE
::RPCEnvironment
::get
();
2895 my $authuser = $rpcenv->get_user();
2897 my $vmid = extract_param
($param, 'vmid');
2899 my $snapname = extract_param
($param, 'snapname');
2901 return undef if !defined($param->{description
});
2903 my $updatefn = sub {
2905 my $conf = PVE
::QemuServer
::load_config
($vmid);
2907 PVE
::QemuServer
::check_lock
($conf);
2909 my $snap = $conf->{snapshots
}->{$snapname};
2911 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2913 $snap->{description
} = $param->{description
} if defined($param->{description
});
2915 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2918 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2923 __PACKAGE__-
>register_method({
2924 name
=> 'get_snapshot_config',
2925 path
=> '{vmid}/snapshot/{snapname}/config',
2928 description
=> "Get snapshot configuration",
2930 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2933 additionalProperties
=> 0,
2935 node
=> get_standard_option
('pve-node'),
2936 vmid
=> get_standard_option
('pve-vmid'),
2937 snapname
=> get_standard_option
('pve-snapshot-name'),
2940 returns
=> { type
=> "object" },
2944 my $rpcenv = PVE
::RPCEnvironment
::get
();
2946 my $authuser = $rpcenv->get_user();
2948 my $vmid = extract_param
($param, 'vmid');
2950 my $snapname = extract_param
($param, 'snapname');
2952 my $conf = PVE
::QemuServer
::load_config
($vmid);
2954 my $snap = $conf->{snapshots
}->{$snapname};
2956 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2961 __PACKAGE__-
>register_method({
2963 path
=> '{vmid}/snapshot/{snapname}/rollback',
2967 description
=> "Rollback VM state to specified snapshot.",
2969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2972 additionalProperties
=> 0,
2974 node
=> get_standard_option
('pve-node'),
2975 vmid
=> get_standard_option
('pve-vmid'),
2976 snapname
=> get_standard_option
('pve-snapshot-name'),
2981 description
=> "the task ID.",
2986 my $rpcenv = PVE
::RPCEnvironment
::get
();
2988 my $authuser = $rpcenv->get_user();
2990 my $node = extract_param
($param, 'node');
2992 my $vmid = extract_param
($param, 'vmid');
2994 my $snapname = extract_param
($param, 'snapname');
2997 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2998 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3001 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3004 __PACKAGE__-
>register_method({
3005 name
=> 'delsnapshot',
3006 path
=> '{vmid}/snapshot/{snapname}',
3010 description
=> "Delete a VM snapshot.",
3012 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3015 additionalProperties
=> 0,
3017 node
=> get_standard_option
('pve-node'),
3018 vmid
=> get_standard_option
('pve-vmid'),
3019 snapname
=> get_standard_option
('pve-snapshot-name'),
3023 description
=> "For removal from config file, even if removing disk snapshots fails.",
3029 description
=> "the task ID.",
3034 my $rpcenv = PVE
::RPCEnvironment
::get
();
3036 my $authuser = $rpcenv->get_user();
3038 my $node = extract_param
($param, 'node');
3040 my $vmid = extract_param
($param, 'vmid');
3042 my $snapname = extract_param
($param, 'snapname');
3045 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3046 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3049 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3052 __PACKAGE__-
>register_method({
3054 path
=> '{vmid}/template',
3058 description
=> "Create a Template.",
3060 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3061 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3064 additionalProperties
=> 0,
3066 node
=> get_standard_option
('pve-node'),
3067 vmid
=> get_standard_option
('pve-vmid'),
3071 description
=> "If you want to convert only 1 disk to base image.",
3072 enum
=> [PVE
::QemuServer
::disknames
()],
3077 returns
=> { type
=> 'null'},
3081 my $rpcenv = PVE
::RPCEnvironment
::get
();
3083 my $authuser = $rpcenv->get_user();
3085 my $node = extract_param
($param, 'node');
3087 my $vmid = extract_param
($param, 'vmid');
3089 my $disk = extract_param
($param, 'disk');
3091 my $updatefn = sub {
3093 my $conf = PVE
::QemuServer
::load_config
($vmid);
3095 PVE
::QemuServer
::check_lock
($conf);
3097 die "unable to create template, because VM contains snapshots\n"
3098 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3100 die "you can't convert a template to a template\n"
3101 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3103 die "you can't convert a VM to template if VM is running\n"
3104 if PVE
::QemuServer
::check_running
($vmid);
3107 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3110 $conf->{template
} = 1;
3111 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3113 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3116 PVE
::QemuServer
::lock_config
($vmid, $updatefn);