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 die "unable to remove VM $vmid - used in HA resources\n"
1146 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1151 syslog
('info', "destroy VM $vmid: $upid\n");
1153 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1155 PVE
::AccessControl
::remove_vm_access
($vmid);
1157 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1160 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1163 __PACKAGE__-
>register_method({
1165 path
=> '{vmid}/unlink',
1169 description
=> "Unlink/delete disk images.",
1171 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1174 additionalProperties
=> 0,
1176 node
=> get_standard_option
('pve-node'),
1177 vmid
=> get_standard_option
('pve-vmid'),
1179 type
=> 'string', format
=> 'pve-configid-list',
1180 description
=> "A list of disk IDs you want to delete.",
1184 description
=> $opt_force_description,
1189 returns
=> { type
=> 'null'},
1193 $param->{delete} = extract_param
($param, 'idlist');
1195 __PACKAGE__-
>update_vm($param);
1202 __PACKAGE__-
>register_method({
1204 path
=> '{vmid}/vncproxy',
1208 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1210 description
=> "Creates a TCP VNC proxy connections.",
1212 additionalProperties
=> 0,
1214 node
=> get_standard_option
('pve-node'),
1215 vmid
=> get_standard_option
('pve-vmid'),
1219 description
=> "starts websockify instead of vncproxy",
1224 additionalProperties
=> 0,
1226 user
=> { type
=> 'string' },
1227 ticket
=> { type
=> 'string' },
1228 cert
=> { type
=> 'string' },
1229 port
=> { type
=> 'integer' },
1230 upid
=> { type
=> 'string' },
1236 my $rpcenv = PVE
::RPCEnvironment
::get
();
1238 my $authuser = $rpcenv->get_user();
1240 my $vmid = $param->{vmid
};
1241 my $node = $param->{node
};
1242 my $websocket = $param->{websocket
};
1244 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1246 my $authpath = "/vms/$vmid";
1248 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1250 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1253 my ($remip, $family);
1256 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1257 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1258 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1259 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1261 $family = PVE
::Tools
::get_host_address_family
($node);
1264 my $port = PVE
::Tools
::next_vnc_port
($family);
1271 syslog
('info', "starting vnc proxy $upid\n");
1275 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1277 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1279 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1280 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1281 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1282 '-timeout', $timeout, '-authpath', $authpath,
1283 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1286 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1288 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1290 my $qmstr = join(' ', @$qmcmd);
1292 # also redirect stderr (else we get RFB protocol errors)
1293 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1296 PVE
::Tools
::run_command
($cmd);
1301 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1303 PVE
::Tools
::wait_for_vnc_port
($port);
1314 __PACKAGE__-
>register_method({
1315 name
=> 'vncwebsocket',
1316 path
=> '{vmid}/vncwebsocket',
1319 description
=> "You also need to pass a valid ticket (vncticket).",
1320 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1322 description
=> "Opens a weksocket for VNC traffic.",
1324 additionalProperties
=> 0,
1326 node
=> get_standard_option
('pve-node'),
1327 vmid
=> get_standard_option
('pve-vmid'),
1329 description
=> "Ticket from previous call to vncproxy.",
1334 description
=> "Port number returned by previous vncproxy call.",
1344 port
=> { type
=> 'string' },
1350 my $rpcenv = PVE
::RPCEnvironment
::get
();
1352 my $authuser = $rpcenv->get_user();
1354 my $vmid = $param->{vmid
};
1355 my $node = $param->{node
};
1357 my $authpath = "/vms/$vmid";
1359 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1361 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1363 # Note: VNC ports are acessible from outside, so we do not gain any
1364 # security if we verify that $param->{port} belongs to VM $vmid. This
1365 # check is done by verifying the VNC ticket (inside VNC protocol).
1367 my $port = $param->{port
};
1369 return { port
=> $port };
1372 __PACKAGE__-
>register_method({
1373 name
=> 'spiceproxy',
1374 path
=> '{vmid}/spiceproxy',
1379 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1381 description
=> "Returns a SPICE configuration to connect to the VM.",
1383 additionalProperties
=> 0,
1385 node
=> get_standard_option
('pve-node'),
1386 vmid
=> get_standard_option
('pve-vmid'),
1387 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1390 returns
=> get_standard_option
('remote-viewer-config'),
1394 my $rpcenv = PVE
::RPCEnvironment
::get
();
1396 my $authuser = $rpcenv->get_user();
1398 my $vmid = $param->{vmid
};
1399 my $node = $param->{node
};
1400 my $proxy = $param->{proxy
};
1402 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1403 my $title = "VM $vmid";
1404 $title .= " - ". $conf->{name
} if $conf->{name
};
1406 my $port = PVE
::QemuServer
::spice_port
($vmid);
1408 my ($ticket, undef, $remote_viewer_config) =
1409 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1411 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1412 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1414 return $remote_viewer_config;
1417 __PACKAGE__-
>register_method({
1419 path
=> '{vmid}/status',
1422 description
=> "Directory index",
1427 additionalProperties
=> 0,
1429 node
=> get_standard_option
('pve-node'),
1430 vmid
=> get_standard_option
('pve-vmid'),
1438 subdir
=> { type
=> 'string' },
1441 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1447 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1450 { subdir
=> 'current' },
1451 { subdir
=> 'start' },
1452 { subdir
=> 'stop' },
1458 __PACKAGE__-
>register_method({
1459 name
=> 'vm_status',
1460 path
=> '{vmid}/status/current',
1463 protected
=> 1, # qemu pid files are only readable by root
1464 description
=> "Get virtual machine status.",
1466 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1469 additionalProperties
=> 0,
1471 node
=> get_standard_option
('pve-node'),
1472 vmid
=> get_standard_option
('pve-vmid'),
1475 returns
=> { type
=> 'object' },
1480 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1482 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1483 my $status = $vmstatus->{$param->{vmid
}};
1485 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1487 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1492 __PACKAGE__-
>register_method({
1494 path
=> '{vmid}/status/start',
1498 description
=> "Start virtual machine.",
1500 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1503 additionalProperties
=> 0,
1505 node
=> get_standard_option
('pve-node'),
1506 vmid
=> get_standard_option
('pve-vmid'),
1507 skiplock
=> get_standard_option
('skiplock'),
1508 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1509 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1510 machine
=> get_standard_option
('pve-qm-machine'),
1519 my $rpcenv = PVE
::RPCEnvironment
::get
();
1521 my $authuser = $rpcenv->get_user();
1523 my $node = extract_param
($param, 'node');
1525 my $vmid = extract_param
($param, 'vmid');
1527 my $machine = extract_param
($param, 'machine');
1529 my $stateuri = extract_param
($param, 'stateuri');
1530 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1531 if $stateuri && $authuser ne 'root@pam';
1533 my $skiplock = extract_param
($param, 'skiplock');
1534 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1535 if $skiplock && $authuser ne 'root@pam';
1537 my $migratedfrom = extract_param
($param, 'migratedfrom');
1538 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1539 if $migratedfrom && $authuser ne 'root@pam';
1541 # read spice ticket from STDIN
1543 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1544 if (defined(my $line = <>)) {
1546 $spice_ticket = $line;
1550 my $storecfg = PVE
::Storage
::config
();
1552 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1553 $rpcenv->{type
} ne 'ha') {
1558 my $service = "vm:$vmid";
1560 my $cmd = ['ha-manager', 'enable', $service];
1562 print "Executing HA start for VM $vmid\n";
1564 PVE
::Tools
::run_command
($cmd);
1569 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1576 syslog
('info', "start VM $vmid: $upid\n");
1578 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1579 $machine, $spice_ticket);
1584 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1588 __PACKAGE__-
>register_method({
1590 path
=> '{vmid}/status/stop',
1594 description
=> "Stop virtual machine.",
1596 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1599 additionalProperties
=> 0,
1601 node
=> get_standard_option
('pve-node'),
1602 vmid
=> get_standard_option
('pve-vmid'),
1603 skiplock
=> get_standard_option
('skiplock'),
1604 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1606 description
=> "Wait maximal timeout seconds.",
1612 description
=> "Do not decativate storage volumes.",
1625 my $rpcenv = PVE
::RPCEnvironment
::get
();
1627 my $authuser = $rpcenv->get_user();
1629 my $node = extract_param
($param, 'node');
1631 my $vmid = extract_param
($param, 'vmid');
1633 my $skiplock = extract_param
($param, 'skiplock');
1634 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1635 if $skiplock && $authuser ne 'root@pam';
1637 my $keepActive = extract_param
($param, 'keepActive');
1638 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1639 if $keepActive && $authuser ne 'root@pam';
1641 my $migratedfrom = extract_param
($param, 'migratedfrom');
1642 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1643 if $migratedfrom && $authuser ne 'root@pam';
1646 my $storecfg = PVE
::Storage
::config
();
1648 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1653 my $service = "vm:$vmid";
1655 my $cmd = ['ha-manager', 'disable', $service];
1657 print "Executing HA stop for VM $vmid\n";
1659 PVE
::Tools
::run_command
($cmd);
1664 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1670 syslog
('info', "stop VM $vmid: $upid\n");
1672 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1673 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1678 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1682 __PACKAGE__-
>register_method({
1684 path
=> '{vmid}/status/reset',
1688 description
=> "Reset virtual machine.",
1690 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1693 additionalProperties
=> 0,
1695 node
=> get_standard_option
('pve-node'),
1696 vmid
=> get_standard_option
('pve-vmid'),
1697 skiplock
=> get_standard_option
('skiplock'),
1706 my $rpcenv = PVE
::RPCEnvironment
::get
();
1708 my $authuser = $rpcenv->get_user();
1710 my $node = extract_param
($param, 'node');
1712 my $vmid = extract_param
($param, 'vmid');
1714 my $skiplock = extract_param
($param, 'skiplock');
1715 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1716 if $skiplock && $authuser ne 'root@pam';
1718 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1723 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1728 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1731 __PACKAGE__-
>register_method({
1732 name
=> 'vm_shutdown',
1733 path
=> '{vmid}/status/shutdown',
1737 description
=> "Shutdown virtual machine.",
1739 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1742 additionalProperties
=> 0,
1744 node
=> get_standard_option
('pve-node'),
1745 vmid
=> get_standard_option
('pve-vmid'),
1746 skiplock
=> get_standard_option
('skiplock'),
1748 description
=> "Wait maximal timeout seconds.",
1754 description
=> "Make sure the VM stops.",
1760 description
=> "Do not decativate storage volumes.",
1773 my $rpcenv = PVE
::RPCEnvironment
::get
();
1775 my $authuser = $rpcenv->get_user();
1777 my $node = extract_param
($param, 'node');
1779 my $vmid = extract_param
($param, 'vmid');
1781 my $skiplock = extract_param
($param, 'skiplock');
1782 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1783 if $skiplock && $authuser ne 'root@pam';
1785 my $keepActive = extract_param
($param, 'keepActive');
1786 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1787 if $keepActive && $authuser ne 'root@pam';
1789 my $storecfg = PVE
::Storage
::config
();
1794 syslog
('info', "shutdown VM $vmid: $upid\n");
1796 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1797 1, $param->{forceStop
}, $keepActive);
1802 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1805 __PACKAGE__-
>register_method({
1806 name
=> 'vm_suspend',
1807 path
=> '{vmid}/status/suspend',
1811 description
=> "Suspend virtual machine.",
1813 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1816 additionalProperties
=> 0,
1818 node
=> get_standard_option
('pve-node'),
1819 vmid
=> get_standard_option
('pve-vmid'),
1820 skiplock
=> get_standard_option
('skiplock'),
1829 my $rpcenv = PVE
::RPCEnvironment
::get
();
1831 my $authuser = $rpcenv->get_user();
1833 my $node = extract_param
($param, 'node');
1835 my $vmid = extract_param
($param, 'vmid');
1837 my $skiplock = extract_param
($param, 'skiplock');
1838 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1839 if $skiplock && $authuser ne 'root@pam';
1841 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1846 syslog
('info', "suspend VM $vmid: $upid\n");
1848 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1853 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1856 __PACKAGE__-
>register_method({
1857 name
=> 'vm_resume',
1858 path
=> '{vmid}/status/resume',
1862 description
=> "Resume virtual machine.",
1864 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1867 additionalProperties
=> 0,
1869 node
=> get_standard_option
('pve-node'),
1870 vmid
=> get_standard_option
('pve-vmid'),
1871 skiplock
=> get_standard_option
('skiplock'),
1880 my $rpcenv = PVE
::RPCEnvironment
::get
();
1882 my $authuser = $rpcenv->get_user();
1884 my $node = extract_param
($param, 'node');
1886 my $vmid = extract_param
($param, 'vmid');
1888 my $skiplock = extract_param
($param, 'skiplock');
1889 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1890 if $skiplock && $authuser ne 'root@pam';
1892 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1897 syslog
('info', "resume VM $vmid: $upid\n");
1899 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1904 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1907 __PACKAGE__-
>register_method({
1908 name
=> 'vm_sendkey',
1909 path
=> '{vmid}/sendkey',
1913 description
=> "Send key event to virtual machine.",
1915 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1918 additionalProperties
=> 0,
1920 node
=> get_standard_option
('pve-node'),
1921 vmid
=> get_standard_option
('pve-vmid'),
1922 skiplock
=> get_standard_option
('skiplock'),
1924 description
=> "The key (qemu monitor encoding).",
1929 returns
=> { type
=> 'null'},
1933 my $rpcenv = PVE
::RPCEnvironment
::get
();
1935 my $authuser = $rpcenv->get_user();
1937 my $node = extract_param
($param, 'node');
1939 my $vmid = extract_param
($param, 'vmid');
1941 my $skiplock = extract_param
($param, 'skiplock');
1942 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1943 if $skiplock && $authuser ne 'root@pam';
1945 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1950 __PACKAGE__-
>register_method({
1951 name
=> 'vm_feature',
1952 path
=> '{vmid}/feature',
1956 description
=> "Check if feature for virtual machine is available.",
1958 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1961 additionalProperties
=> 0,
1963 node
=> get_standard_option
('pve-node'),
1964 vmid
=> get_standard_option
('pve-vmid'),
1966 description
=> "Feature to check.",
1968 enum
=> [ 'snapshot', 'clone', 'copy' ],
1970 snapname
=> get_standard_option
('pve-snapshot-name', {
1978 hasFeature
=> { type
=> 'boolean' },
1981 items
=> { type
=> 'string' },
1988 my $node = extract_param
($param, 'node');
1990 my $vmid = extract_param
($param, 'vmid');
1992 my $snapname = extract_param
($param, 'snapname');
1994 my $feature = extract_param
($param, 'feature');
1996 my $running = PVE
::QemuServer
::check_running
($vmid);
1998 my $conf = PVE
::QemuServer
::load_config
($vmid);
2001 my $snap = $conf->{snapshots
}->{$snapname};
2002 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2005 my $storecfg = PVE
::Storage
::config
();
2007 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2008 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2011 hasFeature
=> $hasFeature,
2012 nodes
=> [ keys %$nodelist ],
2016 __PACKAGE__-
>register_method({
2018 path
=> '{vmid}/clone',
2022 description
=> "Create a copy of virtual machine/template.",
2024 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2025 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2026 "'Datastore.AllocateSpace' on any used storage.",
2029 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2031 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2032 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2037 additionalProperties
=> 0,
2039 node
=> get_standard_option
('pve-node'),
2040 vmid
=> get_standard_option
('pve-vmid'),
2041 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2044 type
=> 'string', format
=> 'dns-name',
2045 description
=> "Set a name for the new VM.",
2050 description
=> "Description for the new VM.",
2054 type
=> 'string', format
=> 'pve-poolid',
2055 description
=> "Add the new VM to the specified pool.",
2057 snapname
=> get_standard_option
('pve-snapshot-name', {
2060 storage
=> get_standard_option
('pve-storage-id', {
2061 description
=> "Target storage for full clone.",
2066 description
=> "Target format for file storage.",
2070 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2075 description
=> "Create a full copy of all disk. This is always done when " .
2076 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2079 target
=> get_standard_option
('pve-node', {
2080 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2091 my $rpcenv = PVE
::RPCEnvironment
::get
();
2093 my $authuser = $rpcenv->get_user();
2095 my $node = extract_param
($param, 'node');
2097 my $vmid = extract_param
($param, 'vmid');
2099 my $newid = extract_param
($param, 'newid');
2101 my $pool = extract_param
($param, 'pool');
2103 if (defined($pool)) {
2104 $rpcenv->check_pool_exist($pool);
2107 my $snapname = extract_param
($param, 'snapname');
2109 my $storage = extract_param
($param, 'storage');
2111 my $format = extract_param
($param, 'format');
2113 my $target = extract_param
($param, 'target');
2115 my $localnode = PVE
::INotify
::nodename
();
2117 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2119 PVE
::Cluster
::check_node_exists
($target) if $target;
2121 my $storecfg = PVE
::Storage
::config
();
2124 # check if storage is enabled on local node
2125 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2127 # check if storage is available on target node
2128 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2129 # clone only works if target storage is shared
2130 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2131 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2135 PVE
::Cluster
::check_cfs_quorum
();
2137 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2139 # exclusive lock if VM is running - else shared lock is enough;
2140 my $shared_lock = $running ?
0 : 1;
2144 # do all tests after lock
2145 # we also try to do all tests before we fork the worker
2147 my $conf = PVE
::QemuServer
::load_config
($vmid);
2149 PVE
::QemuServer
::check_lock
($conf);
2151 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2153 die "unexpected state change\n" if $verify_running != $running;
2155 die "snapshot '$snapname' does not exist\n"
2156 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2158 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2160 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2162 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2164 my $conffile = PVE
::QemuServer
::config_file
($newid);
2166 die "unable to create VM $newid: config file already exists\n"
2169 my $newconf = { lock => 'clone' };
2173 foreach my $opt (keys %$oldconf) {
2174 my $value = $oldconf->{$opt};
2176 # do not copy snapshot related info
2177 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2178 $opt eq 'vmstate' || $opt eq 'snapstate';
2180 # no need to copy unused images, because VMID(owner) changes anyways
2181 next if $opt =~ m/^unused\d+$/;
2183 # always change MAC! address
2184 if ($opt =~ m/^net(\d+)$/) {
2185 my $net = PVE
::QemuServer
::parse_net
($value);
2186 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2187 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2188 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2189 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2190 die "unable to parse drive options for '$opt'\n" if !$drive;
2191 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2192 $newconf->{$opt} = $value; # simply copy configuration
2194 if ($param->{full
}) {
2195 die "Full clone feature is not available"
2196 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2199 # not full means clone instead of copy
2200 die "Linked clone feature is not available"
2201 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2203 $drives->{$opt} = $drive;
2204 push @$vollist, $drive->{file
};
2207 # copy everything else
2208 $newconf->{$opt} = $value;
2212 # auto generate a new uuid
2213 my ($uuid, $uuid_str);
2214 UUID
::generate
($uuid);
2215 UUID
::unparse
($uuid, $uuid_str);
2216 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2217 $smbios1->{uuid
} = $uuid_str;
2218 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2220 delete $newconf->{template
};
2222 if ($param->{name
}) {
2223 $newconf->{name
} = $param->{name
};
2225 if ($oldconf->{name
}) {
2226 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2228 $newconf->{name
} = "Copy-of-VM-$vmid";
2232 if ($param->{description
}) {
2233 $newconf->{description
} = $param->{description
};
2236 # create empty/temp config - this fails if VM already exists on other node
2237 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2242 my $newvollist = [];
2245 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2247 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2249 foreach my $opt (keys %$drives) {
2250 my $drive = $drives->{$opt};
2252 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2253 $newid, $storage, $format, $drive->{full
}, $newvollist);
2255 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2257 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2260 delete $newconf->{lock};
2261 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2264 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2265 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2267 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2268 die "Failed to move config to node '$target' - rename failed: $!\n"
2269 if !rename($conffile, $newconffile);
2272 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2277 sleep 1; # some storage like rbd need to wait before release volume - really?
2279 foreach my $volid (@$newvollist) {
2280 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2283 die "clone failed: $err";
2289 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2291 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2294 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2295 # Aquire exclusive lock lock for $newid
2296 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2301 __PACKAGE__-
>register_method({
2302 name
=> 'move_vm_disk',
2303 path
=> '{vmid}/move_disk',
2307 description
=> "Move volume to different storage.",
2309 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2310 "and 'Datastore.AllocateSpace' permissions on the storage.",
2313 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2314 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2318 additionalProperties
=> 0,
2320 node
=> get_standard_option
('pve-node'),
2321 vmid
=> get_standard_option
('pve-vmid'),
2324 description
=> "The disk you want to move.",
2325 enum
=> [ PVE
::QemuServer
::disknames
() ],
2327 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2330 description
=> "Target Format.",
2331 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2336 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2342 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2350 description
=> "the task ID.",
2355 my $rpcenv = PVE
::RPCEnvironment
::get
();
2357 my $authuser = $rpcenv->get_user();
2359 my $node = extract_param
($param, 'node');
2361 my $vmid = extract_param
($param, 'vmid');
2363 my $digest = extract_param
($param, 'digest');
2365 my $disk = extract_param
($param, 'disk');
2367 my $storeid = extract_param
($param, 'storage');
2369 my $format = extract_param
($param, 'format');
2371 my $storecfg = PVE
::Storage
::config
();
2373 my $updatefn = sub {
2375 my $conf = PVE
::QemuServer
::load_config
($vmid);
2377 die "checksum missmatch (file change by other user?)\n"
2378 if $digest && $digest ne $conf->{digest
};
2380 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2382 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2384 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2386 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2389 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2390 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2394 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2395 (!$format || !$oldfmt || $oldfmt eq $format);
2397 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2399 my $running = PVE
::QemuServer
::check_running
($vmid);
2401 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2405 my $newvollist = [];
2408 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2410 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2411 $vmid, $storeid, $format, 1, $newvollist);
2413 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2415 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2417 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2420 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2421 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2428 foreach my $volid (@$newvollist) {
2429 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2432 die "storage migration failed: $err";
2435 if ($param->{delete}) {
2436 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2437 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2438 if ($used_paths->{$path}){
2439 warn "volume $old_volid have snapshots. Can't delete it\n";
2440 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2441 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2443 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2449 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2452 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2455 __PACKAGE__-
>register_method({
2456 name
=> 'migrate_vm',
2457 path
=> '{vmid}/migrate',
2461 description
=> "Migrate virtual machine. Creates a new migration task.",
2463 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2466 additionalProperties
=> 0,
2468 node
=> get_standard_option
('pve-node'),
2469 vmid
=> get_standard_option
('pve-vmid'),
2470 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2473 description
=> "Use online/live migration.",
2478 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2485 description
=> "the task ID.",
2490 my $rpcenv = PVE
::RPCEnvironment
::get
();
2492 my $authuser = $rpcenv->get_user();
2494 my $target = extract_param
($param, 'target');
2496 my $localnode = PVE
::INotify
::nodename
();
2497 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2499 PVE
::Cluster
::check_cfs_quorum
();
2501 PVE
::Cluster
::check_node_exists
($target);
2503 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2505 my $vmid = extract_param
($param, 'vmid');
2507 raise_param_exc
({ force
=> "Only root may use this option." })
2508 if $param->{force
} && $authuser ne 'root@pam';
2511 my $conf = PVE
::QemuServer
::load_config
($vmid);
2513 # try to detect errors early
2515 PVE
::QemuServer
::check_lock
($conf);
2517 if (PVE
::QemuServer
::check_running
($vmid)) {
2518 die "cant migrate running VM without --online\n"
2519 if !$param->{online
};
2522 my $storecfg = PVE
::Storage
::config
();
2523 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2525 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2530 my $service = "vm:$vmid";
2532 my $cmd = ['ha-manager', 'migrate', $service, $target];
2534 print "Executing HA migrate for VM $vmid to node $target\n";
2536 PVE
::Tools
::run_command
($cmd);
2541 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2548 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2551 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2556 __PACKAGE__-
>register_method({
2558 path
=> '{vmid}/monitor',
2562 description
=> "Execute Qemu monitor commands.",
2564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2567 additionalProperties
=> 0,
2569 node
=> get_standard_option
('pve-node'),
2570 vmid
=> get_standard_option
('pve-vmid'),
2573 description
=> "The monitor command.",
2577 returns
=> { type
=> 'string'},
2581 my $vmid = $param->{vmid
};
2583 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2587 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2589 $res = "ERROR: $@" if $@;
2594 __PACKAGE__-
>register_method({
2595 name
=> 'resize_vm',
2596 path
=> '{vmid}/resize',
2600 description
=> "Extend volume size.",
2602 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2605 additionalProperties
=> 0,
2607 node
=> get_standard_option
('pve-node'),
2608 vmid
=> get_standard_option
('pve-vmid'),
2609 skiplock
=> get_standard_option
('skiplock'),
2612 description
=> "The disk you want to resize.",
2613 enum
=> [PVE
::QemuServer
::disknames
()],
2617 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2618 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.",
2622 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2628 returns
=> { type
=> 'null'},
2632 my $rpcenv = PVE
::RPCEnvironment
::get
();
2634 my $authuser = $rpcenv->get_user();
2636 my $node = extract_param
($param, 'node');
2638 my $vmid = extract_param
($param, 'vmid');
2640 my $digest = extract_param
($param, 'digest');
2642 my $disk = extract_param
($param, 'disk');
2644 my $sizestr = extract_param
($param, 'size');
2646 my $skiplock = extract_param
($param, 'skiplock');
2647 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2648 if $skiplock && $authuser ne 'root@pam';
2650 my $storecfg = PVE
::Storage
::config
();
2652 my $updatefn = sub {
2654 my $conf = PVE
::QemuServer
::load_config
($vmid);
2656 die "checksum missmatch (file change by other user?)\n"
2657 if $digest && $digest ne $conf->{digest
};
2658 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2660 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2662 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2664 my (undef, undef, undef, undef, undef, undef, $format) =
2665 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2667 die "can't resize volume: $disk if snapshot exists\n"
2668 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2670 my $volid = $drive->{file
};
2672 die "disk '$disk' has no associated volume\n" if !$volid;
2674 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2676 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2678 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2680 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2682 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2683 my ($ext, $newsize, $unit) = ($1, $2, $4);
2686 $newsize = $newsize * 1024;
2687 } elsif ($unit eq 'M') {
2688 $newsize = $newsize * 1024 * 1024;
2689 } elsif ($unit eq 'G') {
2690 $newsize = $newsize * 1024 * 1024 * 1024;
2691 } elsif ($unit eq 'T') {
2692 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2695 $newsize += $size if $ext;
2696 $newsize = int($newsize);
2698 die "unable to skrink disk size\n" if $newsize < $size;
2700 return if $size == $newsize;
2702 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2704 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2706 $drive->{size
} = $newsize;
2707 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2709 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2712 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2716 __PACKAGE__-
>register_method({
2717 name
=> 'snapshot_list',
2718 path
=> '{vmid}/snapshot',
2720 description
=> "List all snapshots.",
2722 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2725 protected
=> 1, # qemu pid files are only readable by root
2727 additionalProperties
=> 0,
2729 vmid
=> get_standard_option
('pve-vmid'),
2730 node
=> get_standard_option
('pve-node'),
2739 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2744 my $vmid = $param->{vmid
};
2746 my $conf = PVE
::QemuServer
::load_config
($vmid);
2747 my $snaphash = $conf->{snapshots
} || {};
2751 foreach my $name (keys %$snaphash) {
2752 my $d = $snaphash->{$name};
2755 snaptime
=> $d->{snaptime
} || 0,
2756 vmstate
=> $d->{vmstate
} ?
1 : 0,
2757 description
=> $d->{description
} || '',
2759 $item->{parent
} = $d->{parent
} if $d->{parent
};
2760 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2764 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2765 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2766 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2768 push @$res, $current;
2773 __PACKAGE__-
>register_method({
2775 path
=> '{vmid}/snapshot',
2779 description
=> "Snapshot a VM.",
2781 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2784 additionalProperties
=> 0,
2786 node
=> get_standard_option
('pve-node'),
2787 vmid
=> get_standard_option
('pve-vmid'),
2788 snapname
=> get_standard_option
('pve-snapshot-name'),
2792 description
=> "Save the vmstate",
2797 description
=> "A textual description or comment.",
2803 description
=> "the task ID.",
2808 my $rpcenv = PVE
::RPCEnvironment
::get
();
2810 my $authuser = $rpcenv->get_user();
2812 my $node = extract_param
($param, 'node');
2814 my $vmid = extract_param
($param, 'vmid');
2816 my $snapname = extract_param
($param, 'snapname');
2818 die "unable to use snapshot name 'current' (reserved name)\n"
2819 if $snapname eq 'current';
2822 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2823 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2824 $param->{description
});
2827 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2830 __PACKAGE__-
>register_method({
2831 name
=> 'snapshot_cmd_idx',
2832 path
=> '{vmid}/snapshot/{snapname}',
2839 additionalProperties
=> 0,
2841 vmid
=> get_standard_option
('pve-vmid'),
2842 node
=> get_standard_option
('pve-node'),
2843 snapname
=> get_standard_option
('pve-snapshot-name'),
2852 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2859 push @$res, { cmd
=> 'rollback' };
2860 push @$res, { cmd
=> 'config' };
2865 __PACKAGE__-
>register_method({
2866 name
=> 'update_snapshot_config',
2867 path
=> '{vmid}/snapshot/{snapname}/config',
2871 description
=> "Update snapshot metadata.",
2873 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2876 additionalProperties
=> 0,
2878 node
=> get_standard_option
('pve-node'),
2879 vmid
=> get_standard_option
('pve-vmid'),
2880 snapname
=> get_standard_option
('pve-snapshot-name'),
2884 description
=> "A textual description or comment.",
2888 returns
=> { type
=> 'null' },
2892 my $rpcenv = PVE
::RPCEnvironment
::get
();
2894 my $authuser = $rpcenv->get_user();
2896 my $vmid = extract_param
($param, 'vmid');
2898 my $snapname = extract_param
($param, 'snapname');
2900 return undef if !defined($param->{description
});
2902 my $updatefn = sub {
2904 my $conf = PVE
::QemuServer
::load_config
($vmid);
2906 PVE
::QemuServer
::check_lock
($conf);
2908 my $snap = $conf->{snapshots
}->{$snapname};
2910 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2912 $snap->{description
} = $param->{description
} if defined($param->{description
});
2914 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2917 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2922 __PACKAGE__-
>register_method({
2923 name
=> 'get_snapshot_config',
2924 path
=> '{vmid}/snapshot/{snapname}/config',
2927 description
=> "Get snapshot configuration",
2929 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2932 additionalProperties
=> 0,
2934 node
=> get_standard_option
('pve-node'),
2935 vmid
=> get_standard_option
('pve-vmid'),
2936 snapname
=> get_standard_option
('pve-snapshot-name'),
2939 returns
=> { type
=> "object" },
2943 my $rpcenv = PVE
::RPCEnvironment
::get
();
2945 my $authuser = $rpcenv->get_user();
2947 my $vmid = extract_param
($param, 'vmid');
2949 my $snapname = extract_param
($param, 'snapname');
2951 my $conf = PVE
::QemuServer
::load_config
($vmid);
2953 my $snap = $conf->{snapshots
}->{$snapname};
2955 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2960 __PACKAGE__-
>register_method({
2962 path
=> '{vmid}/snapshot/{snapname}/rollback',
2966 description
=> "Rollback VM state to specified snapshot.",
2968 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2971 additionalProperties
=> 0,
2973 node
=> get_standard_option
('pve-node'),
2974 vmid
=> get_standard_option
('pve-vmid'),
2975 snapname
=> get_standard_option
('pve-snapshot-name'),
2980 description
=> "the task ID.",
2985 my $rpcenv = PVE
::RPCEnvironment
::get
();
2987 my $authuser = $rpcenv->get_user();
2989 my $node = extract_param
($param, 'node');
2991 my $vmid = extract_param
($param, 'vmid');
2993 my $snapname = extract_param
($param, 'snapname');
2996 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2997 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3000 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3003 __PACKAGE__-
>register_method({
3004 name
=> 'delsnapshot',
3005 path
=> '{vmid}/snapshot/{snapname}',
3009 description
=> "Delete a VM snapshot.",
3011 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3014 additionalProperties
=> 0,
3016 node
=> get_standard_option
('pve-node'),
3017 vmid
=> get_standard_option
('pve-vmid'),
3018 snapname
=> get_standard_option
('pve-snapshot-name'),
3022 description
=> "For removal from config file, even if removing disk snapshots fails.",
3028 description
=> "the task ID.",
3033 my $rpcenv = PVE
::RPCEnvironment
::get
();
3035 my $authuser = $rpcenv->get_user();
3037 my $node = extract_param
($param, 'node');
3039 my $vmid = extract_param
($param, 'vmid');
3041 my $snapname = extract_param
($param, 'snapname');
3044 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3045 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3048 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3051 __PACKAGE__-
>register_method({
3053 path
=> '{vmid}/template',
3057 description
=> "Create a Template.",
3059 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3060 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3063 additionalProperties
=> 0,
3065 node
=> get_standard_option
('pve-node'),
3066 vmid
=> get_standard_option
('pve-vmid'),
3070 description
=> "If you want to convert only 1 disk to base image.",
3071 enum
=> [PVE
::QemuServer
::disknames
()],
3076 returns
=> { type
=> 'null'},
3080 my $rpcenv = PVE
::RPCEnvironment
::get
();
3082 my $authuser = $rpcenv->get_user();
3084 my $node = extract_param
($param, 'node');
3086 my $vmid = extract_param
($param, 'vmid');
3088 my $disk = extract_param
($param, 'disk');
3090 my $updatefn = sub {
3092 my $conf = PVE
::QemuServer
::load_config
($vmid);
3094 PVE
::QemuServer
::check_lock
($conf);
3096 die "unable to create template, because VM contains snapshots\n"
3097 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3099 die "you can't convert a template to a template\n"
3100 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3102 die "you can't convert a VM to template if VM is running\n"
3103 if PVE
::QemuServer
::check_running
($vmid);
3106 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3109 $conf->{template
} = 1;
3110 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3112 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3115 PVE
::QemuServer
::lock_config
($vmid, $updatefn);