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 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2802 additionalProperties
=> 0,
2804 node
=> get_standard_option
('pve-node'),
2805 vmid
=> get_standard_option
('pve-vmid'),
2808 description
=> "The monitor command.",
2812 returns
=> { type
=> 'string'},
2816 my $vmid = $param->{vmid
};
2818 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2822 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2824 $res = "ERROR: $@" if $@;
2829 __PACKAGE__-
>register_method({
2830 name
=> 'resize_vm',
2831 path
=> '{vmid}/resize',
2835 description
=> "Extend volume size.",
2837 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2840 additionalProperties
=> 0,
2842 node
=> get_standard_option
('pve-node'),
2843 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2844 skiplock
=> get_standard_option
('skiplock'),
2847 description
=> "The disk you want to resize.",
2848 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2852 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2853 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.",
2857 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2863 returns
=> { type
=> 'null'},
2867 my $rpcenv = PVE
::RPCEnvironment
::get
();
2869 my $authuser = $rpcenv->get_user();
2871 my $node = extract_param
($param, 'node');
2873 my $vmid = extract_param
($param, 'vmid');
2875 my $digest = extract_param
($param, 'digest');
2877 my $disk = extract_param
($param, 'disk');
2879 my $sizestr = extract_param
($param, 'size');
2881 my $skiplock = extract_param
($param, 'skiplock');
2882 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2883 if $skiplock && $authuser ne 'root@pam';
2885 my $storecfg = PVE
::Storage
::config
();
2887 my $updatefn = sub {
2889 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2891 die "checksum missmatch (file change by other user?)\n"
2892 if $digest && $digest ne $conf->{digest
};
2893 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2895 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2897 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2899 my (undef, undef, undef, undef, undef, undef, $format) =
2900 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2902 die "can't resize volume: $disk if snapshot exists\n"
2903 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2905 my $volid = $drive->{file
};
2907 die "disk '$disk' has no associated volume\n" if !$volid;
2909 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2911 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2913 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2915 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2916 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2918 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2919 my ($ext, $newsize, $unit) = ($1, $2, $4);
2922 $newsize = $newsize * 1024;
2923 } elsif ($unit eq 'M') {
2924 $newsize = $newsize * 1024 * 1024;
2925 } elsif ($unit eq 'G') {
2926 $newsize = $newsize * 1024 * 1024 * 1024;
2927 } elsif ($unit eq 'T') {
2928 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2931 $newsize += $size if $ext;
2932 $newsize = int($newsize);
2934 die "unable to skrink disk size\n" if $newsize < $size;
2936 return if $size == $newsize;
2938 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2940 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2942 $drive->{size
} = $newsize;
2943 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2945 PVE
::QemuConfig-
>write_config($vmid, $conf);
2948 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2952 __PACKAGE__-
>register_method({
2953 name
=> 'snapshot_list',
2954 path
=> '{vmid}/snapshot',
2956 description
=> "List all snapshots.",
2958 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2961 protected
=> 1, # qemu pid files are only readable by root
2963 additionalProperties
=> 0,
2965 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2966 node
=> get_standard_option
('pve-node'),
2975 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2980 my $vmid = $param->{vmid
};
2982 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2983 my $snaphash = $conf->{snapshots
} || {};
2987 foreach my $name (keys %$snaphash) {
2988 my $d = $snaphash->{$name};
2991 snaptime
=> $d->{snaptime
} || 0,
2992 vmstate
=> $d->{vmstate
} ?
1 : 0,
2993 description
=> $d->{description
} || '',
2995 $item->{parent
} = $d->{parent
} if $d->{parent
};
2996 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3000 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3001 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3002 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3004 push @$res, $current;
3009 __PACKAGE__-
>register_method({
3011 path
=> '{vmid}/snapshot',
3015 description
=> "Snapshot a VM.",
3017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3020 additionalProperties
=> 0,
3022 node
=> get_standard_option
('pve-node'),
3023 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3024 snapname
=> get_standard_option
('pve-snapshot-name'),
3028 description
=> "Save the vmstate",
3033 description
=> "A textual description or comment.",
3039 description
=> "the task ID.",
3044 my $rpcenv = PVE
::RPCEnvironment
::get
();
3046 my $authuser = $rpcenv->get_user();
3048 my $node = extract_param
($param, 'node');
3050 my $vmid = extract_param
($param, 'vmid');
3052 my $snapname = extract_param
($param, 'snapname');
3054 die "unable to use snapshot name 'current' (reserved name)\n"
3055 if $snapname eq 'current';
3058 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3059 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3060 $param->{description
});
3063 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3066 __PACKAGE__-
>register_method({
3067 name
=> 'snapshot_cmd_idx',
3068 path
=> '{vmid}/snapshot/{snapname}',
3075 additionalProperties
=> 0,
3077 vmid
=> get_standard_option
('pve-vmid'),
3078 node
=> get_standard_option
('pve-node'),
3079 snapname
=> get_standard_option
('pve-snapshot-name'),
3088 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3095 push @$res, { cmd
=> 'rollback' };
3096 push @$res, { cmd
=> 'config' };
3101 __PACKAGE__-
>register_method({
3102 name
=> 'update_snapshot_config',
3103 path
=> '{vmid}/snapshot/{snapname}/config',
3107 description
=> "Update snapshot metadata.",
3109 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3112 additionalProperties
=> 0,
3114 node
=> get_standard_option
('pve-node'),
3115 vmid
=> get_standard_option
('pve-vmid'),
3116 snapname
=> get_standard_option
('pve-snapshot-name'),
3120 description
=> "A textual description or comment.",
3124 returns
=> { type
=> 'null' },
3128 my $rpcenv = PVE
::RPCEnvironment
::get
();
3130 my $authuser = $rpcenv->get_user();
3132 my $vmid = extract_param
($param, 'vmid');
3134 my $snapname = extract_param
($param, 'snapname');
3136 return undef if !defined($param->{description
});
3138 my $updatefn = sub {
3140 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3142 PVE
::QemuConfig-
>check_lock($conf);
3144 my $snap = $conf->{snapshots
}->{$snapname};
3146 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3148 $snap->{description
} = $param->{description
} if defined($param->{description
});
3150 PVE
::QemuConfig-
>write_config($vmid, $conf);
3153 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3158 __PACKAGE__-
>register_method({
3159 name
=> 'get_snapshot_config',
3160 path
=> '{vmid}/snapshot/{snapname}/config',
3163 description
=> "Get snapshot configuration",
3165 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3168 additionalProperties
=> 0,
3170 node
=> get_standard_option
('pve-node'),
3171 vmid
=> get_standard_option
('pve-vmid'),
3172 snapname
=> get_standard_option
('pve-snapshot-name'),
3175 returns
=> { type
=> "object" },
3179 my $rpcenv = PVE
::RPCEnvironment
::get
();
3181 my $authuser = $rpcenv->get_user();
3183 my $vmid = extract_param
($param, 'vmid');
3185 my $snapname = extract_param
($param, 'snapname');
3187 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3189 my $snap = $conf->{snapshots
}->{$snapname};
3191 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3196 __PACKAGE__-
>register_method({
3198 path
=> '{vmid}/snapshot/{snapname}/rollback',
3202 description
=> "Rollback VM state to specified snapshot.",
3204 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3207 additionalProperties
=> 0,
3209 node
=> get_standard_option
('pve-node'),
3210 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3211 snapname
=> get_standard_option
('pve-snapshot-name'),
3216 description
=> "the task ID.",
3221 my $rpcenv = PVE
::RPCEnvironment
::get
();
3223 my $authuser = $rpcenv->get_user();
3225 my $node = extract_param
($param, 'node');
3227 my $vmid = extract_param
($param, 'vmid');
3229 my $snapname = extract_param
($param, 'snapname');
3232 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3233 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3236 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3239 __PACKAGE__-
>register_method({
3240 name
=> 'delsnapshot',
3241 path
=> '{vmid}/snapshot/{snapname}',
3245 description
=> "Delete a VM snapshot.",
3247 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3250 additionalProperties
=> 0,
3252 node
=> get_standard_option
('pve-node'),
3253 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3254 snapname
=> get_standard_option
('pve-snapshot-name'),
3258 description
=> "For removal from config file, even if removing disk snapshots fails.",
3264 description
=> "the task ID.",
3269 my $rpcenv = PVE
::RPCEnvironment
::get
();
3271 my $authuser = $rpcenv->get_user();
3273 my $node = extract_param
($param, 'node');
3275 my $vmid = extract_param
($param, 'vmid');
3277 my $snapname = extract_param
($param, 'snapname');
3280 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3281 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3284 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3287 __PACKAGE__-
>register_method({
3289 path
=> '{vmid}/template',
3293 description
=> "Create a Template.",
3295 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3296 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3299 additionalProperties
=> 0,
3301 node
=> get_standard_option
('pve-node'),
3302 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3306 description
=> "If you want to convert only 1 disk to base image.",
3307 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3312 returns
=> { type
=> 'null'},
3316 my $rpcenv = PVE
::RPCEnvironment
::get
();
3318 my $authuser = $rpcenv->get_user();
3320 my $node = extract_param
($param, 'node');
3322 my $vmid = extract_param
($param, 'vmid');
3324 my $disk = extract_param
($param, 'disk');
3326 my $updatefn = sub {
3328 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3330 PVE
::QemuConfig-
>check_lock($conf);
3332 die "unable to create template, because VM contains snapshots\n"
3333 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3335 die "you can't convert a template to a template\n"
3336 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3338 die "you can't convert a VM to template if VM is running\n"
3339 if PVE
::QemuServer
::check_running
($vmid);
3342 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3345 $conf->{template
} = 1;
3346 PVE
::QemuConfig-
>write_config($vmid, $conf);
3348 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3351 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);