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
=> {
1639 type
=> 'string', format
=> 'CIDR',
1640 description
=> "CIDR of the (sub) network that is used for migration.",
1643 machine
=> get_standard_option
('pve-qm-machine'),
1652 my $rpcenv = PVE
::RPCEnvironment
::get
();
1654 my $authuser = $rpcenv->get_user();
1656 my $node = extract_param
($param, 'node');
1658 my $vmid = extract_param
($param, 'vmid');
1660 my $machine = extract_param
($param, 'machine');
1662 my $stateuri = extract_param
($param, 'stateuri');
1663 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1664 if $stateuri && $authuser ne 'root@pam';
1666 my $skiplock = extract_param
($param, 'skiplock');
1667 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1668 if $skiplock && $authuser ne 'root@pam';
1670 my $migratedfrom = extract_param
($param, 'migratedfrom');
1671 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1672 if $migratedfrom && $authuser ne 'root@pam';
1674 my $migration_type = extract_param
($param, 'migration_type');
1675 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1676 if $migration_type && $authuser ne 'root@pam';
1678 my $migration_network = extract_param
($param, 'migration_network');
1679 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1680 if $migration_network && $authuser ne 'root@pam';
1682 # read spice ticket from STDIN
1684 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1685 if (defined(my $line = <>)) {
1687 $spice_ticket = $line;
1691 PVE
::Cluster
::check_cfs_quorum
();
1693 my $storecfg = PVE
::Storage
::config
();
1695 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1696 $rpcenv->{type
} ne 'ha') {
1701 my $service = "vm:$vmid";
1703 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1705 print "Executing HA start for VM $vmid\n";
1707 PVE
::Tools
::run_command
($cmd);
1712 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1719 syslog
('info', "start VM $vmid: $upid\n");
1721 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1722 $machine, $spice_ticket, $migration_network, $migration_type);
1727 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1731 __PACKAGE__-
>register_method({
1733 path
=> '{vmid}/status/stop',
1737 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1738 "is akin to pulling the power plug of a running computer and may damage the VM data",
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid',
1747 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1748 skiplock
=> get_standard_option
('skiplock'),
1749 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1751 description
=> "Wait maximal timeout seconds.",
1757 description
=> "Do not deactivate storage volumes.",
1770 my $rpcenv = PVE
::RPCEnvironment
::get
();
1772 my $authuser = $rpcenv->get_user();
1774 my $node = extract_param
($param, 'node');
1776 my $vmid = extract_param
($param, 'vmid');
1778 my $skiplock = extract_param
($param, 'skiplock');
1779 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1780 if $skiplock && $authuser ne 'root@pam';
1782 my $keepActive = extract_param
($param, 'keepActive');
1783 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1784 if $keepActive && $authuser ne 'root@pam';
1786 my $migratedfrom = extract_param
($param, 'migratedfrom');
1787 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1788 if $migratedfrom && $authuser ne 'root@pam';
1791 my $storecfg = PVE
::Storage
::config
();
1793 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1798 my $service = "vm:$vmid";
1800 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1802 print "Executing HA stop for VM $vmid\n";
1804 PVE
::Tools
::run_command
($cmd);
1809 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1815 syslog
('info', "stop VM $vmid: $upid\n");
1817 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1818 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1823 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1827 __PACKAGE__-
>register_method({
1829 path
=> '{vmid}/status/reset',
1833 description
=> "Reset virtual machine.",
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'),
1852 my $rpcenv = PVE
::RPCEnvironment
::get
();
1854 my $authuser = $rpcenv->get_user();
1856 my $node = extract_param
($param, 'node');
1858 my $vmid = extract_param
($param, 'vmid');
1860 my $skiplock = extract_param
($param, 'skiplock');
1861 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1862 if $skiplock && $authuser ne 'root@pam';
1864 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1869 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1874 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1877 __PACKAGE__-
>register_method({
1878 name
=> 'vm_shutdown',
1879 path
=> '{vmid}/status/shutdown',
1883 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1884 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1886 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1889 additionalProperties
=> 0,
1891 node
=> get_standard_option
('pve-node'),
1892 vmid
=> get_standard_option
('pve-vmid',
1893 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1894 skiplock
=> get_standard_option
('skiplock'),
1896 description
=> "Wait maximal timeout seconds.",
1902 description
=> "Make sure the VM stops.",
1908 description
=> "Do not deactivate storage volumes.",
1921 my $rpcenv = PVE
::RPCEnvironment
::get
();
1923 my $authuser = $rpcenv->get_user();
1925 my $node = extract_param
($param, 'node');
1927 my $vmid = extract_param
($param, 'vmid');
1929 my $skiplock = extract_param
($param, 'skiplock');
1930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1931 if $skiplock && $authuser ne 'root@pam';
1933 my $keepActive = extract_param
($param, 'keepActive');
1934 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1935 if $keepActive && $authuser ne 'root@pam';
1937 my $storecfg = PVE
::Storage
::config
();
1941 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1942 # otherwise, we will infer a shutdown command, but run into the timeout,
1943 # then when the vm is resumed, it will instantly shutdown
1945 # checking the qmp status here to get feedback to the gui/cli/api
1946 # and the status query should not take too long
1949 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1953 if (!$err && $qmpstatus->{status
} eq "paused") {
1954 if ($param->{forceStop
}) {
1955 warn "VM is paused - stop instead of shutdown\n";
1958 die "VM is paused - cannot shutdown\n";
1962 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
1963 ($rpcenv->{type
} ne 'ha')) {
1968 my $service = "vm:$vmid";
1970 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1972 print "Executing HA stop for VM $vmid\n";
1974 PVE
::Tools
::run_command
($cmd);
1979 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1986 syslog
('info', "shutdown VM $vmid: $upid\n");
1988 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1989 $shutdown, $param->{forceStop
}, $keepActive);
1994 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1998 __PACKAGE__-
>register_method({
1999 name
=> 'vm_suspend',
2000 path
=> '{vmid}/status/suspend',
2004 description
=> "Suspend virtual machine.",
2006 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2009 additionalProperties
=> 0,
2011 node
=> get_standard_option
('pve-node'),
2012 vmid
=> get_standard_option
('pve-vmid',
2013 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2014 skiplock
=> get_standard_option
('skiplock'),
2023 my $rpcenv = PVE
::RPCEnvironment
::get
();
2025 my $authuser = $rpcenv->get_user();
2027 my $node = extract_param
($param, 'node');
2029 my $vmid = extract_param
($param, 'vmid');
2031 my $skiplock = extract_param
($param, 'skiplock');
2032 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2033 if $skiplock && $authuser ne 'root@pam';
2035 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2040 syslog
('info', "suspend VM $vmid: $upid\n");
2042 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2047 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2050 __PACKAGE__-
>register_method({
2051 name
=> 'vm_resume',
2052 path
=> '{vmid}/status/resume',
2056 description
=> "Resume virtual machine.",
2058 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2061 additionalProperties
=> 0,
2063 node
=> get_standard_option
('pve-node'),
2064 vmid
=> get_standard_option
('pve-vmid',
2065 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2066 skiplock
=> get_standard_option
('skiplock'),
2067 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2077 my $rpcenv = PVE
::RPCEnvironment
::get
();
2079 my $authuser = $rpcenv->get_user();
2081 my $node = extract_param
($param, 'node');
2083 my $vmid = extract_param
($param, 'vmid');
2085 my $skiplock = extract_param
($param, 'skiplock');
2086 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2087 if $skiplock && $authuser ne 'root@pam';
2089 my $nocheck = extract_param
($param, 'nocheck');
2091 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2096 syslog
('info', "resume VM $vmid: $upid\n");
2098 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2103 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2106 __PACKAGE__-
>register_method({
2107 name
=> 'vm_sendkey',
2108 path
=> '{vmid}/sendkey',
2112 description
=> "Send key event to virtual machine.",
2114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2117 additionalProperties
=> 0,
2119 node
=> get_standard_option
('pve-node'),
2120 vmid
=> get_standard_option
('pve-vmid',
2121 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2122 skiplock
=> get_standard_option
('skiplock'),
2124 description
=> "The key (qemu monitor encoding).",
2129 returns
=> { type
=> 'null'},
2133 my $rpcenv = PVE
::RPCEnvironment
::get
();
2135 my $authuser = $rpcenv->get_user();
2137 my $node = extract_param
($param, 'node');
2139 my $vmid = extract_param
($param, 'vmid');
2141 my $skiplock = extract_param
($param, 'skiplock');
2142 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2143 if $skiplock && $authuser ne 'root@pam';
2145 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2150 __PACKAGE__-
>register_method({
2151 name
=> 'vm_feature',
2152 path
=> '{vmid}/feature',
2156 description
=> "Check if feature for virtual machine is available.",
2158 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2161 additionalProperties
=> 0,
2163 node
=> get_standard_option
('pve-node'),
2164 vmid
=> get_standard_option
('pve-vmid'),
2166 description
=> "Feature to check.",
2168 enum
=> [ 'snapshot', 'clone', 'copy' ],
2170 snapname
=> get_standard_option
('pve-snapshot-name', {
2178 hasFeature
=> { type
=> 'boolean' },
2181 items
=> { type
=> 'string' },
2188 my $node = extract_param
($param, 'node');
2190 my $vmid = extract_param
($param, 'vmid');
2192 my $snapname = extract_param
($param, 'snapname');
2194 my $feature = extract_param
($param, 'feature');
2196 my $running = PVE
::QemuServer
::check_running
($vmid);
2198 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2201 my $snap = $conf->{snapshots
}->{$snapname};
2202 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2205 my $storecfg = PVE
::Storage
::config
();
2207 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2208 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2211 hasFeature
=> $hasFeature,
2212 nodes
=> [ keys %$nodelist ],
2216 __PACKAGE__-
>register_method({
2218 path
=> '{vmid}/clone',
2222 description
=> "Create a copy of virtual machine/template.",
2224 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2225 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2226 "'Datastore.AllocateSpace' on any used storage.",
2229 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2231 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2232 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2237 additionalProperties
=> 0,
2239 node
=> get_standard_option
('pve-node'),
2240 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2241 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2244 type
=> 'string', format
=> 'dns-name',
2245 description
=> "Set a name for the new VM.",
2250 description
=> "Description for the new VM.",
2254 type
=> 'string', format
=> 'pve-poolid',
2255 description
=> "Add the new VM to the specified pool.",
2257 snapname
=> get_standard_option
('pve-snapshot-name', {
2260 storage
=> get_standard_option
('pve-storage-id', {
2261 description
=> "Target storage for full clone.",
2266 description
=> "Target format for file storage.",
2270 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2275 description
=> "Create a full copy of all disk. This is always done when " .
2276 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2279 target
=> get_standard_option
('pve-node', {
2280 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2291 my $rpcenv = PVE
::RPCEnvironment
::get
();
2293 my $authuser = $rpcenv->get_user();
2295 my $node = extract_param
($param, 'node');
2297 my $vmid = extract_param
($param, 'vmid');
2299 my $newid = extract_param
($param, 'newid');
2301 my $pool = extract_param
($param, 'pool');
2303 if (defined($pool)) {
2304 $rpcenv->check_pool_exist($pool);
2307 my $snapname = extract_param
($param, 'snapname');
2309 my $storage = extract_param
($param, 'storage');
2311 my $format = extract_param
($param, 'format');
2313 my $target = extract_param
($param, 'target');
2315 my $localnode = PVE
::INotify
::nodename
();
2317 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2319 PVE
::Cluster
::check_node_exists
($target) if $target;
2321 my $storecfg = PVE
::Storage
::config
();
2324 # check if storage is enabled on local node
2325 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2327 # check if storage is available on target node
2328 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2329 # clone only works if target storage is shared
2330 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2331 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2335 PVE
::Cluster
::check_cfs_quorum
();
2337 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2339 # exclusive lock if VM is running - else shared lock is enough;
2340 my $shared_lock = $running ?
0 : 1;
2344 # do all tests after lock
2345 # we also try to do all tests before we fork the worker
2347 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2349 PVE
::QemuConfig-
>check_lock($conf);
2351 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2353 die "unexpected state change\n" if $verify_running != $running;
2355 die "snapshot '$snapname' does not exist\n"
2356 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2358 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2360 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2362 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2364 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2366 die "unable to create VM $newid: config file already exists\n"
2369 my $newconf = { lock => 'clone' };
2374 foreach my $opt (keys %$oldconf) {
2375 my $value = $oldconf->{$opt};
2377 # do not copy snapshot related info
2378 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2379 $opt eq 'vmstate' || $opt eq 'snapstate';
2381 # no need to copy unused images, because VMID(owner) changes anyways
2382 next if $opt =~ m/^unused\d+$/;
2384 # always change MAC! address
2385 if ($opt =~ m/^net(\d+)$/) {
2386 my $net = PVE
::QemuServer
::parse_net
($value);
2387 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2388 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2389 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2390 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2391 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2392 die "unable to parse drive options for '$opt'\n" if !$drive;
2393 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2394 $newconf->{$opt} = $value; # simply copy configuration
2396 if ($param->{full
}) {
2397 die "Full clone feature is not available"
2398 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2399 $fullclone->{$opt} = 1;
2401 # not full means clone instead of copy
2402 die "Linked clone feature is not available"
2403 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2405 $drives->{$opt} = $drive;
2406 push @$vollist, $drive->{file
};
2409 # copy everything else
2410 $newconf->{$opt} = $value;
2414 # auto generate a new uuid
2415 my ($uuid, $uuid_str);
2416 UUID
::generate
($uuid);
2417 UUID
::unparse
($uuid, $uuid_str);
2418 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2419 $smbios1->{uuid
} = $uuid_str;
2420 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2422 delete $newconf->{template
};
2424 if ($param->{name
}) {
2425 $newconf->{name
} = $param->{name
};
2427 if ($oldconf->{name
}) {
2428 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2430 $newconf->{name
} = "Copy-of-VM-$vmid";
2434 if ($param->{description
}) {
2435 $newconf->{description
} = $param->{description
};
2438 # create empty/temp config - this fails if VM already exists on other node
2439 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2444 my $newvollist = [];
2447 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2449 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2451 foreach my $opt (keys %$drives) {
2452 my $drive = $drives->{$opt};
2454 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2455 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2457 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2459 PVE
::QemuConfig-
>write_config($newid, $newconf);
2462 delete $newconf->{lock};
2463 PVE
::QemuConfig-
>write_config($newid, $newconf);
2466 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2467 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2468 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2470 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2471 die "Failed to move config to node '$target' - rename failed: $!\n"
2472 if !rename($conffile, $newconffile);
2475 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2480 sleep 1; # some storage like rbd need to wait before release volume - really?
2482 foreach my $volid (@$newvollist) {
2483 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2486 die "clone failed: $err";
2492 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2494 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2497 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2498 # Aquire exclusive lock lock for $newid
2499 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2504 __PACKAGE__-
>register_method({
2505 name
=> 'move_vm_disk',
2506 path
=> '{vmid}/move_disk',
2510 description
=> "Move volume to different storage.",
2512 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2513 "and 'Datastore.AllocateSpace' permissions on the storage.",
2516 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2517 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2521 additionalProperties
=> 0,
2523 node
=> get_standard_option
('pve-node'),
2524 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2527 description
=> "The disk you want to move.",
2528 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2530 storage
=> get_standard_option
('pve-storage-id', {
2531 description
=> "Target storage.",
2532 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2536 description
=> "Target Format.",
2537 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2542 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2548 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2556 description
=> "the task ID.",
2561 my $rpcenv = PVE
::RPCEnvironment
::get
();
2563 my $authuser = $rpcenv->get_user();
2565 my $node = extract_param
($param, 'node');
2567 my $vmid = extract_param
($param, 'vmid');
2569 my $digest = extract_param
($param, 'digest');
2571 my $disk = extract_param
($param, 'disk');
2573 my $storeid = extract_param
($param, 'storage');
2575 my $format = extract_param
($param, 'format');
2577 my $storecfg = PVE
::Storage
::config
();
2579 my $updatefn = sub {
2581 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2583 PVE
::QemuConfig-
>check_lock($conf);
2585 die "checksum missmatch (file change by other user?)\n"
2586 if $digest && $digest ne $conf->{digest
};
2588 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2590 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2592 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2594 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2597 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2598 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2602 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2603 (!$format || !$oldfmt || $oldfmt eq $format);
2605 # this only checks snapshots because $disk is passed!
2606 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2607 die "you can't move a disk with snapshots and delete the source\n"
2608 if $snapshotted && $param->{delete};
2610 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2612 my $running = PVE
::QemuServer
::check_running
($vmid);
2614 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2618 my $newvollist = [];
2621 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2623 warn "moving disk with snapshots, snapshots will not be moved!\n"
2626 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2627 $vmid, $storeid, $format, 1, $newvollist);
2629 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2631 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2633 PVE
::QemuConfig-
>write_config($vmid, $conf);
2636 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2637 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2644 foreach my $volid (@$newvollist) {
2645 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2648 die "storage migration failed: $err";
2651 if ($param->{delete}) {
2653 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2654 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2660 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2663 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2666 __PACKAGE__-
>register_method({
2667 name
=> 'migrate_vm',
2668 path
=> '{vmid}/migrate',
2672 description
=> "Migrate virtual machine. Creates a new migration task.",
2674 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2677 additionalProperties
=> 0,
2679 node
=> get_standard_option
('pve-node'),
2680 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2681 target
=> get_standard_option
('pve-node', {
2682 description
=> "Target node.",
2683 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2687 description
=> "Use online/live migration.",
2692 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2697 enum
=> ['secure', 'insecure'],
2698 description
=> "Migration traffic is encrypted using an SSH " .
2699 "tunnel by default. On secure, completely private networks " .
2700 "this can be disabled to increase performance.",
2703 migration_network
=> {
2706 description
=> "CIDR of the (sub) network that is used for migration.",
2713 description
=> "the task ID.",
2718 my $rpcenv = PVE
::RPCEnvironment
::get
();
2720 my $authuser = $rpcenv->get_user();
2722 my $target = extract_param
($param, 'target');
2724 my $localnode = PVE
::INotify
::nodename
();
2725 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2727 PVE
::Cluster
::check_cfs_quorum
();
2729 PVE
::Cluster
::check_node_exists
($target);
2731 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2733 my $vmid = extract_param
($param, 'vmid');
2735 raise_param_exc
({ force
=> "Only root may use this option." })
2736 if $param->{force
} && $authuser ne 'root@pam';
2738 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2739 if $param->{migration_type
} && $authuser ne 'root@pam';
2741 # allow root only until better network permissions are available
2742 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2743 if $param->{migration_network
} && $authuser ne 'root@pam';
2746 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2748 # try to detect errors early
2750 PVE
::QemuConfig-
>check_lock($conf);
2752 if (PVE
::QemuServer
::check_running
($vmid)) {
2753 die "cant migrate running VM without --online\n"
2754 if !$param->{online
};
2757 my $storecfg = PVE
::Storage
::config
();
2758 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2760 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2765 my $service = "vm:$vmid";
2767 my $cmd = ['ha-manager', 'migrate', $service, $target];
2769 print "Executing HA migrate for VM $vmid to node $target\n";
2771 PVE
::Tools
::run_command
($cmd);
2776 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2783 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2786 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2791 __PACKAGE__-
>register_method({
2793 path
=> '{vmid}/monitor',
2797 description
=> "Execute Qemu monitor commands.",
2799 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2800 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2803 additionalProperties
=> 0,
2805 node
=> get_standard_option
('pve-node'),
2806 vmid
=> get_standard_option
('pve-vmid'),
2809 description
=> "The monitor command.",
2813 returns
=> { type
=> 'string'},
2817 my $rpcenv = PVE
::RPCEnvironment
::get
();
2818 my $authuser = $rpcenv->get_user();
2821 my $command = shift;
2822 return $command =~ m/^\s*info(\s+|$)/
2823 || $command =~ m/^\s*help\s*$/;
2826 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2827 if !&$is_ro($param->{command
});
2829 my $vmid = $param->{vmid
};
2831 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2835 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2837 $res = "ERROR: $@" if $@;
2842 __PACKAGE__-
>register_method({
2843 name
=> 'resize_vm',
2844 path
=> '{vmid}/resize',
2848 description
=> "Extend volume size.",
2850 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2853 additionalProperties
=> 0,
2855 node
=> get_standard_option
('pve-node'),
2856 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2857 skiplock
=> get_standard_option
('skiplock'),
2860 description
=> "The disk you want to resize.",
2861 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2865 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2866 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.",
2870 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2876 returns
=> { type
=> 'null'},
2880 my $rpcenv = PVE
::RPCEnvironment
::get
();
2882 my $authuser = $rpcenv->get_user();
2884 my $node = extract_param
($param, 'node');
2886 my $vmid = extract_param
($param, 'vmid');
2888 my $digest = extract_param
($param, 'digest');
2890 my $disk = extract_param
($param, 'disk');
2892 my $sizestr = extract_param
($param, 'size');
2894 my $skiplock = extract_param
($param, 'skiplock');
2895 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2896 if $skiplock && $authuser ne 'root@pam';
2898 my $storecfg = PVE
::Storage
::config
();
2900 my $updatefn = sub {
2902 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2904 die "checksum missmatch (file change by other user?)\n"
2905 if $digest && $digest ne $conf->{digest
};
2906 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2908 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2910 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2912 my (undef, undef, undef, undef, undef, undef, $format) =
2913 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2915 die "can't resize volume: $disk if snapshot exists\n"
2916 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2918 my $volid = $drive->{file
};
2920 die "disk '$disk' has no associated volume\n" if !$volid;
2922 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2924 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2926 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2928 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2929 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2931 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2932 my ($ext, $newsize, $unit) = ($1, $2, $4);
2935 $newsize = $newsize * 1024;
2936 } elsif ($unit eq 'M') {
2937 $newsize = $newsize * 1024 * 1024;
2938 } elsif ($unit eq 'G') {
2939 $newsize = $newsize * 1024 * 1024 * 1024;
2940 } elsif ($unit eq 'T') {
2941 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2944 $newsize += $size if $ext;
2945 $newsize = int($newsize);
2947 die "unable to skrink disk size\n" if $newsize < $size;
2949 return if $size == $newsize;
2951 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2953 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2955 $drive->{size
} = $newsize;
2956 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2958 PVE
::QemuConfig-
>write_config($vmid, $conf);
2961 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2965 __PACKAGE__-
>register_method({
2966 name
=> 'snapshot_list',
2967 path
=> '{vmid}/snapshot',
2969 description
=> "List all snapshots.",
2971 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2974 protected
=> 1, # qemu pid files are only readable by root
2976 additionalProperties
=> 0,
2978 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2979 node
=> get_standard_option
('pve-node'),
2988 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2993 my $vmid = $param->{vmid
};
2995 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2996 my $snaphash = $conf->{snapshots
} || {};
3000 foreach my $name (keys %$snaphash) {
3001 my $d = $snaphash->{$name};
3004 snaptime
=> $d->{snaptime
} || 0,
3005 vmstate
=> $d->{vmstate
} ?
1 : 0,
3006 description
=> $d->{description
} || '',
3008 $item->{parent
} = $d->{parent
} if $d->{parent
};
3009 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3013 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3014 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3015 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3017 push @$res, $current;
3022 __PACKAGE__-
>register_method({
3024 path
=> '{vmid}/snapshot',
3028 description
=> "Snapshot a VM.",
3030 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3033 additionalProperties
=> 0,
3035 node
=> get_standard_option
('pve-node'),
3036 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3037 snapname
=> get_standard_option
('pve-snapshot-name'),
3041 description
=> "Save the vmstate",
3046 description
=> "A textual description or comment.",
3052 description
=> "the task ID.",
3057 my $rpcenv = PVE
::RPCEnvironment
::get
();
3059 my $authuser = $rpcenv->get_user();
3061 my $node = extract_param
($param, 'node');
3063 my $vmid = extract_param
($param, 'vmid');
3065 my $snapname = extract_param
($param, 'snapname');
3067 die "unable to use snapshot name 'current' (reserved name)\n"
3068 if $snapname eq 'current';
3071 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3072 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3073 $param->{description
});
3076 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3079 __PACKAGE__-
>register_method({
3080 name
=> 'snapshot_cmd_idx',
3081 path
=> '{vmid}/snapshot/{snapname}',
3088 additionalProperties
=> 0,
3090 vmid
=> get_standard_option
('pve-vmid'),
3091 node
=> get_standard_option
('pve-node'),
3092 snapname
=> get_standard_option
('pve-snapshot-name'),
3101 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3108 push @$res, { cmd
=> 'rollback' };
3109 push @$res, { cmd
=> 'config' };
3114 __PACKAGE__-
>register_method({
3115 name
=> 'update_snapshot_config',
3116 path
=> '{vmid}/snapshot/{snapname}/config',
3120 description
=> "Update snapshot metadata.",
3122 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3125 additionalProperties
=> 0,
3127 node
=> get_standard_option
('pve-node'),
3128 vmid
=> get_standard_option
('pve-vmid'),
3129 snapname
=> get_standard_option
('pve-snapshot-name'),
3133 description
=> "A textual description or comment.",
3137 returns
=> { type
=> 'null' },
3141 my $rpcenv = PVE
::RPCEnvironment
::get
();
3143 my $authuser = $rpcenv->get_user();
3145 my $vmid = extract_param
($param, 'vmid');
3147 my $snapname = extract_param
($param, 'snapname');
3149 return undef if !defined($param->{description
});
3151 my $updatefn = sub {
3153 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3155 PVE
::QemuConfig-
>check_lock($conf);
3157 my $snap = $conf->{snapshots
}->{$snapname};
3159 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3161 $snap->{description
} = $param->{description
} if defined($param->{description
});
3163 PVE
::QemuConfig-
>write_config($vmid, $conf);
3166 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3171 __PACKAGE__-
>register_method({
3172 name
=> 'get_snapshot_config',
3173 path
=> '{vmid}/snapshot/{snapname}/config',
3176 description
=> "Get snapshot configuration",
3178 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3181 additionalProperties
=> 0,
3183 node
=> get_standard_option
('pve-node'),
3184 vmid
=> get_standard_option
('pve-vmid'),
3185 snapname
=> get_standard_option
('pve-snapshot-name'),
3188 returns
=> { type
=> "object" },
3192 my $rpcenv = PVE
::RPCEnvironment
::get
();
3194 my $authuser = $rpcenv->get_user();
3196 my $vmid = extract_param
($param, 'vmid');
3198 my $snapname = extract_param
($param, 'snapname');
3200 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3202 my $snap = $conf->{snapshots
}->{$snapname};
3204 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3209 __PACKAGE__-
>register_method({
3211 path
=> '{vmid}/snapshot/{snapname}/rollback',
3215 description
=> "Rollback VM state to specified snapshot.",
3217 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3220 additionalProperties
=> 0,
3222 node
=> get_standard_option
('pve-node'),
3223 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3224 snapname
=> get_standard_option
('pve-snapshot-name'),
3229 description
=> "the task ID.",
3234 my $rpcenv = PVE
::RPCEnvironment
::get
();
3236 my $authuser = $rpcenv->get_user();
3238 my $node = extract_param
($param, 'node');
3240 my $vmid = extract_param
($param, 'vmid');
3242 my $snapname = extract_param
($param, 'snapname');
3245 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3246 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3249 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3252 __PACKAGE__-
>register_method({
3253 name
=> 'delsnapshot',
3254 path
=> '{vmid}/snapshot/{snapname}',
3258 description
=> "Delete a VM snapshot.",
3260 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3263 additionalProperties
=> 0,
3265 node
=> get_standard_option
('pve-node'),
3266 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3267 snapname
=> get_standard_option
('pve-snapshot-name'),
3271 description
=> "For removal from config file, even if removing disk snapshots fails.",
3277 description
=> "the task ID.",
3282 my $rpcenv = PVE
::RPCEnvironment
::get
();
3284 my $authuser = $rpcenv->get_user();
3286 my $node = extract_param
($param, 'node');
3288 my $vmid = extract_param
($param, 'vmid');
3290 my $snapname = extract_param
($param, 'snapname');
3293 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3294 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3297 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3300 __PACKAGE__-
>register_method({
3302 path
=> '{vmid}/template',
3306 description
=> "Create a Template.",
3308 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3309 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3312 additionalProperties
=> 0,
3314 node
=> get_standard_option
('pve-node'),
3315 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3319 description
=> "If you want to convert only 1 disk to base image.",
3320 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3325 returns
=> { type
=> 'null'},
3329 my $rpcenv = PVE
::RPCEnvironment
::get
();
3331 my $authuser = $rpcenv->get_user();
3333 my $node = extract_param
($param, 'node');
3335 my $vmid = extract_param
($param, 'vmid');
3337 my $disk = extract_param
($param, 'disk');
3339 my $updatefn = sub {
3341 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3343 PVE
::QemuConfig-
>check_lock($conf);
3345 die "unable to create template, because VM contains snapshots\n"
3346 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3348 die "you can't convert a template to a template\n"
3349 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3351 die "you can't convert a VM to template if VM is running\n"
3352 if PVE
::QemuServer
::check_running
($vmid);
3355 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3358 $conf->{template
} = 1;
3359 PVE
::QemuConfig-
>write_config($vmid, $conf);
3361 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3364 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);