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';
1472 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1473 my $storecfg = PVE
::Storage
::config
();
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 # early tests (repeat after locking)
1487 die "VM $vmid is running - destroy failed\n"
1488 if PVE
::QemuServer
::check_running
($vmid);
1493 syslog
('info', "destroy VM $vmid: $upid\n");
1494 PVE
::QemuConfig-
>lock_config($vmid, sub {
1495 die "VM $vmid is running - destroy failed\n"
1496 if (PVE
::QemuServer
::check_running
($vmid));
1498 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1500 PVE
::AccessControl
::remove_vm_access
($vmid);
1501 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1502 if ($param->{purge
}) {
1503 print "purging VM $vmid from related configurations..\n";
1504 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1505 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1508 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1509 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1513 # only now remove the zombie config, else we can have reuse race
1514 PVE
::QemuConfig-
>destroy_config($vmid);
1518 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1521 __PACKAGE__-
>register_method({
1523 path
=> '{vmid}/unlink',
1527 description
=> "Unlink/delete disk images.",
1529 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1532 additionalProperties
=> 0,
1534 node
=> get_standard_option
('pve-node'),
1535 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1537 type
=> 'string', format
=> 'pve-configid-list',
1538 description
=> "A list of disk IDs you want to delete.",
1542 description
=> $opt_force_description,
1547 returns
=> { type
=> 'null'},
1551 $param->{delete} = extract_param
($param, 'idlist');
1553 __PACKAGE__-
>update_vm($param);
1560 __PACKAGE__-
>register_method({
1562 path
=> '{vmid}/vncproxy',
1566 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1568 description
=> "Creates a TCP VNC proxy connections.",
1570 additionalProperties
=> 0,
1572 node
=> get_standard_option
('pve-node'),
1573 vmid
=> get_standard_option
('pve-vmid'),
1577 description
=> "starts websockify instead of vncproxy",
1582 additionalProperties
=> 0,
1584 user
=> { type
=> 'string' },
1585 ticket
=> { type
=> 'string' },
1586 cert
=> { type
=> 'string' },
1587 port
=> { type
=> 'integer' },
1588 upid
=> { type
=> 'string' },
1594 my $rpcenv = PVE
::RPCEnvironment
::get
();
1596 my $authuser = $rpcenv->get_user();
1598 my $vmid = $param->{vmid
};
1599 my $node = $param->{node
};
1600 my $websocket = $param->{websocket
};
1602 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1603 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1605 my $authpath = "/vms/$vmid";
1607 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1609 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1615 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1616 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1617 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1618 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1619 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1621 $family = PVE
::Tools
::get_host_address_family
($node);
1624 my $port = PVE
::Tools
::next_vnc_port
($family);
1631 syslog
('info', "starting vnc proxy $upid\n");
1637 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1639 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1640 '-timeout', $timeout, '-authpath', $authpath,
1641 '-perm', 'Sys.Console'];
1643 if ($param->{websocket
}) {
1644 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1645 push @$cmd, '-notls', '-listen', 'localhost';
1648 push @$cmd, '-c', @$remcmd, @$termcmd;
1650 PVE
::Tools
::run_command
($cmd);
1654 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1656 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1658 my $sock = IO
::Socket
::IP-
>new(
1663 GetAddrInfoFlags
=> 0,
1664 ) or die "failed to create socket: $!\n";
1665 # Inside the worker we shouldn't have any previous alarms
1666 # running anyway...:
1668 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1670 accept(my $cli, $sock) or die "connection failed: $!\n";
1673 if (PVE
::Tools
::run_command
($cmd,
1674 output
=> '>&'.fileno($cli),
1675 input
=> '<&'.fileno($cli),
1678 die "Failed to run vncproxy.\n";
1685 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1687 PVE
::Tools
::wait_for_vnc_port
($port);
1698 __PACKAGE__-
>register_method({
1699 name
=> 'termproxy',
1700 path
=> '{vmid}/termproxy',
1704 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1706 description
=> "Creates a TCP proxy connections.",
1708 additionalProperties
=> 0,
1710 node
=> get_standard_option
('pve-node'),
1711 vmid
=> get_standard_option
('pve-vmid'),
1715 enum
=> [qw(serial0 serial1 serial2 serial3)],
1716 description
=> "opens a serial terminal (defaults to display)",
1721 additionalProperties
=> 0,
1723 user
=> { type
=> 'string' },
1724 ticket
=> { type
=> 'string' },
1725 port
=> { type
=> 'integer' },
1726 upid
=> { type
=> 'string' },
1732 my $rpcenv = PVE
::RPCEnvironment
::get
();
1734 my $authuser = $rpcenv->get_user();
1736 my $vmid = $param->{vmid
};
1737 my $node = $param->{node
};
1738 my $serial = $param->{serial
};
1740 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1742 if (!defined($serial)) {
1743 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1744 $serial = $conf->{vga
};
1748 my $authpath = "/vms/$vmid";
1750 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1755 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1756 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1757 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1758 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1759 push @$remcmd, '--';
1761 $family = PVE
::Tools
::get_host_address_family
($node);
1764 my $port = PVE
::Tools
::next_vnc_port
($family);
1766 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1767 push @$termcmd, '-iface', $serial if $serial;
1772 syslog
('info', "starting qemu termproxy $upid\n");
1774 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1775 '--perm', 'VM.Console', '--'];
1776 push @$cmd, @$remcmd, @$termcmd;
1778 PVE
::Tools
::run_command
($cmd);
1781 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1783 PVE
::Tools
::wait_for_vnc_port
($port);
1793 __PACKAGE__-
>register_method({
1794 name
=> 'vncwebsocket',
1795 path
=> '{vmid}/vncwebsocket',
1798 description
=> "You also need to pass a valid ticket (vncticket).",
1799 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1801 description
=> "Opens a weksocket for VNC traffic.",
1803 additionalProperties
=> 0,
1805 node
=> get_standard_option
('pve-node'),
1806 vmid
=> get_standard_option
('pve-vmid'),
1808 description
=> "Ticket from previous call to vncproxy.",
1813 description
=> "Port number returned by previous vncproxy call.",
1823 port
=> { type
=> 'string' },
1829 my $rpcenv = PVE
::RPCEnvironment
::get
();
1831 my $authuser = $rpcenv->get_user();
1833 my $vmid = $param->{vmid
};
1834 my $node = $param->{node
};
1836 my $authpath = "/vms/$vmid";
1838 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1840 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1842 # Note: VNC ports are acessible from outside, so we do not gain any
1843 # security if we verify that $param->{port} belongs to VM $vmid. This
1844 # check is done by verifying the VNC ticket (inside VNC protocol).
1846 my $port = $param->{port
};
1848 return { port
=> $port };
1851 __PACKAGE__-
>register_method({
1852 name
=> 'spiceproxy',
1853 path
=> '{vmid}/spiceproxy',
1858 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1860 description
=> "Returns a SPICE configuration to connect to the VM.",
1862 additionalProperties
=> 0,
1864 node
=> get_standard_option
('pve-node'),
1865 vmid
=> get_standard_option
('pve-vmid'),
1866 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1869 returns
=> get_standard_option
('remote-viewer-config'),
1873 my $rpcenv = PVE
::RPCEnvironment
::get
();
1875 my $authuser = $rpcenv->get_user();
1877 my $vmid = $param->{vmid
};
1878 my $node = $param->{node
};
1879 my $proxy = $param->{proxy
};
1881 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1882 my $title = "VM $vmid";
1883 $title .= " - ". $conf->{name
} if $conf->{name
};
1885 my $port = PVE
::QemuServer
::spice_port
($vmid);
1887 my ($ticket, undef, $remote_viewer_config) =
1888 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1890 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1891 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1893 return $remote_viewer_config;
1896 __PACKAGE__-
>register_method({
1898 path
=> '{vmid}/status',
1901 description
=> "Directory index",
1906 additionalProperties
=> 0,
1908 node
=> get_standard_option
('pve-node'),
1909 vmid
=> get_standard_option
('pve-vmid'),
1917 subdir
=> { type
=> 'string' },
1920 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1926 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1929 { subdir
=> 'current' },
1930 { subdir
=> 'start' },
1931 { subdir
=> 'stop' },
1932 { subdir
=> 'reset' },
1933 { subdir
=> 'shutdown' },
1934 { subdir
=> 'suspend' },
1935 { subdir
=> 'reboot' },
1941 __PACKAGE__-
>register_method({
1942 name
=> 'vm_status',
1943 path
=> '{vmid}/status/current',
1946 protected
=> 1, # qemu pid files are only readable by root
1947 description
=> "Get virtual machine status.",
1949 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1952 additionalProperties
=> 0,
1954 node
=> get_standard_option
('pve-node'),
1955 vmid
=> get_standard_option
('pve-vmid'),
1961 %$PVE::QemuServer
::vmstatus_return_properties
,
1963 description
=> "HA manager service status.",
1967 description
=> "Qemu VGA configuration supports spice.",
1972 description
=> "Qemu GuestAgent enabled in config.",
1982 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1984 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1985 my $status = $vmstatus->{$param->{vmid
}};
1987 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1989 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1990 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1995 __PACKAGE__-
>register_method({
1997 path
=> '{vmid}/status/start',
2001 description
=> "Start virtual machine.",
2003 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2006 additionalProperties
=> 0,
2008 node
=> get_standard_option
('pve-node'),
2009 vmid
=> get_standard_option
('pve-vmid',
2010 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2011 skiplock
=> get_standard_option
('skiplock'),
2012 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2013 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2016 enum
=> ['secure', 'insecure'],
2017 description
=> "Migration traffic is encrypted using an SSH " .
2018 "tunnel by default. On secure, completely private networks " .
2019 "this can be disabled to increase performance.",
2022 migration_network
=> {
2023 type
=> 'string', format
=> 'CIDR',
2024 description
=> "CIDR of the (sub) network that is used for migration.",
2027 machine
=> get_standard_option
('pve-qemu-machine'),
2029 description
=> "Override QEMU's -cpu argument with the given string.",
2033 targetstorage
=> get_standard_option
('pve-targetstorage'),
2035 description
=> "Wait maximal timeout seconds.",
2038 default => 'max(30, vm memory in GiB)',
2049 my $rpcenv = PVE
::RPCEnvironment
::get
();
2050 my $authuser = $rpcenv->get_user();
2052 my $node = extract_param
($param, 'node');
2053 my $vmid = extract_param
($param, 'vmid');
2054 my $timeout = extract_param
($param, 'timeout');
2056 my $machine = extract_param
($param, 'machine');
2057 my $force_cpu = extract_param
($param, 'force-cpu');
2059 my $get_root_param = sub {
2060 my $value = extract_param
($param, $_[0]);
2061 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2062 if $value && $authuser ne 'root@pam';
2066 my $stateuri = $get_root_param->('stateuri');
2067 my $skiplock = $get_root_param->('skiplock');
2068 my $migratedfrom = $get_root_param->('migratedfrom');
2069 my $migration_type = $get_root_param->('migration_type');
2070 my $migration_network = $get_root_param->('migration_network');
2071 my $targetstorage = $get_root_param->('targetstorage');
2075 if ($targetstorage) {
2076 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2078 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2079 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2083 # read spice ticket from STDIN
2085 my $nbd_protocol_version = 0;
2086 my $replicated_volumes = {};
2087 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2088 while (defined(my $line = <STDIN
>)) {
2090 if ($line =~ m/^spice_ticket: (.+)$/) {
2092 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2093 $nbd_protocol_version = $1;
2094 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2095 $replicated_volumes->{$1} = 1;
2097 # fallback for old source node
2098 $spice_ticket = $line;
2103 PVE
::Cluster
::check_cfs_quorum
();
2105 my $storecfg = PVE
::Storage
::config
();
2107 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2111 print "Requesting HA start for VM $vmid\n";
2113 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2114 PVE
::Tools
::run_command
($cmd);
2118 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2125 syslog
('info', "start VM $vmid: $upid\n");
2127 my $migrate_opts = {
2128 migratedfrom
=> $migratedfrom,
2129 spice_ticket
=> $spice_ticket,
2130 network
=> $migration_network,
2131 type
=> $migration_type,
2132 storagemap
=> $storagemap,
2133 nbd_proto_version
=> $nbd_protocol_version,
2134 replicated_volumes
=> $replicated_volumes,
2138 statefile
=> $stateuri,
2139 skiplock
=> $skiplock,
2140 forcemachine
=> $machine,
2141 timeout
=> $timeout,
2142 forcecpu
=> $force_cpu,
2145 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2149 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2153 __PACKAGE__-
>register_method({
2155 path
=> '{vmid}/status/stop',
2159 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2160 "is akin to pulling the power plug of a running computer and may damage the VM data",
2162 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2165 additionalProperties
=> 0,
2167 node
=> get_standard_option
('pve-node'),
2168 vmid
=> get_standard_option
('pve-vmid',
2169 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2170 skiplock
=> get_standard_option
('skiplock'),
2171 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2173 description
=> "Wait maximal timeout seconds.",
2179 description
=> "Do not deactivate storage volumes.",
2192 my $rpcenv = PVE
::RPCEnvironment
::get
();
2193 my $authuser = $rpcenv->get_user();
2195 my $node = extract_param
($param, 'node');
2196 my $vmid = extract_param
($param, 'vmid');
2198 my $skiplock = extract_param
($param, 'skiplock');
2199 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2200 if $skiplock && $authuser ne 'root@pam';
2202 my $keepActive = extract_param
($param, 'keepActive');
2203 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2204 if $keepActive && $authuser ne 'root@pam';
2206 my $migratedfrom = extract_param
($param, 'migratedfrom');
2207 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2208 if $migratedfrom && $authuser ne 'root@pam';
2211 my $storecfg = PVE
::Storage
::config
();
2213 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2218 print "Requesting HA stop for VM $vmid\n";
2220 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2221 PVE
::Tools
::run_command
($cmd);
2225 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2231 syslog
('info', "stop VM $vmid: $upid\n");
2233 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2234 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2238 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2242 __PACKAGE__-
>register_method({
2244 path
=> '{vmid}/status/reset',
2248 description
=> "Reset virtual machine.",
2250 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2253 additionalProperties
=> 0,
2255 node
=> get_standard_option
('pve-node'),
2256 vmid
=> get_standard_option
('pve-vmid',
2257 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2258 skiplock
=> get_standard_option
('skiplock'),
2267 my $rpcenv = PVE
::RPCEnvironment
::get
();
2269 my $authuser = $rpcenv->get_user();
2271 my $node = extract_param
($param, 'node');
2273 my $vmid = extract_param
($param, 'vmid');
2275 my $skiplock = extract_param
($param, 'skiplock');
2276 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2277 if $skiplock && $authuser ne 'root@pam';
2279 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2284 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2289 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2292 __PACKAGE__-
>register_method({
2293 name
=> 'vm_shutdown',
2294 path
=> '{vmid}/status/shutdown',
2298 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2299 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2301 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2304 additionalProperties
=> 0,
2306 node
=> get_standard_option
('pve-node'),
2307 vmid
=> get_standard_option
('pve-vmid',
2308 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2309 skiplock
=> get_standard_option
('skiplock'),
2311 description
=> "Wait maximal timeout seconds.",
2317 description
=> "Make sure the VM stops.",
2323 description
=> "Do not deactivate storage volumes.",
2336 my $rpcenv = PVE
::RPCEnvironment
::get
();
2337 my $authuser = $rpcenv->get_user();
2339 my $node = extract_param
($param, 'node');
2340 my $vmid = extract_param
($param, 'vmid');
2342 my $skiplock = extract_param
($param, 'skiplock');
2343 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2344 if $skiplock && $authuser ne 'root@pam';
2346 my $keepActive = extract_param
($param, 'keepActive');
2347 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2348 if $keepActive && $authuser ne 'root@pam';
2350 my $storecfg = PVE
::Storage
::config
();
2354 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2355 # otherwise, we will infer a shutdown command, but run into the timeout,
2356 # then when the vm is resumed, it will instantly shutdown
2358 # checking the qmp status here to get feedback to the gui/cli/api
2359 # and the status query should not take too long
2360 my $qmpstatus = eval {
2361 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2362 mon_cmd
($vmid, "query-status");
2366 if (!$err && $qmpstatus->{status
} eq "paused") {
2367 if ($param->{forceStop
}) {
2368 warn "VM is paused - stop instead of shutdown\n";
2371 die "VM is paused - cannot shutdown\n";
2375 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2377 my $timeout = $param->{timeout
} // 60;
2381 print "Requesting HA stop for VM $vmid\n";
2383 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2384 PVE
::Tools
::run_command
($cmd);
2388 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2395 syslog
('info', "shutdown VM $vmid: $upid\n");
2397 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2398 $shutdown, $param->{forceStop
}, $keepActive);
2402 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2406 __PACKAGE__-
>register_method({
2407 name
=> 'vm_reboot',
2408 path
=> '{vmid}/status/reboot',
2412 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2414 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2417 additionalProperties
=> 0,
2419 node
=> get_standard_option
('pve-node'),
2420 vmid
=> get_standard_option
('pve-vmid',
2421 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2423 description
=> "Wait maximal timeout seconds for the shutdown.",
2436 my $rpcenv = PVE
::RPCEnvironment
::get
();
2437 my $authuser = $rpcenv->get_user();
2439 my $node = extract_param
($param, 'node');
2440 my $vmid = extract_param
($param, 'vmid');
2442 my $qmpstatus = eval {
2443 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2444 mon_cmd
($vmid, "query-status");
2448 if (!$err && $qmpstatus->{status
} eq "paused") {
2449 die "VM is paused - cannot shutdown\n";
2452 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2457 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2458 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2462 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2465 __PACKAGE__-
>register_method({
2466 name
=> 'vm_suspend',
2467 path
=> '{vmid}/status/suspend',
2471 description
=> "Suspend virtual machine.",
2473 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2474 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2475 " on the storage for the vmstate.",
2476 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2479 additionalProperties
=> 0,
2481 node
=> get_standard_option
('pve-node'),
2482 vmid
=> get_standard_option
('pve-vmid',
2483 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2484 skiplock
=> get_standard_option
('skiplock'),
2489 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2491 statestorage
=> get_standard_option
('pve-storage-id', {
2492 description
=> "The storage for the VM state",
2493 requires
=> 'todisk',
2495 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2505 my $rpcenv = PVE
::RPCEnvironment
::get
();
2506 my $authuser = $rpcenv->get_user();
2508 my $node = extract_param
($param, 'node');
2509 my $vmid = extract_param
($param, 'vmid');
2511 my $todisk = extract_param
($param, 'todisk') // 0;
2513 my $statestorage = extract_param
($param, 'statestorage');
2515 my $skiplock = extract_param
($param, 'skiplock');
2516 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2517 if $skiplock && $authuser ne 'root@pam';
2519 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2521 die "Cannot suspend HA managed VM to disk\n"
2522 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2524 # early check for storage permission, for better user feedback
2526 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2528 if (!$statestorage) {
2529 # get statestorage from config if none is given
2530 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2531 my $storecfg = PVE
::Storage
::config
();
2532 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2535 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2541 syslog
('info', "suspend VM $vmid: $upid\n");
2543 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2548 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2549 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2552 __PACKAGE__-
>register_method({
2553 name
=> 'vm_resume',
2554 path
=> '{vmid}/status/resume',
2558 description
=> "Resume virtual machine.",
2560 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2563 additionalProperties
=> 0,
2565 node
=> get_standard_option
('pve-node'),
2566 vmid
=> get_standard_option
('pve-vmid',
2567 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2568 skiplock
=> get_standard_option
('skiplock'),
2569 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2579 my $rpcenv = PVE
::RPCEnvironment
::get
();
2581 my $authuser = $rpcenv->get_user();
2583 my $node = extract_param
($param, 'node');
2585 my $vmid = extract_param
($param, 'vmid');
2587 my $skiplock = extract_param
($param, 'skiplock');
2588 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2589 if $skiplock && $authuser ne 'root@pam';
2591 my $nocheck = extract_param
($param, 'nocheck');
2593 my $to_disk_suspended;
2595 PVE
::QemuConfig-
>lock_config($vmid, sub {
2596 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2597 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2601 die "VM $vmid not running\n"
2602 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2607 syslog
('info', "resume VM $vmid: $upid\n");
2609 if (!$to_disk_suspended) {
2610 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2612 my $storecfg = PVE
::Storage
::config
();
2613 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2619 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2622 __PACKAGE__-
>register_method({
2623 name
=> 'vm_sendkey',
2624 path
=> '{vmid}/sendkey',
2628 description
=> "Send key event to virtual machine.",
2630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2633 additionalProperties
=> 0,
2635 node
=> get_standard_option
('pve-node'),
2636 vmid
=> get_standard_option
('pve-vmid',
2637 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2638 skiplock
=> get_standard_option
('skiplock'),
2640 description
=> "The key (qemu monitor encoding).",
2645 returns
=> { type
=> 'null'},
2649 my $rpcenv = PVE
::RPCEnvironment
::get
();
2651 my $authuser = $rpcenv->get_user();
2653 my $node = extract_param
($param, 'node');
2655 my $vmid = extract_param
($param, 'vmid');
2657 my $skiplock = extract_param
($param, 'skiplock');
2658 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2659 if $skiplock && $authuser ne 'root@pam';
2661 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2666 __PACKAGE__-
>register_method({
2667 name
=> 'vm_feature',
2668 path
=> '{vmid}/feature',
2672 description
=> "Check if feature for virtual machine is available.",
2674 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2677 additionalProperties
=> 0,
2679 node
=> get_standard_option
('pve-node'),
2680 vmid
=> get_standard_option
('pve-vmid'),
2682 description
=> "Feature to check.",
2684 enum
=> [ 'snapshot', 'clone', 'copy' ],
2686 snapname
=> get_standard_option
('pve-snapshot-name', {
2694 hasFeature
=> { type
=> 'boolean' },
2697 items
=> { type
=> 'string' },
2704 my $node = extract_param
($param, 'node');
2706 my $vmid = extract_param
($param, 'vmid');
2708 my $snapname = extract_param
($param, 'snapname');
2710 my $feature = extract_param
($param, 'feature');
2712 my $running = PVE
::QemuServer
::check_running
($vmid);
2714 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2717 my $snap = $conf->{snapshots
}->{$snapname};
2718 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2721 my $storecfg = PVE
::Storage
::config
();
2723 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2724 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2727 hasFeature
=> $hasFeature,
2728 nodes
=> [ keys %$nodelist ],
2732 __PACKAGE__-
>register_method({
2734 path
=> '{vmid}/clone',
2738 description
=> "Create a copy of virtual machine/template.",
2740 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2741 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2742 "'Datastore.AllocateSpace' on any used storage.",
2745 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2747 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2748 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2753 additionalProperties
=> 0,
2755 node
=> get_standard_option
('pve-node'),
2756 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2757 newid
=> get_standard_option
('pve-vmid', {
2758 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2759 description
=> 'VMID for the clone.' }),
2762 type
=> 'string', format
=> 'dns-name',
2763 description
=> "Set a name for the new VM.",
2768 description
=> "Description for the new VM.",
2772 type
=> 'string', format
=> 'pve-poolid',
2773 description
=> "Add the new VM to the specified pool.",
2775 snapname
=> get_standard_option
('pve-snapshot-name', {
2778 storage
=> get_standard_option
('pve-storage-id', {
2779 description
=> "Target storage for full clone.",
2783 description
=> "Target format for file storage. Only valid for full clone.",
2786 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2791 description
=> "Create a full copy of all disks. This is always done when " .
2792 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2794 target
=> get_standard_option
('pve-node', {
2795 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2799 description
=> "Override I/O bandwidth limit (in KiB/s).",
2803 default => 'clone limit from datacenter or storage config',
2813 my $rpcenv = PVE
::RPCEnvironment
::get
();
2814 my $authuser = $rpcenv->get_user();
2816 my $node = extract_param
($param, 'node');
2817 my $vmid = extract_param
($param, 'vmid');
2818 my $newid = extract_param
($param, 'newid');
2819 my $pool = extract_param
($param, 'pool');
2820 $rpcenv->check_pool_exist($pool) if defined($pool);
2822 my $snapname = extract_param
($param, 'snapname');
2823 my $storage = extract_param
($param, 'storage');
2824 my $format = extract_param
($param, 'format');
2825 my $target = extract_param
($param, 'target');
2827 my $localnode = PVE
::INotify
::nodename
();
2829 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2833 PVE
::Cluster
::check_node_exists
($target) if $target;
2835 my $storecfg = PVE
::Storage
::config
();
2838 # check if storage is enabled on local node
2839 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2841 # check if storage is available on target node
2842 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2843 # clone only works if target storage is shared
2844 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2845 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2849 PVE
::Cluster
::check_cfs_quorum
();
2851 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2853 # exclusive lock if VM is running - else shared lock is enough;
2854 my $shared_lock = $running ?
0 : 1;
2857 # do all tests after lock but before forking worker - if possible
2859 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2860 PVE
::QemuConfig-
>check_lock($conf);
2862 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2863 die "unexpected state change\n" if $verify_running != $running;
2865 die "snapshot '$snapname' does not exist\n"
2866 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2868 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2870 die "parameter 'storage' not allowed for linked clones\n"
2871 if defined($storage) && !$full;
2873 die "parameter 'format' not allowed for linked clones\n"
2874 if defined($format) && !$full;
2876 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2878 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2880 die "can't clone VM to node '$target' (VM uses local storage)\n"
2881 if $target && !$sharedvm;
2883 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2884 die "unable to create VM $newid: config file already exists\n"
2887 my $newconf = { lock => 'clone' };
2892 foreach my $opt (keys %$oldconf) {
2893 my $value = $oldconf->{$opt};
2895 # do not copy snapshot related info
2896 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2897 $opt eq 'vmstate' || $opt eq 'snapstate';
2899 # no need to copy unused images, because VMID(owner) changes anyways
2900 next if $opt =~ m/^unused\d+$/;
2902 # always change MAC! address
2903 if ($opt =~ m/^net(\d+)$/) {
2904 my $net = PVE
::QemuServer
::parse_net
($value);
2905 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2906 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2907 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2908 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2909 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2910 die "unable to parse drive options for '$opt'\n" if !$drive;
2911 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2912 $newconf->{$opt} = $value; # simply copy configuration
2914 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2915 die "Full clone feature is not supported for drive '$opt'\n"
2916 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2917 $fullclone->{$opt} = 1;
2919 # not full means clone instead of copy
2920 die "Linked clone feature is not supported for drive '$opt'\n"
2921 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2923 $drives->{$opt} = $drive;
2924 push @$vollist, $drive->{file
};
2927 # copy everything else
2928 $newconf->{$opt} = $value;
2932 # auto generate a new uuid
2933 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2934 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2935 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2936 # auto generate a new vmgenid only if the option was set for template
2937 if ($newconf->{vmgenid
}) {
2938 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2941 delete $newconf->{template
};
2943 if ($param->{name
}) {
2944 $newconf->{name
} = $param->{name
};
2946 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2949 if ($param->{description
}) {
2950 $newconf->{description
} = $param->{description
};
2953 # create empty/temp config - this fails if VM already exists on other node
2954 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2955 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2960 my $newvollist = [];
2967 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2969 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2971 my $bwlimit = extract_param
($param, 'bwlimit');
2973 my $total_jobs = scalar(keys %{$drives});
2976 foreach my $opt (keys %$drives) {
2977 my $drive = $drives->{$opt};
2978 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2979 my $completion = $skipcomplete ?
'skip' : 'complete';
2981 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2982 my $storage_list = [ $src_sid ];
2983 push @$storage_list, $storage if defined($storage);
2984 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2986 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2987 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2988 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
2990 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2992 PVE
::QemuConfig-
>write_config($newid, $newconf);
2996 delete $newconf->{lock};
2998 # do not write pending changes
2999 if (my @changes = keys %{$newconf->{pending
}}) {
3000 my $pending = join(',', @changes);
3001 warn "found pending changes for '$pending', discarding for clone\n";
3002 delete $newconf->{pending
};
3005 PVE
::QemuConfig-
>write_config($newid, $newconf);
3008 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3009 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3010 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3012 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3013 die "Failed to move config to node '$target' - rename failed: $!\n"
3014 if !rename($conffile, $newconffile);
3017 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3020 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3021 sleep 1; # some storage like rbd need to wait before release volume - really?
3023 foreach my $volid (@$newvollist) {
3024 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3028 PVE
::Firewall
::remove_vmfw_conf
($newid);
3030 unlink $conffile; # avoid races -> last thing before die
3032 die "clone failed: $err";
3038 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3040 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3043 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3044 # Aquire exclusive lock lock for $newid
3045 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3050 __PACKAGE__-
>register_method({
3051 name
=> 'move_vm_disk',
3052 path
=> '{vmid}/move_disk',
3056 description
=> "Move volume to different storage.",
3058 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3060 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3061 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3065 additionalProperties
=> 0,
3067 node
=> get_standard_option
('pve-node'),
3068 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3071 description
=> "The disk you want to move.",
3072 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3074 storage
=> get_standard_option
('pve-storage-id', {
3075 description
=> "Target storage.",
3076 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3080 description
=> "Target Format.",
3081 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3086 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3092 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3097 description
=> "Override I/O bandwidth limit (in KiB/s).",
3101 default => 'move limit from datacenter or storage config',
3107 description
=> "the task ID.",
3112 my $rpcenv = PVE
::RPCEnvironment
::get
();
3113 my $authuser = $rpcenv->get_user();
3115 my $node = extract_param
($param, 'node');
3116 my $vmid = extract_param
($param, 'vmid');
3117 my $digest = extract_param
($param, 'digest');
3118 my $disk = extract_param
($param, 'disk');
3119 my $storeid = extract_param
($param, 'storage');
3120 my $format = extract_param
($param, 'format');
3122 my $storecfg = PVE
::Storage
::config
();
3124 my $updatefn = sub {
3125 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3126 PVE
::QemuConfig-
>check_lock($conf);
3128 die "VM config checksum missmatch (file change by other user?)\n"
3129 if $digest && $digest ne $conf->{digest
};
3131 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3133 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3135 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3136 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3138 my $old_volid = $drive->{file
};
3140 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3141 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3145 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3146 (!$format || !$oldfmt || $oldfmt eq $format);
3148 # this only checks snapshots because $disk is passed!
3149 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3150 die "you can't move a disk with snapshots and delete the source\n"
3151 if $snapshotted && $param->{delete};
3153 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3155 my $running = PVE
::QemuServer
::check_running
($vmid);
3157 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3160 my $newvollist = [];
3166 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3168 warn "moving disk with snapshots, snapshots will not be moved!\n"
3171 my $bwlimit = extract_param
($param, 'bwlimit');
3172 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3174 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3175 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3177 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3179 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3181 # convert moved disk to base if part of template
3182 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3183 if PVE
::QemuConfig-
>is_template($conf);
3185 PVE
::QemuConfig-
>write_config($vmid, $conf);
3187 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3188 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3189 eval { mon_cmd
($vmid, "guest-fstrim") };
3193 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3194 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3200 foreach my $volid (@$newvollist) {
3201 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3204 die "storage migration failed: $err";
3207 if ($param->{delete}) {
3209 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3210 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3216 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3219 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3222 my $check_vm_disks_local = sub {
3223 my ($storecfg, $vmconf, $vmid) = @_;
3225 my $local_disks = {};
3227 # add some more information to the disks e.g. cdrom
3228 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3229 my ($volid, $attr) = @_;
3231 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3233 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3234 return if $scfg->{shared
};
3236 # The shared attr here is just a special case where the vdisk
3237 # is marked as shared manually
3238 return if $attr->{shared
};
3239 return if $attr->{cdrom
} and $volid eq "none";
3241 if (exists $local_disks->{$volid}) {
3242 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3244 $local_disks->{$volid} = $attr;
3245 # ensure volid is present in case it's needed
3246 $local_disks->{$volid}->{volid
} = $volid;
3250 return $local_disks;
3253 __PACKAGE__-
>register_method({
3254 name
=> 'migrate_vm_precondition',
3255 path
=> '{vmid}/migrate',
3259 description
=> "Get preconditions for migration.",
3261 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3264 additionalProperties
=> 0,
3266 node
=> get_standard_option
('pve-node'),
3267 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3268 target
=> get_standard_option
('pve-node', {
3269 description
=> "Target node.",
3270 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3278 running
=> { type
=> 'boolean' },
3282 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3284 not_allowed_nodes
=> {
3287 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3291 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3293 local_resources
=> {
3295 description
=> "List local resources e.g. pci, usb"
3302 my $rpcenv = PVE
::RPCEnvironment
::get
();
3304 my $authuser = $rpcenv->get_user();
3306 PVE
::Cluster
::check_cfs_quorum
();
3310 my $vmid = extract_param
($param, 'vmid');
3311 my $target = extract_param
($param, 'target');
3312 my $localnode = PVE
::INotify
::nodename
();
3316 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3317 my $storecfg = PVE
::Storage
::config
();
3320 # try to detect errors early
3321 PVE
::QemuConfig-
>check_lock($vmconf);
3323 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3325 # if vm is not running, return target nodes where local storage is available
3326 # for offline migration
3327 if (!$res->{running
}) {
3328 $res->{allowed_nodes
} = [];
3329 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3330 delete $checked_nodes->{$localnode};
3332 foreach my $node (keys %$checked_nodes) {
3333 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3334 push @{$res->{allowed_nodes
}}, $node;
3338 $res->{not_allowed_nodes
} = $checked_nodes;
3342 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3343 $res->{local_disks
} = [ values %$local_disks ];;
3345 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3347 $res->{local_resources
} = $local_resources;
3354 __PACKAGE__-
>register_method({
3355 name
=> 'migrate_vm',
3356 path
=> '{vmid}/migrate',
3360 description
=> "Migrate virtual machine. Creates a new migration task.",
3362 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3365 additionalProperties
=> 0,
3367 node
=> get_standard_option
('pve-node'),
3368 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3369 target
=> get_standard_option
('pve-node', {
3370 description
=> "Target node.",
3371 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3375 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3380 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3385 enum
=> ['secure', 'insecure'],
3386 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3389 migration_network
=> {
3390 type
=> 'string', format
=> 'CIDR',
3391 description
=> "CIDR of the (sub) network that is used for migration.",
3394 "with-local-disks" => {
3396 description
=> "Enable live storage migration for local disk",
3399 targetstorage
=> get_standard_option
('pve-targetstorage', {
3400 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3403 description
=> "Override I/O bandwidth limit (in KiB/s).",
3407 default => 'migrate limit from datacenter or storage config',
3413 description
=> "the task ID.",
3418 my $rpcenv = PVE
::RPCEnvironment
::get
();
3419 my $authuser = $rpcenv->get_user();
3421 my $target = extract_param
($param, 'target');
3423 my $localnode = PVE
::INotify
::nodename
();
3424 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3426 PVE
::Cluster
::check_cfs_quorum
();
3428 PVE
::Cluster
::check_node_exists
($target);
3430 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3432 my $vmid = extract_param
($param, 'vmid');
3434 raise_param_exc
({ force
=> "Only root may use this option." })
3435 if $param->{force
} && $authuser ne 'root@pam';
3437 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3438 if $param->{migration_type
} && $authuser ne 'root@pam';
3440 # allow root only until better network permissions are available
3441 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3442 if $param->{migration_network
} && $authuser ne 'root@pam';
3445 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3447 # try to detect errors early
3449 PVE
::QemuConfig-
>check_lock($conf);
3451 if (PVE
::QemuServer
::check_running
($vmid)) {
3452 die "can't migrate running VM without --online\n" if !$param->{online
};
3454 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3455 $param->{online
} = 0;
3458 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3459 if !$param->{online
} && $param->{targetstorage
};
3461 my $storecfg = PVE
::Storage
::config
();
3463 if (my $targetstorage = $param->{targetstorage
}) {
3464 my $check_storage = sub {
3465 my ($target_sid) = @_;
3466 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3467 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3468 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3469 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3470 if !$scfg->{content
}->{images
};
3473 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3474 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3477 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3478 if !defined($storagemap->{identity
});
3480 foreach my $source (values %{$storagemap->{entries
}}) {
3481 $check_storage->($source);
3484 $check_storage->($storagemap->{default})
3485 if $storagemap->{default};
3487 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3488 if $storagemap->{identity
};
3490 $param->{storagemap
} = $storagemap;
3492 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3495 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3500 print "Requesting HA migration for VM $vmid to node $target\n";
3502 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3503 PVE
::Tools
::run_command
($cmd);
3507 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3512 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3516 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3519 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3524 __PACKAGE__-
>register_method({
3526 path
=> '{vmid}/monitor',
3530 description
=> "Execute Qemu monitor commands.",
3532 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3533 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3536 additionalProperties
=> 0,
3538 node
=> get_standard_option
('pve-node'),
3539 vmid
=> get_standard_option
('pve-vmid'),
3542 description
=> "The monitor command.",
3546 returns
=> { type
=> 'string'},
3550 my $rpcenv = PVE
::RPCEnvironment
::get
();
3551 my $authuser = $rpcenv->get_user();
3554 my $command = shift;
3555 return $command =~ m/^\s*info(\s+|$)/
3556 || $command =~ m/^\s*help\s*$/;
3559 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3560 if !&$is_ro($param->{command
});
3562 my $vmid = $param->{vmid
};
3564 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3568 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3570 $res = "ERROR: $@" if $@;
3575 __PACKAGE__-
>register_method({
3576 name
=> 'resize_vm',
3577 path
=> '{vmid}/resize',
3581 description
=> "Extend volume size.",
3583 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3586 additionalProperties
=> 0,
3588 node
=> get_standard_option
('pve-node'),
3589 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3590 skiplock
=> get_standard_option
('skiplock'),
3593 description
=> "The disk you want to resize.",
3594 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3598 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3599 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.",
3603 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3609 returns
=> { type
=> 'null'},
3613 my $rpcenv = PVE
::RPCEnvironment
::get
();
3615 my $authuser = $rpcenv->get_user();
3617 my $node = extract_param
($param, 'node');
3619 my $vmid = extract_param
($param, 'vmid');
3621 my $digest = extract_param
($param, 'digest');
3623 my $disk = extract_param
($param, 'disk');
3625 my $sizestr = extract_param
($param, 'size');
3627 my $skiplock = extract_param
($param, 'skiplock');
3628 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3629 if $skiplock && $authuser ne 'root@pam';
3631 my $storecfg = PVE
::Storage
::config
();
3633 my $updatefn = sub {
3635 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3637 die "checksum missmatch (file change by other user?)\n"
3638 if $digest && $digest ne $conf->{digest
};
3639 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3641 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3643 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3645 my (undef, undef, undef, undef, undef, undef, $format) =
3646 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3648 die "can't resize volume: $disk if snapshot exists\n"
3649 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3651 my $volid = $drive->{file
};
3653 die "disk '$disk' has no associated volume\n" if !$volid;
3655 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3657 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3659 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3661 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3662 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3664 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3666 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3667 my ($ext, $newsize, $unit) = ($1, $2, $4);
3670 $newsize = $newsize * 1024;
3671 } elsif ($unit eq 'M') {
3672 $newsize = $newsize * 1024 * 1024;
3673 } elsif ($unit eq 'G') {
3674 $newsize = $newsize * 1024 * 1024 * 1024;
3675 } elsif ($unit eq 'T') {
3676 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3679 $newsize += $size if $ext;
3680 $newsize = int($newsize);
3682 die "shrinking disks is not supported\n" if $newsize < $size;
3684 return if $size == $newsize;
3686 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3688 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3690 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3691 $drive->{size
} = $effective_size // $newsize;
3692 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3694 PVE
::QemuConfig-
>write_config($vmid, $conf);
3697 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3701 __PACKAGE__-
>register_method({
3702 name
=> 'snapshot_list',
3703 path
=> '{vmid}/snapshot',
3705 description
=> "List all snapshots.",
3707 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3710 protected
=> 1, # qemu pid files are only readable by root
3712 additionalProperties
=> 0,
3714 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3715 node
=> get_standard_option
('pve-node'),
3724 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3728 description
=> "Snapshot includes RAM.",
3733 description
=> "Snapshot description.",
3737 description
=> "Snapshot creation time",
3739 renderer
=> 'timestamp',
3743 description
=> "Parent snapshot identifier.",
3749 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3754 my $vmid = $param->{vmid
};
3756 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3757 my $snaphash = $conf->{snapshots
} || {};
3761 foreach my $name (keys %$snaphash) {
3762 my $d = $snaphash->{$name};
3765 snaptime
=> $d->{snaptime
} || 0,
3766 vmstate
=> $d->{vmstate
} ?
1 : 0,
3767 description
=> $d->{description
} || '',
3769 $item->{parent
} = $d->{parent
} if $d->{parent
};
3770 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3774 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3777 digest
=> $conf->{digest
},
3778 running
=> $running,
3779 description
=> "You are here!",
3781 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3783 push @$res, $current;
3788 __PACKAGE__-
>register_method({
3790 path
=> '{vmid}/snapshot',
3794 description
=> "Snapshot a VM.",
3796 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3799 additionalProperties
=> 0,
3801 node
=> get_standard_option
('pve-node'),
3802 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3803 snapname
=> get_standard_option
('pve-snapshot-name'),
3807 description
=> "Save the vmstate",
3812 description
=> "A textual description or comment.",
3818 description
=> "the task ID.",
3823 my $rpcenv = PVE
::RPCEnvironment
::get
();
3825 my $authuser = $rpcenv->get_user();
3827 my $node = extract_param
($param, 'node');
3829 my $vmid = extract_param
($param, 'vmid');
3831 my $snapname = extract_param
($param, 'snapname');
3833 die "unable to use snapshot name 'current' (reserved name)\n"
3834 if $snapname eq 'current';
3836 die "unable to use snapshot name 'pending' (reserved name)\n"
3837 if lc($snapname) eq 'pending';
3840 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3841 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3842 $param->{description
});
3845 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3848 __PACKAGE__-
>register_method({
3849 name
=> 'snapshot_cmd_idx',
3850 path
=> '{vmid}/snapshot/{snapname}',
3857 additionalProperties
=> 0,
3859 vmid
=> get_standard_option
('pve-vmid'),
3860 node
=> get_standard_option
('pve-node'),
3861 snapname
=> get_standard_option
('pve-snapshot-name'),
3870 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3877 push @$res, { cmd
=> 'rollback' };
3878 push @$res, { cmd
=> 'config' };
3883 __PACKAGE__-
>register_method({
3884 name
=> 'update_snapshot_config',
3885 path
=> '{vmid}/snapshot/{snapname}/config',
3889 description
=> "Update snapshot metadata.",
3891 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3894 additionalProperties
=> 0,
3896 node
=> get_standard_option
('pve-node'),
3897 vmid
=> get_standard_option
('pve-vmid'),
3898 snapname
=> get_standard_option
('pve-snapshot-name'),
3902 description
=> "A textual description or comment.",
3906 returns
=> { type
=> 'null' },
3910 my $rpcenv = PVE
::RPCEnvironment
::get
();
3912 my $authuser = $rpcenv->get_user();
3914 my $vmid = extract_param
($param, 'vmid');
3916 my $snapname = extract_param
($param, 'snapname');
3918 return undef if !defined($param->{description
});
3920 my $updatefn = sub {
3922 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3924 PVE
::QemuConfig-
>check_lock($conf);
3926 my $snap = $conf->{snapshots
}->{$snapname};
3928 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3930 $snap->{description
} = $param->{description
} if defined($param->{description
});
3932 PVE
::QemuConfig-
>write_config($vmid, $conf);
3935 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3940 __PACKAGE__-
>register_method({
3941 name
=> 'get_snapshot_config',
3942 path
=> '{vmid}/snapshot/{snapname}/config',
3945 description
=> "Get snapshot configuration",
3947 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3950 additionalProperties
=> 0,
3952 node
=> get_standard_option
('pve-node'),
3953 vmid
=> get_standard_option
('pve-vmid'),
3954 snapname
=> get_standard_option
('pve-snapshot-name'),
3957 returns
=> { type
=> "object" },
3961 my $rpcenv = PVE
::RPCEnvironment
::get
();
3963 my $authuser = $rpcenv->get_user();
3965 my $vmid = extract_param
($param, 'vmid');
3967 my $snapname = extract_param
($param, 'snapname');
3969 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3971 my $snap = $conf->{snapshots
}->{$snapname};
3973 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3978 __PACKAGE__-
>register_method({
3980 path
=> '{vmid}/snapshot/{snapname}/rollback',
3984 description
=> "Rollback VM state to specified snapshot.",
3986 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3989 additionalProperties
=> 0,
3991 node
=> get_standard_option
('pve-node'),
3992 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3993 snapname
=> get_standard_option
('pve-snapshot-name'),
3998 description
=> "the task ID.",
4003 my $rpcenv = PVE
::RPCEnvironment
::get
();
4005 my $authuser = $rpcenv->get_user();
4007 my $node = extract_param
($param, 'node');
4009 my $vmid = extract_param
($param, 'vmid');
4011 my $snapname = extract_param
($param, 'snapname');
4014 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4015 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4019 # hold migration lock, this makes sure that nobody create replication snapshots
4020 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4023 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4026 __PACKAGE__-
>register_method({
4027 name
=> 'delsnapshot',
4028 path
=> '{vmid}/snapshot/{snapname}',
4032 description
=> "Delete a VM snapshot.",
4034 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4037 additionalProperties
=> 0,
4039 node
=> get_standard_option
('pve-node'),
4040 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4041 snapname
=> get_standard_option
('pve-snapshot-name'),
4045 description
=> "For removal from config file, even if removing disk snapshots fails.",
4051 description
=> "the task ID.",
4056 my $rpcenv = PVE
::RPCEnvironment
::get
();
4058 my $authuser = $rpcenv->get_user();
4060 my $node = extract_param
($param, 'node');
4062 my $vmid = extract_param
($param, 'vmid');
4064 my $snapname = extract_param
($param, 'snapname');
4067 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4068 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4071 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4074 __PACKAGE__-
>register_method({
4076 path
=> '{vmid}/template',
4080 description
=> "Create a Template.",
4082 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4083 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4086 additionalProperties
=> 0,
4088 node
=> get_standard_option
('pve-node'),
4089 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4093 description
=> "If you want to convert only 1 disk to base image.",
4094 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4099 returns
=> { type
=> 'null'},
4103 my $rpcenv = PVE
::RPCEnvironment
::get
();
4105 my $authuser = $rpcenv->get_user();
4107 my $node = extract_param
($param, 'node');
4109 my $vmid = extract_param
($param, 'vmid');
4111 my $disk = extract_param
($param, 'disk');
4113 my $updatefn = sub {
4115 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4117 PVE
::QemuConfig-
>check_lock($conf);
4119 die "unable to create template, because VM contains snapshots\n"
4120 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4122 die "you can't convert a template to a template\n"
4123 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4125 die "you can't convert a VM to template if VM is running\n"
4126 if PVE
::QemuServer
::check_running
($vmid);
4129 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4132 $conf->{template
} = 1;
4133 PVE
::QemuConfig-
>write_config($vmid, $conf);
4135 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4138 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4142 __PACKAGE__-
>register_method({
4143 name
=> 'cloudinit_generated_config_dump',
4144 path
=> '{vmid}/cloudinit/dump',
4147 description
=> "Get automatically generated cloudinit config.",
4149 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4152 additionalProperties
=> 0,
4154 node
=> get_standard_option
('pve-node'),
4155 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4157 description
=> 'Config type.',
4159 enum
=> ['user', 'network', 'meta'],
4169 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4171 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});