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};
190 my $memoryoptions = {
196 my $hwtypeoptions = {
208 my $generaloptions = {
215 'migrate_downtime' => 1,
216 'migrate_speed' => 1,
228 my $vmpoweroptions = {
237 my $check_vm_modify_config_perm = sub {
238 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
240 return 1 if $authuser eq 'root@pam';
242 foreach my $opt (@$key_list) {
243 # disk checks need to be done somewhere else
244 next if PVE
::QemuServer
::is_valid_drivename
($opt);
245 next if $opt eq 'cdrom';
246 next if $opt =~ m/^unused\d+$/;
248 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
249 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
250 } elsif ($memoryoptions->{$opt}) {
251 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
252 } elsif ($hwtypeoptions->{$opt}) {
253 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
254 } elsif ($generaloptions->{$opt}) {
255 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
256 # special case for startup since it changes host behaviour
257 if ($opt eq 'startup') {
258 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
260 } elsif ($vmpoweroptions->{$opt}) {
261 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
262 } elsif ($diskoptions->{$opt}) {
263 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
264 } elsif ($opt =~ m/^net\d+$/) {
265 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
267 # catches usb\d+, hostpci\d+, args, lock, etc.
268 # new options will be checked here
269 die "only root can set '$opt' config\n";
276 __PACKAGE__-
>register_method({
280 description
=> "Virtual machine index (per node).",
282 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
286 protected
=> 1, # qemu pid files are only readable by root
288 additionalProperties
=> 0,
290 node
=> get_standard_option
('pve-node'),
294 description
=> "Determine the full status of active VMs.",
304 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
309 my $rpcenv = PVE
::RPCEnvironment
::get
();
310 my $authuser = $rpcenv->get_user();
312 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
315 foreach my $vmid (keys %$vmstatus) {
316 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
318 my $data = $vmstatus->{$vmid};
319 $data->{vmid
} = int($vmid);
328 __PACKAGE__-
>register_method({
332 description
=> "Create or restore a virtual machine.",
334 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
335 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
336 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
337 user
=> 'all', # check inside
342 additionalProperties
=> 0,
343 properties
=> PVE
::QemuServer
::json_config_properties
(
345 node
=> get_standard_option
('pve-node'),
346 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
348 description
=> "The backup file.",
352 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
354 storage
=> get_standard_option
('pve-storage-id', {
355 description
=> "Default storage.",
357 completion
=> \
&PVE
::QemuServer
::complete_storage
,
362 description
=> "Allow to overwrite existing VM.",
363 requires
=> 'archive',
368 description
=> "Assign a unique random ethernet address.",
369 requires
=> 'archive',
373 type
=> 'string', format
=> 'pve-poolid',
374 description
=> "Add the VM to the specified pool.",
384 my $rpcenv = PVE
::RPCEnvironment
::get
();
386 my $authuser = $rpcenv->get_user();
388 my $node = extract_param
($param, 'node');
390 my $vmid = extract_param
($param, 'vmid');
392 my $archive = extract_param
($param, 'archive');
394 my $storage = extract_param
($param, 'storage');
396 my $force = extract_param
($param, 'force');
398 my $unique = extract_param
($param, 'unique');
400 my $pool = extract_param
($param, 'pool');
402 my $filename = PVE
::QemuConfig-
>config_file($vmid);
404 my $storecfg = PVE
::Storage
::config
();
406 PVE
::Cluster
::check_cfs_quorum
();
408 if (defined($pool)) {
409 $rpcenv->check_pool_exist($pool);
412 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
413 if defined($storage);
415 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
417 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
419 } elsif ($archive && $force && (-f
$filename) &&
420 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
421 # OK: user has VM.Backup permissions, and want to restore an existing VM
427 &$resolve_cdrom_alias($param);
429 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
431 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
433 foreach my $opt (keys %$param) {
434 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
435 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
436 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
438 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
439 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
443 PVE
::QemuServer
::add_random_macs
($param);
445 my $keystr = join(' ', keys %$param);
446 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
448 if ($archive eq '-') {
449 die "pipe requires cli environment\n"
450 if $rpcenv->{type
} ne 'cli';
452 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
453 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
457 my $restorefn = sub {
458 my $vmlist = PVE
::Cluster
::get_vmlist
();
459 if ($vmlist->{ids
}->{$vmid}) {
460 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
461 if ($current_node eq $node) {
462 my $conf = PVE
::QemuConfig-
>load_config($vmid);
464 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
466 die "unable to restore vm $vmid - config file already exists\n"
469 die "unable to restore vm $vmid - vm is running\n"
470 if PVE
::QemuServer
::check_running
($vmid);
472 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
477 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
480 unique
=> $unique });
482 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
485 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
491 PVE
::Cluster
::check_vmid_unused
($vmid);
501 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
503 # try to be smart about bootdisk
504 my @disks = PVE
::QemuServer
::valid_drive_names
();
506 foreach my $ds (reverse @disks) {
507 next if !$conf->{$ds};
508 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
509 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
513 if (!$conf->{bootdisk
} && $firstdisk) {
514 $conf->{bootdisk
} = $firstdisk;
517 # auto generate uuid if user did not specify smbios1 option
518 if (!$conf->{smbios1
}) {
519 my ($uuid, $uuid_str);
520 UUID
::generate
($uuid);
521 UUID
::unparse
($uuid, $uuid_str);
522 $conf->{smbios1
} = "uuid=$uuid_str";
525 PVE
::QemuConfig-
>write_config($vmid, $conf);
531 foreach my $volid (@$vollist) {
532 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
535 die "create failed - $err";
538 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
541 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
544 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
547 __PACKAGE__-
>register_method({
552 description
=> "Directory index",
557 additionalProperties
=> 0,
559 node
=> get_standard_option
('pve-node'),
560 vmid
=> get_standard_option
('pve-vmid'),
568 subdir
=> { type
=> 'string' },
571 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
577 { subdir
=> 'config' },
578 { subdir
=> 'pending' },
579 { subdir
=> 'status' },
580 { subdir
=> 'unlink' },
581 { subdir
=> 'vncproxy' },
582 { subdir
=> 'migrate' },
583 { subdir
=> 'resize' },
584 { subdir
=> 'move' },
586 { subdir
=> 'rrddata' },
587 { subdir
=> 'monitor' },
588 { subdir
=> 'snapshot' },
589 { subdir
=> 'spiceproxy' },
590 { subdir
=> 'sendkey' },
591 { subdir
=> 'firewall' },
597 __PACKAGE__-
>register_method ({
598 subclass
=> "PVE::API2::Firewall::VM",
599 path
=> '{vmid}/firewall',
602 __PACKAGE__-
>register_method({
604 path
=> '{vmid}/rrd',
606 protected
=> 1, # fixme: can we avoid that?
608 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
610 description
=> "Read VM RRD statistics (returns PNG)",
612 additionalProperties
=> 0,
614 node
=> get_standard_option
('pve-node'),
615 vmid
=> get_standard_option
('pve-vmid'),
617 description
=> "Specify the time frame you are interested in.",
619 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
622 description
=> "The list of datasources you want to display.",
623 type
=> 'string', format
=> 'pve-configid-list',
626 description
=> "The RRD consolidation function",
628 enum
=> [ 'AVERAGE', 'MAX' ],
636 filename
=> { type
=> 'string' },
642 return PVE
::Cluster
::create_rrd_graph
(
643 "pve2-vm/$param->{vmid}", $param->{timeframe
},
644 $param->{ds
}, $param->{cf
});
648 __PACKAGE__-
>register_method({
650 path
=> '{vmid}/rrddata',
652 protected
=> 1, # fixme: can we avoid that?
654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
656 description
=> "Read VM RRD statistics",
658 additionalProperties
=> 0,
660 node
=> get_standard_option
('pve-node'),
661 vmid
=> get_standard_option
('pve-vmid'),
663 description
=> "Specify the time frame you are interested in.",
665 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
668 description
=> "The RRD consolidation function",
670 enum
=> [ 'AVERAGE', 'MAX' ],
685 return PVE
::Cluster
::create_rrd_data
(
686 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
690 __PACKAGE__-
>register_method({
692 path
=> '{vmid}/config',
695 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
700 additionalProperties
=> 0,
702 node
=> get_standard_option
('pve-node'),
703 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
705 description
=> "Get current values (instead of pending values).",
717 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
724 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
726 delete $conf->{snapshots
};
728 if (!$param->{current
}) {
729 foreach my $opt (keys %{$conf->{pending
}}) {
730 next if $opt eq 'delete';
731 my $value = $conf->{pending
}->{$opt};
732 next if ref($value); # just to be sure
733 $conf->{$opt} = $value;
735 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
736 foreach my $opt (keys %$pending_delete_hash) {
737 delete $conf->{$opt} if $conf->{$opt};
741 delete $conf->{pending
};
746 __PACKAGE__-
>register_method({
747 name
=> 'vm_pending',
748 path
=> '{vmid}/pending',
751 description
=> "Get virtual machine configuration, including pending changes.",
753 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
756 additionalProperties
=> 0,
758 node
=> get_standard_option
('pve-node'),
759 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
768 description
=> "Configuration option name.",
772 description
=> "Current value.",
777 description
=> "Pending value.",
782 description
=> "Indicates a pending delete request if present and not 0. " .
783 "The value 2 indicates a force-delete request.",
795 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
797 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
801 foreach my $opt (keys %$conf) {
802 next if ref($conf->{$opt});
803 my $item = { key
=> $opt };
804 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
805 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
806 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
810 foreach my $opt (keys %{$conf->{pending
}}) {
811 next if $opt eq 'delete';
812 next if ref($conf->{pending
}->{$opt}); # just to be sure
813 next if defined($conf->{$opt});
814 my $item = { key
=> $opt };
815 $item->{pending
} = $conf->{pending
}->{$opt};
819 while (my ($opt, $force) = each %$pending_delete_hash) {
820 next if $conf->{pending
}->{$opt}; # just to be sure
821 next if $conf->{$opt};
822 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
829 # POST/PUT {vmid}/config implementation
831 # The original API used PUT (idempotent) an we assumed that all operations
832 # are fast. But it turned out that almost any configuration change can
833 # involve hot-plug actions, or disk alloc/free. Such actions can take long
834 # time to complete and have side effects (not idempotent).
836 # The new implementation uses POST and forks a worker process. We added
837 # a new option 'background_delay'. If specified we wait up to
838 # 'background_delay' second for the worker task to complete. It returns null
839 # if the task is finished within that time, else we return the UPID.
841 my $update_vm_api = sub {
842 my ($param, $sync) = @_;
844 my $rpcenv = PVE
::RPCEnvironment
::get
();
846 my $authuser = $rpcenv->get_user();
848 my $node = extract_param
($param, 'node');
850 my $vmid = extract_param
($param, 'vmid');
852 my $digest = extract_param
($param, 'digest');
854 my $background_delay = extract_param
($param, 'background_delay');
856 my @paramarr = (); # used for log message
857 foreach my $key (keys %$param) {
858 push @paramarr, "-$key", $param->{$key};
861 my $skiplock = extract_param
($param, 'skiplock');
862 raise_param_exc
({ skiplock
=> "Only root may use this option." })
863 if $skiplock && $authuser ne 'root@pam';
865 my $delete_str = extract_param
($param, 'delete');
867 my $revert_str = extract_param
($param, 'revert');
869 my $force = extract_param
($param, 'force');
871 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
873 my $storecfg = PVE
::Storage
::config
();
875 my $defaults = PVE
::QemuServer
::load_defaults
();
877 &$resolve_cdrom_alias($param);
879 # now try to verify all parameters
882 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
883 if (!PVE
::QemuServer
::option_exists
($opt)) {
884 raise_param_exc
({ revert
=> "unknown option '$opt'" });
887 raise_param_exc
({ delete => "you can't use '-$opt' and " .
888 "-revert $opt' at the same time" })
889 if defined($param->{$opt});
895 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
896 $opt = 'ide2' if $opt eq 'cdrom';
898 raise_param_exc
({ delete => "you can't use '-$opt' and " .
899 "-delete $opt' at the same time" })
900 if defined($param->{$opt});
902 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
903 "-revert $opt' at the same time" })
906 if (!PVE
::QemuServer
::option_exists
($opt)) {
907 raise_param_exc
({ delete => "unknown option '$opt'" });
913 foreach my $opt (keys %$param) {
914 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
916 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
917 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
918 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
919 } elsif ($opt =~ m/^net(\d+)$/) {
921 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
922 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
926 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
928 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
930 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
934 my $conf = PVE
::QemuConfig-
>load_config($vmid);
936 die "checksum missmatch (file change by other user?)\n"
937 if $digest && $digest ne $conf->{digest
};
939 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
941 foreach my $opt (keys %$revert) {
942 if (defined($conf->{$opt})) {
943 $param->{$opt} = $conf->{$opt};
944 } elsif (defined($conf->{pending
}->{$opt})) {
949 if ($param->{memory
} || defined($param->{balloon
})) {
950 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
951 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
953 die "balloon value too large (must be smaller than assigned memory)\n"
954 if $balloon && $balloon > $maxmem;
957 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
961 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
963 # write updates to pending section
965 my $modified = {}; # record what $option we modify
967 foreach my $opt (@delete) {
968 $modified->{$opt} = 1;
969 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
970 if ($opt =~ m/^unused/) {
971 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
972 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
973 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
974 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
975 delete $conf->{$opt};
976 PVE
::QemuConfig-
>write_config($vmid, $conf);
978 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
979 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
980 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
981 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
982 if defined($conf->{pending
}->{$opt});
983 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
984 PVE
::QemuConfig-
>write_config($vmid, $conf);
986 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
987 PVE
::QemuConfig-
>write_config($vmid, $conf);
991 foreach my $opt (keys %$param) { # add/change
992 $modified->{$opt} = 1;
993 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
994 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
996 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
997 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
998 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
999 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1001 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1003 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1004 if defined($conf->{pending
}->{$opt});
1006 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1008 $conf->{pending
}->{$opt} = $param->{$opt};
1010 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1011 PVE
::QemuConfig-
>write_config($vmid, $conf);
1014 # remove pending changes when nothing changed
1015 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1016 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1017 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1019 return if !scalar(keys %{$conf->{pending
}});
1021 my $running = PVE
::QemuServer
::check_running
($vmid);
1023 # apply pending changes
1025 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1029 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1030 raise_param_exc
($errors) if scalar(keys %$errors);
1032 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1042 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1044 if ($background_delay) {
1046 # Note: It would be better to do that in the Event based HTTPServer
1047 # to avoid blocking call to sleep.
1049 my $end_time = time() + $background_delay;
1051 my $task = PVE
::Tools
::upid_decode
($upid);
1054 while (time() < $end_time) {
1055 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1057 sleep(1); # this gets interrupted when child process ends
1061 my $status = PVE
::Tools
::upid_read_status
($upid);
1062 return undef if $status eq 'OK';
1071 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1074 my $vm_config_perm_list = [
1079 'VM.Config.Network',
1081 'VM.Config.Options',
1084 __PACKAGE__-
>register_method({
1085 name
=> 'update_vm_async',
1086 path
=> '{vmid}/config',
1090 description
=> "Set virtual machine options (asynchrounous API).",
1092 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1095 additionalProperties
=> 0,
1096 properties
=> PVE
::QemuServer
::json_config_properties
(
1098 node
=> get_standard_option
('pve-node'),
1099 vmid
=> get_standard_option
('pve-vmid'),
1100 skiplock
=> get_standard_option
('skiplock'),
1102 type
=> 'string', format
=> 'pve-configid-list',
1103 description
=> "A list of settings you want to delete.",
1107 type
=> 'string', format
=> 'pve-configid-list',
1108 description
=> "Revert a pending change.",
1113 description
=> $opt_force_description,
1115 requires
=> 'delete',
1119 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1123 background_delay
=> {
1125 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1136 code
=> $update_vm_api,
1139 __PACKAGE__-
>register_method({
1140 name
=> 'update_vm',
1141 path
=> '{vmid}/config',
1145 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1147 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1150 additionalProperties
=> 0,
1151 properties
=> PVE
::QemuServer
::json_config_properties
(
1153 node
=> get_standard_option
('pve-node'),
1154 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1155 skiplock
=> get_standard_option
('skiplock'),
1157 type
=> 'string', format
=> 'pve-configid-list',
1158 description
=> "A list of settings you want to delete.",
1162 type
=> 'string', format
=> 'pve-configid-list',
1163 description
=> "Revert a pending change.",
1168 description
=> $opt_force_description,
1170 requires
=> 'delete',
1174 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1180 returns
=> { type
=> 'null' },
1183 &$update_vm_api($param, 1);
1189 __PACKAGE__-
>register_method({
1190 name
=> 'destroy_vm',
1195 description
=> "Destroy the vm (also delete all used/owned volumes).",
1197 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1200 additionalProperties
=> 0,
1202 node
=> get_standard_option
('pve-node'),
1203 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1204 skiplock
=> get_standard_option
('skiplock'),
1213 my $rpcenv = PVE
::RPCEnvironment
::get
();
1215 my $authuser = $rpcenv->get_user();
1217 my $vmid = $param->{vmid
};
1219 my $skiplock = $param->{skiplock
};
1220 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1221 if $skiplock && $authuser ne 'root@pam';
1224 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1226 my $storecfg = PVE
::Storage
::config
();
1228 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1230 die "unable to remove VM $vmid - used in HA resources\n"
1231 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1233 # early tests (repeat after locking)
1234 die "VM $vmid is running - destroy failed\n"
1235 if PVE
::QemuServer
::check_running
($vmid);
1240 syslog
('info', "destroy VM $vmid: $upid\n");
1242 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1244 PVE
::AccessControl
::remove_vm_access
($vmid);
1246 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1249 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1252 __PACKAGE__-
>register_method({
1254 path
=> '{vmid}/unlink',
1258 description
=> "Unlink/delete disk images.",
1260 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1263 additionalProperties
=> 0,
1265 node
=> get_standard_option
('pve-node'),
1266 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1268 type
=> 'string', format
=> 'pve-configid-list',
1269 description
=> "A list of disk IDs you want to delete.",
1273 description
=> $opt_force_description,
1278 returns
=> { type
=> 'null'},
1282 $param->{delete} = extract_param
($param, 'idlist');
1284 __PACKAGE__-
>update_vm($param);
1291 __PACKAGE__-
>register_method({
1293 path
=> '{vmid}/vncproxy',
1297 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1299 description
=> "Creates a TCP VNC proxy connections.",
1301 additionalProperties
=> 0,
1303 node
=> get_standard_option
('pve-node'),
1304 vmid
=> get_standard_option
('pve-vmid'),
1308 description
=> "starts websockify instead of vncproxy",
1313 additionalProperties
=> 0,
1315 user
=> { type
=> 'string' },
1316 ticket
=> { type
=> 'string' },
1317 cert
=> { type
=> 'string' },
1318 port
=> { type
=> 'integer' },
1319 upid
=> { type
=> 'string' },
1325 my $rpcenv = PVE
::RPCEnvironment
::get
();
1327 my $authuser = $rpcenv->get_user();
1329 my $vmid = $param->{vmid
};
1330 my $node = $param->{node
};
1331 my $websocket = $param->{websocket
};
1333 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1335 my $authpath = "/vms/$vmid";
1337 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1339 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1342 my ($remip, $family);
1345 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1346 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1347 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1348 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1350 $family = PVE
::Tools
::get_host_address_family
($node);
1353 my $port = PVE
::Tools
::next_vnc_port
($family);
1360 syslog
('info', "starting vnc proxy $upid\n");
1364 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1366 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1368 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1369 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1370 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1371 '-timeout', $timeout, '-authpath', $authpath,
1372 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1375 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1377 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1379 my $qmstr = join(' ', @$qmcmd);
1381 # also redirect stderr (else we get RFB protocol errors)
1382 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1385 PVE
::Tools
::run_command
($cmd);
1390 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1392 PVE
::Tools
::wait_for_vnc_port
($port);
1403 __PACKAGE__-
>register_method({
1404 name
=> 'vncwebsocket',
1405 path
=> '{vmid}/vncwebsocket',
1408 description
=> "You also need to pass a valid ticket (vncticket).",
1409 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1411 description
=> "Opens a weksocket for VNC traffic.",
1413 additionalProperties
=> 0,
1415 node
=> get_standard_option
('pve-node'),
1416 vmid
=> get_standard_option
('pve-vmid'),
1418 description
=> "Ticket from previous call to vncproxy.",
1423 description
=> "Port number returned by previous vncproxy call.",
1433 port
=> { type
=> 'string' },
1439 my $rpcenv = PVE
::RPCEnvironment
::get
();
1441 my $authuser = $rpcenv->get_user();
1443 my $vmid = $param->{vmid
};
1444 my $node = $param->{node
};
1446 my $authpath = "/vms/$vmid";
1448 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1450 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1452 # Note: VNC ports are acessible from outside, so we do not gain any
1453 # security if we verify that $param->{port} belongs to VM $vmid. This
1454 # check is done by verifying the VNC ticket (inside VNC protocol).
1456 my $port = $param->{port
};
1458 return { port
=> $port };
1461 __PACKAGE__-
>register_method({
1462 name
=> 'spiceproxy',
1463 path
=> '{vmid}/spiceproxy',
1468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1470 description
=> "Returns a SPICE configuration to connect to the VM.",
1472 additionalProperties
=> 0,
1474 node
=> get_standard_option
('pve-node'),
1475 vmid
=> get_standard_option
('pve-vmid'),
1476 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1479 returns
=> get_standard_option
('remote-viewer-config'),
1483 my $rpcenv = PVE
::RPCEnvironment
::get
();
1485 my $authuser = $rpcenv->get_user();
1487 my $vmid = $param->{vmid
};
1488 my $node = $param->{node
};
1489 my $proxy = $param->{proxy
};
1491 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1492 my $title = "VM $vmid";
1493 $title .= " - ". $conf->{name
} if $conf->{name
};
1495 my $port = PVE
::QemuServer
::spice_port
($vmid);
1497 my ($ticket, undef, $remote_viewer_config) =
1498 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1500 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1501 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1503 return $remote_viewer_config;
1506 __PACKAGE__-
>register_method({
1508 path
=> '{vmid}/status',
1511 description
=> "Directory index",
1516 additionalProperties
=> 0,
1518 node
=> get_standard_option
('pve-node'),
1519 vmid
=> get_standard_option
('pve-vmid'),
1527 subdir
=> { type
=> 'string' },
1530 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1536 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1539 { subdir
=> 'current' },
1540 { subdir
=> 'start' },
1541 { subdir
=> 'stop' },
1547 __PACKAGE__-
>register_method({
1548 name
=> 'vm_status',
1549 path
=> '{vmid}/status/current',
1552 protected
=> 1, # qemu pid files are only readable by root
1553 description
=> "Get virtual machine status.",
1555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1558 additionalProperties
=> 0,
1560 node
=> get_standard_option
('pve-node'),
1561 vmid
=> get_standard_option
('pve-vmid'),
1564 returns
=> { type
=> 'object' },
1569 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1571 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1572 my $status = $vmstatus->{$param->{vmid
}};
1574 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1576 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1581 __PACKAGE__-
>register_method({
1583 path
=> '{vmid}/status/start',
1587 description
=> "Start virtual machine.",
1589 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1592 additionalProperties
=> 0,
1594 node
=> get_standard_option
('pve-node'),
1595 vmid
=> get_standard_option
('pve-vmid',
1596 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1597 skiplock
=> get_standard_option
('skiplock'),
1598 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1599 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1600 machine
=> get_standard_option
('pve-qm-machine'),
1609 my $rpcenv = PVE
::RPCEnvironment
::get
();
1611 my $authuser = $rpcenv->get_user();
1613 my $node = extract_param
($param, 'node');
1615 my $vmid = extract_param
($param, 'vmid');
1617 my $machine = extract_param
($param, 'machine');
1619 my $stateuri = extract_param
($param, 'stateuri');
1620 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1621 if $stateuri && $authuser ne 'root@pam';
1623 my $skiplock = extract_param
($param, 'skiplock');
1624 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1625 if $skiplock && $authuser ne 'root@pam';
1627 my $migratedfrom = extract_param
($param, 'migratedfrom');
1628 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1629 if $migratedfrom && $authuser ne 'root@pam';
1631 # read spice ticket from STDIN
1633 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1634 if (defined(my $line = <>)) {
1636 $spice_ticket = $line;
1640 PVE
::Cluster
::check_cfs_quorum
();
1642 my $storecfg = PVE
::Storage
::config
();
1644 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1645 $rpcenv->{type
} ne 'ha') {
1650 my $service = "vm:$vmid";
1652 my $cmd = ['ha-manager', 'enable', $service];
1654 print "Executing HA start for VM $vmid\n";
1656 PVE
::Tools
::run_command
($cmd);
1661 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1668 syslog
('info', "start VM $vmid: $upid\n");
1670 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1671 $machine, $spice_ticket);
1676 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1680 __PACKAGE__-
>register_method({
1682 path
=> '{vmid}/status/stop',
1686 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1687 "is akin to pulling the power plug of a running computer and may damage the VM data",
1689 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1692 additionalProperties
=> 0,
1694 node
=> get_standard_option
('pve-node'),
1695 vmid
=> get_standard_option
('pve-vmid',
1696 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1697 skiplock
=> get_standard_option
('skiplock'),
1698 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1700 description
=> "Wait maximal timeout seconds.",
1706 description
=> "Do not decativate storage volumes.",
1719 my $rpcenv = PVE
::RPCEnvironment
::get
();
1721 my $authuser = $rpcenv->get_user();
1723 my $node = extract_param
($param, 'node');
1725 my $vmid = extract_param
($param, 'vmid');
1727 my $skiplock = extract_param
($param, 'skiplock');
1728 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1729 if $skiplock && $authuser ne 'root@pam';
1731 my $keepActive = extract_param
($param, 'keepActive');
1732 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1733 if $keepActive && $authuser ne 'root@pam';
1735 my $migratedfrom = extract_param
($param, 'migratedfrom');
1736 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1737 if $migratedfrom && $authuser ne 'root@pam';
1740 my $storecfg = PVE
::Storage
::config
();
1742 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1747 my $service = "vm:$vmid";
1749 my $cmd = ['ha-manager', 'disable', $service];
1751 print "Executing HA stop for VM $vmid\n";
1753 PVE
::Tools
::run_command
($cmd);
1758 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1764 syslog
('info', "stop VM $vmid: $upid\n");
1766 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1767 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1772 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1776 __PACKAGE__-
>register_method({
1778 path
=> '{vmid}/status/reset',
1782 description
=> "Reset virtual machine.",
1784 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1787 additionalProperties
=> 0,
1789 node
=> get_standard_option
('pve-node'),
1790 vmid
=> get_standard_option
('pve-vmid',
1791 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1792 skiplock
=> get_standard_option
('skiplock'),
1801 my $rpcenv = PVE
::RPCEnvironment
::get
();
1803 my $authuser = $rpcenv->get_user();
1805 my $node = extract_param
($param, 'node');
1807 my $vmid = extract_param
($param, 'vmid');
1809 my $skiplock = extract_param
($param, 'skiplock');
1810 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1811 if $skiplock && $authuser ne 'root@pam';
1813 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1818 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1823 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1826 __PACKAGE__-
>register_method({
1827 name
=> 'vm_shutdown',
1828 path
=> '{vmid}/status/shutdown',
1832 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1833 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1835 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1838 additionalProperties
=> 0,
1840 node
=> get_standard_option
('pve-node'),
1841 vmid
=> get_standard_option
('pve-vmid',
1842 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1843 skiplock
=> get_standard_option
('skiplock'),
1845 description
=> "Wait maximal timeout seconds.",
1851 description
=> "Make sure the VM stops.",
1857 description
=> "Do not decativate storage volumes.",
1870 my $rpcenv = PVE
::RPCEnvironment
::get
();
1872 my $authuser = $rpcenv->get_user();
1874 my $node = extract_param
($param, 'node');
1876 my $vmid = extract_param
($param, 'vmid');
1878 my $skiplock = extract_param
($param, 'skiplock');
1879 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1880 if $skiplock && $authuser ne 'root@pam';
1882 my $keepActive = extract_param
($param, 'keepActive');
1883 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1884 if $keepActive && $authuser ne 'root@pam';
1886 my $storecfg = PVE
::Storage
::config
();
1890 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1891 # otherwise, we will infer a shutdown command, but run into the timeout,
1892 # then when the vm is resumed, it will instantly shutdown
1894 # checking the qmp status here to get feedback to the gui/cli/api
1895 # and the status query should not take too long
1898 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1902 if (!$err && $qmpstatus->{status
} eq "paused") {
1903 if ($param->{forceStop
}) {
1904 warn "VM is paused - stop instead of shutdown\n";
1907 die "VM is paused - cannot shutdown\n";
1914 syslog
('info', "shutdown VM $vmid: $upid\n");
1916 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1917 $shutdown, $param->{forceStop
}, $keepActive);
1922 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1925 __PACKAGE__-
>register_method({
1926 name
=> 'vm_suspend',
1927 path
=> '{vmid}/status/suspend',
1931 description
=> "Suspend virtual machine.",
1933 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1936 additionalProperties
=> 0,
1938 node
=> get_standard_option
('pve-node'),
1939 vmid
=> get_standard_option
('pve-vmid',
1940 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1941 skiplock
=> get_standard_option
('skiplock'),
1950 my $rpcenv = PVE
::RPCEnvironment
::get
();
1952 my $authuser = $rpcenv->get_user();
1954 my $node = extract_param
($param, 'node');
1956 my $vmid = extract_param
($param, 'vmid');
1958 my $skiplock = extract_param
($param, 'skiplock');
1959 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1960 if $skiplock && $authuser ne 'root@pam';
1962 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1967 syslog
('info', "suspend VM $vmid: $upid\n");
1969 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1974 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1977 __PACKAGE__-
>register_method({
1978 name
=> 'vm_resume',
1979 path
=> '{vmid}/status/resume',
1983 description
=> "Resume virtual machine.",
1985 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1988 additionalProperties
=> 0,
1990 node
=> get_standard_option
('pve-node'),
1991 vmid
=> get_standard_option
('pve-vmid',
1992 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1993 skiplock
=> get_standard_option
('skiplock'),
1994 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2004 my $rpcenv = PVE
::RPCEnvironment
::get
();
2006 my $authuser = $rpcenv->get_user();
2008 my $node = extract_param
($param, 'node');
2010 my $vmid = extract_param
($param, 'vmid');
2012 my $skiplock = extract_param
($param, 'skiplock');
2013 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2014 if $skiplock && $authuser ne 'root@pam';
2016 my $nocheck = extract_param
($param, 'nocheck');
2018 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2023 syslog
('info', "resume VM $vmid: $upid\n");
2025 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2030 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2033 __PACKAGE__-
>register_method({
2034 name
=> 'vm_sendkey',
2035 path
=> '{vmid}/sendkey',
2039 description
=> "Send key event to virtual machine.",
2041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2044 additionalProperties
=> 0,
2046 node
=> get_standard_option
('pve-node'),
2047 vmid
=> get_standard_option
('pve-vmid',
2048 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2049 skiplock
=> get_standard_option
('skiplock'),
2051 description
=> "The key (qemu monitor encoding).",
2056 returns
=> { type
=> 'null'},
2060 my $rpcenv = PVE
::RPCEnvironment
::get
();
2062 my $authuser = $rpcenv->get_user();
2064 my $node = extract_param
($param, 'node');
2066 my $vmid = extract_param
($param, 'vmid');
2068 my $skiplock = extract_param
($param, 'skiplock');
2069 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2070 if $skiplock && $authuser ne 'root@pam';
2072 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2077 __PACKAGE__-
>register_method({
2078 name
=> 'vm_feature',
2079 path
=> '{vmid}/feature',
2083 description
=> "Check if feature for virtual machine is available.",
2085 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2088 additionalProperties
=> 0,
2090 node
=> get_standard_option
('pve-node'),
2091 vmid
=> get_standard_option
('pve-vmid'),
2093 description
=> "Feature to check.",
2095 enum
=> [ 'snapshot', 'clone', 'copy' ],
2097 snapname
=> get_standard_option
('pve-snapshot-name', {
2105 hasFeature
=> { type
=> 'boolean' },
2108 items
=> { type
=> 'string' },
2115 my $node = extract_param
($param, 'node');
2117 my $vmid = extract_param
($param, 'vmid');
2119 my $snapname = extract_param
($param, 'snapname');
2121 my $feature = extract_param
($param, 'feature');
2123 my $running = PVE
::QemuServer
::check_running
($vmid);
2125 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2128 my $snap = $conf->{snapshots
}->{$snapname};
2129 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2132 my $storecfg = PVE
::Storage
::config
();
2134 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2135 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2138 hasFeature
=> $hasFeature,
2139 nodes
=> [ keys %$nodelist ],
2143 __PACKAGE__-
>register_method({
2145 path
=> '{vmid}/clone',
2149 description
=> "Create a copy of virtual machine/template.",
2151 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2152 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2153 "'Datastore.AllocateSpace' on any used storage.",
2156 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2158 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2159 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2164 additionalProperties
=> 0,
2166 node
=> get_standard_option
('pve-node'),
2167 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2168 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2171 type
=> 'string', format
=> 'dns-name',
2172 description
=> "Set a name for the new VM.",
2177 description
=> "Description for the new VM.",
2181 type
=> 'string', format
=> 'pve-poolid',
2182 description
=> "Add the new VM to the specified pool.",
2184 snapname
=> get_standard_option
('pve-snapshot-name', {
2187 storage
=> get_standard_option
('pve-storage-id', {
2188 description
=> "Target storage for full clone.",
2193 description
=> "Target format for file storage.",
2197 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2202 description
=> "Create a full copy of all disk. This is always done when " .
2203 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2206 target
=> get_standard_option
('pve-node', {
2207 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2218 my $rpcenv = PVE
::RPCEnvironment
::get
();
2220 my $authuser = $rpcenv->get_user();
2222 my $node = extract_param
($param, 'node');
2224 my $vmid = extract_param
($param, 'vmid');
2226 my $newid = extract_param
($param, 'newid');
2228 my $pool = extract_param
($param, 'pool');
2230 if (defined($pool)) {
2231 $rpcenv->check_pool_exist($pool);
2234 my $snapname = extract_param
($param, 'snapname');
2236 my $storage = extract_param
($param, 'storage');
2238 my $format = extract_param
($param, 'format');
2240 my $target = extract_param
($param, 'target');
2242 my $localnode = PVE
::INotify
::nodename
();
2244 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2246 PVE
::Cluster
::check_node_exists
($target) if $target;
2248 my $storecfg = PVE
::Storage
::config
();
2251 # check if storage is enabled on local node
2252 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2254 # check if storage is available on target node
2255 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2256 # clone only works if target storage is shared
2257 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2258 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2262 PVE
::Cluster
::check_cfs_quorum
();
2264 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2266 # exclusive lock if VM is running - else shared lock is enough;
2267 my $shared_lock = $running ?
0 : 1;
2271 # do all tests after lock
2272 # we also try to do all tests before we fork the worker
2274 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2276 PVE
::QemuConfig-
>check_lock($conf);
2278 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2280 die "unexpected state change\n" if $verify_running != $running;
2282 die "snapshot '$snapname' does not exist\n"
2283 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2285 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2287 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2289 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2291 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2293 die "unable to create VM $newid: config file already exists\n"
2296 my $newconf = { lock => 'clone' };
2301 foreach my $opt (keys %$oldconf) {
2302 my $value = $oldconf->{$opt};
2304 # do not copy snapshot related info
2305 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2306 $opt eq 'vmstate' || $opt eq 'snapstate';
2308 # no need to copy unused images, because VMID(owner) changes anyways
2309 next if $opt =~ m/^unused\d+$/;
2311 # always change MAC! address
2312 if ($opt =~ m/^net(\d+)$/) {
2313 my $net = PVE
::QemuServer
::parse_net
($value);
2314 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2315 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2316 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2317 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2318 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2319 die "unable to parse drive options for '$opt'\n" if !$drive;
2320 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2321 $newconf->{$opt} = $value; # simply copy configuration
2323 if ($param->{full
}) {
2324 die "Full clone feature is not available"
2325 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2326 $fullclone->{$opt} = 1;
2328 # not full means clone instead of copy
2329 die "Linked clone feature is not available"
2330 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2332 $drives->{$opt} = $drive;
2333 push @$vollist, $drive->{file
};
2336 # copy everything else
2337 $newconf->{$opt} = $value;
2341 # auto generate a new uuid
2342 my ($uuid, $uuid_str);
2343 UUID
::generate
($uuid);
2344 UUID
::unparse
($uuid, $uuid_str);
2345 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2346 $smbios1->{uuid
} = $uuid_str;
2347 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2349 delete $newconf->{template
};
2351 if ($param->{name
}) {
2352 $newconf->{name
} = $param->{name
};
2354 if ($oldconf->{name
}) {
2355 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2357 $newconf->{name
} = "Copy-of-VM-$vmid";
2361 if ($param->{description
}) {
2362 $newconf->{description
} = $param->{description
};
2365 # create empty/temp config - this fails if VM already exists on other node
2366 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2371 my $newvollist = [];
2374 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2376 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2378 foreach my $opt (keys %$drives) {
2379 my $drive = $drives->{$opt};
2381 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2382 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2384 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2386 PVE
::QemuConfig-
>write_config($newid, $newconf);
2389 delete $newconf->{lock};
2390 PVE
::QemuConfig-
>write_config($newid, $newconf);
2393 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2394 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2395 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2397 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2398 die "Failed to move config to node '$target' - rename failed: $!\n"
2399 if !rename($conffile, $newconffile);
2402 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2407 sleep 1; # some storage like rbd need to wait before release volume - really?
2409 foreach my $volid (@$newvollist) {
2410 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2413 die "clone failed: $err";
2419 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2421 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2424 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2425 # Aquire exclusive lock lock for $newid
2426 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2431 __PACKAGE__-
>register_method({
2432 name
=> 'move_vm_disk',
2433 path
=> '{vmid}/move_disk',
2437 description
=> "Move volume to different storage.",
2439 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2440 "and 'Datastore.AllocateSpace' permissions on the storage.",
2443 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2444 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2448 additionalProperties
=> 0,
2450 node
=> get_standard_option
('pve-node'),
2451 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2454 description
=> "The disk you want to move.",
2455 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2457 storage
=> get_standard_option
('pve-storage-id', {
2458 description
=> "Target storage.",
2459 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2463 description
=> "Target Format.",
2464 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2469 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2475 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2483 description
=> "the task ID.",
2488 my $rpcenv = PVE
::RPCEnvironment
::get
();
2490 my $authuser = $rpcenv->get_user();
2492 my $node = extract_param
($param, 'node');
2494 my $vmid = extract_param
($param, 'vmid');
2496 my $digest = extract_param
($param, 'digest');
2498 my $disk = extract_param
($param, 'disk');
2500 my $storeid = extract_param
($param, 'storage');
2502 my $format = extract_param
($param, 'format');
2504 my $storecfg = PVE
::Storage
::config
();
2506 my $updatefn = sub {
2508 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2510 PVE
::QemuConfig-
>check_lock($conf);
2512 die "checksum missmatch (file change by other user?)\n"
2513 if $digest && $digest ne $conf->{digest
};
2515 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2517 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2519 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2521 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2524 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2525 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2529 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2530 (!$format || !$oldfmt || $oldfmt eq $format);
2532 # this only checks snapshots because $disk is passed!
2533 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2534 die "you can't move a disk with snapshots and delete the source\n"
2535 if $snapshotted && $param->{delete};
2537 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2539 my $running = PVE
::QemuServer
::check_running
($vmid);
2541 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2545 my $newvollist = [];
2548 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2550 warn "moving disk with snapshots, snapshots will not be moved!\n"
2553 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2554 $vmid, $storeid, $format, 1, $newvollist);
2556 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2558 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2560 PVE
::QemuConfig-
>write_config($vmid, $conf);
2563 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2564 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2571 foreach my $volid (@$newvollist) {
2572 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2575 die "storage migration failed: $err";
2578 if ($param->{delete}) {
2580 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2581 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2587 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2590 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2593 __PACKAGE__-
>register_method({
2594 name
=> 'migrate_vm',
2595 path
=> '{vmid}/migrate',
2599 description
=> "Migrate virtual machine. Creates a new migration task.",
2601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2604 additionalProperties
=> 0,
2606 node
=> get_standard_option
('pve-node'),
2607 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2608 target
=> get_standard_option
('pve-node', {
2609 description
=> "Target node.",
2610 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2614 description
=> "Use online/live migration.",
2619 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2626 description
=> "the task ID.",
2631 my $rpcenv = PVE
::RPCEnvironment
::get
();
2633 my $authuser = $rpcenv->get_user();
2635 my $target = extract_param
($param, 'target');
2637 my $localnode = PVE
::INotify
::nodename
();
2638 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2640 PVE
::Cluster
::check_cfs_quorum
();
2642 PVE
::Cluster
::check_node_exists
($target);
2644 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2646 my $vmid = extract_param
($param, 'vmid');
2648 raise_param_exc
({ force
=> "Only root may use this option." })
2649 if $param->{force
} && $authuser ne 'root@pam';
2652 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2654 # try to detect errors early
2656 PVE
::QemuConfig-
>check_lock($conf);
2658 if (PVE
::QemuServer
::check_running
($vmid)) {
2659 die "cant migrate running VM without --online\n"
2660 if !$param->{online
};
2663 my $storecfg = PVE
::Storage
::config
();
2664 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2666 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2671 my $service = "vm:$vmid";
2673 my $cmd = ['ha-manager', 'migrate', $service, $target];
2675 print "Executing HA migrate for VM $vmid to node $target\n";
2677 PVE
::Tools
::run_command
($cmd);
2682 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2689 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2692 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2697 __PACKAGE__-
>register_method({
2699 path
=> '{vmid}/monitor',
2703 description
=> "Execute Qemu monitor commands.",
2705 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2708 additionalProperties
=> 0,
2710 node
=> get_standard_option
('pve-node'),
2711 vmid
=> get_standard_option
('pve-vmid'),
2714 description
=> "The monitor command.",
2718 returns
=> { type
=> 'string'},
2722 my $vmid = $param->{vmid
};
2724 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2728 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2730 $res = "ERROR: $@" if $@;
2735 __PACKAGE__-
>register_method({
2736 name
=> 'resize_vm',
2737 path
=> '{vmid}/resize',
2741 description
=> "Extend volume size.",
2743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2746 additionalProperties
=> 0,
2748 node
=> get_standard_option
('pve-node'),
2749 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2750 skiplock
=> get_standard_option
('skiplock'),
2753 description
=> "The disk you want to resize.",
2754 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2758 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2759 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.",
2763 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2769 returns
=> { type
=> 'null'},
2773 my $rpcenv = PVE
::RPCEnvironment
::get
();
2775 my $authuser = $rpcenv->get_user();
2777 my $node = extract_param
($param, 'node');
2779 my $vmid = extract_param
($param, 'vmid');
2781 my $digest = extract_param
($param, 'digest');
2783 my $disk = extract_param
($param, 'disk');
2785 my $sizestr = extract_param
($param, 'size');
2787 my $skiplock = extract_param
($param, 'skiplock');
2788 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2789 if $skiplock && $authuser ne 'root@pam';
2791 my $storecfg = PVE
::Storage
::config
();
2793 my $updatefn = sub {
2795 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2797 die "checksum missmatch (file change by other user?)\n"
2798 if $digest && $digest ne $conf->{digest
};
2799 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2801 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2803 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2805 my (undef, undef, undef, undef, undef, undef, $format) =
2806 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2808 die "can't resize volume: $disk if snapshot exists\n"
2809 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2811 my $volid = $drive->{file
};
2813 die "disk '$disk' has no associated volume\n" if !$volid;
2815 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2817 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2819 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2821 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2822 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2824 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2825 my ($ext, $newsize, $unit) = ($1, $2, $4);
2828 $newsize = $newsize * 1024;
2829 } elsif ($unit eq 'M') {
2830 $newsize = $newsize * 1024 * 1024;
2831 } elsif ($unit eq 'G') {
2832 $newsize = $newsize * 1024 * 1024 * 1024;
2833 } elsif ($unit eq 'T') {
2834 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2837 $newsize += $size if $ext;
2838 $newsize = int($newsize);
2840 die "unable to skrink disk size\n" if $newsize < $size;
2842 return if $size == $newsize;
2844 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2846 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2848 $drive->{size
} = $newsize;
2849 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2851 PVE
::QemuConfig-
>write_config($vmid, $conf);
2854 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2858 __PACKAGE__-
>register_method({
2859 name
=> 'snapshot_list',
2860 path
=> '{vmid}/snapshot',
2862 description
=> "List all snapshots.",
2864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2867 protected
=> 1, # qemu pid files are only readable by root
2869 additionalProperties
=> 0,
2871 vmid
=> get_standard_option
('pve-vmid'),
2872 node
=> get_standard_option
('pve-node'),
2881 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2886 my $vmid = $param->{vmid
};
2888 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2889 my $snaphash = $conf->{snapshots
} || {};
2893 foreach my $name (keys %$snaphash) {
2894 my $d = $snaphash->{$name};
2897 snaptime
=> $d->{snaptime
} || 0,
2898 vmstate
=> $d->{vmstate
} ?
1 : 0,
2899 description
=> $d->{description
} || '',
2901 $item->{parent
} = $d->{parent
} if $d->{parent
};
2902 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2906 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2907 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2908 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2910 push @$res, $current;
2915 __PACKAGE__-
>register_method({
2917 path
=> '{vmid}/snapshot',
2921 description
=> "Snapshot a VM.",
2923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2926 additionalProperties
=> 0,
2928 node
=> get_standard_option
('pve-node'),
2929 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2930 snapname
=> get_standard_option
('pve-snapshot-name'),
2934 description
=> "Save the vmstate",
2939 description
=> "A textual description or comment.",
2945 description
=> "the task ID.",
2950 my $rpcenv = PVE
::RPCEnvironment
::get
();
2952 my $authuser = $rpcenv->get_user();
2954 my $node = extract_param
($param, 'node');
2956 my $vmid = extract_param
($param, 'vmid');
2958 my $snapname = extract_param
($param, 'snapname');
2960 die "unable to use snapshot name 'current' (reserved name)\n"
2961 if $snapname eq 'current';
2964 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2965 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2966 $param->{description
});
2969 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2972 __PACKAGE__-
>register_method({
2973 name
=> 'snapshot_cmd_idx',
2974 path
=> '{vmid}/snapshot/{snapname}',
2981 additionalProperties
=> 0,
2983 vmid
=> get_standard_option
('pve-vmid'),
2984 node
=> get_standard_option
('pve-node'),
2985 snapname
=> get_standard_option
('pve-snapshot-name'),
2994 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3001 push @$res, { cmd
=> 'rollback' };
3002 push @$res, { cmd
=> 'config' };
3007 __PACKAGE__-
>register_method({
3008 name
=> 'update_snapshot_config',
3009 path
=> '{vmid}/snapshot/{snapname}/config',
3013 description
=> "Update snapshot metadata.",
3015 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3018 additionalProperties
=> 0,
3020 node
=> get_standard_option
('pve-node'),
3021 vmid
=> get_standard_option
('pve-vmid'),
3022 snapname
=> get_standard_option
('pve-snapshot-name'),
3026 description
=> "A textual description or comment.",
3030 returns
=> { type
=> 'null' },
3034 my $rpcenv = PVE
::RPCEnvironment
::get
();
3036 my $authuser = $rpcenv->get_user();
3038 my $vmid = extract_param
($param, 'vmid');
3040 my $snapname = extract_param
($param, 'snapname');
3042 return undef if !defined($param->{description
});
3044 my $updatefn = sub {
3046 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3048 PVE
::QemuConfig-
>check_lock($conf);
3050 my $snap = $conf->{snapshots
}->{$snapname};
3052 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3054 $snap->{description
} = $param->{description
} if defined($param->{description
});
3056 PVE
::QemuConfig-
>write_config($vmid, $conf);
3059 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3064 __PACKAGE__-
>register_method({
3065 name
=> 'get_snapshot_config',
3066 path
=> '{vmid}/snapshot/{snapname}/config',
3069 description
=> "Get snapshot configuration",
3071 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3074 additionalProperties
=> 0,
3076 node
=> get_standard_option
('pve-node'),
3077 vmid
=> get_standard_option
('pve-vmid'),
3078 snapname
=> get_standard_option
('pve-snapshot-name'),
3081 returns
=> { type
=> "object" },
3085 my $rpcenv = PVE
::RPCEnvironment
::get
();
3087 my $authuser = $rpcenv->get_user();
3089 my $vmid = extract_param
($param, 'vmid');
3091 my $snapname = extract_param
($param, 'snapname');
3093 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3095 my $snap = $conf->{snapshots
}->{$snapname};
3097 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3102 __PACKAGE__-
>register_method({
3104 path
=> '{vmid}/snapshot/{snapname}/rollback',
3108 description
=> "Rollback VM state to specified snapshot.",
3110 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3113 additionalProperties
=> 0,
3115 node
=> get_standard_option
('pve-node'),
3116 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3117 snapname
=> get_standard_option
('pve-snapshot-name'),
3122 description
=> "the task ID.",
3127 my $rpcenv = PVE
::RPCEnvironment
::get
();
3129 my $authuser = $rpcenv->get_user();
3131 my $node = extract_param
($param, 'node');
3133 my $vmid = extract_param
($param, 'vmid');
3135 my $snapname = extract_param
($param, 'snapname');
3138 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3139 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3142 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3145 __PACKAGE__-
>register_method({
3146 name
=> 'delsnapshot',
3147 path
=> '{vmid}/snapshot/{snapname}',
3151 description
=> "Delete a VM snapshot.",
3153 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3156 additionalProperties
=> 0,
3158 node
=> get_standard_option
('pve-node'),
3159 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3160 snapname
=> get_standard_option
('pve-snapshot-name'),
3164 description
=> "For removal from config file, even if removing disk snapshots fails.",
3170 description
=> "the task ID.",
3175 my $rpcenv = PVE
::RPCEnvironment
::get
();
3177 my $authuser = $rpcenv->get_user();
3179 my $node = extract_param
($param, 'node');
3181 my $vmid = extract_param
($param, 'vmid');
3183 my $snapname = extract_param
($param, 'snapname');
3186 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3187 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3190 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3193 __PACKAGE__-
>register_method({
3195 path
=> '{vmid}/template',
3199 description
=> "Create a Template.",
3201 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3202 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3205 additionalProperties
=> 0,
3207 node
=> get_standard_option
('pve-node'),
3208 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3212 description
=> "If you want to convert only 1 disk to base image.",
3213 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3218 returns
=> { type
=> 'null'},
3222 my $rpcenv = PVE
::RPCEnvironment
::get
();
3224 my $authuser = $rpcenv->get_user();
3226 my $node = extract_param
($param, 'node');
3228 my $vmid = extract_param
($param, 'vmid');
3230 my $disk = extract_param
($param, 'disk');
3232 my $updatefn = sub {
3234 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3236 PVE
::QemuConfig-
>check_lock($conf);
3238 die "unable to create template, because VM contains snapshots\n"
3239 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3241 die "you can't convert a template to a template\n"
3242 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3244 die "you can't convert a VM to template if VM is running\n"
3245 if PVE
::QemuServer
::check_running
($vmid);
3248 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3251 $conf->{template
} = 1;
3252 PVE
::QemuConfig-
>write_config($vmid, $conf);
3254 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3257 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);