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);
19 use PVE
::RPCEnvironment
;
20 use PVE
::AccessControl
;
24 use PVE
::API2
::Firewall
::VM
;
25 use PVE
::HA
::Env
::PVE2
;
28 use Data
::Dumper
; # fixme: remove
30 use base
qw(PVE::RESTHandler);
32 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.";
34 my $resolve_cdrom_alias = sub {
37 if (my $value = $param->{cdrom
}) {
38 $value .= ",media=cdrom" if $value !~ m/media=/;
39 $param->{ide2
} = $value;
40 delete $param->{cdrom
};
44 my $check_storage_access = sub {
45 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
47 PVE
::QemuServer
::foreach_drive
($settings, sub {
48 my ($ds, $drive) = @_;
50 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
52 my $volid = $drive->{file
};
54 if (!$volid || $volid eq 'none') {
56 } elsif ($isCDROM && ($volid eq 'cdrom')) {
57 $rpcenv->check($authuser, "/", ['Sys.Console']);
58 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
59 my ($storeid, $size) = ($2 || $default_storage, $3);
60 die "no storage ID specified (and no default storage)\n" if !$storeid;
61 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
63 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
68 my $check_storage_access_clone = sub {
69 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
73 PVE
::QemuServer
::foreach_drive
($conf, sub {
74 my ($ds, $drive) = @_;
76 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
78 my $volid = $drive->{file
};
80 return if !$volid || $volid eq 'none';
83 if ($volid eq 'cdrom') {
84 $rpcenv->check($authuser, "/", ['Sys.Console']);
86 # we simply allow access
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
93 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
94 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
95 $sharedvm = 0 if !$scfg->{shared
};
97 $sid = $storage if $storage;
98 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
105 # Note: $pool is only needed when creating a VM, because pool permissions
106 # are automatically inherited if VM already exists inside a pool.
107 my $create_disks = sub {
108 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
113 PVE
::QemuServer
::foreach_drive
($settings, sub {
114 my ($ds, $disk) = @_;
116 my $volid = $disk->{file
};
118 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
119 delete $disk->{size
};
120 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
121 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
122 my ($storeid, $size) = ($2 || $default_storage, $3);
123 die "no storage ID specified (and no default storage)\n" if !$storeid;
124 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
125 my $fmt = $disk->{format
} || $defformat;
126 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
127 $fmt, undef, $size*1024*1024);
128 $disk->{file
} = $volid;
129 $disk->{size
} = $size*1024*1024*1024;
130 push @$vollist, $volid;
131 delete $disk->{format
}; # no longer needed
132 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
135 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
137 my $volid_is_new = 1;
140 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
141 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
148 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
150 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
152 die "volume $volid does not exists\n" if !$size;
154 $disk->{size
} = $size;
157 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
161 # free allocated images on error
163 syslog
('err', "VM $vmid creating disks failed");
164 foreach my $volid (@$vollist) {
165 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
171 # modify vm config if everything went well
172 foreach my $ds (keys %$res) {
173 $conf->{$ds} = $res->{$ds};
179 my $check_vm_modify_config_perm = sub {
180 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
182 return 1 if $authuser eq 'root@pam';
184 foreach my $opt (@$key_list) {
185 # disk checks need to be done somewhere else
186 next if PVE
::QemuServer
::is_valid_drivename
($opt);
188 if ($opt eq 'sockets' || $opt eq 'cores' ||
189 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
190 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
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'),
227 description
=> "Determine the full status of active VMs.",
237 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
242 my $rpcenv = PVE
::RPCEnvironment
::get
();
243 my $authuser = $rpcenv->get_user();
245 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
248 foreach my $vmid (keys %$vmstatus) {
249 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
251 my $data = $vmstatus->{$vmid};
252 $data->{vmid
} = int($vmid);
261 __PACKAGE__-
>register_method({
265 description
=> "Create or restore a virtual machine.",
267 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
268 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
269 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
270 user
=> 'all', # check inside
275 additionalProperties
=> 0,
276 properties
=> PVE
::QemuServer
::json_config_properties
(
278 node
=> get_standard_option
('pve-node'),
279 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
281 description
=> "The backup file.",
285 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
287 storage
=> get_standard_option
('pve-storage-id', {
288 description
=> "Default storage.",
290 completion
=> \
&PVE
::QemuServer
::complete_storage
,
295 description
=> "Allow to overwrite existing VM.",
296 requires
=> 'archive',
301 description
=> "Assign a unique random ethernet address.",
302 requires
=> 'archive',
306 type
=> 'string', format
=> 'pve-poolid',
307 description
=> "Add the VM to the specified pool.",
317 my $rpcenv = PVE
::RPCEnvironment
::get
();
319 my $authuser = $rpcenv->get_user();
321 my $node = extract_param
($param, 'node');
323 my $vmid = extract_param
($param, 'vmid');
325 my $archive = extract_param
($param, 'archive');
327 my $storage = extract_param
($param, 'storage');
329 my $force = extract_param
($param, 'force');
331 my $unique = extract_param
($param, 'unique');
333 my $pool = extract_param
($param, 'pool');
335 my $filename = PVE
::QemuConfig-
>config_file($vmid);
337 my $storecfg = PVE
::Storage
::config
();
339 PVE
::Cluster
::check_cfs_quorum
();
341 if (defined($pool)) {
342 $rpcenv->check_pool_exist($pool);
345 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
346 if defined($storage);
348 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
350 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
352 } elsif ($archive && $force && (-f
$filename) &&
353 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
354 # OK: user has VM.Backup permissions, and want to restore an existing VM
360 &$resolve_cdrom_alias($param);
362 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
364 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
366 foreach my $opt (keys %$param) {
367 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
368 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
369 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
371 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
372 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
376 PVE
::QemuServer
::add_random_macs
($param);
378 my $keystr = join(' ', keys %$param);
379 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
381 if ($archive eq '-') {
382 die "pipe requires cli environment\n"
383 if $rpcenv->{type
} ne 'cli';
385 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
386 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
390 my $restorefn = sub {
391 my $vmlist = PVE
::Cluster
::get_vmlist
();
392 if ($vmlist->{ids
}->{$vmid}) {
393 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
394 if ($current_node eq $node) {
395 my $conf = PVE
::QemuConfig-
>load_config($vmid);
397 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
399 die "unable to restore vm $vmid - config file already exists\n"
402 die "unable to restore vm $vmid - vm is running\n"
403 if PVE
::QemuServer
::check_running
($vmid);
405 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
410 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
413 unique
=> $unique });
415 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
418 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
424 PVE
::Cluster
::check_vmid_unused
($vmid);
434 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
436 # try to be smart about bootdisk
437 my @disks = PVE
::QemuServer
::valid_drive_names
();
439 foreach my $ds (reverse @disks) {
440 next if !$conf->{$ds};
441 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
442 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
446 if (!$conf->{bootdisk
} && $firstdisk) {
447 $conf->{bootdisk
} = $firstdisk;
450 # auto generate uuid if user did not specify smbios1 option
451 if (!$conf->{smbios1
}) {
452 my ($uuid, $uuid_str);
453 UUID
::generate
($uuid);
454 UUID
::unparse
($uuid, $uuid_str);
455 $conf->{smbios1
} = "uuid=$uuid_str";
458 PVE
::QemuConfig-
>write_config($vmid, $conf);
464 foreach my $volid (@$vollist) {
465 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
468 die "create failed - $err";
471 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
474 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
477 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
480 __PACKAGE__-
>register_method({
485 description
=> "Directory index",
490 additionalProperties
=> 0,
492 node
=> get_standard_option
('pve-node'),
493 vmid
=> get_standard_option
('pve-vmid'),
501 subdir
=> { type
=> 'string' },
504 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
510 { subdir
=> 'config' },
511 { subdir
=> 'pending' },
512 { subdir
=> 'status' },
513 { subdir
=> 'unlink' },
514 { subdir
=> 'vncproxy' },
515 { subdir
=> 'migrate' },
516 { subdir
=> 'resize' },
517 { subdir
=> 'move' },
519 { subdir
=> 'rrddata' },
520 { subdir
=> 'monitor' },
521 { subdir
=> 'snapshot' },
522 { subdir
=> 'spiceproxy' },
523 { subdir
=> 'sendkey' },
524 { subdir
=> 'firewall' },
530 __PACKAGE__-
>register_method ({
531 subclass
=> "PVE::API2::Firewall::VM",
532 path
=> '{vmid}/firewall',
535 __PACKAGE__-
>register_method({
537 path
=> '{vmid}/rrd',
539 protected
=> 1, # fixme: can we avoid that?
541 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
543 description
=> "Read VM RRD statistics (returns PNG)",
545 additionalProperties
=> 0,
547 node
=> get_standard_option
('pve-node'),
548 vmid
=> get_standard_option
('pve-vmid'),
550 description
=> "Specify the time frame you are interested in.",
552 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
555 description
=> "The list of datasources you want to display.",
556 type
=> 'string', format
=> 'pve-configid-list',
559 description
=> "The RRD consolidation function",
561 enum
=> [ 'AVERAGE', 'MAX' ],
569 filename
=> { type
=> 'string' },
575 return PVE
::Cluster
::create_rrd_graph
(
576 "pve2-vm/$param->{vmid}", $param->{timeframe
},
577 $param->{ds
}, $param->{cf
});
581 __PACKAGE__-
>register_method({
583 path
=> '{vmid}/rrddata',
585 protected
=> 1, # fixme: can we avoid that?
587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
589 description
=> "Read VM RRD statistics",
591 additionalProperties
=> 0,
593 node
=> get_standard_option
('pve-node'),
594 vmid
=> get_standard_option
('pve-vmid'),
596 description
=> "Specify the time frame you are interested in.",
598 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
601 description
=> "The RRD consolidation function",
603 enum
=> [ 'AVERAGE', 'MAX' ],
618 return PVE
::Cluster
::create_rrd_data
(
619 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
623 __PACKAGE__-
>register_method({
625 path
=> '{vmid}/config',
628 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
633 additionalProperties
=> 0,
635 node
=> get_standard_option
('pve-node'),
636 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
638 description
=> "Get current values (instead of pending values).",
650 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
657 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
659 delete $conf->{snapshots
};
661 if (!$param->{current
}) {
662 foreach my $opt (keys %{$conf->{pending
}}) {
663 next if $opt eq 'delete';
664 my $value = $conf->{pending
}->{$opt};
665 next if ref($value); # just to be sure
666 $conf->{$opt} = $value;
668 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
669 foreach my $opt (keys %$pending_delete_hash) {
670 delete $conf->{$opt} if $conf->{$opt};
674 delete $conf->{pending
};
679 __PACKAGE__-
>register_method({
680 name
=> 'vm_pending',
681 path
=> '{vmid}/pending',
684 description
=> "Get virtual machine configuration, including pending changes.",
686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
689 additionalProperties
=> 0,
691 node
=> get_standard_option
('pve-node'),
692 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
701 description
=> "Configuration option name.",
705 description
=> "Current value.",
710 description
=> "Pending value.",
715 description
=> "Indicates a pending delete request if present and not 0. " .
716 "The value 2 indicates a force-delete request.",
728 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
730 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
734 foreach my $opt (keys %$conf) {
735 next if ref($conf->{$opt});
736 my $item = { key
=> $opt };
737 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
738 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
739 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
743 foreach my $opt (keys %{$conf->{pending
}}) {
744 next if $opt eq 'delete';
745 next if ref($conf->{pending
}->{$opt}); # just to be sure
746 next if defined($conf->{$opt});
747 my $item = { key
=> $opt };
748 $item->{pending
} = $conf->{pending
}->{$opt};
752 while (my ($opt, $force) = each %$pending_delete_hash) {
753 next if $conf->{pending
}->{$opt}; # just to be sure
754 next if $conf->{$opt};
755 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
762 # POST/PUT {vmid}/config implementation
764 # The original API used PUT (idempotent) an we assumed that all operations
765 # are fast. But it turned out that almost any configuration change can
766 # involve hot-plug actions, or disk alloc/free. Such actions can take long
767 # time to complete and have side effects (not idempotent).
769 # The new implementation uses POST and forks a worker process. We added
770 # a new option 'background_delay'. If specified we wait up to
771 # 'background_delay' second for the worker task to complete. It returns null
772 # if the task is finished within that time, else we return the UPID.
774 my $update_vm_api = sub {
775 my ($param, $sync) = @_;
777 my $rpcenv = PVE
::RPCEnvironment
::get
();
779 my $authuser = $rpcenv->get_user();
781 my $node = extract_param
($param, 'node');
783 my $vmid = extract_param
($param, 'vmid');
785 my $digest = extract_param
($param, 'digest');
787 my $background_delay = extract_param
($param, 'background_delay');
789 my @paramarr = (); # used for log message
790 foreach my $key (keys %$param) {
791 push @paramarr, "-$key", $param->{$key};
794 my $skiplock = extract_param
($param, 'skiplock');
795 raise_param_exc
({ skiplock
=> "Only root may use this option." })
796 if $skiplock && $authuser ne 'root@pam';
798 my $delete_str = extract_param
($param, 'delete');
800 my $revert_str = extract_param
($param, 'revert');
802 my $force = extract_param
($param, 'force');
804 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
806 my $storecfg = PVE
::Storage
::config
();
808 my $defaults = PVE
::QemuServer
::load_defaults
();
810 &$resolve_cdrom_alias($param);
812 # now try to verify all parameters
815 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
816 if (!PVE
::QemuServer
::option_exists
($opt)) {
817 raise_param_exc
({ revert
=> "unknown option '$opt'" });
820 raise_param_exc
({ delete => "you can't use '-$opt' and " .
821 "-revert $opt' at the same time" })
822 if defined($param->{$opt});
828 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
829 $opt = 'ide2' if $opt eq 'cdrom';
831 raise_param_exc
({ delete => "you can't use '-$opt' and " .
832 "-delete $opt' at the same time" })
833 if defined($param->{$opt});
835 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
836 "-revert $opt' at the same time" })
839 if (!PVE
::QemuServer
::option_exists
($opt)) {
840 raise_param_exc
({ delete => "unknown option '$opt'" });
846 foreach my $opt (keys %$param) {
847 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
849 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
850 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
851 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
852 } elsif ($opt =~ m/^net(\d+)$/) {
854 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
855 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
859 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
861 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
863 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
867 my $conf = PVE
::QemuConfig-
>load_config($vmid);
869 die "checksum missmatch (file change by other user?)\n"
870 if $digest && $digest ne $conf->{digest
};
872 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
874 foreach my $opt (keys %$revert) {
875 if (defined($conf->{$opt})) {
876 $param->{$opt} = $conf->{$opt};
877 } elsif (defined($conf->{pending
}->{$opt})) {
882 if ($param->{memory
} || defined($param->{balloon
})) {
883 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
884 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
886 die "balloon value too large (must be smaller than assigned memory)\n"
887 if $balloon && $balloon > $maxmem;
890 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
894 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
896 # write updates to pending section
898 my $modified = {}; # record what $option we modify
900 foreach my $opt (@delete) {
901 $modified->{$opt} = 1;
902 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
903 if ($opt =~ m/^unused/) {
904 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
905 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
906 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
907 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
908 delete $conf->{$opt};
909 PVE
::QemuConfig-
>write_config($vmid, $conf);
911 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
912 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
913 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
914 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
915 if defined($conf->{pending
}->{$opt});
916 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
917 PVE
::QemuConfig-
>write_config($vmid, $conf);
919 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
920 PVE
::QemuConfig-
>write_config($vmid, $conf);
924 foreach my $opt (keys %$param) { # add/change
925 $modified->{$opt} = 1;
926 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
927 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
929 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
930 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
931 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
932 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
934 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
936 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
937 if defined($conf->{pending
}->{$opt});
939 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
941 $conf->{pending
}->{$opt} = $param->{$opt};
943 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
944 PVE
::QemuConfig-
>write_config($vmid, $conf);
947 # remove pending changes when nothing changed
948 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
949 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
950 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
952 return if !scalar(keys %{$conf->{pending
}});
954 my $running = PVE
::QemuServer
::check_running
($vmid);
956 # apply pending changes
958 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
962 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
963 raise_param_exc
($errors) if scalar(keys %$errors);
965 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
975 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
977 if ($background_delay) {
979 # Note: It would be better to do that in the Event based HTTPServer
980 # to avoid blocking call to sleep.
982 my $end_time = time() + $background_delay;
984 my $task = PVE
::Tools
::upid_decode
($upid);
987 while (time() < $end_time) {
988 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
990 sleep(1); # this gets interrupted when child process ends
994 my $status = PVE
::Tools
::upid_read_status
($upid);
995 return undef if $status eq 'OK';
1004 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1007 my $vm_config_perm_list = [
1012 'VM.Config.Network',
1014 'VM.Config.Options',
1017 __PACKAGE__-
>register_method({
1018 name
=> 'update_vm_async',
1019 path
=> '{vmid}/config',
1023 description
=> "Set virtual machine options (asynchrounous API).",
1025 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1028 additionalProperties
=> 0,
1029 properties
=> PVE
::QemuServer
::json_config_properties
(
1031 node
=> get_standard_option
('pve-node'),
1032 vmid
=> get_standard_option
('pve-vmid'),
1033 skiplock
=> get_standard_option
('skiplock'),
1035 type
=> 'string', format
=> 'pve-configid-list',
1036 description
=> "A list of settings you want to delete.",
1040 type
=> 'string', format
=> 'pve-configid-list',
1041 description
=> "Revert a pending change.",
1046 description
=> $opt_force_description,
1048 requires
=> 'delete',
1052 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1056 background_delay
=> {
1058 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1069 code
=> $update_vm_api,
1072 __PACKAGE__-
>register_method({
1073 name
=> 'update_vm',
1074 path
=> '{vmid}/config',
1078 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1080 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1083 additionalProperties
=> 0,
1084 properties
=> PVE
::QemuServer
::json_config_properties
(
1086 node
=> get_standard_option
('pve-node'),
1087 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1088 skiplock
=> get_standard_option
('skiplock'),
1090 type
=> 'string', format
=> 'pve-configid-list',
1091 description
=> "A list of settings you want to delete.",
1095 type
=> 'string', format
=> 'pve-configid-list',
1096 description
=> "Revert a pending change.",
1101 description
=> $opt_force_description,
1103 requires
=> 'delete',
1107 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1113 returns
=> { type
=> 'null' },
1116 &$update_vm_api($param, 1);
1122 __PACKAGE__-
>register_method({
1123 name
=> 'destroy_vm',
1128 description
=> "Destroy the vm (also delete all used/owned volumes).",
1130 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1133 additionalProperties
=> 0,
1135 node
=> get_standard_option
('pve-node'),
1136 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1137 skiplock
=> get_standard_option
('skiplock'),
1146 my $rpcenv = PVE
::RPCEnvironment
::get
();
1148 my $authuser = $rpcenv->get_user();
1150 my $vmid = $param->{vmid
};
1152 my $skiplock = $param->{skiplock
};
1153 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1154 if $skiplock && $authuser ne 'root@pam';
1157 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1159 my $storecfg = PVE
::Storage
::config
();
1161 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1163 die "unable to remove VM $vmid - used in HA resources\n"
1164 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1166 # early tests (repeat after locking)
1167 die "VM $vmid is running - destroy failed\n"
1168 if PVE
::QemuServer
::check_running
($vmid);
1173 syslog
('info', "destroy VM $vmid: $upid\n");
1175 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1177 PVE
::AccessControl
::remove_vm_access
($vmid);
1179 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1182 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1185 __PACKAGE__-
>register_method({
1187 path
=> '{vmid}/unlink',
1191 description
=> "Unlink/delete disk images.",
1193 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1196 additionalProperties
=> 0,
1198 node
=> get_standard_option
('pve-node'),
1199 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1201 type
=> 'string', format
=> 'pve-configid-list',
1202 description
=> "A list of disk IDs you want to delete.",
1206 description
=> $opt_force_description,
1211 returns
=> { type
=> 'null'},
1215 $param->{delete} = extract_param
($param, 'idlist');
1217 __PACKAGE__-
>update_vm($param);
1224 __PACKAGE__-
>register_method({
1226 path
=> '{vmid}/vncproxy',
1230 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1232 description
=> "Creates a TCP VNC proxy connections.",
1234 additionalProperties
=> 0,
1236 node
=> get_standard_option
('pve-node'),
1237 vmid
=> get_standard_option
('pve-vmid'),
1241 description
=> "starts websockify instead of vncproxy",
1246 additionalProperties
=> 0,
1248 user
=> { type
=> 'string' },
1249 ticket
=> { type
=> 'string' },
1250 cert
=> { type
=> 'string' },
1251 port
=> { type
=> 'integer' },
1252 upid
=> { type
=> 'string' },
1258 my $rpcenv = PVE
::RPCEnvironment
::get
();
1260 my $authuser = $rpcenv->get_user();
1262 my $vmid = $param->{vmid
};
1263 my $node = $param->{node
};
1264 my $websocket = $param->{websocket
};
1266 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1268 my $authpath = "/vms/$vmid";
1270 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1272 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1275 my ($remip, $family);
1278 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1279 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1280 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1281 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1283 $family = PVE
::Tools
::get_host_address_family
($node);
1286 my $port = PVE
::Tools
::next_vnc_port
($family);
1293 syslog
('info', "starting vnc proxy $upid\n");
1297 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1299 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1301 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1302 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1303 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1304 '-timeout', $timeout, '-authpath', $authpath,
1305 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1308 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1310 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1312 my $qmstr = join(' ', @$qmcmd);
1314 # also redirect stderr (else we get RFB protocol errors)
1315 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1318 PVE
::Tools
::run_command
($cmd);
1323 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1325 PVE
::Tools
::wait_for_vnc_port
($port);
1336 __PACKAGE__-
>register_method({
1337 name
=> 'vncwebsocket',
1338 path
=> '{vmid}/vncwebsocket',
1341 description
=> "You also need to pass a valid ticket (vncticket).",
1342 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1344 description
=> "Opens a weksocket for VNC traffic.",
1346 additionalProperties
=> 0,
1348 node
=> get_standard_option
('pve-node'),
1349 vmid
=> get_standard_option
('pve-vmid'),
1351 description
=> "Ticket from previous call to vncproxy.",
1356 description
=> "Port number returned by previous vncproxy call.",
1366 port
=> { type
=> 'string' },
1372 my $rpcenv = PVE
::RPCEnvironment
::get
();
1374 my $authuser = $rpcenv->get_user();
1376 my $vmid = $param->{vmid
};
1377 my $node = $param->{node
};
1379 my $authpath = "/vms/$vmid";
1381 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1383 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1385 # Note: VNC ports are acessible from outside, so we do not gain any
1386 # security if we verify that $param->{port} belongs to VM $vmid. This
1387 # check is done by verifying the VNC ticket (inside VNC protocol).
1389 my $port = $param->{port
};
1391 return { port
=> $port };
1394 __PACKAGE__-
>register_method({
1395 name
=> 'spiceproxy',
1396 path
=> '{vmid}/spiceproxy',
1401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1403 description
=> "Returns a SPICE configuration to connect to the VM.",
1405 additionalProperties
=> 0,
1407 node
=> get_standard_option
('pve-node'),
1408 vmid
=> get_standard_option
('pve-vmid'),
1409 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1412 returns
=> get_standard_option
('remote-viewer-config'),
1416 my $rpcenv = PVE
::RPCEnvironment
::get
();
1418 my $authuser = $rpcenv->get_user();
1420 my $vmid = $param->{vmid
};
1421 my $node = $param->{node
};
1422 my $proxy = $param->{proxy
};
1424 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1425 my $title = "VM $vmid";
1426 $title .= " - ". $conf->{name
} if $conf->{name
};
1428 my $port = PVE
::QemuServer
::spice_port
($vmid);
1430 my ($ticket, undef, $remote_viewer_config) =
1431 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1433 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1434 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1436 return $remote_viewer_config;
1439 __PACKAGE__-
>register_method({
1441 path
=> '{vmid}/status',
1444 description
=> "Directory index",
1449 additionalProperties
=> 0,
1451 node
=> get_standard_option
('pve-node'),
1452 vmid
=> get_standard_option
('pve-vmid'),
1460 subdir
=> { type
=> 'string' },
1463 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1469 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1472 { subdir
=> 'current' },
1473 { subdir
=> 'start' },
1474 { subdir
=> 'stop' },
1480 __PACKAGE__-
>register_method({
1481 name
=> 'vm_status',
1482 path
=> '{vmid}/status/current',
1485 protected
=> 1, # qemu pid files are only readable by root
1486 description
=> "Get virtual machine status.",
1488 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1491 additionalProperties
=> 0,
1493 node
=> get_standard_option
('pve-node'),
1494 vmid
=> get_standard_option
('pve-vmid'),
1497 returns
=> { type
=> 'object' },
1502 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1504 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1505 my $status = $vmstatus->{$param->{vmid
}};
1507 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1509 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1514 __PACKAGE__-
>register_method({
1516 path
=> '{vmid}/status/start',
1520 description
=> "Start virtual machine.",
1522 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1525 additionalProperties
=> 0,
1527 node
=> get_standard_option
('pve-node'),
1528 vmid
=> get_standard_option
('pve-vmid',
1529 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1530 skiplock
=> get_standard_option
('skiplock'),
1531 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1532 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1533 machine
=> get_standard_option
('pve-qm-machine'),
1542 my $rpcenv = PVE
::RPCEnvironment
::get
();
1544 my $authuser = $rpcenv->get_user();
1546 my $node = extract_param
($param, 'node');
1548 my $vmid = extract_param
($param, 'vmid');
1550 my $machine = extract_param
($param, 'machine');
1552 my $stateuri = extract_param
($param, 'stateuri');
1553 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1554 if $stateuri && $authuser ne 'root@pam';
1556 my $skiplock = extract_param
($param, 'skiplock');
1557 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1558 if $skiplock && $authuser ne 'root@pam';
1560 my $migratedfrom = extract_param
($param, 'migratedfrom');
1561 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1562 if $migratedfrom && $authuser ne 'root@pam';
1564 # read spice ticket from STDIN
1566 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1567 if (defined(my $line = <>)) {
1569 $spice_ticket = $line;
1573 PVE
::Cluster
::check_cfs_quorum
();
1575 my $storecfg = PVE
::Storage
::config
();
1577 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1578 $rpcenv->{type
} ne 'ha') {
1583 my $service = "vm:$vmid";
1585 my $cmd = ['ha-manager', 'enable', $service];
1587 print "Executing HA start for VM $vmid\n";
1589 PVE
::Tools
::run_command
($cmd);
1594 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1601 syslog
('info', "start VM $vmid: $upid\n");
1603 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1604 $machine, $spice_ticket);
1609 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1613 __PACKAGE__-
>register_method({
1615 path
=> '{vmid}/status/stop',
1619 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1620 "is akin to pulling the power plug of a running computer and may damage the VM data",
1622 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1625 additionalProperties
=> 0,
1627 node
=> get_standard_option
('pve-node'),
1628 vmid
=> get_standard_option
('pve-vmid',
1629 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1630 skiplock
=> get_standard_option
('skiplock'),
1631 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1633 description
=> "Wait maximal timeout seconds.",
1639 description
=> "Do not decativate storage volumes.",
1652 my $rpcenv = PVE
::RPCEnvironment
::get
();
1654 my $authuser = $rpcenv->get_user();
1656 my $node = extract_param
($param, 'node');
1658 my $vmid = extract_param
($param, 'vmid');
1660 my $skiplock = extract_param
($param, 'skiplock');
1661 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1662 if $skiplock && $authuser ne 'root@pam';
1664 my $keepActive = extract_param
($param, 'keepActive');
1665 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1666 if $keepActive && $authuser ne 'root@pam';
1668 my $migratedfrom = extract_param
($param, 'migratedfrom');
1669 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1670 if $migratedfrom && $authuser ne 'root@pam';
1673 my $storecfg = PVE
::Storage
::config
();
1675 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1680 my $service = "vm:$vmid";
1682 my $cmd = ['ha-manager', 'disable', $service];
1684 print "Executing HA stop for VM $vmid\n";
1686 PVE
::Tools
::run_command
($cmd);
1691 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1697 syslog
('info', "stop VM $vmid: $upid\n");
1699 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1700 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1705 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1709 __PACKAGE__-
>register_method({
1711 path
=> '{vmid}/status/reset',
1715 description
=> "Reset virtual machine.",
1717 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1720 additionalProperties
=> 0,
1722 node
=> get_standard_option
('pve-node'),
1723 vmid
=> get_standard_option
('pve-vmid',
1724 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1725 skiplock
=> get_standard_option
('skiplock'),
1734 my $rpcenv = PVE
::RPCEnvironment
::get
();
1736 my $authuser = $rpcenv->get_user();
1738 my $node = extract_param
($param, 'node');
1740 my $vmid = extract_param
($param, 'vmid');
1742 my $skiplock = extract_param
($param, 'skiplock');
1743 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1744 if $skiplock && $authuser ne 'root@pam';
1746 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1751 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1756 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1759 __PACKAGE__-
>register_method({
1760 name
=> 'vm_shutdown',
1761 path
=> '{vmid}/status/shutdown',
1765 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1766 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1768 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1771 additionalProperties
=> 0,
1773 node
=> get_standard_option
('pve-node'),
1774 vmid
=> get_standard_option
('pve-vmid',
1775 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1776 skiplock
=> get_standard_option
('skiplock'),
1778 description
=> "Wait maximal timeout seconds.",
1784 description
=> "Make sure the VM stops.",
1790 description
=> "Do not decativate storage volumes.",
1803 my $rpcenv = PVE
::RPCEnvironment
::get
();
1805 my $authuser = $rpcenv->get_user();
1807 my $node = extract_param
($param, 'node');
1809 my $vmid = extract_param
($param, 'vmid');
1811 my $skiplock = extract_param
($param, 'skiplock');
1812 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1813 if $skiplock && $authuser ne 'root@pam';
1815 my $keepActive = extract_param
($param, 'keepActive');
1816 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1817 if $keepActive && $authuser ne 'root@pam';
1819 my $storecfg = PVE
::Storage
::config
();
1823 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1824 # otherwise, we will infer a shutdown command, but run into the timeout,
1825 # then when the vm is resumed, it will instantly shutdown
1827 # checking the qmp status here to get feedback to the gui/cli/api
1828 # and the status query should not take too long
1831 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1835 if (!$err && $qmpstatus->{status
} eq "paused") {
1836 if ($param->{forceStop
}) {
1837 warn "VM is paused - stop instead of shutdown\n";
1840 die "VM is paused - cannot shutdown\n";
1847 syslog
('info', "shutdown VM $vmid: $upid\n");
1849 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1850 $shutdown, $param->{forceStop
}, $keepActive);
1855 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1858 __PACKAGE__-
>register_method({
1859 name
=> 'vm_suspend',
1860 path
=> '{vmid}/status/suspend',
1864 description
=> "Suspend virtual machine.",
1866 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1869 additionalProperties
=> 0,
1871 node
=> get_standard_option
('pve-node'),
1872 vmid
=> get_standard_option
('pve-vmid',
1873 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1874 skiplock
=> get_standard_option
('skiplock'),
1883 my $rpcenv = PVE
::RPCEnvironment
::get
();
1885 my $authuser = $rpcenv->get_user();
1887 my $node = extract_param
($param, 'node');
1889 my $vmid = extract_param
($param, 'vmid');
1891 my $skiplock = extract_param
($param, 'skiplock');
1892 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1893 if $skiplock && $authuser ne 'root@pam';
1895 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1900 syslog
('info', "suspend VM $vmid: $upid\n");
1902 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1907 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1910 __PACKAGE__-
>register_method({
1911 name
=> 'vm_resume',
1912 path
=> '{vmid}/status/resume',
1916 description
=> "Resume virtual machine.",
1918 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1921 additionalProperties
=> 0,
1923 node
=> get_standard_option
('pve-node'),
1924 vmid
=> get_standard_option
('pve-vmid',
1925 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1926 skiplock
=> get_standard_option
('skiplock'),
1927 nocheck
=> { type
=> 'boolean', optional
=> 1 },
1937 my $rpcenv = PVE
::RPCEnvironment
::get
();
1939 my $authuser = $rpcenv->get_user();
1941 my $node = extract_param
($param, 'node');
1943 my $vmid = extract_param
($param, 'vmid');
1945 my $skiplock = extract_param
($param, 'skiplock');
1946 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1947 if $skiplock && $authuser ne 'root@pam';
1949 my $nocheck = extract_param
($param, 'nocheck');
1951 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
1956 syslog
('info', "resume VM $vmid: $upid\n");
1958 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
1963 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1966 __PACKAGE__-
>register_method({
1967 name
=> 'vm_sendkey',
1968 path
=> '{vmid}/sendkey',
1972 description
=> "Send key event to virtual machine.",
1974 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1977 additionalProperties
=> 0,
1979 node
=> get_standard_option
('pve-node'),
1980 vmid
=> get_standard_option
('pve-vmid',
1981 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1982 skiplock
=> get_standard_option
('skiplock'),
1984 description
=> "The key (qemu monitor encoding).",
1989 returns
=> { type
=> 'null'},
1993 my $rpcenv = PVE
::RPCEnvironment
::get
();
1995 my $authuser = $rpcenv->get_user();
1997 my $node = extract_param
($param, 'node');
1999 my $vmid = extract_param
($param, 'vmid');
2001 my $skiplock = extract_param
($param, 'skiplock');
2002 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2003 if $skiplock && $authuser ne 'root@pam';
2005 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2010 __PACKAGE__-
>register_method({
2011 name
=> 'vm_feature',
2012 path
=> '{vmid}/feature',
2016 description
=> "Check if feature for virtual machine is available.",
2018 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2021 additionalProperties
=> 0,
2023 node
=> get_standard_option
('pve-node'),
2024 vmid
=> get_standard_option
('pve-vmid'),
2026 description
=> "Feature to check.",
2028 enum
=> [ 'snapshot', 'clone', 'copy' ],
2030 snapname
=> get_standard_option
('pve-snapshot-name', {
2038 hasFeature
=> { type
=> 'boolean' },
2041 items
=> { type
=> 'string' },
2048 my $node = extract_param
($param, 'node');
2050 my $vmid = extract_param
($param, 'vmid');
2052 my $snapname = extract_param
($param, 'snapname');
2054 my $feature = extract_param
($param, 'feature');
2056 my $running = PVE
::QemuServer
::check_running
($vmid);
2058 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2061 my $snap = $conf->{snapshots
}->{$snapname};
2062 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2065 my $storecfg = PVE
::Storage
::config
();
2067 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2068 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2071 hasFeature
=> $hasFeature,
2072 nodes
=> [ keys %$nodelist ],
2076 __PACKAGE__-
>register_method({
2078 path
=> '{vmid}/clone',
2082 description
=> "Create a copy of virtual machine/template.",
2084 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2085 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2086 "'Datastore.AllocateSpace' on any used storage.",
2089 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2091 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2092 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2097 additionalProperties
=> 0,
2099 node
=> get_standard_option
('pve-node'),
2100 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2101 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2104 type
=> 'string', format
=> 'dns-name',
2105 description
=> "Set a name for the new VM.",
2110 description
=> "Description for the new VM.",
2114 type
=> 'string', format
=> 'pve-poolid',
2115 description
=> "Add the new VM to the specified pool.",
2117 snapname
=> get_standard_option
('pve-snapshot-name', {
2120 storage
=> get_standard_option
('pve-storage-id', {
2121 description
=> "Target storage for full clone.",
2126 description
=> "Target format for file storage.",
2130 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2135 description
=> "Create a full copy of all disk. This is always done when " .
2136 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2139 target
=> get_standard_option
('pve-node', {
2140 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2151 my $rpcenv = PVE
::RPCEnvironment
::get
();
2153 my $authuser = $rpcenv->get_user();
2155 my $node = extract_param
($param, 'node');
2157 my $vmid = extract_param
($param, 'vmid');
2159 my $newid = extract_param
($param, 'newid');
2161 my $pool = extract_param
($param, 'pool');
2163 if (defined($pool)) {
2164 $rpcenv->check_pool_exist($pool);
2167 my $snapname = extract_param
($param, 'snapname');
2169 my $storage = extract_param
($param, 'storage');
2171 my $format = extract_param
($param, 'format');
2173 my $target = extract_param
($param, 'target');
2175 my $localnode = PVE
::INotify
::nodename
();
2177 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2179 PVE
::Cluster
::check_node_exists
($target) if $target;
2181 my $storecfg = PVE
::Storage
::config
();
2184 # check if storage is enabled on local node
2185 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2187 # check if storage is available on target node
2188 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2189 # clone only works if target storage is shared
2190 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2191 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2195 PVE
::Cluster
::check_cfs_quorum
();
2197 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2199 # exclusive lock if VM is running - else shared lock is enough;
2200 my $shared_lock = $running ?
0 : 1;
2204 # do all tests after lock
2205 # we also try to do all tests before we fork the worker
2207 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2209 PVE
::QemuConfig-
>check_lock($conf);
2211 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2213 die "unexpected state change\n" if $verify_running != $running;
2215 die "snapshot '$snapname' does not exist\n"
2216 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2218 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2220 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2222 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2224 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2226 die "unable to create VM $newid: config file already exists\n"
2229 my $newconf = { lock => 'clone' };
2234 foreach my $opt (keys %$oldconf) {
2235 my $value = $oldconf->{$opt};
2237 # do not copy snapshot related info
2238 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2239 $opt eq 'vmstate' || $opt eq 'snapstate';
2241 # no need to copy unused images, because VMID(owner) changes anyways
2242 next if $opt =~ m/^unused\d+$/;
2244 # always change MAC! address
2245 if ($opt =~ m/^net(\d+)$/) {
2246 my $net = PVE
::QemuServer
::parse_net
($value);
2247 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2248 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2249 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2250 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2251 die "unable to parse drive options for '$opt'\n" if !$drive;
2252 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2253 $newconf->{$opt} = $value; # simply copy configuration
2255 if ($param->{full
}) {
2256 die "Full clone feature is not available"
2257 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2258 $fullclone->{$opt} = 1;
2260 # not full means clone instead of copy
2261 die "Linked clone feature is not available"
2262 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2264 $drives->{$opt} = $drive;
2265 push @$vollist, $drive->{file
};
2268 # copy everything else
2269 $newconf->{$opt} = $value;
2273 # auto generate a new uuid
2274 my ($uuid, $uuid_str);
2275 UUID
::generate
($uuid);
2276 UUID
::unparse
($uuid, $uuid_str);
2277 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2278 $smbios1->{uuid
} = $uuid_str;
2279 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2281 delete $newconf->{template
};
2283 if ($param->{name
}) {
2284 $newconf->{name
} = $param->{name
};
2286 if ($oldconf->{name
}) {
2287 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2289 $newconf->{name
} = "Copy-of-VM-$vmid";
2293 if ($param->{description
}) {
2294 $newconf->{description
} = $param->{description
};
2297 # create empty/temp config - this fails if VM already exists on other node
2298 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2303 my $newvollist = [];
2306 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2308 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2310 foreach my $opt (keys %$drives) {
2311 my $drive = $drives->{$opt};
2313 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2314 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2316 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2318 PVE
::QemuConfig-
>write_config($newid, $newconf);
2321 delete $newconf->{lock};
2322 PVE
::QemuConfig-
>write_config($newid, $newconf);
2325 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2326 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2328 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2329 die "Failed to move config to node '$target' - rename failed: $!\n"
2330 if !rename($conffile, $newconffile);
2333 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2338 sleep 1; # some storage like rbd need to wait before release volume - really?
2340 foreach my $volid (@$newvollist) {
2341 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2344 die "clone failed: $err";
2350 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2352 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2355 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2356 # Aquire exclusive lock lock for $newid
2357 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2362 __PACKAGE__-
>register_method({
2363 name
=> 'move_vm_disk',
2364 path
=> '{vmid}/move_disk',
2368 description
=> "Move volume to different storage.",
2370 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2371 "and 'Datastore.AllocateSpace' permissions on the storage.",
2374 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2375 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2379 additionalProperties
=> 0,
2381 node
=> get_standard_option
('pve-node'),
2382 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2385 description
=> "The disk you want to move.",
2386 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2388 storage
=> get_standard_option
('pve-storage-id', {
2389 description
=> "Target storage.",
2390 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2394 description
=> "Target Format.",
2395 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2400 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2406 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2414 description
=> "the task ID.",
2419 my $rpcenv = PVE
::RPCEnvironment
::get
();
2421 my $authuser = $rpcenv->get_user();
2423 my $node = extract_param
($param, 'node');
2425 my $vmid = extract_param
($param, 'vmid');
2427 my $digest = extract_param
($param, 'digest');
2429 my $disk = extract_param
($param, 'disk');
2431 my $storeid = extract_param
($param, 'storage');
2433 my $format = extract_param
($param, 'format');
2435 my $storecfg = PVE
::Storage
::config
();
2437 my $updatefn = sub {
2439 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2441 die "checksum missmatch (file change by other user?)\n"
2442 if $digest && $digest ne $conf->{digest
};
2444 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2446 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2448 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2450 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2453 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2454 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2458 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2459 (!$format || !$oldfmt || $oldfmt eq $format);
2461 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2463 my $running = PVE
::QemuServer
::check_running
($vmid);
2465 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2469 my $newvollist = [];
2472 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2474 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2475 $vmid, $storeid, $format, 1, $newvollist);
2477 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2479 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2481 PVE
::QemuConfig-
>write_config($vmid, $conf);
2484 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2485 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2492 foreach my $volid (@$newvollist) {
2493 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2496 die "storage migration failed: $err";
2499 if ($param->{delete}) {
2500 if (PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, undef, $old_volid)) {
2501 warn "volume $old_volid still has snapshots, can't delete it\n";
2502 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid);
2503 PVE
::QemuConfig-
>write_config($vmid, $conf);
2506 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2507 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2514 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2517 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2520 __PACKAGE__-
>register_method({
2521 name
=> 'migrate_vm',
2522 path
=> '{vmid}/migrate',
2526 description
=> "Migrate virtual machine. Creates a new migration task.",
2528 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2531 additionalProperties
=> 0,
2533 node
=> get_standard_option
('pve-node'),
2534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2535 target
=> get_standard_option
('pve-node', {
2536 description
=> "Target node.",
2537 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2541 description
=> "Use online/live migration.",
2546 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2553 description
=> "the task ID.",
2558 my $rpcenv = PVE
::RPCEnvironment
::get
();
2560 my $authuser = $rpcenv->get_user();
2562 my $target = extract_param
($param, 'target');
2564 my $localnode = PVE
::INotify
::nodename
();
2565 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2567 PVE
::Cluster
::check_cfs_quorum
();
2569 PVE
::Cluster
::check_node_exists
($target);
2571 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2573 my $vmid = extract_param
($param, 'vmid');
2575 raise_param_exc
({ force
=> "Only root may use this option." })
2576 if $param->{force
} && $authuser ne 'root@pam';
2579 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2581 # try to detect errors early
2583 PVE
::QemuConfig-
>check_lock($conf);
2585 if (PVE
::QemuServer
::check_running
($vmid)) {
2586 die "cant migrate running VM without --online\n"
2587 if !$param->{online
};
2590 my $storecfg = PVE
::Storage
::config
();
2591 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2593 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2598 my $service = "vm:$vmid";
2600 my $cmd = ['ha-manager', 'migrate', $service, $target];
2602 print "Executing HA migrate for VM $vmid to node $target\n";
2604 PVE
::Tools
::run_command
($cmd);
2609 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2616 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2619 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2624 __PACKAGE__-
>register_method({
2626 path
=> '{vmid}/monitor',
2630 description
=> "Execute Qemu monitor commands.",
2632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2635 additionalProperties
=> 0,
2637 node
=> get_standard_option
('pve-node'),
2638 vmid
=> get_standard_option
('pve-vmid'),
2641 description
=> "The monitor command.",
2645 returns
=> { type
=> 'string'},
2649 my $vmid = $param->{vmid
};
2651 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2655 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2657 $res = "ERROR: $@" if $@;
2662 __PACKAGE__-
>register_method({
2663 name
=> 'resize_vm',
2664 path
=> '{vmid}/resize',
2668 description
=> "Extend volume size.",
2670 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2673 additionalProperties
=> 0,
2675 node
=> get_standard_option
('pve-node'),
2676 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2677 skiplock
=> get_standard_option
('skiplock'),
2680 description
=> "The disk you want to resize.",
2681 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2685 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2686 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.",
2690 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2696 returns
=> { type
=> 'null'},
2700 my $rpcenv = PVE
::RPCEnvironment
::get
();
2702 my $authuser = $rpcenv->get_user();
2704 my $node = extract_param
($param, 'node');
2706 my $vmid = extract_param
($param, 'vmid');
2708 my $digest = extract_param
($param, 'digest');
2710 my $disk = extract_param
($param, 'disk');
2712 my $sizestr = extract_param
($param, 'size');
2714 my $skiplock = extract_param
($param, 'skiplock');
2715 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2716 if $skiplock && $authuser ne 'root@pam';
2718 my $storecfg = PVE
::Storage
::config
();
2720 my $updatefn = sub {
2722 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2724 die "checksum missmatch (file change by other user?)\n"
2725 if $digest && $digest ne $conf->{digest
};
2726 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2728 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2730 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2732 my (undef, undef, undef, undef, undef, undef, $format) =
2733 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2735 die "can't resize volume: $disk if snapshot exists\n"
2736 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2738 my $volid = $drive->{file
};
2740 die "disk '$disk' has no associated volume\n" if !$volid;
2742 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2744 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2746 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2748 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2750 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2751 my ($ext, $newsize, $unit) = ($1, $2, $4);
2754 $newsize = $newsize * 1024;
2755 } elsif ($unit eq 'M') {
2756 $newsize = $newsize * 1024 * 1024;
2757 } elsif ($unit eq 'G') {
2758 $newsize = $newsize * 1024 * 1024 * 1024;
2759 } elsif ($unit eq 'T') {
2760 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2763 $newsize += $size if $ext;
2764 $newsize = int($newsize);
2766 die "unable to skrink disk size\n" if $newsize < $size;
2768 return if $size == $newsize;
2770 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2772 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2774 $drive->{size
} = $newsize;
2775 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2777 PVE
::QemuConfig-
>write_config($vmid, $conf);
2780 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2784 __PACKAGE__-
>register_method({
2785 name
=> 'snapshot_list',
2786 path
=> '{vmid}/snapshot',
2788 description
=> "List all snapshots.",
2790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2793 protected
=> 1, # qemu pid files are only readable by root
2795 additionalProperties
=> 0,
2797 vmid
=> get_standard_option
('pve-vmid'),
2798 node
=> get_standard_option
('pve-node'),
2807 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2812 my $vmid = $param->{vmid
};
2814 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2815 my $snaphash = $conf->{snapshots
} || {};
2819 foreach my $name (keys %$snaphash) {
2820 my $d = $snaphash->{$name};
2823 snaptime
=> $d->{snaptime
} || 0,
2824 vmstate
=> $d->{vmstate
} ?
1 : 0,
2825 description
=> $d->{description
} || '',
2827 $item->{parent
} = $d->{parent
} if $d->{parent
};
2828 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2832 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2833 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2834 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2836 push @$res, $current;
2841 __PACKAGE__-
>register_method({
2843 path
=> '{vmid}/snapshot',
2847 description
=> "Snapshot a VM.",
2849 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2852 additionalProperties
=> 0,
2854 node
=> get_standard_option
('pve-node'),
2855 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2856 snapname
=> get_standard_option
('pve-snapshot-name'),
2860 description
=> "Save the vmstate",
2865 description
=> "A textual description or comment.",
2871 description
=> "the task ID.",
2876 my $rpcenv = PVE
::RPCEnvironment
::get
();
2878 my $authuser = $rpcenv->get_user();
2880 my $node = extract_param
($param, 'node');
2882 my $vmid = extract_param
($param, 'vmid');
2884 my $snapname = extract_param
($param, 'snapname');
2886 die "unable to use snapshot name 'current' (reserved name)\n"
2887 if $snapname eq 'current';
2890 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2891 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2892 $param->{description
});
2895 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2898 __PACKAGE__-
>register_method({
2899 name
=> 'snapshot_cmd_idx',
2900 path
=> '{vmid}/snapshot/{snapname}',
2907 additionalProperties
=> 0,
2909 vmid
=> get_standard_option
('pve-vmid'),
2910 node
=> get_standard_option
('pve-node'),
2911 snapname
=> get_standard_option
('pve-snapshot-name'),
2920 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2927 push @$res, { cmd
=> 'rollback' };
2928 push @$res, { cmd
=> 'config' };
2933 __PACKAGE__-
>register_method({
2934 name
=> 'update_snapshot_config',
2935 path
=> '{vmid}/snapshot/{snapname}/config',
2939 description
=> "Update snapshot metadata.",
2941 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2944 additionalProperties
=> 0,
2946 node
=> get_standard_option
('pve-node'),
2947 vmid
=> get_standard_option
('pve-vmid'),
2948 snapname
=> get_standard_option
('pve-snapshot-name'),
2952 description
=> "A textual description or comment.",
2956 returns
=> { type
=> 'null' },
2960 my $rpcenv = PVE
::RPCEnvironment
::get
();
2962 my $authuser = $rpcenv->get_user();
2964 my $vmid = extract_param
($param, 'vmid');
2966 my $snapname = extract_param
($param, 'snapname');
2968 return undef if !defined($param->{description
});
2970 my $updatefn = sub {
2972 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2974 PVE
::QemuConfig-
>check_lock($conf);
2976 my $snap = $conf->{snapshots
}->{$snapname};
2978 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2980 $snap->{description
} = $param->{description
} if defined($param->{description
});
2982 PVE
::QemuConfig-
>write_config($vmid, $conf);
2985 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2990 __PACKAGE__-
>register_method({
2991 name
=> 'get_snapshot_config',
2992 path
=> '{vmid}/snapshot/{snapname}/config',
2995 description
=> "Get snapshot configuration",
2997 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3000 additionalProperties
=> 0,
3002 node
=> get_standard_option
('pve-node'),
3003 vmid
=> get_standard_option
('pve-vmid'),
3004 snapname
=> get_standard_option
('pve-snapshot-name'),
3007 returns
=> { type
=> "object" },
3011 my $rpcenv = PVE
::RPCEnvironment
::get
();
3013 my $authuser = $rpcenv->get_user();
3015 my $vmid = extract_param
($param, 'vmid');
3017 my $snapname = extract_param
($param, 'snapname');
3019 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3021 my $snap = $conf->{snapshots
}->{$snapname};
3023 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3028 __PACKAGE__-
>register_method({
3030 path
=> '{vmid}/snapshot/{snapname}/rollback',
3034 description
=> "Rollback VM state to specified snapshot.",
3036 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3039 additionalProperties
=> 0,
3041 node
=> get_standard_option
('pve-node'),
3042 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3043 snapname
=> get_standard_option
('pve-snapshot-name'),
3048 description
=> "the task ID.",
3053 my $rpcenv = PVE
::RPCEnvironment
::get
();
3055 my $authuser = $rpcenv->get_user();
3057 my $node = extract_param
($param, 'node');
3059 my $vmid = extract_param
($param, 'vmid');
3061 my $snapname = extract_param
($param, 'snapname');
3064 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3065 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3068 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3071 __PACKAGE__-
>register_method({
3072 name
=> 'delsnapshot',
3073 path
=> '{vmid}/snapshot/{snapname}',
3077 description
=> "Delete a VM snapshot.",
3079 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3082 additionalProperties
=> 0,
3084 node
=> get_standard_option
('pve-node'),
3085 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3086 snapname
=> get_standard_option
('pve-snapshot-name'),
3090 description
=> "For removal from config file, even if removing disk snapshots fails.",
3096 description
=> "the task ID.",
3101 my $rpcenv = PVE
::RPCEnvironment
::get
();
3103 my $authuser = $rpcenv->get_user();
3105 my $node = extract_param
($param, 'node');
3107 my $vmid = extract_param
($param, 'vmid');
3109 my $snapname = extract_param
($param, 'snapname');
3112 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3113 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3116 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3119 __PACKAGE__-
>register_method({
3121 path
=> '{vmid}/template',
3125 description
=> "Create a Template.",
3127 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3128 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3131 additionalProperties
=> 0,
3133 node
=> get_standard_option
('pve-node'),
3134 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3138 description
=> "If you want to convert only 1 disk to base image.",
3139 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3144 returns
=> { type
=> 'null'},
3148 my $rpcenv = PVE
::RPCEnvironment
::get
();
3150 my $authuser = $rpcenv->get_user();
3152 my $node = extract_param
($param, 'node');
3154 my $vmid = extract_param
($param, 'vmid');
3156 my $disk = extract_param
($param, 'disk');
3158 my $updatefn = sub {
3160 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3162 PVE
::QemuConfig-
>check_lock($conf);
3164 die "unable to create template, because VM contains snapshots\n"
3165 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3167 die "you can't convert a template to a template\n"
3168 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3170 die "you can't convert a VM to template if VM is running\n"
3171 if PVE
::QemuServer
::check_running
($vmid);
3174 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3177 $conf->{template
} = 1;
3178 PVE
::QemuConfig-
>write_config($vmid, $conf);
3180 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3183 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);