1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
26 use PVE
::RPCEnvironment
;
27 use PVE
::AccessControl
;
31 use PVE
::API2
::Firewall
::VM
;
32 use PVE
::API2
::Qemu
::Agent
;
33 use PVE
::VZDump
::Plugin
;
34 use PVE
::DataCenterConfig
;
38 if (!$ENV{PVE_GENERATING_DOCS
}) {
39 require PVE
::HA
::Env
::PVE2
;
40 import PVE
::HA
::Env
::PVE2
;
41 require PVE
::HA
::Config
;
42 import PVE
::HA
::Config
;
46 use Data
::Dumper
; # fixme: remove
48 use base
qw(PVE::RESTHandler);
50 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.";
52 my $resolve_cdrom_alias = sub {
55 if (my $value = $param->{cdrom
}) {
56 $value .= ",media=cdrom" if $value !~ m/media=/;
57 $param->{ide2
} = $value;
58 delete $param->{cdrom
};
62 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
63 my $check_storage_access = sub {
64 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
66 PVE
::QemuServer
::foreach_drive
($settings, sub {
67 my ($ds, $drive) = @_;
69 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
71 my $volid = $drive->{file
};
72 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
74 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
76 } elsif ($isCDROM && ($volid eq 'cdrom')) {
77 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
83 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
84 if !$scfg->{content
}->{images
};
86 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
90 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
91 if defined($settings->{vmstatestorage
});
94 my $check_storage_access_clone = sub {
95 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
99 PVE
::QemuServer
::foreach_drive
($conf, sub {
100 my ($ds, $drive) = @_;
102 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
104 my $volid = $drive->{file
};
106 return if !$volid || $volid eq 'none';
109 if ($volid eq 'cdrom') {
110 $rpcenv->check($authuser, "/", ['Sys.Console']);
112 # we simply allow access
113 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
115 $sharedvm = 0 if !$scfg->{shared
};
119 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
121 $sharedvm = 0 if !$scfg->{shared
};
123 $sid = $storage if $storage;
124 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
128 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
129 if defined($conf->{vmstatestorage
});
134 # Note: $pool is only needed when creating a VM, because pool permissions
135 # are automatically inherited if VM already exists inside a pool.
136 my $create_disks = sub {
137 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
144 my ($ds, $disk) = @_;
146 my $volid = $disk->{file
};
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
149 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
150 delete $disk->{size
};
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
152 } elsif (defined($volname) && $volname eq 'cloudinit') {
153 $storeid = $storeid // $default_storage;
154 die "no storage ID specified (and no default storage)\n" if !$storeid;
155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
156 my $name = "vm-$vmid-cloudinit";
160 $fmt = $disk->{format
} // "qcow2";
163 $fmt = $disk->{format
} // "raw";
166 # Initial disk created with 4 MB and aligned to 4MB on regeneration
167 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
168 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
169 $disk->{file
} = $volid;
170 $disk->{media
} = 'cdrom';
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
174 } elsif ($volid =~ $NEW_DISK_RE) {
175 my ($storeid, $size) = ($2 || $default_storage, $3);
176 die "no storage ID specified (and no default storage)\n" if !$storeid;
177 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
178 my $fmt = $disk->{format
} || $defformat;
180 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
183 if ($ds eq 'efidisk0') {
184 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
188 push @$vollist, $volid;
189 $disk->{file
} = $volid;
190 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
191 delete $disk->{format
}; # no longer needed
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
195 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
197 my $volid_is_new = 1;
200 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
201 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
206 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
208 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
210 die "volume $volid does not exist\n" if !$size;
212 $disk->{size
} = $size;
215 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
219 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
221 # free allocated images on error
223 syslog
('err', "VM $vmid creating disks failed");
224 foreach my $volid (@$vollist) {
225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
231 # modify vm config if everything went well
232 foreach my $ds (keys %$res) {
233 $conf->{$ds} = $res->{$ds};
250 my $memoryoptions = {
256 my $hwtypeoptions = {
269 my $generaloptions = {
276 'migrate_downtime' => 1,
277 'migrate_speed' => 1,
290 my $vmpoweroptions = {
297 'vmstatestorage' => 1,
300 my $cloudinitoptions = {
310 my $check_vm_modify_config_perm = sub {
311 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
313 return 1 if $authuser eq 'root@pam';
315 foreach my $opt (@$key_list) {
316 # some checks (e.g., disk, serial port, usb) need to be done somewhere
317 # else, as there the permission can be value dependend
318 next if PVE
::QemuServer
::is_valid_drivename
($opt);
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 } elsif ($opt eq 'vmstate') {
342 # the user needs Disk and PowerMgmt privileges to change the vmstate
343 # also needs privileges on the storage, that will be checked later
344 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
346 # catches hostpci\d+, args, lock, etc.
347 # new options will be checked here
348 die "only root can set '$opt' config\n";
355 __PACKAGE__-
>register_method({
359 description
=> "Virtual machine index (per node).",
361 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
365 protected
=> 1, # qemu pid files are only readable by root
367 additionalProperties
=> 0,
369 node
=> get_standard_option
('pve-node'),
373 description
=> "Determine the full status of active VMs.",
381 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
383 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
388 my $rpcenv = PVE
::RPCEnvironment
::get
();
389 my $authuser = $rpcenv->get_user();
391 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
394 foreach my $vmid (keys %$vmstatus) {
395 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
397 my $data = $vmstatus->{$vmid};
404 my $parse_restore_archive = sub {
405 my ($storecfg, $archive) = @_;
407 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
409 if (defined($archive_storeid)) {
410 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
411 if ($scfg->{type
} eq 'pbs') {
418 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 __PACKAGE__-
>register_method({
430 description
=> "Create or restore a virtual machine.",
432 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
433 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
434 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
435 user
=> 'all', # check inside
440 additionalProperties
=> 0,
441 properties
=> PVE
::QemuServer
::json_config_properties
(
443 node
=> get_standard_option
('pve-node'),
444 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
446 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
450 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
452 storage
=> get_standard_option
('pve-storage-id', {
453 description
=> "Default storage.",
455 completion
=> \
&PVE
::QemuServer
::complete_storage
,
460 description
=> "Allow to overwrite existing VM.",
461 requires
=> 'archive',
466 description
=> "Assign a unique random ethernet address.",
467 requires
=> 'archive',
471 type
=> 'string', format
=> 'pve-poolid',
472 description
=> "Add the VM to the specified pool.",
475 description
=> "Override I/O bandwidth limit (in KiB/s).",
479 default => 'restore limit from datacenter or storage config',
485 description
=> "Start VM after it was created successfully.",
495 my $rpcenv = PVE
::RPCEnvironment
::get
();
496 my $authuser = $rpcenv->get_user();
498 my $node = extract_param
($param, 'node');
499 my $vmid = extract_param
($param, 'vmid');
501 my $archive = extract_param
($param, 'archive');
502 my $is_restore = !!$archive;
504 my $bwlimit = extract_param
($param, 'bwlimit');
505 my $force = extract_param
($param, 'force');
506 my $pool = extract_param
($param, 'pool');
507 my $start_after_create = extract_param
($param, 'start');
508 my $storage = extract_param
($param, 'storage');
509 my $unique = extract_param
($param, 'unique');
511 if (defined(my $ssh_keys = $param->{sshkeys
})) {
512 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
513 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
516 PVE
::Cluster
::check_cfs_quorum
();
518 my $filename = PVE
::QemuConfig-
>config_file($vmid);
519 my $storecfg = PVE
::Storage
::config
();
521 if (defined($pool)) {
522 $rpcenv->check_pool_exist($pool);
525 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
526 if defined($storage);
528 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
530 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
532 } elsif ($archive && $force && (-f
$filename) &&
533 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
534 # OK: user has VM.Backup permissions, and want to restore an existing VM
540 &$resolve_cdrom_alias($param);
542 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
544 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
546 foreach my $opt (keys %$param) {
547 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
548 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
549 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
551 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
552 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
556 PVE
::QemuServer
::add_random_macs
($param);
558 my $keystr = join(' ', keys %$param);
559 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
561 if ($archive eq '-') {
562 die "pipe requires cli environment\n"
563 if $rpcenv->{type
} ne 'cli';
564 $archive = { type
=> 'pipe' };
566 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
568 $archive = $parse_restore_archive->($storecfg, $archive);
572 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
574 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
575 die "$emsg $@" if $@;
577 my $restorefn = sub {
578 my $conf = PVE
::QemuConfig-
>load_config($vmid);
580 PVE
::QemuConfig-
>check_protection($conf, $emsg);
582 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
585 my $restore_options = {
591 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
592 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
593 } elsif ($archive->{type
} eq 'pbs') {
594 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
596 die "unknown backup archive type\n";
598 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
599 # Convert restored VM to template if backup was VM template
600 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
601 warn "Convert to template.\n";
602 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
606 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
609 # ensure no old replication state are exists
610 PVE
::ReplicationState
::delete_guest_states
($vmid);
612 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
614 if ($start_after_create) {
615 print "Execute autostart\n";
616 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
622 # ensure no old replication state are exists
623 PVE
::ReplicationState
::delete_guest_states
($vmid);
627 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
631 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
633 if (!$conf->{bootdisk
}) {
634 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
635 $conf->{bootdisk
} = $firstdisk if $firstdisk;
638 # auto generate uuid if user did not specify smbios1 option
639 if (!$conf->{smbios1
}) {
640 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
643 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
644 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
647 PVE
::QemuConfig-
>write_config($vmid, $conf);
653 foreach my $volid (@$vollist) {
654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
660 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
663 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
665 if ($start_after_create) {
666 print "Execute autostart\n";
667 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
672 my ($code, $worker_name);
674 $worker_name = 'qmrestore';
676 eval { $restorefn->() };
678 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
684 $worker_name = 'qmcreate';
686 eval { $createfn->() };
689 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
690 unlink($conffile) or die "failed to remove config file: $!\n";
698 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
701 __PACKAGE__-
>register_method({
706 description
=> "Directory index",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
722 subdir
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
731 { subdir
=> 'config' },
732 { subdir
=> 'pending' },
733 { subdir
=> 'status' },
734 { subdir
=> 'unlink' },
735 { subdir
=> 'vncproxy' },
736 { subdir
=> 'termproxy' },
737 { subdir
=> 'migrate' },
738 { subdir
=> 'resize' },
739 { subdir
=> 'move' },
741 { subdir
=> 'rrddata' },
742 { subdir
=> 'monitor' },
743 { subdir
=> 'agent' },
744 { subdir
=> 'snapshot' },
745 { subdir
=> 'spiceproxy' },
746 { subdir
=> 'sendkey' },
747 { subdir
=> 'firewall' },
753 __PACKAGE__-
>register_method ({
754 subclass
=> "PVE::API2::Firewall::VM",
755 path
=> '{vmid}/firewall',
758 __PACKAGE__-
>register_method ({
759 subclass
=> "PVE::API2::Qemu::Agent",
760 path
=> '{vmid}/agent',
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/rrd',
767 protected
=> 1, # fixme: can we avoid that?
769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
771 description
=> "Read VM RRD statistics (returns PNG)",
773 additionalProperties
=> 0,
775 node
=> get_standard_option
('pve-node'),
776 vmid
=> get_standard_option
('pve-vmid'),
778 description
=> "Specify the time frame you are interested in.",
780 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
783 description
=> "The list of datasources you want to display.",
784 type
=> 'string', format
=> 'pve-configid-list',
787 description
=> "The RRD consolidation function",
789 enum
=> [ 'AVERAGE', 'MAX' ],
797 filename
=> { type
=> 'string' },
803 return PVE
::RRD
::create_rrd_graph
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
},
805 $param->{ds
}, $param->{cf
});
809 __PACKAGE__-
>register_method({
811 path
=> '{vmid}/rrddata',
813 protected
=> 1, # fixme: can we avoid that?
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 description
=> "Read VM RRD statistics",
819 additionalProperties
=> 0,
821 node
=> get_standard_option
('pve-node'),
822 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "Specify the time frame you are interested in.",
826 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
829 description
=> "The RRD consolidation function",
831 enum
=> [ 'AVERAGE', 'MAX' ],
846 return PVE
::RRD
::create_rrd_data
(
847 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
856 description
=> "Get the virtual machine configuration with pending configuration " .
857 "changes applied. Set the 'current' parameter to get the current configuration instead.",
859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 additionalProperties
=> 0,
864 node
=> get_standard_option
('pve-node'),
865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
867 description
=> "Get current values (instead of pending values).",
872 snapshot
=> get_standard_option
('pve-snapshot-name', {
873 description
=> "Fetch config values from given snapshot.",
876 my ($cmd, $pname, $cur, $args) = @_;
877 PVE
::QemuConfig-
>snapshot_list($args->[0]);
883 description
=> "The VM configuration.",
885 properties
=> PVE
::QemuServer
::json_config_properties
({
888 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
895 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
896 current
=> "cannot use 'snapshot' parameter with 'current'"})
897 if ($param->{snapshot
} && $param->{current
});
900 if ($param->{snapshot
}) {
901 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
903 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
905 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
910 __PACKAGE__-
>register_method({
911 name
=> 'vm_pending',
912 path
=> '{vmid}/pending',
915 description
=> "Get the virtual machine configuration with both current and pending values.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
932 description
=> "Configuration option name.",
936 description
=> "Current value.",
941 description
=> "Pending value.",
946 description
=> "Indicates a pending delete request if present and not 0. " .
947 "The value 2 indicates a force-delete request.",
959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
961 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
963 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
964 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
966 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1075 return if $volname eq 'cloudinit';
1078 if ($volid =~ $NEW_DISK_RE) {
1080 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1082 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1084 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1085 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1086 return if $scfg->{shared
};
1087 die "cannot add non-replicatable volume to a replicated VM\n";
1090 foreach my $opt (keys %$param) {
1091 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1092 # cleanup drive path
1093 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1094 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1095 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1096 $check_replication->($drive);
1097 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1098 } elsif ($opt =~ m/^net(\d+)$/) {
1100 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1101 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1102 } elsif ($opt eq 'vmgenid') {
1103 if ($param->{$opt} eq '1') {
1104 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 } elsif ($opt eq 'hookscript') {
1107 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1108 raise_param_exc
({ $opt => $@ }) if $@;
1112 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1114 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1116 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1118 my $updatefn = sub {
1120 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1122 die "checksum missmatch (file change by other user?)\n"
1123 if $digest && $digest ne $conf->{digest
};
1125 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1126 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1127 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1128 delete $conf->{lock}; # for check lock check, not written out
1129 push @delete, 'lock'; # this is the real deal to write it out
1131 push @delete, 'runningmachine' if $conf->{runningmachine
};
1134 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1136 foreach my $opt (keys %$revert) {
1137 if (defined($conf->{$opt})) {
1138 $param->{$opt} = $conf->{$opt};
1139 } elsif (defined($conf->{pending
}->{$opt})) {
1144 if ($param->{memory
} || defined($param->{balloon
})) {
1145 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1146 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1148 die "balloon value too large (must be smaller than assigned memory)\n"
1149 if $balloon && $balloon > $maxmem;
1152 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1156 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1158 # write updates to pending section
1160 my $modified = {}; # record what $option we modify
1162 foreach my $opt (@delete) {
1163 $modified->{$opt} = 1;
1164 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1166 # value of what we want to delete, independent if pending or not
1167 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1168 if (!defined($val)) {
1169 warn "cannot delete '$opt' - not set in current configuration!\n";
1170 $modified->{$opt} = 0;
1173 my $is_pending_val = defined($conf->{pending
}->{$opt});
1174 delete $conf->{pending
}->{$opt};
1176 if ($opt =~ m/^unused/) {
1177 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1178 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1180 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1181 delete $conf->{$opt};
1182 PVE
::QemuConfig-
>write_config($vmid, $conf);
1184 } elsif ($opt eq 'vmstate') {
1185 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1186 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1187 delete $conf->{$opt};
1188 PVE
::QemuConfig-
>write_config($vmid, $conf);
1190 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1191 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1192 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1193 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1195 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1196 PVE
::QemuConfig-
>write_config($vmid, $conf);
1197 } elsif ($opt =~ m/^serial\d+$/) {
1198 if ($val eq 'socket') {
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1200 } elsif ($authuser ne 'root@pam') {
1201 die "only root can delete '$opt' config for real devices\n";
1203 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1204 PVE
::QemuConfig-
>write_config($vmid, $conf);
1205 } elsif ($opt =~ m/^usb\d+$/) {
1206 if ($val =~ m/spice/) {
1207 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1208 } elsif ($authuser ne 'root@pam') {
1209 die "only root can delete '$opt' config for real devices\n";
1211 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1212 PVE
::QemuConfig-
>write_config($vmid, $conf);
1214 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf);
1219 foreach my $opt (keys %$param) { # add/change
1220 $modified->{$opt} = 1;
1221 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1222 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1224 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1226 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1227 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1228 # FIXME: cloudinit: CDROM or Disk?
1229 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1230 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1232 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1234 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1235 if defined($conf->{pending
}->{$opt});
1237 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1238 } elsif ($opt =~ m/^serial\d+/) {
1239 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1240 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1241 } elsif ($authuser ne 'root@pam') {
1242 die "only root can modify '$opt' config for real devices\n";
1244 $conf->{pending
}->{$opt} = $param->{$opt};
1245 } elsif ($opt =~ m/^usb\d+/) {
1246 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1247 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1248 } elsif ($authuser ne 'root@pam') {
1249 die "only root can modify '$opt' config for real devices\n";
1251 $conf->{pending
}->{$opt} = $param->{$opt};
1253 $conf->{pending
}->{$opt} = $param->{$opt};
1255 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1256 PVE
::QemuConfig-
>write_config($vmid, $conf);
1259 # remove pending changes when nothing changed
1260 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1261 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1262 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1264 return if !scalar(keys %{$conf->{pending
}});
1266 my $running = PVE
::QemuServer
::check_running
($vmid);
1268 # apply pending changes
1270 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1274 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1276 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1278 raise_param_exc
($errors) if scalar(keys %$errors);
1287 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1289 if ($background_delay) {
1291 # Note: It would be better to do that in the Event based HTTPServer
1292 # to avoid blocking call to sleep.
1294 my $end_time = time() + $background_delay;
1296 my $task = PVE
::Tools
::upid_decode
($upid);
1299 while (time() < $end_time) {
1300 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1302 sleep(1); # this gets interrupted when child process ends
1306 my $status = PVE
::Tools
::upid_read_status
($upid);
1307 return undef if $status eq 'OK';
1316 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1319 my $vm_config_perm_list = [
1324 'VM.Config.Network',
1326 'VM.Config.Options',
1329 __PACKAGE__-
>register_method({
1330 name
=> 'update_vm_async',
1331 path
=> '{vmid}/config',
1335 description
=> "Set virtual machine options (asynchrounous API).",
1337 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1340 additionalProperties
=> 0,
1341 properties
=> PVE
::QemuServer
::json_config_properties
(
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid'),
1345 skiplock
=> get_standard_option
('skiplock'),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of settings you want to delete.",
1352 type
=> 'string', format
=> 'pve-configid-list',
1353 description
=> "Revert a pending change.",
1358 description
=> $opt_force_description,
1360 requires
=> 'delete',
1364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1368 background_delay
=> {
1370 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1381 code
=> $update_vm_api,
1384 __PACKAGE__-
>register_method({
1385 name
=> 'update_vm',
1386 path
=> '{vmid}/config',
1390 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1392 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1395 additionalProperties
=> 0,
1396 properties
=> PVE
::QemuServer
::json_config_properties
(
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1400 skiplock
=> get_standard_option
('skiplock'),
1402 type
=> 'string', format
=> 'pve-configid-list',
1403 description
=> "A list of settings you want to delete.",
1407 type
=> 'string', format
=> 'pve-configid-list',
1408 description
=> "Revert a pending change.",
1413 description
=> $opt_force_description,
1415 requires
=> 'delete',
1419 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1425 returns
=> { type
=> 'null' },
1428 &$update_vm_api($param, 1);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'destroy_vm',
1439 description
=> "Destroy the vm (also delete all used/owned volumes).",
1441 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1448 skiplock
=> get_standard_option
('skiplock'),
1451 description
=> "Remove vmid from backup cron jobs.",
1462 my $rpcenv = PVE
::RPCEnvironment
::get
();
1463 my $authuser = $rpcenv->get_user();
1464 my $vmid = $param->{vmid
};
1466 my $skiplock = $param->{skiplock
};
1467 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1468 if $skiplock && $authuser ne 'root@pam';
1471 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1472 my $storecfg = PVE
::Storage
::config
();
1473 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1475 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1477 if (!$param->{purge
}) {
1478 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1480 # don't allow destroy if with replication jobs but no purge param
1481 my $repl_conf = PVE
::ReplicationConfig-
>new();
1482 $repl_conf->check_for_existing_jobs($vmid);
1485 # early tests (repeat after locking)
1486 die "VM $vmid is running - destroy failed\n"
1487 if PVE
::QemuServer
::check_running
($vmid);
1492 syslog
('info', "destroy VM $vmid: $upid\n");
1493 PVE
::QemuConfig-
>lock_config($vmid, sub {
1494 die "VM $vmid is running - destroy failed\n"
1495 if (PVE
::QemuServer
::check_running
($vmid));
1497 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1499 PVE
::AccessControl
::remove_vm_access
($vmid);
1500 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1501 if ($param->{purge
}) {
1502 print "purging VM $vmid from related configurations..\n";
1503 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1504 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1507 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1508 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1512 # only now remove the zombie config, else we can have reuse race
1513 PVE
::QemuConfig-
>destroy_config($vmid);
1517 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/unlink',
1526 description
=> "Unlink/delete disk images.",
1528 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1531 additionalProperties
=> 0,
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1536 type
=> 'string', format
=> 'pve-configid-list',
1537 description
=> "A list of disk IDs you want to delete.",
1541 description
=> $opt_force_description,
1546 returns
=> { type
=> 'null'},
1550 $param->{delete} = extract_param
($param, 'idlist');
1552 __PACKAGE__-
>update_vm($param);
1559 __PACKAGE__-
>register_method({
1561 path
=> '{vmid}/vncproxy',
1565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1567 description
=> "Creates a TCP VNC proxy connections.",
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1576 description
=> "starts websockify instead of vncproxy",
1581 additionalProperties
=> 0,
1583 user
=> { type
=> 'string' },
1584 ticket
=> { type
=> 'string' },
1585 cert
=> { type
=> 'string' },
1586 port
=> { type
=> 'integer' },
1587 upid
=> { type
=> 'string' },
1593 my $rpcenv = PVE
::RPCEnvironment
::get
();
1595 my $authuser = $rpcenv->get_user();
1597 my $vmid = $param->{vmid
};
1598 my $node = $param->{node
};
1599 my $websocket = $param->{websocket
};
1601 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1602 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1604 my $authpath = "/vms/$vmid";
1606 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1608 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1614 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1615 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1616 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1617 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1618 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1620 $family = PVE
::Tools
::get_host_address_family
($node);
1623 my $port = PVE
::Tools
::next_vnc_port
($family);
1630 syslog
('info', "starting vnc proxy $upid\n");
1636 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1638 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1639 '-timeout', $timeout, '-authpath', $authpath,
1640 '-perm', 'Sys.Console'];
1642 if ($param->{websocket
}) {
1643 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1644 push @$cmd, '-notls', '-listen', 'localhost';
1647 push @$cmd, '-c', @$remcmd, @$termcmd;
1649 PVE
::Tools
::run_command
($cmd);
1653 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1655 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1657 my $sock = IO
::Socket
::IP-
>new(
1662 GetAddrInfoFlags
=> 0,
1663 ) or die "failed to create socket: $!\n";
1664 # Inside the worker we shouldn't have any previous alarms
1665 # running anyway...:
1667 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1669 accept(my $cli, $sock) or die "connection failed: $!\n";
1672 if (PVE
::Tools
::run_command
($cmd,
1673 output
=> '>&'.fileno($cli),
1674 input
=> '<&'.fileno($cli),
1677 die "Failed to run vncproxy.\n";
1684 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1686 PVE
::Tools
::wait_for_vnc_port
($port);
1697 __PACKAGE__-
>register_method({
1698 name
=> 'termproxy',
1699 path
=> '{vmid}/termproxy',
1703 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1705 description
=> "Creates a TCP proxy connections.",
1707 additionalProperties
=> 0,
1709 node
=> get_standard_option
('pve-node'),
1710 vmid
=> get_standard_option
('pve-vmid'),
1714 enum
=> [qw(serial0 serial1 serial2 serial3)],
1715 description
=> "opens a serial terminal (defaults to display)",
1720 additionalProperties
=> 0,
1722 user
=> { type
=> 'string' },
1723 ticket
=> { type
=> 'string' },
1724 port
=> { type
=> 'integer' },
1725 upid
=> { type
=> 'string' },
1731 my $rpcenv = PVE
::RPCEnvironment
::get
();
1733 my $authuser = $rpcenv->get_user();
1735 my $vmid = $param->{vmid
};
1736 my $node = $param->{node
};
1737 my $serial = $param->{serial
};
1739 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1741 if (!defined($serial)) {
1742 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1743 $serial = $conf->{vga
};
1747 my $authpath = "/vms/$vmid";
1749 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1754 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1755 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1756 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1757 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1758 push @$remcmd, '--';
1760 $family = PVE
::Tools
::get_host_address_family
($node);
1763 my $port = PVE
::Tools
::next_vnc_port
($family);
1765 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1766 push @$termcmd, '-iface', $serial if $serial;
1771 syslog
('info', "starting qemu termproxy $upid\n");
1773 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1774 '--perm', 'VM.Console', '--'];
1775 push @$cmd, @$remcmd, @$termcmd;
1777 PVE
::Tools
::run_command
($cmd);
1780 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1782 PVE
::Tools
::wait_for_vnc_port
($port);
1792 __PACKAGE__-
>register_method({
1793 name
=> 'vncwebsocket',
1794 path
=> '{vmid}/vncwebsocket',
1797 description
=> "You also need to pass a valid ticket (vncticket).",
1798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1800 description
=> "Opens a weksocket for VNC traffic.",
1802 additionalProperties
=> 0,
1804 node
=> get_standard_option
('pve-node'),
1805 vmid
=> get_standard_option
('pve-vmid'),
1807 description
=> "Ticket from previous call to vncproxy.",
1812 description
=> "Port number returned by previous vncproxy call.",
1822 port
=> { type
=> 'string' },
1828 my $rpcenv = PVE
::RPCEnvironment
::get
();
1830 my $authuser = $rpcenv->get_user();
1832 my $vmid = $param->{vmid
};
1833 my $node = $param->{node
};
1835 my $authpath = "/vms/$vmid";
1837 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1839 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1841 # Note: VNC ports are acessible from outside, so we do not gain any
1842 # security if we verify that $param->{port} belongs to VM $vmid. This
1843 # check is done by verifying the VNC ticket (inside VNC protocol).
1845 my $port = $param->{port
};
1847 return { port
=> $port };
1850 __PACKAGE__-
>register_method({
1851 name
=> 'spiceproxy',
1852 path
=> '{vmid}/spiceproxy',
1857 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1859 description
=> "Returns a SPICE configuration to connect to the VM.",
1861 additionalProperties
=> 0,
1863 node
=> get_standard_option
('pve-node'),
1864 vmid
=> get_standard_option
('pve-vmid'),
1865 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1868 returns
=> get_standard_option
('remote-viewer-config'),
1872 my $rpcenv = PVE
::RPCEnvironment
::get
();
1874 my $authuser = $rpcenv->get_user();
1876 my $vmid = $param->{vmid
};
1877 my $node = $param->{node
};
1878 my $proxy = $param->{proxy
};
1880 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1881 my $title = "VM $vmid";
1882 $title .= " - ". $conf->{name
} if $conf->{name
};
1884 my $port = PVE
::QemuServer
::spice_port
($vmid);
1886 my ($ticket, undef, $remote_viewer_config) =
1887 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1889 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1890 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1892 return $remote_viewer_config;
1895 __PACKAGE__-
>register_method({
1897 path
=> '{vmid}/status',
1900 description
=> "Directory index",
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid'),
1916 subdir
=> { type
=> 'string' },
1919 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1925 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1928 { subdir
=> 'current' },
1929 { subdir
=> 'start' },
1930 { subdir
=> 'stop' },
1931 { subdir
=> 'reset' },
1932 { subdir
=> 'shutdown' },
1933 { subdir
=> 'suspend' },
1934 { subdir
=> 'reboot' },
1940 __PACKAGE__-
>register_method({
1941 name
=> 'vm_status',
1942 path
=> '{vmid}/status/current',
1945 protected
=> 1, # qemu pid files are only readable by root
1946 description
=> "Get virtual machine status.",
1948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1951 additionalProperties
=> 0,
1953 node
=> get_standard_option
('pve-node'),
1954 vmid
=> get_standard_option
('pve-vmid'),
1960 %$PVE::QemuServer
::vmstatus_return_properties
,
1962 description
=> "HA manager service status.",
1966 description
=> "Qemu VGA configuration supports spice.",
1971 description
=> "Qemu GuestAgent enabled in config.",
1981 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1983 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1984 my $status = $vmstatus->{$param->{vmid
}};
1986 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1988 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1989 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1994 __PACKAGE__-
>register_method({
1996 path
=> '{vmid}/status/start',
2000 description
=> "Start virtual machine.",
2002 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2005 additionalProperties
=> 0,
2007 node
=> get_standard_option
('pve-node'),
2008 vmid
=> get_standard_option
('pve-vmid',
2009 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2010 skiplock
=> get_standard_option
('skiplock'),
2011 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2012 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2015 enum
=> ['secure', 'insecure'],
2016 description
=> "Migration traffic is encrypted using an SSH " .
2017 "tunnel by default. On secure, completely private networks " .
2018 "this can be disabled to increase performance.",
2021 migration_network
=> {
2022 type
=> 'string', format
=> 'CIDR',
2023 description
=> "CIDR of the (sub) network that is used for migration.",
2026 machine
=> get_standard_option
('pve-qemu-machine'),
2027 targetstorage
=> get_standard_option
('pve-targetstorage'),
2029 description
=> "Wait maximal timeout seconds.",
2032 default => 'max(30, vm memory in GiB)',
2043 my $rpcenv = PVE
::RPCEnvironment
::get
();
2044 my $authuser = $rpcenv->get_user();
2046 my $node = extract_param
($param, 'node');
2047 my $vmid = extract_param
($param, 'vmid');
2048 my $timeout = extract_param
($param, 'timeout');
2050 my $machine = extract_param
($param, 'machine');
2052 my $get_root_param = sub {
2053 my $value = extract_param
($param, $_[0]);
2054 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2055 if $value && $authuser ne 'root@pam';
2059 my $stateuri = $get_root_param->('stateuri');
2060 my $skiplock = $get_root_param->('skiplock');
2061 my $migratedfrom = $get_root_param->('migratedfrom');
2062 my $migration_type = $get_root_param->('migration_type');
2063 my $migration_network = $get_root_param->('migration_network');
2064 my $targetstorage = $get_root_param->('targetstorage');
2068 if ($targetstorage) {
2069 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2071 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2072 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2076 # read spice ticket from STDIN
2078 my $nbd_protocol_version = 0;
2079 my $replicated_volumes = {};
2080 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2081 while (defined(my $line = <STDIN
>)) {
2083 if ($line =~ m/^spice_ticket: (.+)$/) {
2085 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2086 $nbd_protocol_version = $1;
2087 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2088 $replicated_volumes->{$1} = 1;
2090 # fallback for old source node
2091 $spice_ticket = $line;
2096 PVE
::Cluster
::check_cfs_quorum
();
2098 my $storecfg = PVE
::Storage
::config
();
2100 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2104 print "Requesting HA start for VM $vmid\n";
2106 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2107 PVE
::Tools
::run_command
($cmd);
2111 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2118 syslog
('info', "start VM $vmid: $upid\n");
2120 my $migrate_opts = {
2121 migratedfrom
=> $migratedfrom,
2122 spice_ticket
=> $spice_ticket,
2123 network
=> $migration_network,
2124 type
=> $migration_type,
2125 storagemap
=> $storagemap,
2126 nbd_proto_version
=> $nbd_protocol_version,
2127 replicated_volumes
=> $replicated_volumes,
2131 statefile
=> $stateuri,
2132 skiplock
=> $skiplock,
2133 forcemachine
=> $machine,
2134 timeout
=> $timeout,
2137 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2141 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2145 __PACKAGE__-
>register_method({
2147 path
=> '{vmid}/status/stop',
2151 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2152 "is akin to pulling the power plug of a running computer and may damage the VM data",
2154 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2157 additionalProperties
=> 0,
2159 node
=> get_standard_option
('pve-node'),
2160 vmid
=> get_standard_option
('pve-vmid',
2161 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2162 skiplock
=> get_standard_option
('skiplock'),
2163 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2165 description
=> "Wait maximal timeout seconds.",
2171 description
=> "Do not deactivate storage volumes.",
2184 my $rpcenv = PVE
::RPCEnvironment
::get
();
2185 my $authuser = $rpcenv->get_user();
2187 my $node = extract_param
($param, 'node');
2188 my $vmid = extract_param
($param, 'vmid');
2190 my $skiplock = extract_param
($param, 'skiplock');
2191 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2192 if $skiplock && $authuser ne 'root@pam';
2194 my $keepActive = extract_param
($param, 'keepActive');
2195 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2196 if $keepActive && $authuser ne 'root@pam';
2198 my $migratedfrom = extract_param
($param, 'migratedfrom');
2199 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2200 if $migratedfrom && $authuser ne 'root@pam';
2203 my $storecfg = PVE
::Storage
::config
();
2205 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2210 print "Requesting HA stop for VM $vmid\n";
2212 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2213 PVE
::Tools
::run_command
($cmd);
2217 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2223 syslog
('info', "stop VM $vmid: $upid\n");
2225 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2226 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2230 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2234 __PACKAGE__-
>register_method({
2236 path
=> '{vmid}/status/reset',
2240 description
=> "Reset virtual machine.",
2242 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2245 additionalProperties
=> 0,
2247 node
=> get_standard_option
('pve-node'),
2248 vmid
=> get_standard_option
('pve-vmid',
2249 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2250 skiplock
=> get_standard_option
('skiplock'),
2259 my $rpcenv = PVE
::RPCEnvironment
::get
();
2261 my $authuser = $rpcenv->get_user();
2263 my $node = extract_param
($param, 'node');
2265 my $vmid = extract_param
($param, 'vmid');
2267 my $skiplock = extract_param
($param, 'skiplock');
2268 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2269 if $skiplock && $authuser ne 'root@pam';
2271 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2276 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2281 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2284 __PACKAGE__-
>register_method({
2285 name
=> 'vm_shutdown',
2286 path
=> '{vmid}/status/shutdown',
2290 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2291 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2293 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2296 additionalProperties
=> 0,
2298 node
=> get_standard_option
('pve-node'),
2299 vmid
=> get_standard_option
('pve-vmid',
2300 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2301 skiplock
=> get_standard_option
('skiplock'),
2303 description
=> "Wait maximal timeout seconds.",
2309 description
=> "Make sure the VM stops.",
2315 description
=> "Do not deactivate storage volumes.",
2328 my $rpcenv = PVE
::RPCEnvironment
::get
();
2329 my $authuser = $rpcenv->get_user();
2331 my $node = extract_param
($param, 'node');
2332 my $vmid = extract_param
($param, 'vmid');
2334 my $skiplock = extract_param
($param, 'skiplock');
2335 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2336 if $skiplock && $authuser ne 'root@pam';
2338 my $keepActive = extract_param
($param, 'keepActive');
2339 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2340 if $keepActive && $authuser ne 'root@pam';
2342 my $storecfg = PVE
::Storage
::config
();
2346 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2347 # otherwise, we will infer a shutdown command, but run into the timeout,
2348 # then when the vm is resumed, it will instantly shutdown
2350 # checking the qmp status here to get feedback to the gui/cli/api
2351 # and the status query should not take too long
2352 my $qmpstatus = eval {
2353 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2354 mon_cmd
($vmid, "query-status");
2358 if (!$err && $qmpstatus->{status
} eq "paused") {
2359 if ($param->{forceStop
}) {
2360 warn "VM is paused - stop instead of shutdown\n";
2363 die "VM is paused - cannot shutdown\n";
2367 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2369 my $timeout = $param->{timeout
} // 60;
2373 print "Requesting HA stop for VM $vmid\n";
2375 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2376 PVE
::Tools
::run_command
($cmd);
2380 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2387 syslog
('info', "shutdown VM $vmid: $upid\n");
2389 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2390 $shutdown, $param->{forceStop
}, $keepActive);
2394 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2398 __PACKAGE__-
>register_method({
2399 name
=> 'vm_reboot',
2400 path
=> '{vmid}/status/reboot',
2404 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2406 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2409 additionalProperties
=> 0,
2411 node
=> get_standard_option
('pve-node'),
2412 vmid
=> get_standard_option
('pve-vmid',
2413 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2415 description
=> "Wait maximal timeout seconds for the shutdown.",
2428 my $rpcenv = PVE
::RPCEnvironment
::get
();
2429 my $authuser = $rpcenv->get_user();
2431 my $node = extract_param
($param, 'node');
2432 my $vmid = extract_param
($param, 'vmid');
2434 my $qmpstatus = eval {
2435 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2436 mon_cmd
($vmid, "query-status");
2440 if (!$err && $qmpstatus->{status
} eq "paused") {
2441 die "VM is paused - cannot shutdown\n";
2444 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2449 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2450 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2454 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2457 __PACKAGE__-
>register_method({
2458 name
=> 'vm_suspend',
2459 path
=> '{vmid}/status/suspend',
2463 description
=> "Suspend virtual machine.",
2465 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2466 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2467 " on the storage for the vmstate.",
2468 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2471 additionalProperties
=> 0,
2473 node
=> get_standard_option
('pve-node'),
2474 vmid
=> get_standard_option
('pve-vmid',
2475 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2476 skiplock
=> get_standard_option
('skiplock'),
2481 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2483 statestorage
=> get_standard_option
('pve-storage-id', {
2484 description
=> "The storage for the VM state",
2485 requires
=> 'todisk',
2487 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2497 my $rpcenv = PVE
::RPCEnvironment
::get
();
2498 my $authuser = $rpcenv->get_user();
2500 my $node = extract_param
($param, 'node');
2501 my $vmid = extract_param
($param, 'vmid');
2503 my $todisk = extract_param
($param, 'todisk') // 0;
2505 my $statestorage = extract_param
($param, 'statestorage');
2507 my $skiplock = extract_param
($param, 'skiplock');
2508 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2509 if $skiplock && $authuser ne 'root@pam';
2511 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2513 die "Cannot suspend HA managed VM to disk\n"
2514 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2516 # early check for storage permission, for better user feedback
2518 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2520 if (!$statestorage) {
2521 # get statestorage from config if none is given
2522 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2523 my $storecfg = PVE
::Storage
::config
();
2524 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2527 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2533 syslog
('info', "suspend VM $vmid: $upid\n");
2535 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2540 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2541 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2544 __PACKAGE__-
>register_method({
2545 name
=> 'vm_resume',
2546 path
=> '{vmid}/status/resume',
2550 description
=> "Resume virtual machine.",
2552 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2555 additionalProperties
=> 0,
2557 node
=> get_standard_option
('pve-node'),
2558 vmid
=> get_standard_option
('pve-vmid',
2559 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2560 skiplock
=> get_standard_option
('skiplock'),
2561 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2571 my $rpcenv = PVE
::RPCEnvironment
::get
();
2573 my $authuser = $rpcenv->get_user();
2575 my $node = extract_param
($param, 'node');
2577 my $vmid = extract_param
($param, 'vmid');
2579 my $skiplock = extract_param
($param, 'skiplock');
2580 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2581 if $skiplock && $authuser ne 'root@pam';
2583 my $nocheck = extract_param
($param, 'nocheck');
2585 my $to_disk_suspended;
2587 PVE
::QemuConfig-
>lock_config($vmid, sub {
2588 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2589 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2593 die "VM $vmid not running\n"
2594 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2599 syslog
('info', "resume VM $vmid: $upid\n");
2601 if (!$to_disk_suspended) {
2602 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2604 my $storecfg = PVE
::Storage
::config
();
2605 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2611 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2614 __PACKAGE__-
>register_method({
2615 name
=> 'vm_sendkey',
2616 path
=> '{vmid}/sendkey',
2620 description
=> "Send key event to virtual machine.",
2622 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2625 additionalProperties
=> 0,
2627 node
=> get_standard_option
('pve-node'),
2628 vmid
=> get_standard_option
('pve-vmid',
2629 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2630 skiplock
=> get_standard_option
('skiplock'),
2632 description
=> "The key (qemu monitor encoding).",
2637 returns
=> { type
=> 'null'},
2641 my $rpcenv = PVE
::RPCEnvironment
::get
();
2643 my $authuser = $rpcenv->get_user();
2645 my $node = extract_param
($param, 'node');
2647 my $vmid = extract_param
($param, 'vmid');
2649 my $skiplock = extract_param
($param, 'skiplock');
2650 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2651 if $skiplock && $authuser ne 'root@pam';
2653 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2658 __PACKAGE__-
>register_method({
2659 name
=> 'vm_feature',
2660 path
=> '{vmid}/feature',
2664 description
=> "Check if feature for virtual machine is available.",
2666 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2669 additionalProperties
=> 0,
2671 node
=> get_standard_option
('pve-node'),
2672 vmid
=> get_standard_option
('pve-vmid'),
2674 description
=> "Feature to check.",
2676 enum
=> [ 'snapshot', 'clone', 'copy' ],
2678 snapname
=> get_standard_option
('pve-snapshot-name', {
2686 hasFeature
=> { type
=> 'boolean' },
2689 items
=> { type
=> 'string' },
2696 my $node = extract_param
($param, 'node');
2698 my $vmid = extract_param
($param, 'vmid');
2700 my $snapname = extract_param
($param, 'snapname');
2702 my $feature = extract_param
($param, 'feature');
2704 my $running = PVE
::QemuServer
::check_running
($vmid);
2706 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2709 my $snap = $conf->{snapshots
}->{$snapname};
2710 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2713 my $storecfg = PVE
::Storage
::config
();
2715 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2716 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2719 hasFeature
=> $hasFeature,
2720 nodes
=> [ keys %$nodelist ],
2724 __PACKAGE__-
>register_method({
2726 path
=> '{vmid}/clone',
2730 description
=> "Create a copy of virtual machine/template.",
2732 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2733 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2734 "'Datastore.AllocateSpace' on any used storage.",
2737 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2739 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2740 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2745 additionalProperties
=> 0,
2747 node
=> get_standard_option
('pve-node'),
2748 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2749 newid
=> get_standard_option
('pve-vmid', {
2750 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2751 description
=> 'VMID for the clone.' }),
2754 type
=> 'string', format
=> 'dns-name',
2755 description
=> "Set a name for the new VM.",
2760 description
=> "Description for the new VM.",
2764 type
=> 'string', format
=> 'pve-poolid',
2765 description
=> "Add the new VM to the specified pool.",
2767 snapname
=> get_standard_option
('pve-snapshot-name', {
2770 storage
=> get_standard_option
('pve-storage-id', {
2771 description
=> "Target storage for full clone.",
2775 description
=> "Target format for file storage. Only valid for full clone.",
2778 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2783 description
=> "Create a full copy of all disks. This is always done when " .
2784 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2786 target
=> get_standard_option
('pve-node', {
2787 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2791 description
=> "Override I/O bandwidth limit (in KiB/s).",
2795 default => 'clone limit from datacenter or storage config',
2805 my $rpcenv = PVE
::RPCEnvironment
::get
();
2806 my $authuser = $rpcenv->get_user();
2808 my $node = extract_param
($param, 'node');
2809 my $vmid = extract_param
($param, 'vmid');
2810 my $newid = extract_param
($param, 'newid');
2811 my $pool = extract_param
($param, 'pool');
2812 $rpcenv->check_pool_exist($pool) if defined($pool);
2814 my $snapname = extract_param
($param, 'snapname');
2815 my $storage = extract_param
($param, 'storage');
2816 my $format = extract_param
($param, 'format');
2817 my $target = extract_param
($param, 'target');
2819 my $localnode = PVE
::INotify
::nodename
();
2821 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2825 PVE
::Cluster
::check_node_exists
($target) if $target;
2827 my $storecfg = PVE
::Storage
::config
();
2830 # check if storage is enabled on local node
2831 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2833 # check if storage is available on target node
2834 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2835 # clone only works if target storage is shared
2836 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2837 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2841 PVE
::Cluster
::check_cfs_quorum
();
2843 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2845 # exclusive lock if VM is running - else shared lock is enough;
2846 my $shared_lock = $running ?
0 : 1;
2849 # do all tests after lock but before forking worker - if possible
2851 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2852 PVE
::QemuConfig-
>check_lock($conf);
2854 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2855 die "unexpected state change\n" if $verify_running != $running;
2857 die "snapshot '$snapname' does not exist\n"
2858 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2860 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2862 die "parameter 'storage' not allowed for linked clones\n"
2863 if defined($storage) && !$full;
2865 die "parameter 'format' not allowed for linked clones\n"
2866 if defined($format) && !$full;
2868 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2870 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2872 die "can't clone VM to node '$target' (VM uses local storage)\n"
2873 if $target && !$sharedvm;
2875 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2876 die "unable to create VM $newid: config file already exists\n"
2879 my $newconf = { lock => 'clone' };
2884 foreach my $opt (keys %$oldconf) {
2885 my $value = $oldconf->{$opt};
2887 # do not copy snapshot related info
2888 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2889 $opt eq 'vmstate' || $opt eq 'snapstate';
2891 # no need to copy unused images, because VMID(owner) changes anyways
2892 next if $opt =~ m/^unused\d+$/;
2894 # always change MAC! address
2895 if ($opt =~ m/^net(\d+)$/) {
2896 my $net = PVE
::QemuServer
::parse_net
($value);
2897 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2898 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2899 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2900 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2901 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2902 die "unable to parse drive options for '$opt'\n" if !$drive;
2903 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2904 $newconf->{$opt} = $value; # simply copy configuration
2906 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2907 die "Full clone feature is not supported for drive '$opt'\n"
2908 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2909 $fullclone->{$opt} = 1;
2911 # not full means clone instead of copy
2912 die "Linked clone feature is not supported for drive '$opt'\n"
2913 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2915 $drives->{$opt} = $drive;
2916 push @$vollist, $drive->{file
};
2919 # copy everything else
2920 $newconf->{$opt} = $value;
2924 # auto generate a new uuid
2925 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2926 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2927 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2928 # auto generate a new vmgenid only if the option was set for template
2929 if ($newconf->{vmgenid
}) {
2930 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2933 delete $newconf->{template
};
2935 if ($param->{name
}) {
2936 $newconf->{name
} = $param->{name
};
2938 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2941 if ($param->{description
}) {
2942 $newconf->{description
} = $param->{description
};
2945 # create empty/temp config - this fails if VM already exists on other node
2946 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2947 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2952 my $newvollist = [];
2959 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2961 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2963 my $bwlimit = extract_param
($param, 'bwlimit');
2965 my $total_jobs = scalar(keys %{$drives});
2968 foreach my $opt (keys %$drives) {
2969 my $drive = $drives->{$opt};
2970 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2971 my $completion = $skipcomplete ?
'skip' : 'complete';
2973 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2974 my $storage_list = [ $src_sid ];
2975 push @$storage_list, $storage if defined($storage);
2976 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2978 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2979 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2980 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
2982 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2984 PVE
::QemuConfig-
>write_config($newid, $newconf);
2988 delete $newconf->{lock};
2990 # do not write pending changes
2991 if (my @changes = keys %{$newconf->{pending
}}) {
2992 my $pending = join(',', @changes);
2993 warn "found pending changes for '$pending', discarding for clone\n";
2994 delete $newconf->{pending
};
2997 PVE
::QemuConfig-
>write_config($newid, $newconf);
3000 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3001 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3002 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3004 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3005 die "Failed to move config to node '$target' - rename failed: $!\n"
3006 if !rename($conffile, $newconffile);
3009 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3012 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3013 sleep 1; # some storage like rbd need to wait before release volume - really?
3015 foreach my $volid (@$newvollist) {
3016 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3020 PVE
::Firewall
::remove_vmfw_conf
($newid);
3022 unlink $conffile; # avoid races -> last thing before die
3024 die "clone failed: $err";
3030 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3032 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3035 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3036 # Aquire exclusive lock lock for $newid
3037 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3042 __PACKAGE__-
>register_method({
3043 name
=> 'move_vm_disk',
3044 path
=> '{vmid}/move_disk',
3048 description
=> "Move volume to different storage.",
3050 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3052 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3053 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3057 additionalProperties
=> 0,
3059 node
=> get_standard_option
('pve-node'),
3060 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3063 description
=> "The disk you want to move.",
3064 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3066 storage
=> get_standard_option
('pve-storage-id', {
3067 description
=> "Target storage.",
3068 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3072 description
=> "Target Format.",
3073 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3078 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3084 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3089 description
=> "Override I/O bandwidth limit (in KiB/s).",
3093 default => 'move limit from datacenter or storage config',
3099 description
=> "the task ID.",
3104 my $rpcenv = PVE
::RPCEnvironment
::get
();
3105 my $authuser = $rpcenv->get_user();
3107 my $node = extract_param
($param, 'node');
3108 my $vmid = extract_param
($param, 'vmid');
3109 my $digest = extract_param
($param, 'digest');
3110 my $disk = extract_param
($param, 'disk');
3111 my $storeid = extract_param
($param, 'storage');
3112 my $format = extract_param
($param, 'format');
3114 my $storecfg = PVE
::Storage
::config
();
3116 my $updatefn = sub {
3117 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3118 PVE
::QemuConfig-
>check_lock($conf);
3120 die "VM config checksum missmatch (file change by other user?)\n"
3121 if $digest && $digest ne $conf->{digest
};
3123 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3125 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3127 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3128 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3130 my $old_volid = $drive->{file
};
3132 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3133 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3137 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3138 (!$format || !$oldfmt || $oldfmt eq $format);
3140 # this only checks snapshots because $disk is passed!
3141 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3142 die "you can't move a disk with snapshots and delete the source\n"
3143 if $snapshotted && $param->{delete};
3145 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3147 my $running = PVE
::QemuServer
::check_running
($vmid);
3149 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3152 my $newvollist = [];
3158 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3160 warn "moving disk with snapshots, snapshots will not be moved!\n"
3163 my $bwlimit = extract_param
($param, 'bwlimit');
3164 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3166 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3167 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3169 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3171 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3173 # convert moved disk to base if part of template
3174 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3175 if PVE
::QemuConfig-
>is_template($conf);
3177 PVE
::QemuConfig-
>write_config($vmid, $conf);
3179 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3180 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3181 eval { mon_cmd
($vmid, "guest-fstrim") };
3185 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3186 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3192 foreach my $volid (@$newvollist) {
3193 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3196 die "storage migration failed: $err";
3199 if ($param->{delete}) {
3201 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3202 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3208 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3211 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3214 my $check_vm_disks_local = sub {
3215 my ($storecfg, $vmconf, $vmid) = @_;
3217 my $local_disks = {};
3219 # add some more information to the disks e.g. cdrom
3220 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3221 my ($volid, $attr) = @_;
3223 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3225 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3226 return if $scfg->{shared
};
3228 # The shared attr here is just a special case where the vdisk
3229 # is marked as shared manually
3230 return if $attr->{shared
};
3231 return if $attr->{cdrom
} and $volid eq "none";
3233 if (exists $local_disks->{$volid}) {
3234 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3236 $local_disks->{$volid} = $attr;
3237 # ensure volid is present in case it's needed
3238 $local_disks->{$volid}->{volid
} = $volid;
3242 return $local_disks;
3245 __PACKAGE__-
>register_method({
3246 name
=> 'migrate_vm_precondition',
3247 path
=> '{vmid}/migrate',
3251 description
=> "Get preconditions for migration.",
3253 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3256 additionalProperties
=> 0,
3258 node
=> get_standard_option
('pve-node'),
3259 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3260 target
=> get_standard_option
('pve-node', {
3261 description
=> "Target node.",
3262 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3270 running
=> { type
=> 'boolean' },
3274 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3276 not_allowed_nodes
=> {
3279 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3283 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3285 local_resources
=> {
3287 description
=> "List local resources e.g. pci, usb"
3294 my $rpcenv = PVE
::RPCEnvironment
::get
();
3296 my $authuser = $rpcenv->get_user();
3298 PVE
::Cluster
::check_cfs_quorum
();
3302 my $vmid = extract_param
($param, 'vmid');
3303 my $target = extract_param
($param, 'target');
3304 my $localnode = PVE
::INotify
::nodename
();
3308 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3309 my $storecfg = PVE
::Storage
::config
();
3312 # try to detect errors early
3313 PVE
::QemuConfig-
>check_lock($vmconf);
3315 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3317 # if vm is not running, return target nodes where local storage is available
3318 # for offline migration
3319 if (!$res->{running
}) {
3320 $res->{allowed_nodes
} = [];
3321 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3322 delete $checked_nodes->{$localnode};
3324 foreach my $node (keys %$checked_nodes) {
3325 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3326 push @{$res->{allowed_nodes
}}, $node;
3330 $res->{not_allowed_nodes
} = $checked_nodes;
3334 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3335 $res->{local_disks
} = [ values %$local_disks ];;
3337 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3339 $res->{local_resources
} = $local_resources;
3346 __PACKAGE__-
>register_method({
3347 name
=> 'migrate_vm',
3348 path
=> '{vmid}/migrate',
3352 description
=> "Migrate virtual machine. Creates a new migration task.",
3354 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3357 additionalProperties
=> 0,
3359 node
=> get_standard_option
('pve-node'),
3360 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3361 target
=> get_standard_option
('pve-node', {
3362 description
=> "Target node.",
3363 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3367 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3372 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3377 enum
=> ['secure', 'insecure'],
3378 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3381 migration_network
=> {
3382 type
=> 'string', format
=> 'CIDR',
3383 description
=> "CIDR of the (sub) network that is used for migration.",
3386 "with-local-disks" => {
3388 description
=> "Enable live storage migration for local disk",
3391 targetstorage
=> get_standard_option
('pve-targetstorage', {
3392 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3395 description
=> "Override I/O bandwidth limit (in KiB/s).",
3399 default => 'migrate limit from datacenter or storage config',
3405 description
=> "the task ID.",
3410 my $rpcenv = PVE
::RPCEnvironment
::get
();
3411 my $authuser = $rpcenv->get_user();
3413 my $target = extract_param
($param, 'target');
3415 my $localnode = PVE
::INotify
::nodename
();
3416 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3418 PVE
::Cluster
::check_cfs_quorum
();
3420 PVE
::Cluster
::check_node_exists
($target);
3422 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3424 my $vmid = extract_param
($param, 'vmid');
3426 raise_param_exc
({ force
=> "Only root may use this option." })
3427 if $param->{force
} && $authuser ne 'root@pam';
3429 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3430 if $param->{migration_type
} && $authuser ne 'root@pam';
3432 # allow root only until better network permissions are available
3433 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3434 if $param->{migration_network
} && $authuser ne 'root@pam';
3437 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3439 # try to detect errors early
3441 PVE
::QemuConfig-
>check_lock($conf);
3443 if (PVE
::QemuServer
::check_running
($vmid)) {
3444 die "can't migrate running VM without --online\n" if !$param->{online
};
3446 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3447 $param->{online
} = 0;
3450 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3451 if !$param->{online
} && $param->{targetstorage
};
3453 my $storecfg = PVE
::Storage
::config
();
3455 if (my $targetstorage = $param->{targetstorage
}) {
3456 my $check_storage = sub {
3457 my ($target_sid) = @_;
3458 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3459 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3460 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3461 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3462 if !$scfg->{content
}->{images
};
3465 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3466 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3469 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3470 if !defined($storagemap->{identity
});
3472 foreach my $source (values %{$storagemap->{entries
}}) {
3473 $check_storage->($source);
3476 $check_storage->($storagemap->{default})
3477 if $storagemap->{default};
3479 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3480 if $storagemap->{identity
};
3482 $param->{storagemap
} = $storagemap;
3484 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3487 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3492 print "Requesting HA migration for VM $vmid to node $target\n";
3494 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3495 PVE
::Tools
::run_command
($cmd);
3499 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3504 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3508 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3511 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3516 __PACKAGE__-
>register_method({
3518 path
=> '{vmid}/monitor',
3522 description
=> "Execute Qemu monitor commands.",
3524 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3528 additionalProperties
=> 0,
3530 node
=> get_standard_option
('pve-node'),
3531 vmid
=> get_standard_option
('pve-vmid'),
3534 description
=> "The monitor command.",
3538 returns
=> { type
=> 'string'},
3542 my $rpcenv = PVE
::RPCEnvironment
::get
();
3543 my $authuser = $rpcenv->get_user();
3546 my $command = shift;
3547 return $command =~ m/^\s*info(\s+|$)/
3548 || $command =~ m/^\s*help\s*$/;
3551 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3552 if !&$is_ro($param->{command
});
3554 my $vmid = $param->{vmid
};
3556 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3560 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3562 $res = "ERROR: $@" if $@;
3567 __PACKAGE__-
>register_method({
3568 name
=> 'resize_vm',
3569 path
=> '{vmid}/resize',
3573 description
=> "Extend volume size.",
3575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3578 additionalProperties
=> 0,
3580 node
=> get_standard_option
('pve-node'),
3581 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3582 skiplock
=> get_standard_option
('skiplock'),
3585 description
=> "The disk you want to resize.",
3586 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3590 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3591 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.",
3595 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3601 returns
=> { type
=> 'null'},
3605 my $rpcenv = PVE
::RPCEnvironment
::get
();
3607 my $authuser = $rpcenv->get_user();
3609 my $node = extract_param
($param, 'node');
3611 my $vmid = extract_param
($param, 'vmid');
3613 my $digest = extract_param
($param, 'digest');
3615 my $disk = extract_param
($param, 'disk');
3617 my $sizestr = extract_param
($param, 'size');
3619 my $skiplock = extract_param
($param, 'skiplock');
3620 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3621 if $skiplock && $authuser ne 'root@pam';
3623 my $storecfg = PVE
::Storage
::config
();
3625 my $updatefn = sub {
3627 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3629 die "checksum missmatch (file change by other user?)\n"
3630 if $digest && $digest ne $conf->{digest
};
3631 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3633 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3635 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3637 my (undef, undef, undef, undef, undef, undef, $format) =
3638 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3640 die "can't resize volume: $disk if snapshot exists\n"
3641 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3643 my $volid = $drive->{file
};
3645 die "disk '$disk' has no associated volume\n" if !$volid;
3647 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3649 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3651 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3653 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3654 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3656 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3658 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3659 my ($ext, $newsize, $unit) = ($1, $2, $4);
3662 $newsize = $newsize * 1024;
3663 } elsif ($unit eq 'M') {
3664 $newsize = $newsize * 1024 * 1024;
3665 } elsif ($unit eq 'G') {
3666 $newsize = $newsize * 1024 * 1024 * 1024;
3667 } elsif ($unit eq 'T') {
3668 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3671 $newsize += $size if $ext;
3672 $newsize = int($newsize);
3674 die "shrinking disks is not supported\n" if $newsize < $size;
3676 return if $size == $newsize;
3678 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3680 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3682 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3683 $drive->{size
} = $effective_size // $newsize;
3684 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3686 PVE
::QemuConfig-
>write_config($vmid, $conf);
3689 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3693 __PACKAGE__-
>register_method({
3694 name
=> 'snapshot_list',
3695 path
=> '{vmid}/snapshot',
3697 description
=> "List all snapshots.",
3699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3702 protected
=> 1, # qemu pid files are only readable by root
3704 additionalProperties
=> 0,
3706 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3707 node
=> get_standard_option
('pve-node'),
3716 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3720 description
=> "Snapshot includes RAM.",
3725 description
=> "Snapshot description.",
3729 description
=> "Snapshot creation time",
3731 renderer
=> 'timestamp',
3735 description
=> "Parent snapshot identifier.",
3741 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3746 my $vmid = $param->{vmid
};
3748 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3749 my $snaphash = $conf->{snapshots
} || {};
3753 foreach my $name (keys %$snaphash) {
3754 my $d = $snaphash->{$name};
3757 snaptime
=> $d->{snaptime
} || 0,
3758 vmstate
=> $d->{vmstate
} ?
1 : 0,
3759 description
=> $d->{description
} || '',
3761 $item->{parent
} = $d->{parent
} if $d->{parent
};
3762 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3766 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3769 digest
=> $conf->{digest
},
3770 running
=> $running,
3771 description
=> "You are here!",
3773 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3775 push @$res, $current;
3780 __PACKAGE__-
>register_method({
3782 path
=> '{vmid}/snapshot',
3786 description
=> "Snapshot a VM.",
3788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3791 additionalProperties
=> 0,
3793 node
=> get_standard_option
('pve-node'),
3794 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3795 snapname
=> get_standard_option
('pve-snapshot-name'),
3799 description
=> "Save the vmstate",
3804 description
=> "A textual description or comment.",
3810 description
=> "the task ID.",
3815 my $rpcenv = PVE
::RPCEnvironment
::get
();
3817 my $authuser = $rpcenv->get_user();
3819 my $node = extract_param
($param, 'node');
3821 my $vmid = extract_param
($param, 'vmid');
3823 my $snapname = extract_param
($param, 'snapname');
3825 die "unable to use snapshot name 'current' (reserved name)\n"
3826 if $snapname eq 'current';
3828 die "unable to use snapshot name 'pending' (reserved name)\n"
3829 if lc($snapname) eq 'pending';
3832 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3833 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3834 $param->{description
});
3837 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3840 __PACKAGE__-
>register_method({
3841 name
=> 'snapshot_cmd_idx',
3842 path
=> '{vmid}/snapshot/{snapname}',
3849 additionalProperties
=> 0,
3851 vmid
=> get_standard_option
('pve-vmid'),
3852 node
=> get_standard_option
('pve-node'),
3853 snapname
=> get_standard_option
('pve-snapshot-name'),
3862 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3869 push @$res, { cmd
=> 'rollback' };
3870 push @$res, { cmd
=> 'config' };
3875 __PACKAGE__-
>register_method({
3876 name
=> 'update_snapshot_config',
3877 path
=> '{vmid}/snapshot/{snapname}/config',
3881 description
=> "Update snapshot metadata.",
3883 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3886 additionalProperties
=> 0,
3888 node
=> get_standard_option
('pve-node'),
3889 vmid
=> get_standard_option
('pve-vmid'),
3890 snapname
=> get_standard_option
('pve-snapshot-name'),
3894 description
=> "A textual description or comment.",
3898 returns
=> { type
=> 'null' },
3902 my $rpcenv = PVE
::RPCEnvironment
::get
();
3904 my $authuser = $rpcenv->get_user();
3906 my $vmid = extract_param
($param, 'vmid');
3908 my $snapname = extract_param
($param, 'snapname');
3910 return undef if !defined($param->{description
});
3912 my $updatefn = sub {
3914 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3916 PVE
::QemuConfig-
>check_lock($conf);
3918 my $snap = $conf->{snapshots
}->{$snapname};
3920 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3922 $snap->{description
} = $param->{description
} if defined($param->{description
});
3924 PVE
::QemuConfig-
>write_config($vmid, $conf);
3927 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3932 __PACKAGE__-
>register_method({
3933 name
=> 'get_snapshot_config',
3934 path
=> '{vmid}/snapshot/{snapname}/config',
3937 description
=> "Get snapshot configuration",
3939 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3942 additionalProperties
=> 0,
3944 node
=> get_standard_option
('pve-node'),
3945 vmid
=> get_standard_option
('pve-vmid'),
3946 snapname
=> get_standard_option
('pve-snapshot-name'),
3949 returns
=> { type
=> "object" },
3953 my $rpcenv = PVE
::RPCEnvironment
::get
();
3955 my $authuser = $rpcenv->get_user();
3957 my $vmid = extract_param
($param, 'vmid');
3959 my $snapname = extract_param
($param, 'snapname');
3961 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3963 my $snap = $conf->{snapshots
}->{$snapname};
3965 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3970 __PACKAGE__-
>register_method({
3972 path
=> '{vmid}/snapshot/{snapname}/rollback',
3976 description
=> "Rollback VM state to specified snapshot.",
3978 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3981 additionalProperties
=> 0,
3983 node
=> get_standard_option
('pve-node'),
3984 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3985 snapname
=> get_standard_option
('pve-snapshot-name'),
3990 description
=> "the task ID.",
3995 my $rpcenv = PVE
::RPCEnvironment
::get
();
3997 my $authuser = $rpcenv->get_user();
3999 my $node = extract_param
($param, 'node');
4001 my $vmid = extract_param
($param, 'vmid');
4003 my $snapname = extract_param
($param, 'snapname');
4006 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4007 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4011 # hold migration lock, this makes sure that nobody create replication snapshots
4012 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4015 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4018 __PACKAGE__-
>register_method({
4019 name
=> 'delsnapshot',
4020 path
=> '{vmid}/snapshot/{snapname}',
4024 description
=> "Delete a VM snapshot.",
4026 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4029 additionalProperties
=> 0,
4031 node
=> get_standard_option
('pve-node'),
4032 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4033 snapname
=> get_standard_option
('pve-snapshot-name'),
4037 description
=> "For removal from config file, even if removing disk snapshots fails.",
4043 description
=> "the task ID.",
4048 my $rpcenv = PVE
::RPCEnvironment
::get
();
4050 my $authuser = $rpcenv->get_user();
4052 my $node = extract_param
($param, 'node');
4054 my $vmid = extract_param
($param, 'vmid');
4056 my $snapname = extract_param
($param, 'snapname');
4059 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4060 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4063 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4066 __PACKAGE__-
>register_method({
4068 path
=> '{vmid}/template',
4072 description
=> "Create a Template.",
4074 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4075 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4078 additionalProperties
=> 0,
4080 node
=> get_standard_option
('pve-node'),
4081 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4085 description
=> "If you want to convert only 1 disk to base image.",
4086 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4091 returns
=> { type
=> 'null'},
4095 my $rpcenv = PVE
::RPCEnvironment
::get
();
4097 my $authuser = $rpcenv->get_user();
4099 my $node = extract_param
($param, 'node');
4101 my $vmid = extract_param
($param, 'vmid');
4103 my $disk = extract_param
($param, 'disk');
4105 my $updatefn = sub {
4107 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4109 PVE
::QemuConfig-
>check_lock($conf);
4111 die "unable to create template, because VM contains snapshots\n"
4112 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4114 die "you can't convert a template to a template\n"
4115 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4117 die "you can't convert a VM to template if VM is running\n"
4118 if PVE
::QemuServer
::check_running
($vmid);
4121 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4124 $conf->{template
} = 1;
4125 PVE
::QemuConfig-
>write_config($vmid, $conf);
4127 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4130 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4134 __PACKAGE__-
>register_method({
4135 name
=> 'cloudinit_generated_config_dump',
4136 path
=> '{vmid}/cloudinit/dump',
4139 description
=> "Get automatically generated cloudinit config.",
4141 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4144 additionalProperties
=> 0,
4146 node
=> get_standard_option
('pve-node'),
4147 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4149 description
=> 'Config type.',
4151 enum
=> ['user', 'network', 'meta'],
4161 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4163 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});