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);
2505 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2511 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2514 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2517 __PACKAGE__-
>register_method({
2518 name
=> 'migrate_vm',
2519 path
=> '{vmid}/migrate',
2523 description
=> "Migrate virtual machine. Creates a new migration task.",
2525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2528 additionalProperties
=> 0,
2530 node
=> get_standard_option
('pve-node'),
2531 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2532 target
=> get_standard_option
('pve-node', {
2533 description
=> "Target node.",
2534 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2538 description
=> "Use online/live migration.",
2543 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2550 description
=> "the task ID.",
2555 my $rpcenv = PVE
::RPCEnvironment
::get
();
2557 my $authuser = $rpcenv->get_user();
2559 my $target = extract_param
($param, 'target');
2561 my $localnode = PVE
::INotify
::nodename
();
2562 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2564 PVE
::Cluster
::check_cfs_quorum
();
2566 PVE
::Cluster
::check_node_exists
($target);
2568 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2570 my $vmid = extract_param
($param, 'vmid');
2572 raise_param_exc
({ force
=> "Only root may use this option." })
2573 if $param->{force
} && $authuser ne 'root@pam';
2576 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2578 # try to detect errors early
2580 PVE
::QemuConfig-
>check_lock($conf);
2582 if (PVE
::QemuServer
::check_running
($vmid)) {
2583 die "cant migrate running VM without --online\n"
2584 if !$param->{online
};
2587 my $storecfg = PVE
::Storage
::config
();
2588 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2590 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2595 my $service = "vm:$vmid";
2597 my $cmd = ['ha-manager', 'migrate', $service, $target];
2599 print "Executing HA migrate for VM $vmid to node $target\n";
2601 PVE
::Tools
::run_command
($cmd);
2606 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2613 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2616 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2621 __PACKAGE__-
>register_method({
2623 path
=> '{vmid}/monitor',
2627 description
=> "Execute Qemu monitor commands.",
2629 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2632 additionalProperties
=> 0,
2634 node
=> get_standard_option
('pve-node'),
2635 vmid
=> get_standard_option
('pve-vmid'),
2638 description
=> "The monitor command.",
2642 returns
=> { type
=> 'string'},
2646 my $vmid = $param->{vmid
};
2648 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2652 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2654 $res = "ERROR: $@" if $@;
2659 __PACKAGE__-
>register_method({
2660 name
=> 'resize_vm',
2661 path
=> '{vmid}/resize',
2665 description
=> "Extend volume size.",
2667 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2670 additionalProperties
=> 0,
2672 node
=> get_standard_option
('pve-node'),
2673 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2674 skiplock
=> get_standard_option
('skiplock'),
2677 description
=> "The disk you want to resize.",
2678 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2682 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2683 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.",
2687 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2693 returns
=> { type
=> 'null'},
2697 my $rpcenv = PVE
::RPCEnvironment
::get
();
2699 my $authuser = $rpcenv->get_user();
2701 my $node = extract_param
($param, 'node');
2703 my $vmid = extract_param
($param, 'vmid');
2705 my $digest = extract_param
($param, 'digest');
2707 my $disk = extract_param
($param, 'disk');
2709 my $sizestr = extract_param
($param, 'size');
2711 my $skiplock = extract_param
($param, 'skiplock');
2712 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2713 if $skiplock && $authuser ne 'root@pam';
2715 my $storecfg = PVE
::Storage
::config
();
2717 my $updatefn = sub {
2719 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2721 die "checksum missmatch (file change by other user?)\n"
2722 if $digest && $digest ne $conf->{digest
};
2723 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2725 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2727 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2729 my (undef, undef, undef, undef, undef, undef, $format) =
2730 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2732 die "can't resize volume: $disk if snapshot exists\n"
2733 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2735 my $volid = $drive->{file
};
2737 die "disk '$disk' has no associated volume\n" if !$volid;
2739 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2741 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2743 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2745 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2747 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2748 my ($ext, $newsize, $unit) = ($1, $2, $4);
2751 $newsize = $newsize * 1024;
2752 } elsif ($unit eq 'M') {
2753 $newsize = $newsize * 1024 * 1024;
2754 } elsif ($unit eq 'G') {
2755 $newsize = $newsize * 1024 * 1024 * 1024;
2756 } elsif ($unit eq 'T') {
2757 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2760 $newsize += $size if $ext;
2761 $newsize = int($newsize);
2763 die "unable to skrink disk size\n" if $newsize < $size;
2765 return if $size == $newsize;
2767 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2769 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2771 $drive->{size
} = $newsize;
2772 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2774 PVE
::QemuConfig-
>write_config($vmid, $conf);
2777 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2781 __PACKAGE__-
>register_method({
2782 name
=> 'snapshot_list',
2783 path
=> '{vmid}/snapshot',
2785 description
=> "List all snapshots.",
2787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2790 protected
=> 1, # qemu pid files are only readable by root
2792 additionalProperties
=> 0,
2794 vmid
=> get_standard_option
('pve-vmid'),
2795 node
=> get_standard_option
('pve-node'),
2804 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2809 my $vmid = $param->{vmid
};
2811 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2812 my $snaphash = $conf->{snapshots
} || {};
2816 foreach my $name (keys %$snaphash) {
2817 my $d = $snaphash->{$name};
2820 snaptime
=> $d->{snaptime
} || 0,
2821 vmstate
=> $d->{vmstate
} ?
1 : 0,
2822 description
=> $d->{description
} || '',
2824 $item->{parent
} = $d->{parent
} if $d->{parent
};
2825 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2829 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2830 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2831 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2833 push @$res, $current;
2838 __PACKAGE__-
>register_method({
2840 path
=> '{vmid}/snapshot',
2844 description
=> "Snapshot a VM.",
2846 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2849 additionalProperties
=> 0,
2851 node
=> get_standard_option
('pve-node'),
2852 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2853 snapname
=> get_standard_option
('pve-snapshot-name'),
2857 description
=> "Save the vmstate",
2862 description
=> "A textual description or comment.",
2868 description
=> "the task ID.",
2873 my $rpcenv = PVE
::RPCEnvironment
::get
();
2875 my $authuser = $rpcenv->get_user();
2877 my $node = extract_param
($param, 'node');
2879 my $vmid = extract_param
($param, 'vmid');
2881 my $snapname = extract_param
($param, 'snapname');
2883 die "unable to use snapshot name 'current' (reserved name)\n"
2884 if $snapname eq 'current';
2887 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2888 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2889 $param->{description
});
2892 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2895 __PACKAGE__-
>register_method({
2896 name
=> 'snapshot_cmd_idx',
2897 path
=> '{vmid}/snapshot/{snapname}',
2904 additionalProperties
=> 0,
2906 vmid
=> get_standard_option
('pve-vmid'),
2907 node
=> get_standard_option
('pve-node'),
2908 snapname
=> get_standard_option
('pve-snapshot-name'),
2917 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2924 push @$res, { cmd
=> 'rollback' };
2925 push @$res, { cmd
=> 'config' };
2930 __PACKAGE__-
>register_method({
2931 name
=> 'update_snapshot_config',
2932 path
=> '{vmid}/snapshot/{snapname}/config',
2936 description
=> "Update snapshot metadata.",
2938 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2941 additionalProperties
=> 0,
2943 node
=> get_standard_option
('pve-node'),
2944 vmid
=> get_standard_option
('pve-vmid'),
2945 snapname
=> get_standard_option
('pve-snapshot-name'),
2949 description
=> "A textual description or comment.",
2953 returns
=> { type
=> 'null' },
2957 my $rpcenv = PVE
::RPCEnvironment
::get
();
2959 my $authuser = $rpcenv->get_user();
2961 my $vmid = extract_param
($param, 'vmid');
2963 my $snapname = extract_param
($param, 'snapname');
2965 return undef if !defined($param->{description
});
2967 my $updatefn = sub {
2969 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2971 PVE
::QemuConfig-
>check_lock($conf);
2973 my $snap = $conf->{snapshots
}->{$snapname};
2975 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2977 $snap->{description
} = $param->{description
} if defined($param->{description
});
2979 PVE
::QemuConfig-
>write_config($vmid, $conf);
2982 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2987 __PACKAGE__-
>register_method({
2988 name
=> 'get_snapshot_config',
2989 path
=> '{vmid}/snapshot/{snapname}/config',
2992 description
=> "Get snapshot configuration",
2994 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2997 additionalProperties
=> 0,
2999 node
=> get_standard_option
('pve-node'),
3000 vmid
=> get_standard_option
('pve-vmid'),
3001 snapname
=> get_standard_option
('pve-snapshot-name'),
3004 returns
=> { type
=> "object" },
3008 my $rpcenv = PVE
::RPCEnvironment
::get
();
3010 my $authuser = $rpcenv->get_user();
3012 my $vmid = extract_param
($param, 'vmid');
3014 my $snapname = extract_param
($param, 'snapname');
3016 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3018 my $snap = $conf->{snapshots
}->{$snapname};
3020 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3025 __PACKAGE__-
>register_method({
3027 path
=> '{vmid}/snapshot/{snapname}/rollback',
3031 description
=> "Rollback VM state to specified snapshot.",
3033 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3036 additionalProperties
=> 0,
3038 node
=> get_standard_option
('pve-node'),
3039 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3040 snapname
=> get_standard_option
('pve-snapshot-name'),
3045 description
=> "the task ID.",
3050 my $rpcenv = PVE
::RPCEnvironment
::get
();
3052 my $authuser = $rpcenv->get_user();
3054 my $node = extract_param
($param, 'node');
3056 my $vmid = extract_param
($param, 'vmid');
3058 my $snapname = extract_param
($param, 'snapname');
3061 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3062 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3065 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3068 __PACKAGE__-
>register_method({
3069 name
=> 'delsnapshot',
3070 path
=> '{vmid}/snapshot/{snapname}',
3074 description
=> "Delete a VM snapshot.",
3076 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3079 additionalProperties
=> 0,
3081 node
=> get_standard_option
('pve-node'),
3082 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3083 snapname
=> get_standard_option
('pve-snapshot-name'),
3087 description
=> "For removal from config file, even if removing disk snapshots fails.",
3093 description
=> "the task ID.",
3098 my $rpcenv = PVE
::RPCEnvironment
::get
();
3100 my $authuser = $rpcenv->get_user();
3102 my $node = extract_param
($param, 'node');
3104 my $vmid = extract_param
($param, 'vmid');
3106 my $snapname = extract_param
($param, 'snapname');
3109 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3110 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3113 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3116 __PACKAGE__-
>register_method({
3118 path
=> '{vmid}/template',
3122 description
=> "Create a Template.",
3124 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3125 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3128 additionalProperties
=> 0,
3130 node
=> get_standard_option
('pve-node'),
3131 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3135 description
=> "If you want to convert only 1 disk to base image.",
3136 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3141 returns
=> { type
=> 'null'},
3145 my $rpcenv = PVE
::RPCEnvironment
::get
();
3147 my $authuser = $rpcenv->get_user();
3149 my $node = extract_param
($param, 'node');
3151 my $vmid = extract_param
($param, 'vmid');
3153 my $disk = extract_param
($param, 'disk');
3155 my $updatefn = sub {
3157 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3159 PVE
::QemuConfig-
>check_lock($conf);
3161 die "unable to create template, because VM contains snapshots\n"
3162 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3164 die "you can't convert a template to a template\n"
3165 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3167 die "you can't convert a VM to template if VM is running\n"
3168 if PVE
::QemuServer
::check_running
($vmid);
3171 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3174 $conf->{template
} = 1;
3175 PVE
::QemuConfig-
>write_config($vmid, $conf);
3177 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3180 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);