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;
128 if ($ds eq 'efidisk0') {
130 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
131 die "uefi vars image not found\n" if ! -f
$ovmfvars;
132 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
134 $disk->{file
} = $volid;
135 $disk->{size
} = 128*1024;
136 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
137 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
138 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
139 my $path = PVE
::Storage
::path
($storecfg, $volid);
140 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
141 push @$efidiskcmd, $ovmfvars;
142 push @$efidiskcmd, $path;
144 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
146 eval { PVE
::Tools
::run_command
($efidiskcmd); };
148 die "Copying of EFI Vars image failed: $err" if $err;
150 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
151 $fmt, undef, $size*1024*1024);
152 $disk->{file
} = $volid;
153 $disk->{size
} = $size*1024*1024*1024;
155 push @$vollist, $volid;
156 delete $disk->{format
}; # no longer needed
157 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
160 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
162 my $volid_is_new = 1;
165 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
166 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
171 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
173 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
175 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
177 die "volume $volid does not exists\n" if !$size;
179 $disk->{size
} = $size;
182 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
186 # free allocated images on error
188 syslog
('err', "VM $vmid creating disks failed");
189 foreach my $volid (@$vollist) {
190 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
196 # modify vm config if everything went well
197 foreach my $ds (keys %$res) {
198 $conf->{$ds} = $res->{$ds};
215 my $memoryoptions = {
221 my $hwtypeoptions = {
233 my $generaloptions = {
240 'migrate_downtime' => 1,
241 'migrate_speed' => 1,
253 my $vmpoweroptions = {
262 my $check_vm_modify_config_perm = sub {
263 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
265 return 1 if $authuser eq 'root@pam';
267 foreach my $opt (@$key_list) {
268 # disk checks need to be done somewhere else
269 next if PVE
::QemuServer
::is_valid_drivename
($opt);
270 next if $opt eq 'cdrom';
271 next if $opt =~ m/^unused\d+$/;
273 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
274 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
275 } elsif ($memoryoptions->{$opt}) {
276 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
277 } elsif ($hwtypeoptions->{$opt}) {
278 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
279 } elsif ($generaloptions->{$opt}) {
280 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
281 # special case for startup since it changes host behaviour
282 if ($opt eq 'startup') {
283 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
285 } elsif ($vmpoweroptions->{$opt}) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
287 } elsif ($diskoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
289 } elsif ($opt =~ m/^net\d+$/) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
292 # catches usb\d+, hostpci\d+, args, lock, etc.
293 # new options will be checked here
294 die "only root can set '$opt' config\n";
301 __PACKAGE__-
>register_method({
305 description
=> "Virtual machine index (per node).",
307 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
311 protected
=> 1, # qemu pid files are only readable by root
313 additionalProperties
=> 0,
315 node
=> get_standard_option
('pve-node'),
319 description
=> "Determine the full status of active VMs.",
329 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
334 my $rpcenv = PVE
::RPCEnvironment
::get
();
335 my $authuser = $rpcenv->get_user();
337 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
340 foreach my $vmid (keys %$vmstatus) {
341 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
343 my $data = $vmstatus->{$vmid};
344 $data->{vmid
} = int($vmid);
353 __PACKAGE__-
>register_method({
357 description
=> "Create or restore a virtual machine.",
359 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
360 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
361 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
362 user
=> 'all', # check inside
367 additionalProperties
=> 0,
368 properties
=> PVE
::QemuServer
::json_config_properties
(
370 node
=> get_standard_option
('pve-node'),
371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
373 description
=> "The backup file.",
377 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
379 storage
=> get_standard_option
('pve-storage-id', {
380 description
=> "Default storage.",
382 completion
=> \
&PVE
::QemuServer
::complete_storage
,
387 description
=> "Allow to overwrite existing VM.",
388 requires
=> 'archive',
393 description
=> "Assign a unique random ethernet address.",
394 requires
=> 'archive',
398 type
=> 'string', format
=> 'pve-poolid',
399 description
=> "Add the VM to the specified pool.",
409 my $rpcenv = PVE
::RPCEnvironment
::get
();
411 my $authuser = $rpcenv->get_user();
413 my $node = extract_param
($param, 'node');
415 my $vmid = extract_param
($param, 'vmid');
417 my $archive = extract_param
($param, 'archive');
419 my $storage = extract_param
($param, 'storage');
421 my $force = extract_param
($param, 'force');
423 my $unique = extract_param
($param, 'unique');
425 my $pool = extract_param
($param, 'pool');
427 my $filename = PVE
::QemuConfig-
>config_file($vmid);
429 my $storecfg = PVE
::Storage
::config
();
431 PVE
::Cluster
::check_cfs_quorum
();
433 if (defined($pool)) {
434 $rpcenv->check_pool_exist($pool);
437 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
438 if defined($storage);
440 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
442 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
444 } elsif ($archive && $force && (-f
$filename) &&
445 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
446 # OK: user has VM.Backup permissions, and want to restore an existing VM
452 &$resolve_cdrom_alias($param);
454 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
456 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
458 foreach my $opt (keys %$param) {
459 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
460 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
461 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
463 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
464 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
468 PVE
::QemuServer
::add_random_macs
($param);
470 my $keystr = join(' ', keys %$param);
471 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
473 if ($archive eq '-') {
474 die "pipe requires cli environment\n"
475 if $rpcenv->{type
} ne 'cli';
477 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
478 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
482 my $restorefn = sub {
483 my $vmlist = PVE
::Cluster
::get_vmlist
();
484 if ($vmlist->{ids
}->{$vmid}) {
485 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
486 if ($current_node eq $node) {
487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
489 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
491 die "unable to restore vm $vmid - config file already exists\n"
494 die "unable to restore vm $vmid - vm is running\n"
495 if PVE
::QemuServer
::check_running
($vmid);
497 die "unable to restore vm $vmid - vm is a template\n"
498 if PVE
::QemuConfig-
>is_template($conf);
501 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
506 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
509 unique
=> $unique });
511 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
514 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
520 PVE
::Cluster
::check_vmid_unused
($vmid);
530 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
532 # try to be smart about bootdisk
533 my @disks = PVE
::QemuServer
::valid_drive_names
();
535 foreach my $ds (reverse @disks) {
536 next if !$conf->{$ds};
537 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
538 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
542 if (!$conf->{bootdisk
} && $firstdisk) {
543 $conf->{bootdisk
} = $firstdisk;
546 # auto generate uuid if user did not specify smbios1 option
547 if (!$conf->{smbios1
}) {
548 my ($uuid, $uuid_str);
549 UUID
::generate
($uuid);
550 UUID
::unparse
($uuid, $uuid_str);
551 $conf->{smbios1
} = "uuid=$uuid_str";
554 PVE
::QemuConfig-
>write_config($vmid, $conf);
560 foreach my $volid (@$vollist) {
561 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
564 die "create failed - $err";
567 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
570 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
573 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
576 __PACKAGE__-
>register_method({
581 description
=> "Directory index",
586 additionalProperties
=> 0,
588 node
=> get_standard_option
('pve-node'),
589 vmid
=> get_standard_option
('pve-vmid'),
597 subdir
=> { type
=> 'string' },
600 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
606 { subdir
=> 'config' },
607 { subdir
=> 'pending' },
608 { subdir
=> 'status' },
609 { subdir
=> 'unlink' },
610 { subdir
=> 'vncproxy' },
611 { subdir
=> 'migrate' },
612 { subdir
=> 'resize' },
613 { subdir
=> 'move' },
615 { subdir
=> 'rrddata' },
616 { subdir
=> 'monitor' },
617 { subdir
=> 'snapshot' },
618 { subdir
=> 'spiceproxy' },
619 { subdir
=> 'sendkey' },
620 { subdir
=> 'firewall' },
626 __PACKAGE__-
>register_method ({
627 subclass
=> "PVE::API2::Firewall::VM",
628 path
=> '{vmid}/firewall',
631 __PACKAGE__-
>register_method({
633 path
=> '{vmid}/rrd',
635 protected
=> 1, # fixme: can we avoid that?
637 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
639 description
=> "Read VM RRD statistics (returns PNG)",
641 additionalProperties
=> 0,
643 node
=> get_standard_option
('pve-node'),
644 vmid
=> get_standard_option
('pve-vmid'),
646 description
=> "Specify the time frame you are interested in.",
648 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
651 description
=> "The list of datasources you want to display.",
652 type
=> 'string', format
=> 'pve-configid-list',
655 description
=> "The RRD consolidation function",
657 enum
=> [ 'AVERAGE', 'MAX' ],
665 filename
=> { type
=> 'string' },
671 return PVE
::Cluster
::create_rrd_graph
(
672 "pve2-vm/$param->{vmid}", $param->{timeframe
},
673 $param->{ds
}, $param->{cf
});
677 __PACKAGE__-
>register_method({
679 path
=> '{vmid}/rrddata',
681 protected
=> 1, # fixme: can we avoid that?
683 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
685 description
=> "Read VM RRD statistics",
687 additionalProperties
=> 0,
689 node
=> get_standard_option
('pve-node'),
690 vmid
=> get_standard_option
('pve-vmid'),
692 description
=> "Specify the time frame you are interested in.",
694 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
697 description
=> "The RRD consolidation function",
699 enum
=> [ 'AVERAGE', 'MAX' ],
714 return PVE
::Cluster
::create_rrd_data
(
715 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
719 __PACKAGE__-
>register_method({
721 path
=> '{vmid}/config',
724 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
726 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
729 additionalProperties
=> 0,
731 node
=> get_standard_option
('pve-node'),
732 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
734 description
=> "Get current values (instead of pending values).",
746 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
753 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
755 delete $conf->{snapshots
};
757 if (!$param->{current
}) {
758 foreach my $opt (keys %{$conf->{pending
}}) {
759 next if $opt eq 'delete';
760 my $value = $conf->{pending
}->{$opt};
761 next if ref($value); # just to be sure
762 $conf->{$opt} = $value;
764 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
765 foreach my $opt (keys %$pending_delete_hash) {
766 delete $conf->{$opt} if $conf->{$opt};
770 delete $conf->{pending
};
775 __PACKAGE__-
>register_method({
776 name
=> 'vm_pending',
777 path
=> '{vmid}/pending',
780 description
=> "Get virtual machine configuration, including pending changes.",
782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
785 additionalProperties
=> 0,
787 node
=> get_standard_option
('pve-node'),
788 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
797 description
=> "Configuration option name.",
801 description
=> "Current value.",
806 description
=> "Pending value.",
811 description
=> "Indicates a pending delete request if present and not 0. " .
812 "The value 2 indicates a force-delete request.",
824 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
826 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
830 foreach my $opt (keys %$conf) {
831 next if ref($conf->{$opt});
832 my $item = { key
=> $opt };
833 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
834 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
835 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
839 foreach my $opt (keys %{$conf->{pending
}}) {
840 next if $opt eq 'delete';
841 next if ref($conf->{pending
}->{$opt}); # just to be sure
842 next if defined($conf->{$opt});
843 my $item = { key
=> $opt };
844 $item->{pending
} = $conf->{pending
}->{$opt};
848 while (my ($opt, $force) = each %$pending_delete_hash) {
849 next if $conf->{pending
}->{$opt}; # just to be sure
850 next if $conf->{$opt};
851 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
858 # POST/PUT {vmid}/config implementation
860 # The original API used PUT (idempotent) an we assumed that all operations
861 # are fast. But it turned out that almost any configuration change can
862 # involve hot-plug actions, or disk alloc/free. Such actions can take long
863 # time to complete and have side effects (not idempotent).
865 # The new implementation uses POST and forks a worker process. We added
866 # a new option 'background_delay'. If specified we wait up to
867 # 'background_delay' second for the worker task to complete. It returns null
868 # if the task is finished within that time, else we return the UPID.
870 my $update_vm_api = sub {
871 my ($param, $sync) = @_;
873 my $rpcenv = PVE
::RPCEnvironment
::get
();
875 my $authuser = $rpcenv->get_user();
877 my $node = extract_param
($param, 'node');
879 my $vmid = extract_param
($param, 'vmid');
881 my $digest = extract_param
($param, 'digest');
883 my $background_delay = extract_param
($param, 'background_delay');
885 my @paramarr = (); # used for log message
886 foreach my $key (keys %$param) {
887 push @paramarr, "-$key", $param->{$key};
890 my $skiplock = extract_param
($param, 'skiplock');
891 raise_param_exc
({ skiplock
=> "Only root may use this option." })
892 if $skiplock && $authuser ne 'root@pam';
894 my $delete_str = extract_param
($param, 'delete');
896 my $revert_str = extract_param
($param, 'revert');
898 my $force = extract_param
($param, 'force');
900 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
902 my $storecfg = PVE
::Storage
::config
();
904 my $defaults = PVE
::QemuServer
::load_defaults
();
906 &$resolve_cdrom_alias($param);
908 # now try to verify all parameters
911 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
912 if (!PVE
::QemuServer
::option_exists
($opt)) {
913 raise_param_exc
({ revert
=> "unknown option '$opt'" });
916 raise_param_exc
({ delete => "you can't use '-$opt' and " .
917 "-revert $opt' at the same time" })
918 if defined($param->{$opt});
924 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
925 $opt = 'ide2' if $opt eq 'cdrom';
927 raise_param_exc
({ delete => "you can't use '-$opt' and " .
928 "-delete $opt' at the same time" })
929 if defined($param->{$opt});
931 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
932 "-revert $opt' at the same time" })
935 if (!PVE
::QemuServer
::option_exists
($opt)) {
936 raise_param_exc
({ delete => "unknown option '$opt'" });
942 foreach my $opt (keys %$param) {
943 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
945 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
946 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
947 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
948 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
949 } elsif ($opt =~ m/^net(\d+)$/) {
951 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
952 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
956 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
958 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
960 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
964 my $conf = PVE
::QemuConfig-
>load_config($vmid);
966 die "checksum missmatch (file change by other user?)\n"
967 if $digest && $digest ne $conf->{digest
};
969 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
971 foreach my $opt (keys %$revert) {
972 if (defined($conf->{$opt})) {
973 $param->{$opt} = $conf->{$opt};
974 } elsif (defined($conf->{pending
}->{$opt})) {
979 if ($param->{memory
} || defined($param->{balloon
})) {
980 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
981 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
983 die "balloon value too large (must be smaller than assigned memory)\n"
984 if $balloon && $balloon > $maxmem;
987 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
991 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
993 # write updates to pending section
995 my $modified = {}; # record what $option we modify
997 foreach my $opt (@delete) {
998 $modified->{$opt} = 1;
999 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1000 if ($opt =~ m/^unused/) {
1001 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1002 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1003 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1004 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1005 delete $conf->{$opt};
1006 PVE
::QemuConfig-
>write_config($vmid, $conf);
1008 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1009 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1010 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1011 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1012 if defined($conf->{pending
}->{$opt});
1013 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1014 PVE
::QemuConfig-
>write_config($vmid, $conf);
1016 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1017 PVE
::QemuConfig-
>write_config($vmid, $conf);
1021 foreach my $opt (keys %$param) { # add/change
1022 $modified->{$opt} = 1;
1023 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1024 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1026 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1027 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1028 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1029 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1031 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1033 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1034 if defined($conf->{pending
}->{$opt});
1036 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1038 $conf->{pending
}->{$opt} = $param->{$opt};
1040 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1041 PVE
::QemuConfig-
>write_config($vmid, $conf);
1044 # remove pending changes when nothing changed
1045 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1046 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1047 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1049 return if !scalar(keys %{$conf->{pending
}});
1051 my $running = PVE
::QemuServer
::check_running
($vmid);
1053 # apply pending changes
1055 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1059 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1060 raise_param_exc
($errors) if scalar(keys %$errors);
1062 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1072 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1074 if ($background_delay) {
1076 # Note: It would be better to do that in the Event based HTTPServer
1077 # to avoid blocking call to sleep.
1079 my $end_time = time() + $background_delay;
1081 my $task = PVE
::Tools
::upid_decode
($upid);
1084 while (time() < $end_time) {
1085 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1087 sleep(1); # this gets interrupted when child process ends
1091 my $status = PVE
::Tools
::upid_read_status
($upid);
1092 return undef if $status eq 'OK';
1101 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1104 my $vm_config_perm_list = [
1109 'VM.Config.Network',
1111 'VM.Config.Options',
1114 __PACKAGE__-
>register_method({
1115 name
=> 'update_vm_async',
1116 path
=> '{vmid}/config',
1120 description
=> "Set virtual machine options (asynchrounous API).",
1122 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1125 additionalProperties
=> 0,
1126 properties
=> PVE
::QemuServer
::json_config_properties
(
1128 node
=> get_standard_option
('pve-node'),
1129 vmid
=> get_standard_option
('pve-vmid'),
1130 skiplock
=> get_standard_option
('skiplock'),
1132 type
=> 'string', format
=> 'pve-configid-list',
1133 description
=> "A list of settings you want to delete.",
1137 type
=> 'string', format
=> 'pve-configid-list',
1138 description
=> "Revert a pending change.",
1143 description
=> $opt_force_description,
1145 requires
=> 'delete',
1149 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1153 background_delay
=> {
1155 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1166 code
=> $update_vm_api,
1169 __PACKAGE__-
>register_method({
1170 name
=> 'update_vm',
1171 path
=> '{vmid}/config',
1175 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1177 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1180 additionalProperties
=> 0,
1181 properties
=> PVE
::QemuServer
::json_config_properties
(
1183 node
=> get_standard_option
('pve-node'),
1184 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1185 skiplock
=> get_standard_option
('skiplock'),
1187 type
=> 'string', format
=> 'pve-configid-list',
1188 description
=> "A list of settings you want to delete.",
1192 type
=> 'string', format
=> 'pve-configid-list',
1193 description
=> "Revert a pending change.",
1198 description
=> $opt_force_description,
1200 requires
=> 'delete',
1204 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1210 returns
=> { type
=> 'null' },
1213 &$update_vm_api($param, 1);
1219 __PACKAGE__-
>register_method({
1220 name
=> 'destroy_vm',
1225 description
=> "Destroy the vm (also delete all used/owned volumes).",
1227 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1230 additionalProperties
=> 0,
1232 node
=> get_standard_option
('pve-node'),
1233 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1234 skiplock
=> get_standard_option
('skiplock'),
1243 my $rpcenv = PVE
::RPCEnvironment
::get
();
1245 my $authuser = $rpcenv->get_user();
1247 my $vmid = $param->{vmid
};
1249 my $skiplock = $param->{skiplock
};
1250 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1251 if $skiplock && $authuser ne 'root@pam';
1254 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1256 my $storecfg = PVE
::Storage
::config
();
1258 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1260 die "unable to remove VM $vmid - used in HA resources\n"
1261 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1263 # early tests (repeat after locking)
1264 die "VM $vmid is running - destroy failed\n"
1265 if PVE
::QemuServer
::check_running
($vmid);
1270 syslog
('info', "destroy VM $vmid: $upid\n");
1272 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1274 PVE
::AccessControl
::remove_vm_access
($vmid);
1276 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1279 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1282 __PACKAGE__-
>register_method({
1284 path
=> '{vmid}/unlink',
1288 description
=> "Unlink/delete disk images.",
1290 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1293 additionalProperties
=> 0,
1295 node
=> get_standard_option
('pve-node'),
1296 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1298 type
=> 'string', format
=> 'pve-configid-list',
1299 description
=> "A list of disk IDs you want to delete.",
1303 description
=> $opt_force_description,
1308 returns
=> { type
=> 'null'},
1312 $param->{delete} = extract_param
($param, 'idlist');
1314 __PACKAGE__-
>update_vm($param);
1321 __PACKAGE__-
>register_method({
1323 path
=> '{vmid}/vncproxy',
1327 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1329 description
=> "Creates a TCP VNC proxy connections.",
1331 additionalProperties
=> 0,
1333 node
=> get_standard_option
('pve-node'),
1334 vmid
=> get_standard_option
('pve-vmid'),
1338 description
=> "starts websockify instead of vncproxy",
1343 additionalProperties
=> 0,
1345 user
=> { type
=> 'string' },
1346 ticket
=> { type
=> 'string' },
1347 cert
=> { type
=> 'string' },
1348 port
=> { type
=> 'integer' },
1349 upid
=> { type
=> 'string' },
1355 my $rpcenv = PVE
::RPCEnvironment
::get
();
1357 my $authuser = $rpcenv->get_user();
1359 my $vmid = $param->{vmid
};
1360 my $node = $param->{node
};
1361 my $websocket = $param->{websocket
};
1363 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1365 my $authpath = "/vms/$vmid";
1367 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1369 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1372 my ($remip, $family);
1375 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1376 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1377 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1378 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1380 $family = PVE
::Tools
::get_host_address_family
($node);
1383 my $port = PVE
::Tools
::next_vnc_port
($family);
1390 syslog
('info', "starting vnc proxy $upid\n");
1394 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1396 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1398 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1399 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1400 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1401 '-timeout', $timeout, '-authpath', $authpath,
1402 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1405 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1407 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1409 my $qmstr = join(' ', @$qmcmd);
1411 # also redirect stderr (else we get RFB protocol errors)
1412 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1415 PVE
::Tools
::run_command
($cmd);
1420 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1422 PVE
::Tools
::wait_for_vnc_port
($port);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'vncwebsocket',
1435 path
=> '{vmid}/vncwebsocket',
1438 description
=> "You also need to pass a valid ticket (vncticket).",
1439 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1441 description
=> "Opens a weksocket for VNC traffic.",
1443 additionalProperties
=> 0,
1445 node
=> get_standard_option
('pve-node'),
1446 vmid
=> get_standard_option
('pve-vmid'),
1448 description
=> "Ticket from previous call to vncproxy.",
1453 description
=> "Port number returned by previous vncproxy call.",
1463 port
=> { type
=> 'string' },
1469 my $rpcenv = PVE
::RPCEnvironment
::get
();
1471 my $authuser = $rpcenv->get_user();
1473 my $vmid = $param->{vmid
};
1474 my $node = $param->{node
};
1476 my $authpath = "/vms/$vmid";
1478 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1480 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1482 # Note: VNC ports are acessible from outside, so we do not gain any
1483 # security if we verify that $param->{port} belongs to VM $vmid. This
1484 # check is done by verifying the VNC ticket (inside VNC protocol).
1486 my $port = $param->{port
};
1488 return { port
=> $port };
1491 __PACKAGE__-
>register_method({
1492 name
=> 'spiceproxy',
1493 path
=> '{vmid}/spiceproxy',
1498 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1500 description
=> "Returns a SPICE configuration to connect to the VM.",
1502 additionalProperties
=> 0,
1504 node
=> get_standard_option
('pve-node'),
1505 vmid
=> get_standard_option
('pve-vmid'),
1506 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1509 returns
=> get_standard_option
('remote-viewer-config'),
1513 my $rpcenv = PVE
::RPCEnvironment
::get
();
1515 my $authuser = $rpcenv->get_user();
1517 my $vmid = $param->{vmid
};
1518 my $node = $param->{node
};
1519 my $proxy = $param->{proxy
};
1521 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1522 my $title = "VM $vmid";
1523 $title .= " - ". $conf->{name
} if $conf->{name
};
1525 my $port = PVE
::QemuServer
::spice_port
($vmid);
1527 my ($ticket, undef, $remote_viewer_config) =
1528 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1530 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1531 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1533 return $remote_viewer_config;
1536 __PACKAGE__-
>register_method({
1538 path
=> '{vmid}/status',
1541 description
=> "Directory index",
1546 additionalProperties
=> 0,
1548 node
=> get_standard_option
('pve-node'),
1549 vmid
=> get_standard_option
('pve-vmid'),
1557 subdir
=> { type
=> 'string' },
1560 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1566 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1569 { subdir
=> 'current' },
1570 { subdir
=> 'start' },
1571 { subdir
=> 'stop' },
1577 __PACKAGE__-
>register_method({
1578 name
=> 'vm_status',
1579 path
=> '{vmid}/status/current',
1582 protected
=> 1, # qemu pid files are only readable by root
1583 description
=> "Get virtual machine status.",
1585 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1588 additionalProperties
=> 0,
1590 node
=> get_standard_option
('pve-node'),
1591 vmid
=> get_standard_option
('pve-vmid'),
1594 returns
=> { type
=> 'object' },
1599 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1601 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1602 my $status = $vmstatus->{$param->{vmid
}};
1604 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1606 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1611 __PACKAGE__-
>register_method({
1613 path
=> '{vmid}/status/start',
1617 description
=> "Start virtual machine.",
1619 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1622 additionalProperties
=> 0,
1624 node
=> get_standard_option
('pve-node'),
1625 vmid
=> get_standard_option
('pve-vmid',
1626 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1627 skiplock
=> get_standard_option
('skiplock'),
1628 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1629 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1632 enum
=> ['secure', 'insecure'],
1633 description
=> "Migration traffic is encrypted using an SSH " .
1634 "tunnel by default. On secure, completely private networks " .
1635 "this can be disabled to increase performance.",
1638 migration_network
=> {
1641 description
=> "CIDR of the (sub) network that is used for migration.",
1644 machine
=> get_standard_option
('pve-qm-machine'),
1653 my $rpcenv = PVE
::RPCEnvironment
::get
();
1655 my $authuser = $rpcenv->get_user();
1657 my $node = extract_param
($param, 'node');
1659 my $vmid = extract_param
($param, 'vmid');
1661 my $machine = extract_param
($param, 'machine');
1663 my $stateuri = extract_param
($param, 'stateuri');
1664 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1665 if $stateuri && $authuser ne 'root@pam';
1667 my $skiplock = extract_param
($param, 'skiplock');
1668 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1669 if $skiplock && $authuser ne 'root@pam';
1671 my $migratedfrom = extract_param
($param, 'migratedfrom');
1672 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1673 if $migratedfrom && $authuser ne 'root@pam';
1675 my $migration_type = extract_param
($param, 'migration_type');
1676 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1677 if $migration_type && $authuser ne 'root@pam';
1679 my $migration_network = extract_param
($param, 'migration_network');
1680 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1681 if $migration_network && $authuser ne 'root@pam';
1683 # read spice ticket from STDIN
1685 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1686 if (defined(my $line = <>)) {
1688 $spice_ticket = $line;
1692 PVE
::Cluster
::check_cfs_quorum
();
1694 my $storecfg = PVE
::Storage
::config
();
1696 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1697 $rpcenv->{type
} ne 'ha') {
1702 my $service = "vm:$vmid";
1704 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1706 print "Executing HA start for VM $vmid\n";
1708 PVE
::Tools
::run_command
($cmd);
1713 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1720 syslog
('info', "start VM $vmid: $upid\n");
1722 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1723 $machine, $spice_ticket, $migration_network, $migration_type);
1728 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1732 __PACKAGE__-
>register_method({
1734 path
=> '{vmid}/status/stop',
1738 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1739 "is akin to pulling the power plug of a running computer and may damage the VM data",
1741 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1744 additionalProperties
=> 0,
1746 node
=> get_standard_option
('pve-node'),
1747 vmid
=> get_standard_option
('pve-vmid',
1748 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1749 skiplock
=> get_standard_option
('skiplock'),
1750 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1752 description
=> "Wait maximal timeout seconds.",
1758 description
=> "Do not deactivate storage volumes.",
1771 my $rpcenv = PVE
::RPCEnvironment
::get
();
1773 my $authuser = $rpcenv->get_user();
1775 my $node = extract_param
($param, 'node');
1777 my $vmid = extract_param
($param, 'vmid');
1779 my $skiplock = extract_param
($param, 'skiplock');
1780 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1781 if $skiplock && $authuser ne 'root@pam';
1783 my $keepActive = extract_param
($param, 'keepActive');
1784 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1785 if $keepActive && $authuser ne 'root@pam';
1787 my $migratedfrom = extract_param
($param, 'migratedfrom');
1788 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1789 if $migratedfrom && $authuser ne 'root@pam';
1792 my $storecfg = PVE
::Storage
::config
();
1794 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1799 my $service = "vm:$vmid";
1801 my $cmd = ['ha-manager', 'set', $service, '--state', 'disabled'];
1803 print "Executing HA stop for VM $vmid\n";
1805 PVE
::Tools
::run_command
($cmd);
1810 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1816 syslog
('info', "stop VM $vmid: $upid\n");
1818 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1819 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1824 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1828 __PACKAGE__-
>register_method({
1830 path
=> '{vmid}/status/reset',
1834 description
=> "Reset virtual machine.",
1836 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1839 additionalProperties
=> 0,
1841 node
=> get_standard_option
('pve-node'),
1842 vmid
=> get_standard_option
('pve-vmid',
1843 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1844 skiplock
=> get_standard_option
('skiplock'),
1853 my $rpcenv = PVE
::RPCEnvironment
::get
();
1855 my $authuser = $rpcenv->get_user();
1857 my $node = extract_param
($param, 'node');
1859 my $vmid = extract_param
($param, 'vmid');
1861 my $skiplock = extract_param
($param, 'skiplock');
1862 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1863 if $skiplock && $authuser ne 'root@pam';
1865 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1870 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1875 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1878 __PACKAGE__-
>register_method({
1879 name
=> 'vm_shutdown',
1880 path
=> '{vmid}/status/shutdown',
1884 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1885 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1887 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1890 additionalProperties
=> 0,
1892 node
=> get_standard_option
('pve-node'),
1893 vmid
=> get_standard_option
('pve-vmid',
1894 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1895 skiplock
=> get_standard_option
('skiplock'),
1897 description
=> "Wait maximal timeout seconds.",
1903 description
=> "Make sure the VM stops.",
1909 description
=> "Do not deactivate storage volumes.",
1922 my $rpcenv = PVE
::RPCEnvironment
::get
();
1924 my $authuser = $rpcenv->get_user();
1926 my $node = extract_param
($param, 'node');
1928 my $vmid = extract_param
($param, 'vmid');
1930 my $skiplock = extract_param
($param, 'skiplock');
1931 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1932 if $skiplock && $authuser ne 'root@pam';
1934 my $keepActive = extract_param
($param, 'keepActive');
1935 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1936 if $keepActive && $authuser ne 'root@pam';
1938 my $storecfg = PVE
::Storage
::config
();
1942 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1943 # otherwise, we will infer a shutdown command, but run into the timeout,
1944 # then when the vm is resumed, it will instantly shutdown
1946 # checking the qmp status here to get feedback to the gui/cli/api
1947 # and the status query should not take too long
1950 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1954 if (!$err && $qmpstatus->{status
} eq "paused") {
1955 if ($param->{forceStop
}) {
1956 warn "VM is paused - stop instead of shutdown\n";
1959 die "VM is paused - cannot shutdown\n";
1966 syslog
('info', "shutdown VM $vmid: $upid\n");
1968 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1969 $shutdown, $param->{forceStop
}, $keepActive);
1974 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1977 __PACKAGE__-
>register_method({
1978 name
=> 'vm_suspend',
1979 path
=> '{vmid}/status/suspend',
1983 description
=> "Suspend 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'),
2002 my $rpcenv = PVE
::RPCEnvironment
::get
();
2004 my $authuser = $rpcenv->get_user();
2006 my $node = extract_param
($param, 'node');
2008 my $vmid = extract_param
($param, 'vmid');
2010 my $skiplock = extract_param
($param, 'skiplock');
2011 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2012 if $skiplock && $authuser ne 'root@pam';
2014 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2019 syslog
('info', "suspend VM $vmid: $upid\n");
2021 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2026 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2029 __PACKAGE__-
>register_method({
2030 name
=> 'vm_resume',
2031 path
=> '{vmid}/status/resume',
2035 description
=> "Resume virtual machine.",
2037 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2040 additionalProperties
=> 0,
2042 node
=> get_standard_option
('pve-node'),
2043 vmid
=> get_standard_option
('pve-vmid',
2044 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2045 skiplock
=> get_standard_option
('skiplock'),
2046 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2056 my $rpcenv = PVE
::RPCEnvironment
::get
();
2058 my $authuser = $rpcenv->get_user();
2060 my $node = extract_param
($param, 'node');
2062 my $vmid = extract_param
($param, 'vmid');
2064 my $skiplock = extract_param
($param, 'skiplock');
2065 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2066 if $skiplock && $authuser ne 'root@pam';
2068 my $nocheck = extract_param
($param, 'nocheck');
2070 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2075 syslog
('info', "resume VM $vmid: $upid\n");
2077 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2082 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2085 __PACKAGE__-
>register_method({
2086 name
=> 'vm_sendkey',
2087 path
=> '{vmid}/sendkey',
2091 description
=> "Send key event to virtual machine.",
2093 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2096 additionalProperties
=> 0,
2098 node
=> get_standard_option
('pve-node'),
2099 vmid
=> get_standard_option
('pve-vmid',
2100 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2101 skiplock
=> get_standard_option
('skiplock'),
2103 description
=> "The key (qemu monitor encoding).",
2108 returns
=> { type
=> 'null'},
2112 my $rpcenv = PVE
::RPCEnvironment
::get
();
2114 my $authuser = $rpcenv->get_user();
2116 my $node = extract_param
($param, 'node');
2118 my $vmid = extract_param
($param, 'vmid');
2120 my $skiplock = extract_param
($param, 'skiplock');
2121 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2122 if $skiplock && $authuser ne 'root@pam';
2124 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2129 __PACKAGE__-
>register_method({
2130 name
=> 'vm_feature',
2131 path
=> '{vmid}/feature',
2135 description
=> "Check if feature for virtual machine is available.",
2137 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2140 additionalProperties
=> 0,
2142 node
=> get_standard_option
('pve-node'),
2143 vmid
=> get_standard_option
('pve-vmid'),
2145 description
=> "Feature to check.",
2147 enum
=> [ 'snapshot', 'clone', 'copy' ],
2149 snapname
=> get_standard_option
('pve-snapshot-name', {
2157 hasFeature
=> { type
=> 'boolean' },
2160 items
=> { type
=> 'string' },
2167 my $node = extract_param
($param, 'node');
2169 my $vmid = extract_param
($param, 'vmid');
2171 my $snapname = extract_param
($param, 'snapname');
2173 my $feature = extract_param
($param, 'feature');
2175 my $running = PVE
::QemuServer
::check_running
($vmid);
2177 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2180 my $snap = $conf->{snapshots
}->{$snapname};
2181 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2184 my $storecfg = PVE
::Storage
::config
();
2186 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2187 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2190 hasFeature
=> $hasFeature,
2191 nodes
=> [ keys %$nodelist ],
2195 __PACKAGE__-
>register_method({
2197 path
=> '{vmid}/clone',
2201 description
=> "Create a copy of virtual machine/template.",
2203 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2204 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2205 "'Datastore.AllocateSpace' on any used storage.",
2208 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2210 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2211 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2216 additionalProperties
=> 0,
2218 node
=> get_standard_option
('pve-node'),
2219 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2220 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2223 type
=> 'string', format
=> 'dns-name',
2224 description
=> "Set a name for the new VM.",
2229 description
=> "Description for the new VM.",
2233 type
=> 'string', format
=> 'pve-poolid',
2234 description
=> "Add the new VM to the specified pool.",
2236 snapname
=> get_standard_option
('pve-snapshot-name', {
2239 storage
=> get_standard_option
('pve-storage-id', {
2240 description
=> "Target storage for full clone.",
2245 description
=> "Target format for file storage.",
2249 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2254 description
=> "Create a full copy of all disk. This is always done when " .
2255 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2258 target
=> get_standard_option
('pve-node', {
2259 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2270 my $rpcenv = PVE
::RPCEnvironment
::get
();
2272 my $authuser = $rpcenv->get_user();
2274 my $node = extract_param
($param, 'node');
2276 my $vmid = extract_param
($param, 'vmid');
2278 my $newid = extract_param
($param, 'newid');
2280 my $pool = extract_param
($param, 'pool');
2282 if (defined($pool)) {
2283 $rpcenv->check_pool_exist($pool);
2286 my $snapname = extract_param
($param, 'snapname');
2288 my $storage = extract_param
($param, 'storage');
2290 my $format = extract_param
($param, 'format');
2292 my $target = extract_param
($param, 'target');
2294 my $localnode = PVE
::INotify
::nodename
();
2296 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2298 PVE
::Cluster
::check_node_exists
($target) if $target;
2300 my $storecfg = PVE
::Storage
::config
();
2303 # check if storage is enabled on local node
2304 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2306 # check if storage is available on target node
2307 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2308 # clone only works if target storage is shared
2309 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2310 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2314 PVE
::Cluster
::check_cfs_quorum
();
2316 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2318 # exclusive lock if VM is running - else shared lock is enough;
2319 my $shared_lock = $running ?
0 : 1;
2323 # do all tests after lock
2324 # we also try to do all tests before we fork the worker
2326 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2328 PVE
::QemuConfig-
>check_lock($conf);
2330 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2332 die "unexpected state change\n" if $verify_running != $running;
2334 die "snapshot '$snapname' does not exist\n"
2335 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2337 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2339 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2341 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2343 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2345 die "unable to create VM $newid: config file already exists\n"
2348 my $newconf = { lock => 'clone' };
2353 foreach my $opt (keys %$oldconf) {
2354 my $value = $oldconf->{$opt};
2356 # do not copy snapshot related info
2357 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2358 $opt eq 'vmstate' || $opt eq 'snapstate';
2360 # no need to copy unused images, because VMID(owner) changes anyways
2361 next if $opt =~ m/^unused\d+$/;
2363 # always change MAC! address
2364 if ($opt =~ m/^net(\d+)$/) {
2365 my $net = PVE
::QemuServer
::parse_net
($value);
2366 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2367 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2368 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2369 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2370 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2371 die "unable to parse drive options for '$opt'\n" if !$drive;
2372 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2373 $newconf->{$opt} = $value; # simply copy configuration
2375 if ($param->{full
}) {
2376 die "Full clone feature is not available"
2377 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2378 $fullclone->{$opt} = 1;
2380 # not full means clone instead of copy
2381 die "Linked clone feature is not available"
2382 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2384 $drives->{$opt} = $drive;
2385 push @$vollist, $drive->{file
};
2388 # copy everything else
2389 $newconf->{$opt} = $value;
2393 # auto generate a new uuid
2394 my ($uuid, $uuid_str);
2395 UUID
::generate
($uuid);
2396 UUID
::unparse
($uuid, $uuid_str);
2397 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2398 $smbios1->{uuid
} = $uuid_str;
2399 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2401 delete $newconf->{template
};
2403 if ($param->{name
}) {
2404 $newconf->{name
} = $param->{name
};
2406 if ($oldconf->{name
}) {
2407 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2409 $newconf->{name
} = "Copy-of-VM-$vmid";
2413 if ($param->{description
}) {
2414 $newconf->{description
} = $param->{description
};
2417 # create empty/temp config - this fails if VM already exists on other node
2418 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2423 my $newvollist = [];
2426 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2428 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2430 foreach my $opt (keys %$drives) {
2431 my $drive = $drives->{$opt};
2433 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2434 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2436 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2438 PVE
::QemuConfig-
>write_config($newid, $newconf);
2441 delete $newconf->{lock};
2442 PVE
::QemuConfig-
>write_config($newid, $newconf);
2445 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2446 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2447 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2449 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2450 die "Failed to move config to node '$target' - rename failed: $!\n"
2451 if !rename($conffile, $newconffile);
2454 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2459 sleep 1; # some storage like rbd need to wait before release volume - really?
2461 foreach my $volid (@$newvollist) {
2462 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2465 die "clone failed: $err";
2471 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2473 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2476 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2477 # Aquire exclusive lock lock for $newid
2478 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2483 __PACKAGE__-
>register_method({
2484 name
=> 'move_vm_disk',
2485 path
=> '{vmid}/move_disk',
2489 description
=> "Move volume to different storage.",
2491 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2492 "and 'Datastore.AllocateSpace' permissions on the storage.",
2495 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2496 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2500 additionalProperties
=> 0,
2502 node
=> get_standard_option
('pve-node'),
2503 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2506 description
=> "The disk you want to move.",
2507 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2509 storage
=> get_standard_option
('pve-storage-id', {
2510 description
=> "Target storage.",
2511 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2515 description
=> "Target Format.",
2516 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2521 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2527 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2535 description
=> "the task ID.",
2540 my $rpcenv = PVE
::RPCEnvironment
::get
();
2542 my $authuser = $rpcenv->get_user();
2544 my $node = extract_param
($param, 'node');
2546 my $vmid = extract_param
($param, 'vmid');
2548 my $digest = extract_param
($param, 'digest');
2550 my $disk = extract_param
($param, 'disk');
2552 my $storeid = extract_param
($param, 'storage');
2554 my $format = extract_param
($param, 'format');
2556 my $storecfg = PVE
::Storage
::config
();
2558 my $updatefn = sub {
2560 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2562 PVE
::QemuConfig-
>check_lock($conf);
2564 die "checksum missmatch (file change by other user?)\n"
2565 if $digest && $digest ne $conf->{digest
};
2567 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2569 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2571 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2573 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2576 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2577 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2581 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2582 (!$format || !$oldfmt || $oldfmt eq $format);
2584 # this only checks snapshots because $disk is passed!
2585 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2586 die "you can't move a disk with snapshots and delete the source\n"
2587 if $snapshotted && $param->{delete};
2589 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2591 my $running = PVE
::QemuServer
::check_running
($vmid);
2593 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2597 my $newvollist = [];
2600 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2602 warn "moving disk with snapshots, snapshots will not be moved!\n"
2605 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2606 $vmid, $storeid, $format, 1, $newvollist);
2608 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2610 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2612 PVE
::QemuConfig-
>write_config($vmid, $conf);
2615 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2616 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2623 foreach my $volid (@$newvollist) {
2624 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2627 die "storage migration failed: $err";
2630 if ($param->{delete}) {
2632 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2633 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2639 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2642 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2645 __PACKAGE__-
>register_method({
2646 name
=> 'migrate_vm',
2647 path
=> '{vmid}/migrate',
2651 description
=> "Migrate virtual machine. Creates a new migration task.",
2653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2656 additionalProperties
=> 0,
2658 node
=> get_standard_option
('pve-node'),
2659 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2660 target
=> get_standard_option
('pve-node', {
2661 description
=> "Target node.",
2662 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2666 description
=> "Use online/live migration.",
2671 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2676 enum
=> ['secure', 'insecure'],
2677 description
=> "Migration traffic is encrypted using an SSH " .
2678 "tunnel by default. On secure, completely private networks " .
2679 "this can be disabled to increase performance.",
2682 migration_network
=> {
2685 description
=> "CIDR of the (sub) network that is used for migration.",
2692 description
=> "the task ID.",
2697 my $rpcenv = PVE
::RPCEnvironment
::get
();
2699 my $authuser = $rpcenv->get_user();
2701 my $target = extract_param
($param, 'target');
2703 my $localnode = PVE
::INotify
::nodename
();
2704 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2706 PVE
::Cluster
::check_cfs_quorum
();
2708 PVE
::Cluster
::check_node_exists
($target);
2710 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2712 my $vmid = extract_param
($param, 'vmid');
2714 raise_param_exc
({ force
=> "Only root may use this option." })
2715 if $param->{force
} && $authuser ne 'root@pam';
2717 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2718 if $param->{migration_type
} && $authuser ne 'root@pam';
2720 # allow root only until better network permissions are available
2721 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2722 if $param->{migration_network
} && $authuser ne 'root@pam';
2725 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2727 # try to detect errors early
2729 PVE
::QemuConfig-
>check_lock($conf);
2731 if (PVE
::QemuServer
::check_running
($vmid)) {
2732 die "cant migrate running VM without --online\n"
2733 if !$param->{online
};
2736 my $storecfg = PVE
::Storage
::config
();
2737 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2739 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2744 my $service = "vm:$vmid";
2746 my $cmd = ['ha-manager', 'migrate', $service, $target];
2748 print "Executing HA migrate for VM $vmid to node $target\n";
2750 PVE
::Tools
::run_command
($cmd);
2755 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2762 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2765 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2770 __PACKAGE__-
>register_method({
2772 path
=> '{vmid}/monitor',
2776 description
=> "Execute Qemu monitor commands.",
2778 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2781 additionalProperties
=> 0,
2783 node
=> get_standard_option
('pve-node'),
2784 vmid
=> get_standard_option
('pve-vmid'),
2787 description
=> "The monitor command.",
2791 returns
=> { type
=> 'string'},
2795 my $vmid = $param->{vmid
};
2797 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2801 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2803 $res = "ERROR: $@" if $@;
2808 __PACKAGE__-
>register_method({
2809 name
=> 'resize_vm',
2810 path
=> '{vmid}/resize',
2814 description
=> "Extend volume size.",
2816 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2819 additionalProperties
=> 0,
2821 node
=> get_standard_option
('pve-node'),
2822 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2823 skiplock
=> get_standard_option
('skiplock'),
2826 description
=> "The disk you want to resize.",
2827 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2831 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2832 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.",
2836 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2842 returns
=> { type
=> 'null'},
2846 my $rpcenv = PVE
::RPCEnvironment
::get
();
2848 my $authuser = $rpcenv->get_user();
2850 my $node = extract_param
($param, 'node');
2852 my $vmid = extract_param
($param, 'vmid');
2854 my $digest = extract_param
($param, 'digest');
2856 my $disk = extract_param
($param, 'disk');
2858 my $sizestr = extract_param
($param, 'size');
2860 my $skiplock = extract_param
($param, 'skiplock');
2861 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2862 if $skiplock && $authuser ne 'root@pam';
2864 my $storecfg = PVE
::Storage
::config
();
2866 my $updatefn = sub {
2868 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2870 die "checksum missmatch (file change by other user?)\n"
2871 if $digest && $digest ne $conf->{digest
};
2872 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2874 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2876 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2878 my (undef, undef, undef, undef, undef, undef, $format) =
2879 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2881 die "can't resize volume: $disk if snapshot exists\n"
2882 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2884 my $volid = $drive->{file
};
2886 die "disk '$disk' has no associated volume\n" if !$volid;
2888 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2890 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2892 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2894 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2895 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2897 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2898 my ($ext, $newsize, $unit) = ($1, $2, $4);
2901 $newsize = $newsize * 1024;
2902 } elsif ($unit eq 'M') {
2903 $newsize = $newsize * 1024 * 1024;
2904 } elsif ($unit eq 'G') {
2905 $newsize = $newsize * 1024 * 1024 * 1024;
2906 } elsif ($unit eq 'T') {
2907 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2910 $newsize += $size if $ext;
2911 $newsize = int($newsize);
2913 die "unable to skrink disk size\n" if $newsize < $size;
2915 return if $size == $newsize;
2917 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2919 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2921 $drive->{size
} = $newsize;
2922 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2924 PVE
::QemuConfig-
>write_config($vmid, $conf);
2927 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2931 __PACKAGE__-
>register_method({
2932 name
=> 'snapshot_list',
2933 path
=> '{vmid}/snapshot',
2935 description
=> "List all snapshots.",
2937 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2940 protected
=> 1, # qemu pid files are only readable by root
2942 additionalProperties
=> 0,
2944 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2945 node
=> get_standard_option
('pve-node'),
2954 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2959 my $vmid = $param->{vmid
};
2961 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2962 my $snaphash = $conf->{snapshots
} || {};
2966 foreach my $name (keys %$snaphash) {
2967 my $d = $snaphash->{$name};
2970 snaptime
=> $d->{snaptime
} || 0,
2971 vmstate
=> $d->{vmstate
} ?
1 : 0,
2972 description
=> $d->{description
} || '',
2974 $item->{parent
} = $d->{parent
} if $d->{parent
};
2975 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2979 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2980 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2981 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2983 push @$res, $current;
2988 __PACKAGE__-
>register_method({
2990 path
=> '{vmid}/snapshot',
2994 description
=> "Snapshot a VM.",
2996 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2999 additionalProperties
=> 0,
3001 node
=> get_standard_option
('pve-node'),
3002 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3003 snapname
=> get_standard_option
('pve-snapshot-name'),
3007 description
=> "Save the vmstate",
3012 description
=> "A textual description or comment.",
3018 description
=> "the task ID.",
3023 my $rpcenv = PVE
::RPCEnvironment
::get
();
3025 my $authuser = $rpcenv->get_user();
3027 my $node = extract_param
($param, 'node');
3029 my $vmid = extract_param
($param, 'vmid');
3031 my $snapname = extract_param
($param, 'snapname');
3033 die "unable to use snapshot name 'current' (reserved name)\n"
3034 if $snapname eq 'current';
3037 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3038 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3039 $param->{description
});
3042 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3045 __PACKAGE__-
>register_method({
3046 name
=> 'snapshot_cmd_idx',
3047 path
=> '{vmid}/snapshot/{snapname}',
3054 additionalProperties
=> 0,
3056 vmid
=> get_standard_option
('pve-vmid'),
3057 node
=> get_standard_option
('pve-node'),
3058 snapname
=> get_standard_option
('pve-snapshot-name'),
3067 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3074 push @$res, { cmd
=> 'rollback' };
3075 push @$res, { cmd
=> 'config' };
3080 __PACKAGE__-
>register_method({
3081 name
=> 'update_snapshot_config',
3082 path
=> '{vmid}/snapshot/{snapname}/config',
3086 description
=> "Update snapshot metadata.",
3088 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3091 additionalProperties
=> 0,
3093 node
=> get_standard_option
('pve-node'),
3094 vmid
=> get_standard_option
('pve-vmid'),
3095 snapname
=> get_standard_option
('pve-snapshot-name'),
3099 description
=> "A textual description or comment.",
3103 returns
=> { type
=> 'null' },
3107 my $rpcenv = PVE
::RPCEnvironment
::get
();
3109 my $authuser = $rpcenv->get_user();
3111 my $vmid = extract_param
($param, 'vmid');
3113 my $snapname = extract_param
($param, 'snapname');
3115 return undef if !defined($param->{description
});
3117 my $updatefn = sub {
3119 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3121 PVE
::QemuConfig-
>check_lock($conf);
3123 my $snap = $conf->{snapshots
}->{$snapname};
3125 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3127 $snap->{description
} = $param->{description
} if defined($param->{description
});
3129 PVE
::QemuConfig-
>write_config($vmid, $conf);
3132 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3137 __PACKAGE__-
>register_method({
3138 name
=> 'get_snapshot_config',
3139 path
=> '{vmid}/snapshot/{snapname}/config',
3142 description
=> "Get snapshot configuration",
3144 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3147 additionalProperties
=> 0,
3149 node
=> get_standard_option
('pve-node'),
3150 vmid
=> get_standard_option
('pve-vmid'),
3151 snapname
=> get_standard_option
('pve-snapshot-name'),
3154 returns
=> { type
=> "object" },
3158 my $rpcenv = PVE
::RPCEnvironment
::get
();
3160 my $authuser = $rpcenv->get_user();
3162 my $vmid = extract_param
($param, 'vmid');
3164 my $snapname = extract_param
($param, 'snapname');
3166 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3168 my $snap = $conf->{snapshots
}->{$snapname};
3170 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3175 __PACKAGE__-
>register_method({
3177 path
=> '{vmid}/snapshot/{snapname}/rollback',
3181 description
=> "Rollback VM state to specified snapshot.",
3183 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3186 additionalProperties
=> 0,
3188 node
=> get_standard_option
('pve-node'),
3189 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3190 snapname
=> get_standard_option
('pve-snapshot-name'),
3195 description
=> "the task ID.",
3200 my $rpcenv = PVE
::RPCEnvironment
::get
();
3202 my $authuser = $rpcenv->get_user();
3204 my $node = extract_param
($param, 'node');
3206 my $vmid = extract_param
($param, 'vmid');
3208 my $snapname = extract_param
($param, 'snapname');
3211 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3212 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3215 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3218 __PACKAGE__-
>register_method({
3219 name
=> 'delsnapshot',
3220 path
=> '{vmid}/snapshot/{snapname}',
3224 description
=> "Delete a VM snapshot.",
3226 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3229 additionalProperties
=> 0,
3231 node
=> get_standard_option
('pve-node'),
3232 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3233 snapname
=> get_standard_option
('pve-snapshot-name'),
3237 description
=> "For removal from config file, even if removing disk snapshots fails.",
3243 description
=> "the task ID.",
3248 my $rpcenv = PVE
::RPCEnvironment
::get
();
3250 my $authuser = $rpcenv->get_user();
3252 my $node = extract_param
($param, 'node');
3254 my $vmid = extract_param
($param, 'vmid');
3256 my $snapname = extract_param
($param, 'snapname');
3259 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3260 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3263 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3266 __PACKAGE__-
>register_method({
3268 path
=> '{vmid}/template',
3272 description
=> "Create a Template.",
3274 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3275 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3278 additionalProperties
=> 0,
3280 node
=> get_standard_option
('pve-node'),
3281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3285 description
=> "If you want to convert only 1 disk to base image.",
3286 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3291 returns
=> { type
=> 'null'},
3295 my $rpcenv = PVE
::RPCEnvironment
::get
();
3297 my $authuser = $rpcenv->get_user();
3299 my $node = extract_param
($param, 'node');
3301 my $vmid = extract_param
($param, 'vmid');
3303 my $disk = extract_param
($param, 'disk');
3305 my $updatefn = sub {
3307 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3309 PVE
::QemuConfig-
>check_lock($conf);
3311 die "unable to create template, because VM contains snapshots\n"
3312 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3314 die "you can't convert a template to a template\n"
3315 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3317 die "you can't convert a VM to template if VM is running\n"
3318 if PVE
::QemuServer
::check_running
($vmid);
3321 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3324 $conf->{template
} = 1;
3325 PVE
::QemuConfig-
>write_config($vmid, $conf);
3327 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3330 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);