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
::QemuConfig-
>foreach_volume($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
::QemuConfig-
>foreach_volume($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
::QemuConfig-
>foreach_volume($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
};
1132 push @delete, 'runningcpu' if $conf->{runningcpu
};
1135 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1137 foreach my $opt (keys %$revert) {
1138 if (defined($conf->{$opt})) {
1139 $param->{$opt} = $conf->{$opt};
1140 } elsif (defined($conf->{pending
}->{$opt})) {
1145 if ($param->{memory
} || defined($param->{balloon
})) {
1146 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1147 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1149 die "balloon value too large (must be smaller than assigned memory)\n"
1150 if $balloon && $balloon > $maxmem;
1153 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1157 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1159 # write updates to pending section
1161 my $modified = {}; # record what $option we modify
1163 foreach my $opt (@delete) {
1164 $modified->{$opt} = 1;
1165 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1167 # value of what we want to delete, independent if pending or not
1168 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1169 if (!defined($val)) {
1170 warn "cannot delete '$opt' - not set in current configuration!\n";
1171 $modified->{$opt} = 0;
1174 my $is_pending_val = defined($conf->{pending
}->{$opt});
1175 delete $conf->{pending
}->{$opt};
1177 if ($opt =~ m/^unused/) {
1178 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1179 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1180 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1181 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1182 delete $conf->{$opt};
1183 PVE
::QemuConfig-
>write_config($vmid, $conf);
1185 } elsif ($opt eq 'vmstate') {
1186 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1187 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1188 delete $conf->{$opt};
1189 PVE
::QemuConfig-
>write_config($vmid, $conf);
1191 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1192 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1193 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1194 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1196 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1197 PVE
::QemuConfig-
>write_config($vmid, $conf);
1198 } elsif ($opt =~ m/^serial\d+$/) {
1199 if ($val eq 'socket') {
1200 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1201 } elsif ($authuser ne 'root@pam') {
1202 die "only root can delete '$opt' config for real devices\n";
1204 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1205 PVE
::QemuConfig-
>write_config($vmid, $conf);
1206 } elsif ($opt =~ m/^usb\d+$/) {
1207 if ($val =~ m/spice/) {
1208 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1209 } elsif ($authuser ne 'root@pam') {
1210 die "only root can delete '$opt' config for real devices\n";
1212 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1213 PVE
::QemuConfig-
>write_config($vmid, $conf);
1215 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1216 PVE
::QemuConfig-
>write_config($vmid, $conf);
1220 foreach my $opt (keys %$param) { # add/change
1221 $modified->{$opt} = 1;
1222 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1223 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1225 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1227 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1228 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1229 # FIXME: cloudinit: CDROM or Disk?
1230 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1231 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1233 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1235 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1236 if defined($conf->{pending
}->{$opt});
1238 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1239 } elsif ($opt =~ m/^serial\d+/) {
1240 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1241 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1242 } elsif ($authuser ne 'root@pam') {
1243 die "only root can modify '$opt' config for real devices\n";
1245 $conf->{pending
}->{$opt} = $param->{$opt};
1246 } elsif ($opt =~ m/^usb\d+/) {
1247 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1248 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1249 } elsif ($authuser ne 'root@pam') {
1250 die "only root can modify '$opt' config for real devices\n";
1252 $conf->{pending
}->{$opt} = $param->{$opt};
1254 $conf->{pending
}->{$opt} = $param->{$opt};
1256 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1257 PVE
::QemuConfig-
>write_config($vmid, $conf);
1260 # remove pending changes when nothing changed
1261 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1262 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1263 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1265 return if !scalar(keys %{$conf->{pending
}});
1267 my $running = PVE
::QemuServer
::check_running
($vmid);
1269 # apply pending changes
1271 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1275 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1277 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1279 raise_param_exc
($errors) if scalar(keys %$errors);
1288 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1290 if ($background_delay) {
1292 # Note: It would be better to do that in the Event based HTTPServer
1293 # to avoid blocking call to sleep.
1295 my $end_time = time() + $background_delay;
1297 my $task = PVE
::Tools
::upid_decode
($upid);
1300 while (time() < $end_time) {
1301 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1303 sleep(1); # this gets interrupted when child process ends
1307 my $status = PVE
::Tools
::upid_read_status
($upid);
1308 return undef if $status eq 'OK';
1317 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1320 my $vm_config_perm_list = [
1325 'VM.Config.Network',
1327 'VM.Config.Options',
1330 __PACKAGE__-
>register_method({
1331 name
=> 'update_vm_async',
1332 path
=> '{vmid}/config',
1336 description
=> "Set virtual machine options (asynchrounous API).",
1338 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1341 additionalProperties
=> 0,
1342 properties
=> PVE
::QemuServer
::json_config_properties
(
1344 node
=> get_standard_option
('pve-node'),
1345 vmid
=> get_standard_option
('pve-vmid'),
1346 skiplock
=> get_standard_option
('skiplock'),
1348 type
=> 'string', format
=> 'pve-configid-list',
1349 description
=> "A list of settings you want to delete.",
1353 type
=> 'string', format
=> 'pve-configid-list',
1354 description
=> "Revert a pending change.",
1359 description
=> $opt_force_description,
1361 requires
=> 'delete',
1365 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1369 background_delay
=> {
1371 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1382 code
=> $update_vm_api,
1385 __PACKAGE__-
>register_method({
1386 name
=> 'update_vm',
1387 path
=> '{vmid}/config',
1391 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1393 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1396 additionalProperties
=> 0,
1397 properties
=> PVE
::QemuServer
::json_config_properties
(
1399 node
=> get_standard_option
('pve-node'),
1400 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1401 skiplock
=> get_standard_option
('skiplock'),
1403 type
=> 'string', format
=> 'pve-configid-list',
1404 description
=> "A list of settings you want to delete.",
1408 type
=> 'string', format
=> 'pve-configid-list',
1409 description
=> "Revert a pending change.",
1414 description
=> $opt_force_description,
1416 requires
=> 'delete',
1420 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1426 returns
=> { type
=> 'null' },
1429 &$update_vm_api($param, 1);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'destroy_vm',
1440 description
=> "Destroy the vm (also delete all used/owned volumes).",
1442 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1449 skiplock
=> get_standard_option
('skiplock'),
1452 description
=> "Remove vmid from backup cron jobs.",
1463 my $rpcenv = PVE
::RPCEnvironment
::get
();
1464 my $authuser = $rpcenv->get_user();
1465 my $vmid = $param->{vmid
};
1467 my $skiplock = $param->{skiplock
};
1468 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1469 if $skiplock && $authuser ne 'root@pam';
1471 my $early_checks = sub {
1473 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1474 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1476 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1478 if (!$param->{purge
}) {
1479 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1481 # don't allow destroy if with replication jobs but no purge param
1482 my $repl_conf = PVE
::ReplicationConfig-
>new();
1483 $repl_conf->check_for_existing_jobs($vmid);
1486 die "VM $vmid is running - destroy failed\n"
1487 if PVE
::QemuServer
::check_running
($vmid);
1497 my $storecfg = PVE
::Storage
::config
();
1499 syslog
('info', "destroy VM $vmid: $upid\n");
1500 PVE
::QemuConfig-
>lock_config($vmid, sub {
1501 # repeat, config might have changed
1502 my $ha_managed = $early_checks->();
1504 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1506 PVE
::AccessControl
::remove_vm_access
($vmid);
1507 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1508 if ($param->{purge
}) {
1509 print "purging VM $vmid from related configurations..\n";
1510 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1511 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1514 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1515 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1519 # only now remove the zombie config, else we can have reuse race
1520 PVE
::QemuConfig-
>destroy_config($vmid);
1524 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1527 __PACKAGE__-
>register_method({
1529 path
=> '{vmid}/unlink',
1533 description
=> "Unlink/delete disk images.",
1535 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1538 additionalProperties
=> 0,
1540 node
=> get_standard_option
('pve-node'),
1541 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1543 type
=> 'string', format
=> 'pve-configid-list',
1544 description
=> "A list of disk IDs you want to delete.",
1548 description
=> $opt_force_description,
1553 returns
=> { type
=> 'null'},
1557 $param->{delete} = extract_param
($param, 'idlist');
1559 __PACKAGE__-
>update_vm($param);
1566 __PACKAGE__-
>register_method({
1568 path
=> '{vmid}/vncproxy',
1572 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1574 description
=> "Creates a TCP VNC proxy connections.",
1576 additionalProperties
=> 0,
1578 node
=> get_standard_option
('pve-node'),
1579 vmid
=> get_standard_option
('pve-vmid'),
1583 description
=> "starts websockify instead of vncproxy",
1588 additionalProperties
=> 0,
1590 user
=> { type
=> 'string' },
1591 ticket
=> { type
=> 'string' },
1592 cert
=> { type
=> 'string' },
1593 port
=> { type
=> 'integer' },
1594 upid
=> { type
=> 'string' },
1600 my $rpcenv = PVE
::RPCEnvironment
::get
();
1602 my $authuser = $rpcenv->get_user();
1604 my $vmid = $param->{vmid
};
1605 my $node = $param->{node
};
1606 my $websocket = $param->{websocket
};
1608 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1609 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1611 my $authpath = "/vms/$vmid";
1613 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1615 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1621 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1622 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1623 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1624 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1625 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1627 $family = PVE
::Tools
::get_host_address_family
($node);
1630 my $port = PVE
::Tools
::next_vnc_port
($family);
1637 syslog
('info', "starting vnc proxy $upid\n");
1643 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1645 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1646 '-timeout', $timeout, '-authpath', $authpath,
1647 '-perm', 'Sys.Console'];
1649 if ($param->{websocket
}) {
1650 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1651 push @$cmd, '-notls', '-listen', 'localhost';
1654 push @$cmd, '-c', @$remcmd, @$termcmd;
1656 PVE
::Tools
::run_command
($cmd);
1660 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1662 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1664 my $sock = IO
::Socket
::IP-
>new(
1669 GetAddrInfoFlags
=> 0,
1670 ) or die "failed to create socket: $!\n";
1671 # Inside the worker we shouldn't have any previous alarms
1672 # running anyway...:
1674 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1676 accept(my $cli, $sock) or die "connection failed: $!\n";
1679 if (PVE
::Tools
::run_command
($cmd,
1680 output
=> '>&'.fileno($cli),
1681 input
=> '<&'.fileno($cli),
1684 die "Failed to run vncproxy.\n";
1691 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1693 PVE
::Tools
::wait_for_vnc_port
($port);
1704 __PACKAGE__-
>register_method({
1705 name
=> 'termproxy',
1706 path
=> '{vmid}/termproxy',
1710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1712 description
=> "Creates a TCP proxy connections.",
1714 additionalProperties
=> 0,
1716 node
=> get_standard_option
('pve-node'),
1717 vmid
=> get_standard_option
('pve-vmid'),
1721 enum
=> [qw(serial0 serial1 serial2 serial3)],
1722 description
=> "opens a serial terminal (defaults to display)",
1727 additionalProperties
=> 0,
1729 user
=> { type
=> 'string' },
1730 ticket
=> { type
=> 'string' },
1731 port
=> { type
=> 'integer' },
1732 upid
=> { type
=> 'string' },
1738 my $rpcenv = PVE
::RPCEnvironment
::get
();
1740 my $authuser = $rpcenv->get_user();
1742 my $vmid = $param->{vmid
};
1743 my $node = $param->{node
};
1744 my $serial = $param->{serial
};
1746 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1748 if (!defined($serial)) {
1749 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1750 $serial = $conf->{vga
};
1754 my $authpath = "/vms/$vmid";
1756 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1761 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1762 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1763 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1764 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1765 push @$remcmd, '--';
1767 $family = PVE
::Tools
::get_host_address_family
($node);
1770 my $port = PVE
::Tools
::next_vnc_port
($family);
1772 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1773 push @$termcmd, '-iface', $serial if $serial;
1778 syslog
('info', "starting qemu termproxy $upid\n");
1780 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1781 '--perm', 'VM.Console', '--'];
1782 push @$cmd, @$remcmd, @$termcmd;
1784 PVE
::Tools
::run_command
($cmd);
1787 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1789 PVE
::Tools
::wait_for_vnc_port
($port);
1799 __PACKAGE__-
>register_method({
1800 name
=> 'vncwebsocket',
1801 path
=> '{vmid}/vncwebsocket',
1804 description
=> "You also need to pass a valid ticket (vncticket).",
1805 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1807 description
=> "Opens a weksocket for VNC traffic.",
1809 additionalProperties
=> 0,
1811 node
=> get_standard_option
('pve-node'),
1812 vmid
=> get_standard_option
('pve-vmid'),
1814 description
=> "Ticket from previous call to vncproxy.",
1819 description
=> "Port number returned by previous vncproxy call.",
1829 port
=> { type
=> 'string' },
1835 my $rpcenv = PVE
::RPCEnvironment
::get
();
1837 my $authuser = $rpcenv->get_user();
1839 my $vmid = $param->{vmid
};
1840 my $node = $param->{node
};
1842 my $authpath = "/vms/$vmid";
1844 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1846 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1848 # Note: VNC ports are acessible from outside, so we do not gain any
1849 # security if we verify that $param->{port} belongs to VM $vmid. This
1850 # check is done by verifying the VNC ticket (inside VNC protocol).
1852 my $port = $param->{port
};
1854 return { port
=> $port };
1857 __PACKAGE__-
>register_method({
1858 name
=> 'spiceproxy',
1859 path
=> '{vmid}/spiceproxy',
1864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1866 description
=> "Returns a SPICE configuration to connect to the VM.",
1868 additionalProperties
=> 0,
1870 node
=> get_standard_option
('pve-node'),
1871 vmid
=> get_standard_option
('pve-vmid'),
1872 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1875 returns
=> get_standard_option
('remote-viewer-config'),
1879 my $rpcenv = PVE
::RPCEnvironment
::get
();
1881 my $authuser = $rpcenv->get_user();
1883 my $vmid = $param->{vmid
};
1884 my $node = $param->{node
};
1885 my $proxy = $param->{proxy
};
1887 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1888 my $title = "VM $vmid";
1889 $title .= " - ". $conf->{name
} if $conf->{name
};
1891 my $port = PVE
::QemuServer
::spice_port
($vmid);
1893 my ($ticket, undef, $remote_viewer_config) =
1894 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1896 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1897 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1899 return $remote_viewer_config;
1902 __PACKAGE__-
>register_method({
1904 path
=> '{vmid}/status',
1907 description
=> "Directory index",
1912 additionalProperties
=> 0,
1914 node
=> get_standard_option
('pve-node'),
1915 vmid
=> get_standard_option
('pve-vmid'),
1923 subdir
=> { type
=> 'string' },
1926 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1932 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1935 { subdir
=> 'current' },
1936 { subdir
=> 'start' },
1937 { subdir
=> 'stop' },
1938 { subdir
=> 'reset' },
1939 { subdir
=> 'shutdown' },
1940 { subdir
=> 'suspend' },
1941 { subdir
=> 'reboot' },
1947 __PACKAGE__-
>register_method({
1948 name
=> 'vm_status',
1949 path
=> '{vmid}/status/current',
1952 protected
=> 1, # qemu pid files are only readable by root
1953 description
=> "Get virtual machine status.",
1955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1958 additionalProperties
=> 0,
1960 node
=> get_standard_option
('pve-node'),
1961 vmid
=> get_standard_option
('pve-vmid'),
1967 %$PVE::QemuServer
::vmstatus_return_properties
,
1969 description
=> "HA manager service status.",
1973 description
=> "Qemu VGA configuration supports spice.",
1978 description
=> "Qemu GuestAgent enabled in config.",
1988 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1990 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1991 my $status = $vmstatus->{$param->{vmid
}};
1993 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1995 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1996 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2001 __PACKAGE__-
>register_method({
2003 path
=> '{vmid}/status/start',
2007 description
=> "Start virtual machine.",
2009 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2012 additionalProperties
=> 0,
2014 node
=> get_standard_option
('pve-node'),
2015 vmid
=> get_standard_option
('pve-vmid',
2016 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2017 skiplock
=> get_standard_option
('skiplock'),
2018 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2019 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2022 enum
=> ['secure', 'insecure'],
2023 description
=> "Migration traffic is encrypted using an SSH " .
2024 "tunnel by default. On secure, completely private networks " .
2025 "this can be disabled to increase performance.",
2028 migration_network
=> {
2029 type
=> 'string', format
=> 'CIDR',
2030 description
=> "CIDR of the (sub) network that is used for migration.",
2033 machine
=> get_standard_option
('pve-qemu-machine'),
2035 description
=> "Override QEMU's -cpu argument with the given string.",
2039 targetstorage
=> get_standard_option
('pve-targetstorage'),
2041 description
=> "Wait maximal timeout seconds.",
2044 default => 'max(30, vm memory in GiB)',
2055 my $rpcenv = PVE
::RPCEnvironment
::get
();
2056 my $authuser = $rpcenv->get_user();
2058 my $node = extract_param
($param, 'node');
2059 my $vmid = extract_param
($param, 'vmid');
2060 my $timeout = extract_param
($param, 'timeout');
2062 my $machine = extract_param
($param, 'machine');
2063 my $force_cpu = extract_param
($param, 'force-cpu');
2065 my $get_root_param = sub {
2066 my $value = extract_param
($param, $_[0]);
2067 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2068 if $value && $authuser ne 'root@pam';
2072 my $stateuri = $get_root_param->('stateuri');
2073 my $skiplock = $get_root_param->('skiplock');
2074 my $migratedfrom = $get_root_param->('migratedfrom');
2075 my $migration_type = $get_root_param->('migration_type');
2076 my $migration_network = $get_root_param->('migration_network');
2077 my $targetstorage = $get_root_param->('targetstorage');
2081 if ($targetstorage) {
2082 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2084 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2085 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2089 # read spice ticket from STDIN
2091 my $nbd_protocol_version = 0;
2092 my $replicated_volumes = {};
2093 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2094 while (defined(my $line = <STDIN
>)) {
2096 if ($line =~ m/^spice_ticket: (.+)$/) {
2098 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2099 $nbd_protocol_version = $1;
2100 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2101 $replicated_volumes->{$1} = 1;
2103 # fallback for old source node
2104 $spice_ticket = $line;
2109 PVE
::Cluster
::check_cfs_quorum
();
2111 my $storecfg = PVE
::Storage
::config
();
2113 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2117 print "Requesting HA start for VM $vmid\n";
2119 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2120 PVE
::Tools
::run_command
($cmd);
2124 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2131 syslog
('info', "start VM $vmid: $upid\n");
2133 my $migrate_opts = {
2134 migratedfrom
=> $migratedfrom,
2135 spice_ticket
=> $spice_ticket,
2136 network
=> $migration_network,
2137 type
=> $migration_type,
2138 storagemap
=> $storagemap,
2139 nbd_proto_version
=> $nbd_protocol_version,
2140 replicated_volumes
=> $replicated_volumes,
2144 statefile
=> $stateuri,
2145 skiplock
=> $skiplock,
2146 forcemachine
=> $machine,
2147 timeout
=> $timeout,
2148 forcecpu
=> $force_cpu,
2151 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2155 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2159 __PACKAGE__-
>register_method({
2161 path
=> '{vmid}/status/stop',
2165 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2166 "is akin to pulling the power plug of a running computer and may damage the VM data",
2168 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2171 additionalProperties
=> 0,
2173 node
=> get_standard_option
('pve-node'),
2174 vmid
=> get_standard_option
('pve-vmid',
2175 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2176 skiplock
=> get_standard_option
('skiplock'),
2177 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2179 description
=> "Wait maximal timeout seconds.",
2185 description
=> "Do not deactivate storage volumes.",
2198 my $rpcenv = PVE
::RPCEnvironment
::get
();
2199 my $authuser = $rpcenv->get_user();
2201 my $node = extract_param
($param, 'node');
2202 my $vmid = extract_param
($param, 'vmid');
2204 my $skiplock = extract_param
($param, 'skiplock');
2205 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2206 if $skiplock && $authuser ne 'root@pam';
2208 my $keepActive = extract_param
($param, 'keepActive');
2209 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2210 if $keepActive && $authuser ne 'root@pam';
2212 my $migratedfrom = extract_param
($param, 'migratedfrom');
2213 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2214 if $migratedfrom && $authuser ne 'root@pam';
2217 my $storecfg = PVE
::Storage
::config
();
2219 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2224 print "Requesting HA stop for VM $vmid\n";
2226 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2227 PVE
::Tools
::run_command
($cmd);
2231 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2237 syslog
('info', "stop VM $vmid: $upid\n");
2239 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2240 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2244 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2248 __PACKAGE__-
>register_method({
2250 path
=> '{vmid}/status/reset',
2254 description
=> "Reset virtual machine.",
2256 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2259 additionalProperties
=> 0,
2261 node
=> get_standard_option
('pve-node'),
2262 vmid
=> get_standard_option
('pve-vmid',
2263 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2264 skiplock
=> get_standard_option
('skiplock'),
2273 my $rpcenv = PVE
::RPCEnvironment
::get
();
2275 my $authuser = $rpcenv->get_user();
2277 my $node = extract_param
($param, 'node');
2279 my $vmid = extract_param
($param, 'vmid');
2281 my $skiplock = extract_param
($param, 'skiplock');
2282 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2283 if $skiplock && $authuser ne 'root@pam';
2285 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2290 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2295 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2298 __PACKAGE__-
>register_method({
2299 name
=> 'vm_shutdown',
2300 path
=> '{vmid}/status/shutdown',
2304 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2305 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2307 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2310 additionalProperties
=> 0,
2312 node
=> get_standard_option
('pve-node'),
2313 vmid
=> get_standard_option
('pve-vmid',
2314 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2315 skiplock
=> get_standard_option
('skiplock'),
2317 description
=> "Wait maximal timeout seconds.",
2323 description
=> "Make sure the VM stops.",
2329 description
=> "Do not deactivate storage volumes.",
2342 my $rpcenv = PVE
::RPCEnvironment
::get
();
2343 my $authuser = $rpcenv->get_user();
2345 my $node = extract_param
($param, 'node');
2346 my $vmid = extract_param
($param, 'vmid');
2348 my $skiplock = extract_param
($param, 'skiplock');
2349 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2350 if $skiplock && $authuser ne 'root@pam';
2352 my $keepActive = extract_param
($param, 'keepActive');
2353 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2354 if $keepActive && $authuser ne 'root@pam';
2356 my $storecfg = PVE
::Storage
::config
();
2360 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2361 # otherwise, we will infer a shutdown command, but run into the timeout,
2362 # then when the vm is resumed, it will instantly shutdown
2364 # checking the qmp status here to get feedback to the gui/cli/api
2365 # and the status query should not take too long
2366 my $qmpstatus = eval {
2367 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2368 mon_cmd
($vmid, "query-status");
2372 if (!$err && $qmpstatus->{status
} eq "paused") {
2373 if ($param->{forceStop
}) {
2374 warn "VM is paused - stop instead of shutdown\n";
2377 die "VM is paused - cannot shutdown\n";
2381 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2383 my $timeout = $param->{timeout
} // 60;
2387 print "Requesting HA stop for VM $vmid\n";
2389 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2390 PVE
::Tools
::run_command
($cmd);
2394 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2401 syslog
('info', "shutdown VM $vmid: $upid\n");
2403 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2404 $shutdown, $param->{forceStop
}, $keepActive);
2408 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2412 __PACKAGE__-
>register_method({
2413 name
=> 'vm_reboot',
2414 path
=> '{vmid}/status/reboot',
2418 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2420 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2423 additionalProperties
=> 0,
2425 node
=> get_standard_option
('pve-node'),
2426 vmid
=> get_standard_option
('pve-vmid',
2427 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2429 description
=> "Wait maximal timeout seconds for the shutdown.",
2442 my $rpcenv = PVE
::RPCEnvironment
::get
();
2443 my $authuser = $rpcenv->get_user();
2445 my $node = extract_param
($param, 'node');
2446 my $vmid = extract_param
($param, 'vmid');
2448 my $qmpstatus = eval {
2449 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2450 mon_cmd
($vmid, "query-status");
2454 if (!$err && $qmpstatus->{status
} eq "paused") {
2455 die "VM is paused - cannot shutdown\n";
2458 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2463 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2464 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2468 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2471 __PACKAGE__-
>register_method({
2472 name
=> 'vm_suspend',
2473 path
=> '{vmid}/status/suspend',
2477 description
=> "Suspend virtual machine.",
2479 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2480 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2481 " on the storage for the vmstate.",
2482 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2485 additionalProperties
=> 0,
2487 node
=> get_standard_option
('pve-node'),
2488 vmid
=> get_standard_option
('pve-vmid',
2489 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2490 skiplock
=> get_standard_option
('skiplock'),
2495 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2497 statestorage
=> get_standard_option
('pve-storage-id', {
2498 description
=> "The storage for the VM state",
2499 requires
=> 'todisk',
2501 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2511 my $rpcenv = PVE
::RPCEnvironment
::get
();
2512 my $authuser = $rpcenv->get_user();
2514 my $node = extract_param
($param, 'node');
2515 my $vmid = extract_param
($param, 'vmid');
2517 my $todisk = extract_param
($param, 'todisk') // 0;
2519 my $statestorage = extract_param
($param, 'statestorage');
2521 my $skiplock = extract_param
($param, 'skiplock');
2522 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2523 if $skiplock && $authuser ne 'root@pam';
2525 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2527 die "Cannot suspend HA managed VM to disk\n"
2528 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2530 # early check for storage permission, for better user feedback
2532 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2534 if (!$statestorage) {
2535 # get statestorage from config if none is given
2536 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2537 my $storecfg = PVE
::Storage
::config
();
2538 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2541 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2547 syslog
('info', "suspend VM $vmid: $upid\n");
2549 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2554 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2555 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2558 __PACKAGE__-
>register_method({
2559 name
=> 'vm_resume',
2560 path
=> '{vmid}/status/resume',
2564 description
=> "Resume virtual machine.",
2566 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2569 additionalProperties
=> 0,
2571 node
=> get_standard_option
('pve-node'),
2572 vmid
=> get_standard_option
('pve-vmid',
2573 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2574 skiplock
=> get_standard_option
('skiplock'),
2575 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2585 my $rpcenv = PVE
::RPCEnvironment
::get
();
2587 my $authuser = $rpcenv->get_user();
2589 my $node = extract_param
($param, 'node');
2591 my $vmid = extract_param
($param, 'vmid');
2593 my $skiplock = extract_param
($param, 'skiplock');
2594 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2595 if $skiplock && $authuser ne 'root@pam';
2597 my $nocheck = extract_param
($param, 'nocheck');
2598 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2599 if $nocheck && $authuser ne 'root@pam';
2601 my $to_disk_suspended;
2603 PVE
::QemuConfig-
>lock_config($vmid, sub {
2604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2605 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2609 die "VM $vmid not running\n"
2610 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2615 syslog
('info', "resume VM $vmid: $upid\n");
2617 if (!$to_disk_suspended) {
2618 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2620 my $storecfg = PVE
::Storage
::config
();
2621 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2627 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2630 __PACKAGE__-
>register_method({
2631 name
=> 'vm_sendkey',
2632 path
=> '{vmid}/sendkey',
2636 description
=> "Send key event to virtual machine.",
2638 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2641 additionalProperties
=> 0,
2643 node
=> get_standard_option
('pve-node'),
2644 vmid
=> get_standard_option
('pve-vmid',
2645 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2646 skiplock
=> get_standard_option
('skiplock'),
2648 description
=> "The key (qemu monitor encoding).",
2653 returns
=> { type
=> 'null'},
2657 my $rpcenv = PVE
::RPCEnvironment
::get
();
2659 my $authuser = $rpcenv->get_user();
2661 my $node = extract_param
($param, 'node');
2663 my $vmid = extract_param
($param, 'vmid');
2665 my $skiplock = extract_param
($param, 'skiplock');
2666 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2667 if $skiplock && $authuser ne 'root@pam';
2669 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2674 __PACKAGE__-
>register_method({
2675 name
=> 'vm_feature',
2676 path
=> '{vmid}/feature',
2680 description
=> "Check if feature for virtual machine is available.",
2682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2685 additionalProperties
=> 0,
2687 node
=> get_standard_option
('pve-node'),
2688 vmid
=> get_standard_option
('pve-vmid'),
2690 description
=> "Feature to check.",
2692 enum
=> [ 'snapshot', 'clone', 'copy' ],
2694 snapname
=> get_standard_option
('pve-snapshot-name', {
2702 hasFeature
=> { type
=> 'boolean' },
2705 items
=> { type
=> 'string' },
2712 my $node = extract_param
($param, 'node');
2714 my $vmid = extract_param
($param, 'vmid');
2716 my $snapname = extract_param
($param, 'snapname');
2718 my $feature = extract_param
($param, 'feature');
2720 my $running = PVE
::QemuServer
::check_running
($vmid);
2722 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2725 my $snap = $conf->{snapshots
}->{$snapname};
2726 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2729 my $storecfg = PVE
::Storage
::config
();
2731 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2732 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2735 hasFeature
=> $hasFeature,
2736 nodes
=> [ keys %$nodelist ],
2740 __PACKAGE__-
>register_method({
2742 path
=> '{vmid}/clone',
2746 description
=> "Create a copy of virtual machine/template.",
2748 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2749 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2750 "'Datastore.AllocateSpace' on any used storage.",
2753 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2755 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2756 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2761 additionalProperties
=> 0,
2763 node
=> get_standard_option
('pve-node'),
2764 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2765 newid
=> get_standard_option
('pve-vmid', {
2766 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2767 description
=> 'VMID for the clone.' }),
2770 type
=> 'string', format
=> 'dns-name',
2771 description
=> "Set a name for the new VM.",
2776 description
=> "Description for the new VM.",
2780 type
=> 'string', format
=> 'pve-poolid',
2781 description
=> "Add the new VM to the specified pool.",
2783 snapname
=> get_standard_option
('pve-snapshot-name', {
2786 storage
=> get_standard_option
('pve-storage-id', {
2787 description
=> "Target storage for full clone.",
2791 description
=> "Target format for file storage. Only valid for full clone.",
2794 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2799 description
=> "Create a full copy of all disks. This is always done when " .
2800 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2802 target
=> get_standard_option
('pve-node', {
2803 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2807 description
=> "Override I/O bandwidth limit (in KiB/s).",
2811 default => 'clone limit from datacenter or storage config',
2821 my $rpcenv = PVE
::RPCEnvironment
::get
();
2822 my $authuser = $rpcenv->get_user();
2824 my $node = extract_param
($param, 'node');
2825 my $vmid = extract_param
($param, 'vmid');
2826 my $newid = extract_param
($param, 'newid');
2827 my $pool = extract_param
($param, 'pool');
2828 $rpcenv->check_pool_exist($pool) if defined($pool);
2830 my $snapname = extract_param
($param, 'snapname');
2831 my $storage = extract_param
($param, 'storage');
2832 my $format = extract_param
($param, 'format');
2833 my $target = extract_param
($param, 'target');
2835 my $localnode = PVE
::INotify
::nodename
();
2837 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2841 PVE
::Cluster
::check_node_exists
($target) if $target;
2843 my $storecfg = PVE
::Storage
::config
();
2846 # check if storage is enabled on local node
2847 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2849 # check if storage is available on target node
2850 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2851 # clone only works if target storage is shared
2852 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2853 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2857 PVE
::Cluster
::check_cfs_quorum
();
2859 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2861 # exclusive lock if VM is running - else shared lock is enough;
2862 my $shared_lock = $running ?
0 : 1;
2865 # do all tests after lock but before forking worker - if possible
2867 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2868 PVE
::QemuConfig-
>check_lock($conf);
2870 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2871 die "unexpected state change\n" if $verify_running != $running;
2873 die "snapshot '$snapname' does not exist\n"
2874 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2876 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2878 die "parameter 'storage' not allowed for linked clones\n"
2879 if defined($storage) && !$full;
2881 die "parameter 'format' not allowed for linked clones\n"
2882 if defined($format) && !$full;
2884 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2886 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2888 die "can't clone VM to node '$target' (VM uses local storage)\n"
2889 if $target && !$sharedvm;
2891 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2892 die "unable to create VM $newid: config file already exists\n"
2895 my $newconf = { lock => 'clone' };
2900 foreach my $opt (keys %$oldconf) {
2901 my $value = $oldconf->{$opt};
2903 # do not copy snapshot related info
2904 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2905 $opt eq 'vmstate' || $opt eq 'snapstate';
2907 # no need to copy unused images, because VMID(owner) changes anyways
2908 next if $opt =~ m/^unused\d+$/;
2910 # always change MAC! address
2911 if ($opt =~ m/^net(\d+)$/) {
2912 my $net = PVE
::QemuServer
::parse_net
($value);
2913 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2914 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2915 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2916 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2917 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2918 die "unable to parse drive options for '$opt'\n" if !$drive;
2919 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2920 $newconf->{$opt} = $value; # simply copy configuration
2922 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2923 die "Full clone feature is not supported for drive '$opt'\n"
2924 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2925 $fullclone->{$opt} = 1;
2927 # not full means clone instead of copy
2928 die "Linked clone feature is not supported for drive '$opt'\n"
2929 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2931 $drives->{$opt} = $drive;
2932 push @$vollist, $drive->{file
};
2935 # copy everything else
2936 $newconf->{$opt} = $value;
2940 # auto generate a new uuid
2941 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2942 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2943 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2944 # auto generate a new vmgenid only if the option was set for template
2945 if ($newconf->{vmgenid
}) {
2946 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2949 delete $newconf->{template
};
2951 if ($param->{name
}) {
2952 $newconf->{name
} = $param->{name
};
2954 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2957 if ($param->{description
}) {
2958 $newconf->{description
} = $param->{description
};
2961 # create empty/temp config - this fails if VM already exists on other node
2962 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2963 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2968 my $newvollist = [];
2975 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2977 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2979 my $bwlimit = extract_param
($param, 'bwlimit');
2981 my $total_jobs = scalar(keys %{$drives});
2984 foreach my $opt (keys %$drives) {
2985 my $drive = $drives->{$opt};
2986 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2987 my $completion = $skipcomplete ?
'skip' : 'complete';
2989 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2990 my $storage_list = [ $src_sid ];
2991 push @$storage_list, $storage if defined($storage);
2992 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2994 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2995 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2996 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
2998 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3000 PVE
::QemuConfig-
>write_config($newid, $newconf);
3004 delete $newconf->{lock};
3006 # do not write pending changes
3007 if (my @changes = keys %{$newconf->{pending
}}) {
3008 my $pending = join(',', @changes);
3009 warn "found pending changes for '$pending', discarding for clone\n";
3010 delete $newconf->{pending
};
3013 PVE
::QemuConfig-
>write_config($newid, $newconf);
3016 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3017 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3018 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3020 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3021 die "Failed to move config to node '$target' - rename failed: $!\n"
3022 if !rename($conffile, $newconffile);
3025 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3028 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3029 sleep 1; # some storage like rbd need to wait before release volume - really?
3031 foreach my $volid (@$newvollist) {
3032 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3036 PVE
::Firewall
::remove_vmfw_conf
($newid);
3038 unlink $conffile; # avoid races -> last thing before die
3040 die "clone failed: $err";
3046 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3048 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3051 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3052 # Aquire exclusive lock lock for $newid
3053 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3058 __PACKAGE__-
>register_method({
3059 name
=> 'move_vm_disk',
3060 path
=> '{vmid}/move_disk',
3064 description
=> "Move volume to different storage.",
3066 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3068 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3069 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3073 additionalProperties
=> 0,
3075 node
=> get_standard_option
('pve-node'),
3076 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3079 description
=> "The disk you want to move.",
3080 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3082 storage
=> get_standard_option
('pve-storage-id', {
3083 description
=> "Target storage.",
3084 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3088 description
=> "Target Format.",
3089 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3094 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3100 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3105 description
=> "Override I/O bandwidth limit (in KiB/s).",
3109 default => 'move limit from datacenter or storage config',
3115 description
=> "the task ID.",
3120 my $rpcenv = PVE
::RPCEnvironment
::get
();
3121 my $authuser = $rpcenv->get_user();
3123 my $node = extract_param
($param, 'node');
3124 my $vmid = extract_param
($param, 'vmid');
3125 my $digest = extract_param
($param, 'digest');
3126 my $disk = extract_param
($param, 'disk');
3127 my $storeid = extract_param
($param, 'storage');
3128 my $format = extract_param
($param, 'format');
3130 my $storecfg = PVE
::Storage
::config
();
3132 my $updatefn = sub {
3133 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3134 PVE
::QemuConfig-
>check_lock($conf);
3136 die "VM config checksum missmatch (file change by other user?)\n"
3137 if $digest && $digest ne $conf->{digest
};
3139 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3141 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3143 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3144 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3146 my $old_volid = $drive->{file
};
3148 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3149 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3153 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3154 (!$format || !$oldfmt || $oldfmt eq $format);
3156 # this only checks snapshots because $disk is passed!
3157 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3158 die "you can't move a disk with snapshots and delete the source\n"
3159 if $snapshotted && $param->{delete};
3161 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3163 my $running = PVE
::QemuServer
::check_running
($vmid);
3165 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3168 my $newvollist = [];
3174 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3176 warn "moving disk with snapshots, snapshots will not be moved!\n"
3179 my $bwlimit = extract_param
($param, 'bwlimit');
3180 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3182 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3183 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3185 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3187 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3189 # convert moved disk to base if part of template
3190 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3191 if PVE
::QemuConfig-
>is_template($conf);
3193 PVE
::QemuConfig-
>write_config($vmid, $conf);
3195 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3196 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3197 eval { mon_cmd
($vmid, "guest-fstrim") };
3201 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3202 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3208 foreach my $volid (@$newvollist) {
3209 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3212 die "storage migration failed: $err";
3215 if ($param->{delete}) {
3217 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3218 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3224 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3227 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3230 my $check_vm_disks_local = sub {
3231 my ($storecfg, $vmconf, $vmid) = @_;
3233 my $local_disks = {};
3235 # add some more information to the disks e.g. cdrom
3236 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3237 my ($volid, $attr) = @_;
3239 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3241 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3242 return if $scfg->{shared
};
3244 # The shared attr here is just a special case where the vdisk
3245 # is marked as shared manually
3246 return if $attr->{shared
};
3247 return if $attr->{cdrom
} and $volid eq "none";
3249 if (exists $local_disks->{$volid}) {
3250 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3252 $local_disks->{$volid} = $attr;
3253 # ensure volid is present in case it's needed
3254 $local_disks->{$volid}->{volid
} = $volid;
3258 return $local_disks;
3261 __PACKAGE__-
>register_method({
3262 name
=> 'migrate_vm_precondition',
3263 path
=> '{vmid}/migrate',
3267 description
=> "Get preconditions for migration.",
3269 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3272 additionalProperties
=> 0,
3274 node
=> get_standard_option
('pve-node'),
3275 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3276 target
=> get_standard_option
('pve-node', {
3277 description
=> "Target node.",
3278 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3286 running
=> { type
=> 'boolean' },
3290 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3292 not_allowed_nodes
=> {
3295 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3299 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3301 local_resources
=> {
3303 description
=> "List local resources e.g. pci, usb"
3310 my $rpcenv = PVE
::RPCEnvironment
::get
();
3312 my $authuser = $rpcenv->get_user();
3314 PVE
::Cluster
::check_cfs_quorum
();
3318 my $vmid = extract_param
($param, 'vmid');
3319 my $target = extract_param
($param, 'target');
3320 my $localnode = PVE
::INotify
::nodename
();
3324 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3325 my $storecfg = PVE
::Storage
::config
();
3328 # try to detect errors early
3329 PVE
::QemuConfig-
>check_lock($vmconf);
3331 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3333 # if vm is not running, return target nodes where local storage is available
3334 # for offline migration
3335 if (!$res->{running
}) {
3336 $res->{allowed_nodes
} = [];
3337 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3338 delete $checked_nodes->{$localnode};
3340 foreach my $node (keys %$checked_nodes) {
3341 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3342 push @{$res->{allowed_nodes
}}, $node;
3346 $res->{not_allowed_nodes
} = $checked_nodes;
3350 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3351 $res->{local_disks
} = [ values %$local_disks ];;
3353 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3355 $res->{local_resources
} = $local_resources;
3362 __PACKAGE__-
>register_method({
3363 name
=> 'migrate_vm',
3364 path
=> '{vmid}/migrate',
3368 description
=> "Migrate virtual machine. Creates a new migration task.",
3370 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3373 additionalProperties
=> 0,
3375 node
=> get_standard_option
('pve-node'),
3376 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3377 target
=> get_standard_option
('pve-node', {
3378 description
=> "Target node.",
3379 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3383 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3388 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3393 enum
=> ['secure', 'insecure'],
3394 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3397 migration_network
=> {
3398 type
=> 'string', format
=> 'CIDR',
3399 description
=> "CIDR of the (sub) network that is used for migration.",
3402 "with-local-disks" => {
3404 description
=> "Enable live storage migration for local disk",
3407 targetstorage
=> get_standard_option
('pve-targetstorage', {
3408 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3411 description
=> "Override I/O bandwidth limit (in KiB/s).",
3415 default => 'migrate limit from datacenter or storage config',
3421 description
=> "the task ID.",
3426 my $rpcenv = PVE
::RPCEnvironment
::get
();
3427 my $authuser = $rpcenv->get_user();
3429 my $target = extract_param
($param, 'target');
3431 my $localnode = PVE
::INotify
::nodename
();
3432 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3434 PVE
::Cluster
::check_cfs_quorum
();
3436 PVE
::Cluster
::check_node_exists
($target);
3438 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3440 my $vmid = extract_param
($param, 'vmid');
3442 raise_param_exc
({ force
=> "Only root may use this option." })
3443 if $param->{force
} && $authuser ne 'root@pam';
3445 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3446 if $param->{migration_type
} && $authuser ne 'root@pam';
3448 # allow root only until better network permissions are available
3449 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3450 if $param->{migration_network
} && $authuser ne 'root@pam';
3453 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3455 # try to detect errors early
3457 PVE
::QemuConfig-
>check_lock($conf);
3459 if (PVE
::QemuServer
::check_running
($vmid)) {
3460 die "can't migrate running VM without --online\n" if !$param->{online
};
3462 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3463 $param->{online
} = 0;
3466 my $storecfg = PVE
::Storage
::config
();
3468 if (my $targetstorage = $param->{targetstorage
}) {
3469 my $check_storage = sub {
3470 my ($target_sid) = @_;
3471 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3472 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3473 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3474 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3475 if !$scfg->{content
}->{images
};
3478 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3479 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3482 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3483 if !defined($storagemap->{identity
});
3485 foreach my $source (values %{$storagemap->{entries
}}) {
3486 $check_storage->($source);
3489 $check_storage->($storagemap->{default})
3490 if $storagemap->{default};
3492 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3493 if $storagemap->{identity
};
3495 $param->{storagemap
} = $storagemap;
3497 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3500 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3505 print "Requesting HA migration for VM $vmid to node $target\n";
3507 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3508 PVE
::Tools
::run_command
($cmd);
3512 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3517 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3521 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3524 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3529 __PACKAGE__-
>register_method({
3531 path
=> '{vmid}/monitor',
3535 description
=> "Execute Qemu monitor commands.",
3537 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3538 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3541 additionalProperties
=> 0,
3543 node
=> get_standard_option
('pve-node'),
3544 vmid
=> get_standard_option
('pve-vmid'),
3547 description
=> "The monitor command.",
3551 returns
=> { type
=> 'string'},
3555 my $rpcenv = PVE
::RPCEnvironment
::get
();
3556 my $authuser = $rpcenv->get_user();
3559 my $command = shift;
3560 return $command =~ m/^\s*info(\s+|$)/
3561 || $command =~ m/^\s*help\s*$/;
3564 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3565 if !&$is_ro($param->{command
});
3567 my $vmid = $param->{vmid
};
3569 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3573 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3575 $res = "ERROR: $@" if $@;
3580 __PACKAGE__-
>register_method({
3581 name
=> 'resize_vm',
3582 path
=> '{vmid}/resize',
3586 description
=> "Extend volume size.",
3588 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3591 additionalProperties
=> 0,
3593 node
=> get_standard_option
('pve-node'),
3594 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3595 skiplock
=> get_standard_option
('skiplock'),
3598 description
=> "The disk you want to resize.",
3599 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3603 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3604 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.",
3608 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3614 returns
=> { type
=> 'null'},
3618 my $rpcenv = PVE
::RPCEnvironment
::get
();
3620 my $authuser = $rpcenv->get_user();
3622 my $node = extract_param
($param, 'node');
3624 my $vmid = extract_param
($param, 'vmid');
3626 my $digest = extract_param
($param, 'digest');
3628 my $disk = extract_param
($param, 'disk');
3630 my $sizestr = extract_param
($param, 'size');
3632 my $skiplock = extract_param
($param, 'skiplock');
3633 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3634 if $skiplock && $authuser ne 'root@pam';
3636 my $storecfg = PVE
::Storage
::config
();
3638 my $updatefn = sub {
3640 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3642 die "checksum missmatch (file change by other user?)\n"
3643 if $digest && $digest ne $conf->{digest
};
3644 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3646 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3648 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3650 my (undef, undef, undef, undef, undef, undef, $format) =
3651 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3653 die "can't resize volume: $disk if snapshot exists\n"
3654 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3656 my $volid = $drive->{file
};
3658 die "disk '$disk' has no associated volume\n" if !$volid;
3660 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3662 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3664 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3666 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3667 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3669 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3671 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3672 my ($ext, $newsize, $unit) = ($1, $2, $4);
3675 $newsize = $newsize * 1024;
3676 } elsif ($unit eq 'M') {
3677 $newsize = $newsize * 1024 * 1024;
3678 } elsif ($unit eq 'G') {
3679 $newsize = $newsize * 1024 * 1024 * 1024;
3680 } elsif ($unit eq 'T') {
3681 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3684 $newsize += $size if $ext;
3685 $newsize = int($newsize);
3687 die "shrinking disks is not supported\n" if $newsize < $size;
3689 return if $size == $newsize;
3691 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3693 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3695 $drive->{size
} = $newsize;
3696 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3698 PVE
::QemuConfig-
>write_config($vmid, $conf);
3701 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3705 __PACKAGE__-
>register_method({
3706 name
=> 'snapshot_list',
3707 path
=> '{vmid}/snapshot',
3709 description
=> "List all snapshots.",
3711 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3714 protected
=> 1, # qemu pid files are only readable by root
3716 additionalProperties
=> 0,
3718 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3719 node
=> get_standard_option
('pve-node'),
3728 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3732 description
=> "Snapshot includes RAM.",
3737 description
=> "Snapshot description.",
3741 description
=> "Snapshot creation time",
3743 renderer
=> 'timestamp',
3747 description
=> "Parent snapshot identifier.",
3753 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3758 my $vmid = $param->{vmid
};
3760 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3761 my $snaphash = $conf->{snapshots
} || {};
3765 foreach my $name (keys %$snaphash) {
3766 my $d = $snaphash->{$name};
3769 snaptime
=> $d->{snaptime
} || 0,
3770 vmstate
=> $d->{vmstate
} ?
1 : 0,
3771 description
=> $d->{description
} || '',
3773 $item->{parent
} = $d->{parent
} if $d->{parent
};
3774 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3778 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3781 digest
=> $conf->{digest
},
3782 running
=> $running,
3783 description
=> "You are here!",
3785 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3787 push @$res, $current;
3792 __PACKAGE__-
>register_method({
3794 path
=> '{vmid}/snapshot',
3798 description
=> "Snapshot a VM.",
3800 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3803 additionalProperties
=> 0,
3805 node
=> get_standard_option
('pve-node'),
3806 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3807 snapname
=> get_standard_option
('pve-snapshot-name'),
3811 description
=> "Save the vmstate",
3816 description
=> "A textual description or comment.",
3822 description
=> "the task ID.",
3827 my $rpcenv = PVE
::RPCEnvironment
::get
();
3829 my $authuser = $rpcenv->get_user();
3831 my $node = extract_param
($param, 'node');
3833 my $vmid = extract_param
($param, 'vmid');
3835 my $snapname = extract_param
($param, 'snapname');
3837 die "unable to use snapshot name 'current' (reserved name)\n"
3838 if $snapname eq 'current';
3840 die "unable to use snapshot name 'pending' (reserved name)\n"
3841 if lc($snapname) eq 'pending';
3844 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3845 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3846 $param->{description
});
3849 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3852 __PACKAGE__-
>register_method({
3853 name
=> 'snapshot_cmd_idx',
3854 path
=> '{vmid}/snapshot/{snapname}',
3861 additionalProperties
=> 0,
3863 vmid
=> get_standard_option
('pve-vmid'),
3864 node
=> get_standard_option
('pve-node'),
3865 snapname
=> get_standard_option
('pve-snapshot-name'),
3874 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3881 push @$res, { cmd
=> 'rollback' };
3882 push @$res, { cmd
=> 'config' };
3887 __PACKAGE__-
>register_method({
3888 name
=> 'update_snapshot_config',
3889 path
=> '{vmid}/snapshot/{snapname}/config',
3893 description
=> "Update snapshot metadata.",
3895 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3898 additionalProperties
=> 0,
3900 node
=> get_standard_option
('pve-node'),
3901 vmid
=> get_standard_option
('pve-vmid'),
3902 snapname
=> get_standard_option
('pve-snapshot-name'),
3906 description
=> "A textual description or comment.",
3910 returns
=> { type
=> 'null' },
3914 my $rpcenv = PVE
::RPCEnvironment
::get
();
3916 my $authuser = $rpcenv->get_user();
3918 my $vmid = extract_param
($param, 'vmid');
3920 my $snapname = extract_param
($param, 'snapname');
3922 return undef if !defined($param->{description
});
3924 my $updatefn = sub {
3926 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3928 PVE
::QemuConfig-
>check_lock($conf);
3930 my $snap = $conf->{snapshots
}->{$snapname};
3932 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3934 $snap->{description
} = $param->{description
} if defined($param->{description
});
3936 PVE
::QemuConfig-
>write_config($vmid, $conf);
3939 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3944 __PACKAGE__-
>register_method({
3945 name
=> 'get_snapshot_config',
3946 path
=> '{vmid}/snapshot/{snapname}/config',
3949 description
=> "Get snapshot configuration",
3951 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3954 additionalProperties
=> 0,
3956 node
=> get_standard_option
('pve-node'),
3957 vmid
=> get_standard_option
('pve-vmid'),
3958 snapname
=> get_standard_option
('pve-snapshot-name'),
3961 returns
=> { type
=> "object" },
3965 my $rpcenv = PVE
::RPCEnvironment
::get
();
3967 my $authuser = $rpcenv->get_user();
3969 my $vmid = extract_param
($param, 'vmid');
3971 my $snapname = extract_param
($param, 'snapname');
3973 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3975 my $snap = $conf->{snapshots
}->{$snapname};
3977 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3982 __PACKAGE__-
>register_method({
3984 path
=> '{vmid}/snapshot/{snapname}/rollback',
3988 description
=> "Rollback VM state to specified snapshot.",
3990 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3993 additionalProperties
=> 0,
3995 node
=> get_standard_option
('pve-node'),
3996 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3997 snapname
=> get_standard_option
('pve-snapshot-name'),
4002 description
=> "the task ID.",
4007 my $rpcenv = PVE
::RPCEnvironment
::get
();
4009 my $authuser = $rpcenv->get_user();
4011 my $node = extract_param
($param, 'node');
4013 my $vmid = extract_param
($param, 'vmid');
4015 my $snapname = extract_param
($param, 'snapname');
4018 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4019 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4023 # hold migration lock, this makes sure that nobody create replication snapshots
4024 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4027 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4030 __PACKAGE__-
>register_method({
4031 name
=> 'delsnapshot',
4032 path
=> '{vmid}/snapshot/{snapname}',
4036 description
=> "Delete a VM snapshot.",
4038 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4041 additionalProperties
=> 0,
4043 node
=> get_standard_option
('pve-node'),
4044 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4045 snapname
=> get_standard_option
('pve-snapshot-name'),
4049 description
=> "For removal from config file, even if removing disk snapshots fails.",
4055 description
=> "the task ID.",
4060 my $rpcenv = PVE
::RPCEnvironment
::get
();
4062 my $authuser = $rpcenv->get_user();
4064 my $node = extract_param
($param, 'node');
4066 my $vmid = extract_param
($param, 'vmid');
4068 my $snapname = extract_param
($param, 'snapname');
4071 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4072 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4075 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4078 __PACKAGE__-
>register_method({
4080 path
=> '{vmid}/template',
4084 description
=> "Create a Template.",
4086 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4087 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4090 additionalProperties
=> 0,
4092 node
=> get_standard_option
('pve-node'),
4093 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4097 description
=> "If you want to convert only 1 disk to base image.",
4098 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4103 returns
=> { type
=> 'null'},
4107 my $rpcenv = PVE
::RPCEnvironment
::get
();
4109 my $authuser = $rpcenv->get_user();
4111 my $node = extract_param
($param, 'node');
4113 my $vmid = extract_param
($param, 'vmid');
4115 my $disk = extract_param
($param, 'disk');
4117 my $updatefn = sub {
4119 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4121 PVE
::QemuConfig-
>check_lock($conf);
4123 die "unable to create template, because VM contains snapshots\n"
4124 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4126 die "you can't convert a template to a template\n"
4127 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4129 die "you can't convert a VM to template if VM is running\n"
4130 if PVE
::QemuServer
::check_running
($vmid);
4133 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4136 $conf->{template
} = 1;
4137 PVE
::QemuConfig-
>write_config($vmid, $conf);
4139 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4142 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4146 __PACKAGE__-
>register_method({
4147 name
=> 'cloudinit_generated_config_dump',
4148 path
=> '{vmid}/cloudinit/dump',
4151 description
=> "Get automatically generated cloudinit config.",
4153 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4156 additionalProperties
=> 0,
4158 node
=> get_standard_option
('pve-node'),
4159 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4161 description
=> 'Config type.',
4163 enum
=> ['user', 'network', 'meta'],
4173 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4175 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});