1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
26 use PVE
::RPCEnvironment
;
27 use PVE
::AccessControl
;
31 use PVE
::API2
::Firewall
::VM
;
32 use PVE
::API2
::Qemu
::Agent
;
33 use PVE
::VZDump
::Plugin
;
34 use PVE
::DataCenterConfig
;
38 if (!$ENV{PVE_GENERATING_DOCS
}) {
39 require PVE
::HA
::Env
::PVE2
;
40 import PVE
::HA
::Env
::PVE2
;
41 require PVE
::HA
::Config
;
42 import PVE
::HA
::Config
;
46 use Data
::Dumper
; # fixme: remove
48 use base
qw(PVE::RESTHandler);
50 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
52 my $resolve_cdrom_alias = sub {
55 if (my $value = $param->{cdrom
}) {
56 $value .= ",media=cdrom" if $value !~ m/media=/;
57 $param->{ide2
} = $value;
58 delete $param->{cdrom
};
62 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
63 my $check_storage_access = sub {
64 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
66 PVE
::QemuConfig-
>foreach_volume($settings, sub {
67 my ($ds, $drive) = @_;
69 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
71 my $volid = $drive->{file
};
72 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
74 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
76 } elsif ($isCDROM && ($volid eq 'cdrom')) {
77 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
83 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
84 if !$scfg->{content
}->{images
};
86 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
90 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
91 if defined($settings->{vmstatestorage
});
94 my $check_storage_access_clone = sub {
95 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
99 PVE
::QemuConfig-
>foreach_volume($conf, sub {
100 my ($ds, $drive) = @_;
102 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
104 my $volid = $drive->{file
};
106 return if !$volid || $volid eq 'none';
109 if ($volid eq 'cdrom') {
110 $rpcenv->check($authuser, "/", ['Sys.Console']);
112 # we simply allow access
113 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
115 $sharedvm = 0 if !$scfg->{shared
};
119 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
121 $sharedvm = 0 if !$scfg->{shared
};
123 $sid = $storage if $storage;
124 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
128 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
129 if defined($conf->{vmstatestorage
});
134 # Note: $pool is only needed when creating a VM, because pool permissions
135 # are automatically inherited if VM already exists inside a pool.
136 my $create_disks = sub {
137 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
144 my ($ds, $disk) = @_;
146 my $volid = $disk->{file
};
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
149 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
150 delete $disk->{size
};
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
152 } elsif (defined($volname) && $volname eq 'cloudinit') {
153 $storeid = $storeid // $default_storage;
154 die "no storage ID specified (and no default storage)\n" if !$storeid;
155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
156 my $name = "vm-$vmid-cloudinit";
160 $fmt = $disk->{format
} // "qcow2";
163 $fmt = $disk->{format
} // "raw";
166 # Initial disk created with 4 MB and aligned to 4MB on regeneration
167 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
168 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
169 $disk->{file
} = $volid;
170 $disk->{media
} = 'cdrom';
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
174 } elsif ($volid =~ $NEW_DISK_RE) {
175 my ($storeid, $size) = ($2 || $default_storage, $3);
176 die "no storage ID specified (and no default storage)\n" if !$storeid;
177 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
178 my $fmt = $disk->{format
} || $defformat;
180 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
183 if ($ds eq 'efidisk0') {
184 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
188 push @$vollist, $volid;
189 $disk->{file
} = $volid;
190 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
191 delete $disk->{format
}; # no longer needed
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
195 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
197 my $volid_is_new = 1;
200 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
201 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
206 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
208 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
210 die "volume $volid does not exist\n" if !$size;
212 $disk->{size
} = $size;
215 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
219 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
221 # free allocated images on error
223 syslog
('err', "VM $vmid creating disks failed");
224 foreach my $volid (@$vollist) {
225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
231 # modify vm config if everything went well
232 foreach my $ds (keys %$res) {
233 $conf->{$ds} = $res->{$ds};
250 my $memoryoptions = {
256 my $hwtypeoptions = {
269 my $generaloptions = {
276 'migrate_downtime' => 1,
277 'migrate_speed' => 1,
290 my $vmpoweroptions = {
297 'vmstatestorage' => 1,
300 my $cloudinitoptions = {
310 my $check_vm_modify_config_perm = sub {
311 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
313 return 1 if $authuser eq 'root@pam';
315 foreach my $opt (@$key_list) {
316 # some checks (e.g., disk, serial port, usb) need to be done somewhere
317 # else, as there the permission can be value dependend
318 next if PVE
::QemuServer
::is_valid_drivename
($opt);
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 } elsif ($opt eq 'vmstate') {
342 # the user needs Disk and PowerMgmt privileges to change the vmstate
343 # also needs privileges on the storage, that will be checked later
344 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
346 # catches hostpci\d+, args, lock, etc.
347 # new options will be checked here
348 die "only root can set '$opt' config\n";
355 __PACKAGE__-
>register_method({
359 description
=> "Virtual machine index (per node).",
361 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
365 protected
=> 1, # qemu pid files are only readable by root
367 additionalProperties
=> 0,
369 node
=> get_standard_option
('pve-node'),
373 description
=> "Determine the full status of active VMs.",
381 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
383 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
388 my $rpcenv = PVE
::RPCEnvironment
::get
();
389 my $authuser = $rpcenv->get_user();
391 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
394 foreach my $vmid (keys %$vmstatus) {
395 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
397 my $data = $vmstatus->{$vmid};
404 my $parse_restore_archive = sub {
405 my ($storecfg, $archive) = @_;
407 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
409 if (defined($archive_storeid)) {
410 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
411 if ($scfg->{type
} eq 'pbs') {
418 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 __PACKAGE__-
>register_method({
430 description
=> "Create or restore a virtual machine.",
432 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
433 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
434 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
435 user
=> 'all', # check inside
440 additionalProperties
=> 0,
441 properties
=> PVE
::QemuServer
::json_config_properties
(
443 node
=> get_standard_option
('pve-node'),
444 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
446 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
450 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
452 storage
=> get_standard_option
('pve-storage-id', {
453 description
=> "Default storage.",
455 completion
=> \
&PVE
::QemuServer
::complete_storage
,
460 description
=> "Allow to overwrite existing VM.",
461 requires
=> 'archive',
466 description
=> "Assign a unique random ethernet address.",
467 requires
=> 'archive',
471 type
=> 'string', format
=> 'pve-poolid',
472 description
=> "Add the VM to the specified pool.",
475 description
=> "Override I/O bandwidth limit (in KiB/s).",
479 default => 'restore limit from datacenter or storage config',
485 description
=> "Start VM after it was created successfully.",
495 my $rpcenv = PVE
::RPCEnvironment
::get
();
496 my $authuser = $rpcenv->get_user();
498 my $node = extract_param
($param, 'node');
499 my $vmid = extract_param
($param, 'vmid');
501 my $archive = extract_param
($param, 'archive');
502 my $is_restore = !!$archive;
504 my $bwlimit = extract_param
($param, 'bwlimit');
505 my $force = extract_param
($param, 'force');
506 my $pool = extract_param
($param, 'pool');
507 my $start_after_create = extract_param
($param, 'start');
508 my $storage = extract_param
($param, 'storage');
509 my $unique = extract_param
($param, 'unique');
511 if (defined(my $ssh_keys = $param->{sshkeys
})) {
512 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
513 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
516 PVE
::Cluster
::check_cfs_quorum
();
518 my $filename = PVE
::QemuConfig-
>config_file($vmid);
519 my $storecfg = PVE
::Storage
::config
();
521 if (defined($pool)) {
522 $rpcenv->check_pool_exist($pool);
525 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
526 if defined($storage);
528 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
530 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
532 } elsif ($archive && $force && (-f
$filename) &&
533 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
534 # OK: user has VM.Backup permissions, and want to restore an existing VM
540 &$resolve_cdrom_alias($param);
542 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
544 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
546 foreach my $opt (keys %$param) {
547 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
548 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
549 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
551 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
552 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
556 PVE
::QemuServer
::add_random_macs
($param);
558 my $keystr = join(' ', keys %$param);
559 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
561 if ($archive eq '-') {
562 die "pipe requires cli environment\n"
563 if $rpcenv->{type
} ne 'cli';
564 $archive = { type
=> 'pipe' };
566 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
568 $archive = $parse_restore_archive->($storecfg, $archive);
572 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
574 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
575 die "$emsg $@" if $@;
577 my $restorefn = sub {
578 my $conf = PVE
::QemuConfig-
>load_config($vmid);
580 PVE
::QemuConfig-
>check_protection($conf, $emsg);
582 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
585 my $restore_options = {
591 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
592 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
593 } elsif ($archive->{type
} eq 'pbs') {
594 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
596 die "unknown backup archive type\n";
598 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
599 # Convert restored VM to template if backup was VM template
600 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
601 warn "Convert to template.\n";
602 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
606 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
609 # ensure no old replication state are exists
610 PVE
::ReplicationState
::delete_guest_states
($vmid);
612 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
614 if ($start_after_create) {
615 print "Execute autostart\n";
616 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
622 # ensure no old replication state are exists
623 PVE
::ReplicationState
::delete_guest_states
($vmid);
627 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
631 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
633 if (!$conf->{bootdisk
}) {
634 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
635 $conf->{bootdisk
} = $firstdisk if $firstdisk;
638 # auto generate uuid if user did not specify smbios1 option
639 if (!$conf->{smbios1
}) {
640 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
643 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
644 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
647 PVE
::QemuConfig-
>write_config($vmid, $conf);
653 foreach my $volid (@$vollist) {
654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
660 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
663 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
665 if ($start_after_create) {
666 print "Execute autostart\n";
667 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
672 my ($code, $worker_name);
674 $worker_name = 'qmrestore';
676 eval { $restorefn->() };
678 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
684 $worker_name = 'qmcreate';
686 eval { $createfn->() };
689 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
690 unlink($conffile) or die "failed to remove config file: $!\n";
698 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
701 __PACKAGE__-
>register_method({
706 description
=> "Directory index",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
722 subdir
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
731 { subdir
=> 'config' },
732 { subdir
=> 'pending' },
733 { subdir
=> 'status' },
734 { subdir
=> 'unlink' },
735 { subdir
=> 'vncproxy' },
736 { subdir
=> 'termproxy' },
737 { subdir
=> 'migrate' },
738 { subdir
=> 'resize' },
739 { subdir
=> 'move' },
741 { subdir
=> 'rrddata' },
742 { subdir
=> 'monitor' },
743 { subdir
=> 'agent' },
744 { subdir
=> 'snapshot' },
745 { subdir
=> 'spiceproxy' },
746 { subdir
=> 'sendkey' },
747 { subdir
=> 'firewall' },
753 __PACKAGE__-
>register_method ({
754 subclass
=> "PVE::API2::Firewall::VM",
755 path
=> '{vmid}/firewall',
758 __PACKAGE__-
>register_method ({
759 subclass
=> "PVE::API2::Qemu::Agent",
760 path
=> '{vmid}/agent',
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/rrd',
767 protected
=> 1, # fixme: can we avoid that?
769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
771 description
=> "Read VM RRD statistics (returns PNG)",
773 additionalProperties
=> 0,
775 node
=> get_standard_option
('pve-node'),
776 vmid
=> get_standard_option
('pve-vmid'),
778 description
=> "Specify the time frame you are interested in.",
780 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
783 description
=> "The list of datasources you want to display.",
784 type
=> 'string', format
=> 'pve-configid-list',
787 description
=> "The RRD consolidation function",
789 enum
=> [ 'AVERAGE', 'MAX' ],
797 filename
=> { type
=> 'string' },
803 return PVE
::RRD
::create_rrd_graph
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
},
805 $param->{ds
}, $param->{cf
});
809 __PACKAGE__-
>register_method({
811 path
=> '{vmid}/rrddata',
813 protected
=> 1, # fixme: can we avoid that?
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 description
=> "Read VM RRD statistics",
819 additionalProperties
=> 0,
821 node
=> get_standard_option
('pve-node'),
822 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "Specify the time frame you are interested in.",
826 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
829 description
=> "The RRD consolidation function",
831 enum
=> [ 'AVERAGE', 'MAX' ],
846 return PVE
::RRD
::create_rrd_data
(
847 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
856 description
=> "Get the virtual machine configuration with pending configuration " .
857 "changes applied. Set the 'current' parameter to get the current configuration instead.",
859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 additionalProperties
=> 0,
864 node
=> get_standard_option
('pve-node'),
865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
867 description
=> "Get current values (instead of pending values).",
872 snapshot
=> get_standard_option
('pve-snapshot-name', {
873 description
=> "Fetch config values from given snapshot.",
876 my ($cmd, $pname, $cur, $args) = @_;
877 PVE
::QemuConfig-
>snapshot_list($args->[0]);
883 description
=> "The VM configuration.",
885 properties
=> PVE
::QemuServer
::json_config_properties
({
888 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
895 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
896 current
=> "cannot use 'snapshot' parameter with 'current'"})
897 if ($param->{snapshot
} && $param->{current
});
900 if ($param->{snapshot
}) {
901 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
903 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
905 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
910 __PACKAGE__-
>register_method({
911 name
=> 'vm_pending',
912 path
=> '{vmid}/pending',
915 description
=> "Get the virtual machine configuration with both current and pending values.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
932 description
=> "Configuration option name.",
936 description
=> "Current value.",
941 description
=> "Pending value.",
946 description
=> "Indicates a pending delete request if present and not 0. " .
947 "The value 2 indicates a force-delete request.",
959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
961 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
963 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
964 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
966 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1075 return if $volname eq 'cloudinit';
1078 if ($volid =~ $NEW_DISK_RE) {
1080 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1082 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1084 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1085 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1086 return if $scfg->{shared
};
1087 die "cannot add non-replicatable volume to a replicated VM\n";
1090 foreach my $opt (keys %$param) {
1091 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1092 # cleanup drive path
1093 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1094 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1095 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1096 $check_replication->($drive);
1097 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1098 } elsif ($opt =~ m/^net(\d+)$/) {
1100 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1101 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1102 } elsif ($opt eq 'vmgenid') {
1103 if ($param->{$opt} eq '1') {
1104 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 } elsif ($opt eq 'hookscript') {
1107 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1108 raise_param_exc
({ $opt => $@ }) if $@;
1112 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1114 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1116 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1118 my $updatefn = sub {
1120 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1122 die "checksum missmatch (file change by other user?)\n"
1123 if $digest && $digest ne $conf->{digest
};
1125 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1126 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1127 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1128 delete $conf->{lock}; # for check lock check, not written out
1129 push @delete, 'lock'; # this is the real deal to write it out
1131 push @delete, 'runningmachine' if $conf->{runningmachine
};
1132 push @delete, 'runningcpu' if $conf->{runningcpu
};
1135 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1137 foreach my $opt (keys %$revert) {
1138 if (defined($conf->{$opt})) {
1139 $param->{$opt} = $conf->{$opt};
1140 } elsif (defined($conf->{pending
}->{$opt})) {
1145 if ($param->{memory
} || defined($param->{balloon
})) {
1146 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1147 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1149 die "balloon value too large (must be smaller than assigned memory)\n"
1150 if $balloon && $balloon > $maxmem;
1153 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1157 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1159 # write updates to pending section
1161 my $modified = {}; # record what $option we modify
1163 foreach my $opt (@delete) {
1164 $modified->{$opt} = 1;
1165 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1167 # value of what we want to delete, independent if pending or not
1168 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1169 if (!defined($val)) {
1170 warn "cannot delete '$opt' - not set in current configuration!\n";
1171 $modified->{$opt} = 0;
1174 my $is_pending_val = defined($conf->{pending
}->{$opt});
1175 delete $conf->{pending
}->{$opt};
1177 if ($opt =~ m/^unused/) {
1178 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1179 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1180 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1181 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1182 delete $conf->{$opt};
1183 PVE
::QemuConfig-
>write_config($vmid, $conf);
1185 } elsif ($opt eq 'vmstate') {
1186 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1187 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1188 delete $conf->{$opt};
1189 PVE
::QemuConfig-
>write_config($vmid, $conf);
1191 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1192 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1193 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1194 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1196 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1197 PVE
::QemuConfig-
>write_config($vmid, $conf);
1198 } elsif ($opt =~ m/^serial\d+$/) {
1199 if ($val eq 'socket') {
1200 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1201 } elsif ($authuser ne 'root@pam') {
1202 die "only root can delete '$opt' config for real devices\n";
1204 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1205 PVE
::QemuConfig-
>write_config($vmid, $conf);
1206 } elsif ($opt =~ m/^usb\d+$/) {
1207 if ($val =~ m/spice/) {
1208 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1209 } elsif ($authuser ne 'root@pam') {
1210 die "only root can delete '$opt' config for real devices\n";
1212 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1213 PVE
::QemuConfig-
>write_config($vmid, $conf);
1215 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1216 PVE
::QemuConfig-
>write_config($vmid, $conf);
1220 foreach my $opt (keys %$param) { # add/change
1221 $modified->{$opt} = 1;
1222 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1223 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1225 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1227 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1228 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1229 # FIXME: cloudinit: CDROM or Disk?
1230 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1231 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1233 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1235 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1236 if defined($conf->{pending
}->{$opt});
1238 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1239 } elsif ($opt =~ m/^serial\d+/) {
1240 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1241 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1242 } elsif ($authuser ne 'root@pam') {
1243 die "only root can modify '$opt' config for real devices\n";
1245 $conf->{pending
}->{$opt} = $param->{$opt};
1246 } elsif ($opt =~ m/^usb\d+/) {
1247 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1248 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1249 } elsif ($authuser ne 'root@pam') {
1250 die "only root can modify '$opt' config for real devices\n";
1252 $conf->{pending
}->{$opt} = $param->{$opt};
1254 $conf->{pending
}->{$opt} = $param->{$opt};
1256 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1257 PVE
::QemuConfig-
>write_config($vmid, $conf);
1260 # remove pending changes when nothing changed
1261 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1262 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1263 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1265 return if !scalar(keys %{$conf->{pending
}});
1267 my $running = PVE
::QemuServer
::check_running
($vmid);
1269 # apply pending changes
1271 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1275 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1277 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1279 raise_param_exc
($errors) if scalar(keys %$errors);
1288 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1290 if ($background_delay) {
1292 # Note: It would be better to do that in the Event based HTTPServer
1293 # to avoid blocking call to sleep.
1295 my $end_time = time() + $background_delay;
1297 my $task = PVE
::Tools
::upid_decode
($upid);
1300 while (time() < $end_time) {
1301 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1303 sleep(1); # this gets interrupted when child process ends
1307 my $status = PVE
::Tools
::upid_read_status
($upid);
1308 return undef if $status eq 'OK';
1317 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1320 my $vm_config_perm_list = [
1325 'VM.Config.Network',
1327 'VM.Config.Options',
1330 __PACKAGE__-
>register_method({
1331 name
=> 'update_vm_async',
1332 path
=> '{vmid}/config',
1336 description
=> "Set virtual machine options (asynchrounous API).",
1338 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1341 additionalProperties
=> 0,
1342 properties
=> PVE
::QemuServer
::json_config_properties
(
1344 node
=> get_standard_option
('pve-node'),
1345 vmid
=> get_standard_option
('pve-vmid'),
1346 skiplock
=> get_standard_option
('skiplock'),
1348 type
=> 'string', format
=> 'pve-configid-list',
1349 description
=> "A list of settings you want to delete.",
1353 type
=> 'string', format
=> 'pve-configid-list',
1354 description
=> "Revert a pending change.",
1359 description
=> $opt_force_description,
1361 requires
=> 'delete',
1365 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1369 background_delay
=> {
1371 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1382 code
=> $update_vm_api,
1385 __PACKAGE__-
>register_method({
1386 name
=> 'update_vm',
1387 path
=> '{vmid}/config',
1391 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1393 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1396 additionalProperties
=> 0,
1397 properties
=> PVE
::QemuServer
::json_config_properties
(
1399 node
=> get_standard_option
('pve-node'),
1400 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1401 skiplock
=> get_standard_option
('skiplock'),
1403 type
=> 'string', format
=> 'pve-configid-list',
1404 description
=> "A list of settings you want to delete.",
1408 type
=> 'string', format
=> 'pve-configid-list',
1409 description
=> "Revert a pending change.",
1414 description
=> $opt_force_description,
1416 requires
=> 'delete',
1420 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1426 returns
=> { type
=> 'null' },
1429 &$update_vm_api($param, 1);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'destroy_vm',
1440 description
=> "Destroy the vm (also delete all used/owned volumes).",
1442 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1449 skiplock
=> get_standard_option
('skiplock'),
1452 description
=> "Remove vmid from backup cron jobs.",
1463 my $rpcenv = PVE
::RPCEnvironment
::get
();
1464 my $authuser = $rpcenv->get_user();
1465 my $vmid = $param->{vmid
};
1467 my $skiplock = $param->{skiplock
};
1468 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1469 if $skiplock && $authuser ne 'root@pam';
1471 my $early_checks = sub {
1473 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1474 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1476 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1478 if (!$param->{purge
}) {
1479 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1481 # don't allow destroy if with replication jobs but no purge param
1482 my $repl_conf = PVE
::ReplicationConfig-
>new();
1483 $repl_conf->check_for_existing_jobs($vmid);
1486 die "VM $vmid is running - destroy failed\n"
1487 if PVE
::QemuServer
::check_running
($vmid);
1497 my $storecfg = PVE
::Storage
::config
();
1499 syslog
('info', "destroy VM $vmid: $upid\n");
1500 PVE
::QemuConfig-
>lock_config($vmid, sub {
1501 # repeat, config might have changed
1502 my $ha_managed = $early_checks->();
1504 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1506 PVE
::AccessControl
::remove_vm_access
($vmid);
1507 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1508 if ($param->{purge
}) {
1509 print "purging VM $vmid from related configurations..\n";
1510 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1511 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1514 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1515 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1519 # only now remove the zombie config, else we can have reuse race
1520 PVE
::QemuConfig-
>destroy_config($vmid);
1524 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1527 __PACKAGE__-
>register_method({
1529 path
=> '{vmid}/unlink',
1533 description
=> "Unlink/delete disk images.",
1535 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1538 additionalProperties
=> 0,
1540 node
=> get_standard_option
('pve-node'),
1541 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1543 type
=> 'string', format
=> 'pve-configid-list',
1544 description
=> "A list of disk IDs you want to delete.",
1548 description
=> $opt_force_description,
1553 returns
=> { type
=> 'null'},
1557 $param->{delete} = extract_param
($param, 'idlist');
1559 __PACKAGE__-
>update_vm($param);
1566 __PACKAGE__-
>register_method({
1568 path
=> '{vmid}/vncproxy',
1572 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1574 description
=> "Creates a TCP VNC proxy connections.",
1576 additionalProperties
=> 0,
1578 node
=> get_standard_option
('pve-node'),
1579 vmid
=> get_standard_option
('pve-vmid'),
1583 description
=> "starts websockify instead of vncproxy",
1588 additionalProperties
=> 0,
1590 user
=> { type
=> 'string' },
1591 ticket
=> { type
=> 'string' },
1592 cert
=> { type
=> 'string' },
1593 port
=> { type
=> 'integer' },
1594 upid
=> { type
=> 'string' },
1600 my $rpcenv = PVE
::RPCEnvironment
::get
();
1602 my $authuser = $rpcenv->get_user();
1604 my $vmid = $param->{vmid
};
1605 my $node = $param->{node
};
1606 my $websocket = $param->{websocket
};
1608 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1609 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1611 my $authpath = "/vms/$vmid";
1613 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1615 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1621 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1622 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1623 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1624 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1625 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1627 $family = PVE
::Tools
::get_host_address_family
($node);
1630 my $port = PVE
::Tools
::next_vnc_port
($family);
1637 syslog
('info', "starting vnc proxy $upid\n");
1643 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1645 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1646 '-timeout', $timeout, '-authpath', $authpath,
1647 '-perm', 'Sys.Console'];
1649 if ($param->{websocket
}) {
1650 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1651 push @$cmd, '-notls', '-listen', 'localhost';
1654 push @$cmd, '-c', @$remcmd, @$termcmd;
1656 PVE
::Tools
::run_command
($cmd);
1660 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1662 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1664 my $sock = IO
::Socket
::IP-
>new(
1669 GetAddrInfoFlags
=> 0,
1670 ) or die "failed to create socket: $!\n";
1671 # Inside the worker we shouldn't have any previous alarms
1672 # running anyway...:
1674 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1676 accept(my $cli, $sock) or die "connection failed: $!\n";
1679 if (PVE
::Tools
::run_command
($cmd,
1680 output
=> '>&'.fileno($cli),
1681 input
=> '<&'.fileno($cli),
1684 die "Failed to run vncproxy.\n";
1691 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1693 PVE
::Tools
::wait_for_vnc_port
($port);
1704 __PACKAGE__-
>register_method({
1705 name
=> 'termproxy',
1706 path
=> '{vmid}/termproxy',
1710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1712 description
=> "Creates a TCP proxy connections.",
1714 additionalProperties
=> 0,
1716 node
=> get_standard_option
('pve-node'),
1717 vmid
=> get_standard_option
('pve-vmid'),
1721 enum
=> [qw(serial0 serial1 serial2 serial3)],
1722 description
=> "opens a serial terminal (defaults to display)",
1727 additionalProperties
=> 0,
1729 user
=> { type
=> 'string' },
1730 ticket
=> { type
=> 'string' },
1731 port
=> { type
=> 'integer' },
1732 upid
=> { type
=> 'string' },
1738 my $rpcenv = PVE
::RPCEnvironment
::get
();
1740 my $authuser = $rpcenv->get_user();
1742 my $vmid = $param->{vmid
};
1743 my $node = $param->{node
};
1744 my $serial = $param->{serial
};
1746 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1748 if (!defined($serial)) {
1749 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1750 $serial = $conf->{vga
};
1754 my $authpath = "/vms/$vmid";
1756 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1761 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1762 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1763 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1764 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1765 push @$remcmd, '--';
1767 $family = PVE
::Tools
::get_host_address_family
($node);
1770 my $port = PVE
::Tools
::next_vnc_port
($family);
1772 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1773 push @$termcmd, '-iface', $serial if $serial;
1778 syslog
('info', "starting qemu termproxy $upid\n");
1780 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1781 '--perm', 'VM.Console', '--'];
1782 push @$cmd, @$remcmd, @$termcmd;
1784 PVE
::Tools
::run_command
($cmd);
1787 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1789 PVE
::Tools
::wait_for_vnc_port
($port);
1799 __PACKAGE__-
>register_method({
1800 name
=> 'vncwebsocket',
1801 path
=> '{vmid}/vncwebsocket',
1804 description
=> "You also need to pass a valid ticket (vncticket).",
1805 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1807 description
=> "Opens a weksocket for VNC traffic.",
1809 additionalProperties
=> 0,
1811 node
=> get_standard_option
('pve-node'),
1812 vmid
=> get_standard_option
('pve-vmid'),
1814 description
=> "Ticket from previous call to vncproxy.",
1819 description
=> "Port number returned by previous vncproxy call.",
1829 port
=> { type
=> 'string' },
1835 my $rpcenv = PVE
::RPCEnvironment
::get
();
1837 my $authuser = $rpcenv->get_user();
1839 my $vmid = $param->{vmid
};
1840 my $node = $param->{node
};
1842 my $authpath = "/vms/$vmid";
1844 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1846 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1848 # Note: VNC ports are acessible from outside, so we do not gain any
1849 # security if we verify that $param->{port} belongs to VM $vmid. This
1850 # check is done by verifying the VNC ticket (inside VNC protocol).
1852 my $port = $param->{port
};
1854 return { port
=> $port };
1857 __PACKAGE__-
>register_method({
1858 name
=> 'spiceproxy',
1859 path
=> '{vmid}/spiceproxy',
1864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1866 description
=> "Returns a SPICE configuration to connect to the VM.",
1868 additionalProperties
=> 0,
1870 node
=> get_standard_option
('pve-node'),
1871 vmid
=> get_standard_option
('pve-vmid'),
1872 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1875 returns
=> get_standard_option
('remote-viewer-config'),
1879 my $rpcenv = PVE
::RPCEnvironment
::get
();
1881 my $authuser = $rpcenv->get_user();
1883 my $vmid = $param->{vmid
};
1884 my $node = $param->{node
};
1885 my $proxy = $param->{proxy
};
1887 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1888 my $title = "VM $vmid";
1889 $title .= " - ". $conf->{name
} if $conf->{name
};
1891 my $port = PVE
::QemuServer
::spice_port
($vmid);
1893 my ($ticket, undef, $remote_viewer_config) =
1894 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1896 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1897 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1899 return $remote_viewer_config;
1902 __PACKAGE__-
>register_method({
1904 path
=> '{vmid}/status',
1907 description
=> "Directory index",
1912 additionalProperties
=> 0,
1914 node
=> get_standard_option
('pve-node'),
1915 vmid
=> get_standard_option
('pve-vmid'),
1923 subdir
=> { type
=> 'string' },
1926 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1932 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1935 { subdir
=> 'current' },
1936 { subdir
=> 'start' },
1937 { subdir
=> 'stop' },
1938 { subdir
=> 'reset' },
1939 { subdir
=> 'shutdown' },
1940 { subdir
=> 'suspend' },
1941 { subdir
=> 'reboot' },
1947 __PACKAGE__-
>register_method({
1948 name
=> 'vm_status',
1949 path
=> '{vmid}/status/current',
1952 protected
=> 1, # qemu pid files are only readable by root
1953 description
=> "Get virtual machine status.",
1955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1958 additionalProperties
=> 0,
1960 node
=> get_standard_option
('pve-node'),
1961 vmid
=> get_standard_option
('pve-vmid'),
1967 %$PVE::QemuServer
::vmstatus_return_properties
,
1969 description
=> "HA manager service status.",
1973 description
=> "Qemu VGA configuration supports spice.",
1978 description
=> "Qemu GuestAgent enabled in config.",
1988 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1990 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1991 my $status = $vmstatus->{$param->{vmid
}};
1993 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1995 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1996 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2001 __PACKAGE__-
>register_method({
2003 path
=> '{vmid}/status/start',
2007 description
=> "Start virtual machine.",
2009 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2012 additionalProperties
=> 0,
2014 node
=> get_standard_option
('pve-node'),
2015 vmid
=> get_standard_option
('pve-vmid',
2016 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2017 skiplock
=> get_standard_option
('skiplock'),
2018 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2019 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2022 enum
=> ['secure', 'insecure'],
2023 description
=> "Migration traffic is encrypted using an SSH " .
2024 "tunnel by default. On secure, completely private networks " .
2025 "this can be disabled to increase performance.",
2028 migration_network
=> {
2029 type
=> 'string', format
=> 'CIDR',
2030 description
=> "CIDR of the (sub) network that is used for migration.",
2033 machine
=> get_standard_option
('pve-qemu-machine'),
2035 description
=> "Override QEMU's -cpu argument with the given string.",
2039 targetstorage
=> get_standard_option
('pve-targetstorage'),
2041 description
=> "Wait maximal timeout seconds.",
2044 default => 'max(30, vm memory in GiB)',
2055 my $rpcenv = PVE
::RPCEnvironment
::get
();
2056 my $authuser = $rpcenv->get_user();
2058 my $node = extract_param
($param, 'node');
2059 my $vmid = extract_param
($param, 'vmid');
2060 my $timeout = extract_param
($param, 'timeout');
2062 my $machine = extract_param
($param, 'machine');
2063 my $force_cpu = extract_param
($param, 'force-cpu');
2065 my $get_root_param = sub {
2066 my $value = extract_param
($param, $_[0]);
2067 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2068 if $value && $authuser ne 'root@pam';
2072 my $stateuri = $get_root_param->('stateuri');
2073 my $skiplock = $get_root_param->('skiplock');
2074 my $migratedfrom = $get_root_param->('migratedfrom');
2075 my $migration_type = $get_root_param->('migration_type');
2076 my $migration_network = $get_root_param->('migration_network');
2077 my $targetstorage = $get_root_param->('targetstorage');
2081 if ($targetstorage) {
2082 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2084 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2085 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2089 # read spice ticket from STDIN
2091 my $nbd_protocol_version = 0;
2092 my $replicated_volumes = {};
2093 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2094 while (defined(my $line = <STDIN
>)) {
2096 if ($line =~ m/^spice_ticket: (.+)$/) {
2098 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2099 $nbd_protocol_version = $1;
2100 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2101 $replicated_volumes->{$1} = 1;
2103 # fallback for old source node
2104 $spice_ticket = $line;
2109 PVE
::Cluster
::check_cfs_quorum
();
2111 my $storecfg = PVE
::Storage
::config
();
2113 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2117 print "Requesting HA start for VM $vmid\n";
2119 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2120 PVE
::Tools
::run_command
($cmd);
2124 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2131 syslog
('info', "start VM $vmid: $upid\n");
2133 my $migrate_opts = {
2134 migratedfrom
=> $migratedfrom,
2135 spice_ticket
=> $spice_ticket,
2136 network
=> $migration_network,
2137 type
=> $migration_type,
2138 storagemap
=> $storagemap,
2139 nbd_proto_version
=> $nbd_protocol_version,
2140 replicated_volumes
=> $replicated_volumes,
2144 statefile
=> $stateuri,
2145 skiplock
=> $skiplock,
2146 forcemachine
=> $machine,
2147 timeout
=> $timeout,
2148 forcecpu
=> $force_cpu,
2151 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2155 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2159 __PACKAGE__-
>register_method({
2161 path
=> '{vmid}/status/stop',
2165 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2166 "is akin to pulling the power plug of a running computer and may damage the VM data",
2168 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2171 additionalProperties
=> 0,
2173 node
=> get_standard_option
('pve-node'),
2174 vmid
=> get_standard_option
('pve-vmid',
2175 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2176 skiplock
=> get_standard_option
('skiplock'),
2177 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2179 description
=> "Wait maximal timeout seconds.",
2185 description
=> "Do not deactivate storage volumes.",
2198 my $rpcenv = PVE
::RPCEnvironment
::get
();
2199 my $authuser = $rpcenv->get_user();
2201 my $node = extract_param
($param, 'node');
2202 my $vmid = extract_param
($param, 'vmid');
2204 my $skiplock = extract_param
($param, 'skiplock');
2205 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2206 if $skiplock && $authuser ne 'root@pam';
2208 my $keepActive = extract_param
($param, 'keepActive');
2209 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2210 if $keepActive && $authuser ne 'root@pam';
2212 my $migratedfrom = extract_param
($param, 'migratedfrom');
2213 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2214 if $migratedfrom && $authuser ne 'root@pam';
2217 my $storecfg = PVE
::Storage
::config
();
2219 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2224 print "Requesting HA stop for VM $vmid\n";
2226 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2227 PVE
::Tools
::run_command
($cmd);
2231 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2237 syslog
('info', "stop VM $vmid: $upid\n");
2239 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2240 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2244 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2248 __PACKAGE__-
>register_method({
2250 path
=> '{vmid}/status/reset',
2254 description
=> "Reset virtual machine.",
2256 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2259 additionalProperties
=> 0,
2261 node
=> get_standard_option
('pve-node'),
2262 vmid
=> get_standard_option
('pve-vmid',
2263 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2264 skiplock
=> get_standard_option
('skiplock'),
2273 my $rpcenv = PVE
::RPCEnvironment
::get
();
2275 my $authuser = $rpcenv->get_user();
2277 my $node = extract_param
($param, 'node');
2279 my $vmid = extract_param
($param, 'vmid');
2281 my $skiplock = extract_param
($param, 'skiplock');
2282 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2283 if $skiplock && $authuser ne 'root@pam';
2285 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2290 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2295 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2298 __PACKAGE__-
>register_method({
2299 name
=> 'vm_shutdown',
2300 path
=> '{vmid}/status/shutdown',
2304 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2305 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2307 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2310 additionalProperties
=> 0,
2312 node
=> get_standard_option
('pve-node'),
2313 vmid
=> get_standard_option
('pve-vmid',
2314 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2315 skiplock
=> get_standard_option
('skiplock'),
2317 description
=> "Wait maximal timeout seconds.",
2323 description
=> "Make sure the VM stops.",
2329 description
=> "Do not deactivate storage volumes.",
2342 my $rpcenv = PVE
::RPCEnvironment
::get
();
2343 my $authuser = $rpcenv->get_user();
2345 my $node = extract_param
($param, 'node');
2346 my $vmid = extract_param
($param, 'vmid');
2348 my $skiplock = extract_param
($param, 'skiplock');
2349 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2350 if $skiplock && $authuser ne 'root@pam';
2352 my $keepActive = extract_param
($param, 'keepActive');
2353 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2354 if $keepActive && $authuser ne 'root@pam';
2356 my $storecfg = PVE
::Storage
::config
();
2360 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2361 # otherwise, we will infer a shutdown command, but run into the timeout,
2362 # then when the vm is resumed, it will instantly shutdown
2364 # checking the qmp status here to get feedback to the gui/cli/api
2365 # and the status query should not take too long
2366 my $qmpstatus = eval {
2367 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2368 mon_cmd
($vmid, "query-status");
2372 if (!$err && $qmpstatus->{status
} eq "paused") {
2373 if ($param->{forceStop
}) {
2374 warn "VM is paused - stop instead of shutdown\n";
2377 die "VM is paused - cannot shutdown\n";
2381 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2383 my $timeout = $param->{timeout
} // 60;
2387 print "Requesting HA stop for VM $vmid\n";
2389 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2390 PVE
::Tools
::run_command
($cmd);
2394 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2401 syslog
('info', "shutdown VM $vmid: $upid\n");
2403 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2404 $shutdown, $param->{forceStop
}, $keepActive);
2408 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2412 __PACKAGE__-
>register_method({
2413 name
=> 'vm_reboot',
2414 path
=> '{vmid}/status/reboot',
2418 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2420 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2423 additionalProperties
=> 0,
2425 node
=> get_standard_option
('pve-node'),
2426 vmid
=> get_standard_option
('pve-vmid',
2427 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2429 description
=> "Wait maximal timeout seconds for the shutdown.",
2442 my $rpcenv = PVE
::RPCEnvironment
::get
();
2443 my $authuser = $rpcenv->get_user();
2445 my $node = extract_param
($param, 'node');
2446 my $vmid = extract_param
($param, 'vmid');
2448 my $qmpstatus = eval {
2449 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2450 mon_cmd
($vmid, "query-status");
2454 if (!$err && $qmpstatus->{status
} eq "paused") {
2455 die "VM is paused - cannot shutdown\n";
2458 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2463 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2464 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2468 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2471 __PACKAGE__-
>register_method({
2472 name
=> 'vm_suspend',
2473 path
=> '{vmid}/status/suspend',
2477 description
=> "Suspend virtual machine.",
2479 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2480 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2481 " on the storage for the vmstate.",
2482 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2485 additionalProperties
=> 0,
2487 node
=> get_standard_option
('pve-node'),
2488 vmid
=> get_standard_option
('pve-vmid',
2489 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2490 skiplock
=> get_standard_option
('skiplock'),
2495 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2497 statestorage
=> get_standard_option
('pve-storage-id', {
2498 description
=> "The storage for the VM state",
2499 requires
=> 'todisk',
2501 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2511 my $rpcenv = PVE
::RPCEnvironment
::get
();
2512 my $authuser = $rpcenv->get_user();
2514 my $node = extract_param
($param, 'node');
2515 my $vmid = extract_param
($param, 'vmid');
2517 my $todisk = extract_param
($param, 'todisk') // 0;
2519 my $statestorage = extract_param
($param, 'statestorage');
2521 my $skiplock = extract_param
($param, 'skiplock');
2522 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2523 if $skiplock && $authuser ne 'root@pam';
2525 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2527 die "Cannot suspend HA managed VM to disk\n"
2528 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2530 # early check for storage permission, for better user feedback
2532 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2534 if (!$statestorage) {
2535 # get statestorage from config if none is given
2536 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2537 my $storecfg = PVE
::Storage
::config
();
2538 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2541 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2547 syslog
('info', "suspend VM $vmid: $upid\n");
2549 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2554 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2555 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2558 __PACKAGE__-
>register_method({
2559 name
=> 'vm_resume',
2560 path
=> '{vmid}/status/resume',
2564 description
=> "Resume virtual machine.",
2566 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2569 additionalProperties
=> 0,
2571 node
=> get_standard_option
('pve-node'),
2572 vmid
=> get_standard_option
('pve-vmid',
2573 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2574 skiplock
=> get_standard_option
('skiplock'),
2575 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2585 my $rpcenv = PVE
::RPCEnvironment
::get
();
2587 my $authuser = $rpcenv->get_user();
2589 my $node = extract_param
($param, 'node');
2591 my $vmid = extract_param
($param, 'vmid');
2593 my $skiplock = extract_param
($param, 'skiplock');
2594 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2595 if $skiplock && $authuser ne 'root@pam';
2597 my $nocheck = extract_param
($param, 'nocheck');
2598 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2599 if $nocheck && $authuser ne 'root@pam';
2601 my $to_disk_suspended;
2603 PVE
::QemuConfig-
>lock_config($vmid, sub {
2604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2605 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2609 die "VM $vmid not running\n"
2610 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2615 syslog
('info', "resume VM $vmid: $upid\n");
2617 if (!$to_disk_suspended) {
2618 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2620 my $storecfg = PVE
::Storage
::config
();
2621 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2627 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2630 __PACKAGE__-
>register_method({
2631 name
=> 'vm_sendkey',
2632 path
=> '{vmid}/sendkey',
2636 description
=> "Send key event to virtual machine.",
2638 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2641 additionalProperties
=> 0,
2643 node
=> get_standard_option
('pve-node'),
2644 vmid
=> get_standard_option
('pve-vmid',
2645 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2646 skiplock
=> get_standard_option
('skiplock'),
2648 description
=> "The key (qemu monitor encoding).",
2653 returns
=> { type
=> 'null'},
2657 my $rpcenv = PVE
::RPCEnvironment
::get
();
2659 my $authuser = $rpcenv->get_user();
2661 my $node = extract_param
($param, 'node');
2663 my $vmid = extract_param
($param, 'vmid');
2665 my $skiplock = extract_param
($param, 'skiplock');
2666 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2667 if $skiplock && $authuser ne 'root@pam';
2669 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2674 __PACKAGE__-
>register_method({
2675 name
=> 'vm_feature',
2676 path
=> '{vmid}/feature',
2680 description
=> "Check if feature for virtual machine is available.",
2682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2685 additionalProperties
=> 0,
2687 node
=> get_standard_option
('pve-node'),
2688 vmid
=> get_standard_option
('pve-vmid'),
2690 description
=> "Feature to check.",
2692 enum
=> [ 'snapshot', 'clone', 'copy' ],
2694 snapname
=> get_standard_option
('pve-snapshot-name', {
2702 hasFeature
=> { type
=> 'boolean' },
2705 items
=> { type
=> 'string' },
2712 my $node = extract_param
($param, 'node');
2714 my $vmid = extract_param
($param, 'vmid');
2716 my $snapname = extract_param
($param, 'snapname');
2718 my $feature = extract_param
($param, 'feature');
2720 my $running = PVE
::QemuServer
::check_running
($vmid);
2722 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2725 my $snap = $conf->{snapshots
}->{$snapname};
2726 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2729 my $storecfg = PVE
::Storage
::config
();
2731 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2732 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2735 hasFeature
=> $hasFeature,
2736 nodes
=> [ keys %$nodelist ],
2740 __PACKAGE__-
>register_method({
2742 path
=> '{vmid}/clone',
2746 description
=> "Create a copy of virtual machine/template.",
2748 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2749 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2750 "'Datastore.AllocateSpace' on any used storage.",
2753 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2755 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2756 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2761 additionalProperties
=> 0,
2763 node
=> get_standard_option
('pve-node'),
2764 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2765 newid
=> get_standard_option
('pve-vmid', {
2766 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2767 description
=> 'VMID for the clone.' }),
2770 type
=> 'string', format
=> 'dns-name',
2771 description
=> "Set a name for the new VM.",
2776 description
=> "Description for the new VM.",
2780 type
=> 'string', format
=> 'pve-poolid',
2781 description
=> "Add the new VM to the specified pool.",
2783 snapname
=> get_standard_option
('pve-snapshot-name', {
2786 storage
=> get_standard_option
('pve-storage-id', {
2787 description
=> "Target storage for full clone.",
2791 description
=> "Target format for file storage. Only valid for full clone.",
2794 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2799 description
=> "Create a full copy of all disks. This is always done when " .
2800 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2802 target
=> get_standard_option
('pve-node', {
2803 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2807 description
=> "Override I/O bandwidth limit (in KiB/s).",
2811 default => 'clone limit from datacenter or storage config',
2821 my $rpcenv = PVE
::RPCEnvironment
::get
();
2822 my $authuser = $rpcenv->get_user();
2824 my $node = extract_param
($param, 'node');
2825 my $vmid = extract_param
($param, 'vmid');
2826 my $newid = extract_param
($param, 'newid');
2827 my $pool = extract_param
($param, 'pool');
2828 $rpcenv->check_pool_exist($pool) if defined($pool);
2830 my $snapname = extract_param
($param, 'snapname');
2831 my $storage = extract_param
($param, 'storage');
2832 my $format = extract_param
($param, 'format');
2833 my $target = extract_param
($param, 'target');
2835 my $localnode = PVE
::INotify
::nodename
();
2837 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2841 PVE
::Cluster
::check_node_exists
($target) if $target;
2843 my $storecfg = PVE
::Storage
::config
();
2846 # check if storage is enabled on local node
2847 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2849 # check if storage is available on target node
2850 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2851 # clone only works if target storage is shared
2852 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2853 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2857 PVE
::Cluster
::check_cfs_quorum
();
2859 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2862 # do all tests after lock but before forking worker - if possible
2864 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2865 PVE
::QemuConfig-
>check_lock($conf);
2867 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2868 die "unexpected state change\n" if $verify_running != $running;
2870 die "snapshot '$snapname' does not exist\n"
2871 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2873 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2875 die "parameter 'storage' not allowed for linked clones\n"
2876 if defined($storage) && !$full;
2878 die "parameter 'format' not allowed for linked clones\n"
2879 if defined($format) && !$full;
2881 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2883 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2885 die "can't clone VM to node '$target' (VM uses local storage)\n"
2886 if $target && !$sharedvm;
2888 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2889 die "unable to create VM $newid: config file already exists\n"
2892 my $newconf = { lock => 'clone' };
2897 foreach my $opt (keys %$oldconf) {
2898 my $value = $oldconf->{$opt};
2900 # do not copy snapshot related info
2901 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2902 $opt eq 'vmstate' || $opt eq 'snapstate';
2904 # no need to copy unused images, because VMID(owner) changes anyways
2905 next if $opt =~ m/^unused\d+$/;
2907 # always change MAC! address
2908 if ($opt =~ m/^net(\d+)$/) {
2909 my $net = PVE
::QemuServer
::parse_net
($value);
2910 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2911 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2912 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2913 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2914 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2915 die "unable to parse drive options for '$opt'\n" if !$drive;
2916 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2917 $newconf->{$opt} = $value; # simply copy configuration
2919 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2920 die "Full clone feature is not supported for drive '$opt'\n"
2921 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2922 $fullclone->{$opt} = 1;
2924 # not full means clone instead of copy
2925 die "Linked clone feature is not supported for drive '$opt'\n"
2926 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2928 $drives->{$opt} = $drive;
2929 push @$vollist, $drive->{file
};
2932 # copy everything else
2933 $newconf->{$opt} = $value;
2937 # auto generate a new uuid
2938 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2939 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2940 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2941 # auto generate a new vmgenid only if the option was set for template
2942 if ($newconf->{vmgenid
}) {
2943 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2946 delete $newconf->{template
};
2948 if ($param->{name
}) {
2949 $newconf->{name
} = $param->{name
};
2951 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2954 if ($param->{description
}) {
2955 $newconf->{description
} = $param->{description
};
2958 # create empty/temp config - this fails if VM already exists on other node
2959 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2960 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2965 my $newvollist = [];
2972 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2974 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2976 my $bwlimit = extract_param
($param, 'bwlimit');
2978 my $total_jobs = scalar(keys %{$drives});
2981 foreach my $opt (keys %$drives) {
2982 my $drive = $drives->{$opt};
2983 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2984 my $completion = $skipcomplete ?
'skip' : 'complete';
2986 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2987 my $storage_list = [ $src_sid ];
2988 push @$storage_list, $storage if defined($storage);
2989 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2991 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2992 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2993 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
2995 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2997 PVE
::QemuConfig-
>write_config($newid, $newconf);
3001 delete $newconf->{lock};
3003 # do not write pending changes
3004 if (my @changes = keys %{$newconf->{pending
}}) {
3005 my $pending = join(',', @changes);
3006 warn "found pending changes for '$pending', discarding for clone\n";
3007 delete $newconf->{pending
};
3010 PVE
::QemuConfig-
>write_config($newid, $newconf);
3013 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3014 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3015 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3017 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3018 die "Failed to move config to node '$target' - rename failed: $!\n"
3019 if !rename($conffile, $newconffile);
3022 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3025 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3026 sleep 1; # some storage like rbd need to wait before release volume - really?
3028 foreach my $volid (@$newvollist) {
3029 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3033 PVE
::Firewall
::remove_vmfw_conf
($newid);
3035 unlink $conffile; # avoid races -> last thing before die
3037 die "clone failed: $err";
3043 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3045 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3048 # Aquire exclusive lock lock for $newid
3049 my $lock_target_vm = sub {
3050 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3053 # exclusive lock if VM is running - else shared lock is enough;
3055 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3057 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3061 __PACKAGE__-
>register_method({
3062 name
=> 'move_vm_disk',
3063 path
=> '{vmid}/move_disk',
3067 description
=> "Move volume to different storage.",
3069 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3071 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3072 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3076 additionalProperties
=> 0,
3078 node
=> get_standard_option
('pve-node'),
3079 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3082 description
=> "The disk you want to move.",
3083 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3085 storage
=> get_standard_option
('pve-storage-id', {
3086 description
=> "Target storage.",
3087 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3091 description
=> "Target Format.",
3092 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3097 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3103 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3108 description
=> "Override I/O bandwidth limit (in KiB/s).",
3112 default => 'move limit from datacenter or storage config',
3118 description
=> "the task ID.",
3123 my $rpcenv = PVE
::RPCEnvironment
::get
();
3124 my $authuser = $rpcenv->get_user();
3126 my $node = extract_param
($param, 'node');
3127 my $vmid = extract_param
($param, 'vmid');
3128 my $digest = extract_param
($param, 'digest');
3129 my $disk = extract_param
($param, 'disk');
3130 my $storeid = extract_param
($param, 'storage');
3131 my $format = extract_param
($param, 'format');
3133 my $storecfg = PVE
::Storage
::config
();
3135 my $updatefn = sub {
3136 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3137 PVE
::QemuConfig-
>check_lock($conf);
3139 die "VM config checksum missmatch (file change by other user?)\n"
3140 if $digest && $digest ne $conf->{digest
};
3142 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3144 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3146 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3147 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3149 my $old_volid = $drive->{file
};
3151 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3152 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3156 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3157 (!$format || !$oldfmt || $oldfmt eq $format);
3159 # this only checks snapshots because $disk is passed!
3160 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3161 die "you can't move a disk with snapshots and delete the source\n"
3162 if $snapshotted && $param->{delete};
3164 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3166 my $running = PVE
::QemuServer
::check_running
($vmid);
3168 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3171 my $newvollist = [];
3177 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3179 warn "moving disk with snapshots, snapshots will not be moved!\n"
3182 my $bwlimit = extract_param
($param, 'bwlimit');
3183 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3185 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3186 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3188 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3190 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3192 # convert moved disk to base if part of template
3193 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3194 if PVE
::QemuConfig-
>is_template($conf);
3196 PVE
::QemuConfig-
>write_config($vmid, $conf);
3198 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3199 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3200 eval { mon_cmd
($vmid, "guest-fstrim") };
3204 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3205 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3211 foreach my $volid (@$newvollist) {
3212 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3215 die "storage migration failed: $err";
3218 if ($param->{delete}) {
3220 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3221 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3227 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3230 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3233 my $check_vm_disks_local = sub {
3234 my ($storecfg, $vmconf, $vmid) = @_;
3236 my $local_disks = {};
3238 # add some more information to the disks e.g. cdrom
3239 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3240 my ($volid, $attr) = @_;
3242 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3244 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3245 return if $scfg->{shared
};
3247 # The shared attr here is just a special case where the vdisk
3248 # is marked as shared manually
3249 return if $attr->{shared
};
3250 return if $attr->{cdrom
} and $volid eq "none";
3252 if (exists $local_disks->{$volid}) {
3253 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3255 $local_disks->{$volid} = $attr;
3256 # ensure volid is present in case it's needed
3257 $local_disks->{$volid}->{volid
} = $volid;
3261 return $local_disks;
3264 __PACKAGE__-
>register_method({
3265 name
=> 'migrate_vm_precondition',
3266 path
=> '{vmid}/migrate',
3270 description
=> "Get preconditions for migration.",
3272 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3275 additionalProperties
=> 0,
3277 node
=> get_standard_option
('pve-node'),
3278 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3279 target
=> get_standard_option
('pve-node', {
3280 description
=> "Target node.",
3281 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3289 running
=> { type
=> 'boolean' },
3293 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3295 not_allowed_nodes
=> {
3298 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3302 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3304 local_resources
=> {
3306 description
=> "List local resources e.g. pci, usb"
3313 my $rpcenv = PVE
::RPCEnvironment
::get
();
3315 my $authuser = $rpcenv->get_user();
3317 PVE
::Cluster
::check_cfs_quorum
();
3321 my $vmid = extract_param
($param, 'vmid');
3322 my $target = extract_param
($param, 'target');
3323 my $localnode = PVE
::INotify
::nodename
();
3327 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3328 my $storecfg = PVE
::Storage
::config
();
3331 # try to detect errors early
3332 PVE
::QemuConfig-
>check_lock($vmconf);
3334 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3336 # if vm is not running, return target nodes where local storage is available
3337 # for offline migration
3338 if (!$res->{running
}) {
3339 $res->{allowed_nodes
} = [];
3340 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3341 delete $checked_nodes->{$localnode};
3343 foreach my $node (keys %$checked_nodes) {
3344 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3345 push @{$res->{allowed_nodes
}}, $node;
3349 $res->{not_allowed_nodes
} = $checked_nodes;
3353 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3354 $res->{local_disks
} = [ values %$local_disks ];;
3356 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3358 $res->{local_resources
} = $local_resources;
3365 __PACKAGE__-
>register_method({
3366 name
=> 'migrate_vm',
3367 path
=> '{vmid}/migrate',
3371 description
=> "Migrate virtual machine. Creates a new migration task.",
3373 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3376 additionalProperties
=> 0,
3378 node
=> get_standard_option
('pve-node'),
3379 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3380 target
=> get_standard_option
('pve-node', {
3381 description
=> "Target node.",
3382 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3386 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3391 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3396 enum
=> ['secure', 'insecure'],
3397 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3400 migration_network
=> {
3401 type
=> 'string', format
=> 'CIDR',
3402 description
=> "CIDR of the (sub) network that is used for migration.",
3405 "with-local-disks" => {
3407 description
=> "Enable live storage migration for local disk",
3410 targetstorage
=> get_standard_option
('pve-targetstorage', {
3411 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3414 description
=> "Override I/O bandwidth limit (in KiB/s).",
3418 default => 'migrate limit from datacenter or storage config',
3424 description
=> "the task ID.",
3429 my $rpcenv = PVE
::RPCEnvironment
::get
();
3430 my $authuser = $rpcenv->get_user();
3432 my $target = extract_param
($param, 'target');
3434 my $localnode = PVE
::INotify
::nodename
();
3435 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3437 PVE
::Cluster
::check_cfs_quorum
();
3439 PVE
::Cluster
::check_node_exists
($target);
3441 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3443 my $vmid = extract_param
($param, 'vmid');
3445 raise_param_exc
({ force
=> "Only root may use this option." })
3446 if $param->{force
} && $authuser ne 'root@pam';
3448 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3449 if $param->{migration_type
} && $authuser ne 'root@pam';
3451 # allow root only until better network permissions are available
3452 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3453 if $param->{migration_network
} && $authuser ne 'root@pam';
3456 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3458 # try to detect errors early
3460 PVE
::QemuConfig-
>check_lock($conf);
3462 if (PVE
::QemuServer
::check_running
($vmid)) {
3463 die "can't migrate running VM without --online\n" if !$param->{online
};
3465 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3466 $param->{online
} = 0;
3469 my $storecfg = PVE
::Storage
::config
();
3471 if (my $targetstorage = $param->{targetstorage
}) {
3472 my $check_storage = sub {
3473 my ($target_sid) = @_;
3474 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3475 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3476 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3477 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3478 if !$scfg->{content
}->{images
};
3481 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3482 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3485 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3486 if !defined($storagemap->{identity
});
3488 foreach my $source (values %{$storagemap->{entries
}}) {
3489 $check_storage->($source);
3492 $check_storage->($storagemap->{default})
3493 if $storagemap->{default};
3495 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3496 if $storagemap->{identity
};
3498 $param->{storagemap
} = $storagemap;
3500 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3503 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3508 print "Requesting HA migration for VM $vmid to node $target\n";
3510 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3511 PVE
::Tools
::run_command
($cmd);
3515 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3520 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3524 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3527 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3532 __PACKAGE__-
>register_method({
3534 path
=> '{vmid}/monitor',
3538 description
=> "Execute Qemu monitor commands.",
3540 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3541 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3544 additionalProperties
=> 0,
3546 node
=> get_standard_option
('pve-node'),
3547 vmid
=> get_standard_option
('pve-vmid'),
3550 description
=> "The monitor command.",
3554 returns
=> { type
=> 'string'},
3558 my $rpcenv = PVE
::RPCEnvironment
::get
();
3559 my $authuser = $rpcenv->get_user();
3562 my $command = shift;
3563 return $command =~ m/^\s*info(\s+|$)/
3564 || $command =~ m/^\s*help\s*$/;
3567 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3568 if !&$is_ro($param->{command
});
3570 my $vmid = $param->{vmid
};
3572 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3576 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3578 $res = "ERROR: $@" if $@;
3583 __PACKAGE__-
>register_method({
3584 name
=> 'resize_vm',
3585 path
=> '{vmid}/resize',
3589 description
=> "Extend volume size.",
3591 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3594 additionalProperties
=> 0,
3596 node
=> get_standard_option
('pve-node'),
3597 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3598 skiplock
=> get_standard_option
('skiplock'),
3601 description
=> "The disk you want to resize.",
3602 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3606 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3607 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.",
3611 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3617 returns
=> { type
=> 'null'},
3621 my $rpcenv = PVE
::RPCEnvironment
::get
();
3623 my $authuser = $rpcenv->get_user();
3625 my $node = extract_param
($param, 'node');
3627 my $vmid = extract_param
($param, 'vmid');
3629 my $digest = extract_param
($param, 'digest');
3631 my $disk = extract_param
($param, 'disk');
3633 my $sizestr = extract_param
($param, 'size');
3635 my $skiplock = extract_param
($param, 'skiplock');
3636 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3637 if $skiplock && $authuser ne 'root@pam';
3639 my $storecfg = PVE
::Storage
::config
();
3641 my $updatefn = sub {
3643 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3645 die "checksum missmatch (file change by other user?)\n"
3646 if $digest && $digest ne $conf->{digest
};
3647 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3649 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3651 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3653 my (undef, undef, undef, undef, undef, undef, $format) =
3654 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3656 die "can't resize volume: $disk if snapshot exists\n"
3657 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3659 my $volid = $drive->{file
};
3661 die "disk '$disk' has no associated volume\n" if !$volid;
3663 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3665 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3667 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3669 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3670 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3672 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3674 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3675 my ($ext, $newsize, $unit) = ($1, $2, $4);
3678 $newsize = $newsize * 1024;
3679 } elsif ($unit eq 'M') {
3680 $newsize = $newsize * 1024 * 1024;
3681 } elsif ($unit eq 'G') {
3682 $newsize = $newsize * 1024 * 1024 * 1024;
3683 } elsif ($unit eq 'T') {
3684 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3687 $newsize += $size if $ext;
3688 $newsize = int($newsize);
3690 die "shrinking disks is not supported\n" if $newsize < $size;
3692 return if $size == $newsize;
3694 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3696 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3698 $drive->{size
} = $newsize;
3699 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3701 PVE
::QemuConfig-
>write_config($vmid, $conf);
3704 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3708 __PACKAGE__-
>register_method({
3709 name
=> 'snapshot_list',
3710 path
=> '{vmid}/snapshot',
3712 description
=> "List all snapshots.",
3714 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3717 protected
=> 1, # qemu pid files are only readable by root
3719 additionalProperties
=> 0,
3721 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3722 node
=> get_standard_option
('pve-node'),
3731 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3735 description
=> "Snapshot includes RAM.",
3740 description
=> "Snapshot description.",
3744 description
=> "Snapshot creation time",
3746 renderer
=> 'timestamp',
3750 description
=> "Parent snapshot identifier.",
3756 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3761 my $vmid = $param->{vmid
};
3763 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3764 my $snaphash = $conf->{snapshots
} || {};
3768 foreach my $name (keys %$snaphash) {
3769 my $d = $snaphash->{$name};
3772 snaptime
=> $d->{snaptime
} || 0,
3773 vmstate
=> $d->{vmstate
} ?
1 : 0,
3774 description
=> $d->{description
} || '',
3776 $item->{parent
} = $d->{parent
} if $d->{parent
};
3777 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3781 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3784 digest
=> $conf->{digest
},
3785 running
=> $running,
3786 description
=> "You are here!",
3788 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3790 push @$res, $current;
3795 __PACKAGE__-
>register_method({
3797 path
=> '{vmid}/snapshot',
3801 description
=> "Snapshot a VM.",
3803 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3806 additionalProperties
=> 0,
3808 node
=> get_standard_option
('pve-node'),
3809 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3810 snapname
=> get_standard_option
('pve-snapshot-name'),
3814 description
=> "Save the vmstate",
3819 description
=> "A textual description or comment.",
3825 description
=> "the task ID.",
3830 my $rpcenv = PVE
::RPCEnvironment
::get
();
3832 my $authuser = $rpcenv->get_user();
3834 my $node = extract_param
($param, 'node');
3836 my $vmid = extract_param
($param, 'vmid');
3838 my $snapname = extract_param
($param, 'snapname');
3840 die "unable to use snapshot name 'current' (reserved name)\n"
3841 if $snapname eq 'current';
3843 die "unable to use snapshot name 'pending' (reserved name)\n"
3844 if lc($snapname) eq 'pending';
3847 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3848 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3849 $param->{description
});
3852 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3855 __PACKAGE__-
>register_method({
3856 name
=> 'snapshot_cmd_idx',
3857 path
=> '{vmid}/snapshot/{snapname}',
3864 additionalProperties
=> 0,
3866 vmid
=> get_standard_option
('pve-vmid'),
3867 node
=> get_standard_option
('pve-node'),
3868 snapname
=> get_standard_option
('pve-snapshot-name'),
3877 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3884 push @$res, { cmd
=> 'rollback' };
3885 push @$res, { cmd
=> 'config' };
3890 __PACKAGE__-
>register_method({
3891 name
=> 'update_snapshot_config',
3892 path
=> '{vmid}/snapshot/{snapname}/config',
3896 description
=> "Update snapshot metadata.",
3898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3901 additionalProperties
=> 0,
3903 node
=> get_standard_option
('pve-node'),
3904 vmid
=> get_standard_option
('pve-vmid'),
3905 snapname
=> get_standard_option
('pve-snapshot-name'),
3909 description
=> "A textual description or comment.",
3913 returns
=> { type
=> 'null' },
3917 my $rpcenv = PVE
::RPCEnvironment
::get
();
3919 my $authuser = $rpcenv->get_user();
3921 my $vmid = extract_param
($param, 'vmid');
3923 my $snapname = extract_param
($param, 'snapname');
3925 return undef if !defined($param->{description
});
3927 my $updatefn = sub {
3929 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3931 PVE
::QemuConfig-
>check_lock($conf);
3933 my $snap = $conf->{snapshots
}->{$snapname};
3935 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3937 $snap->{description
} = $param->{description
} if defined($param->{description
});
3939 PVE
::QemuConfig-
>write_config($vmid, $conf);
3942 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3947 __PACKAGE__-
>register_method({
3948 name
=> 'get_snapshot_config',
3949 path
=> '{vmid}/snapshot/{snapname}/config',
3952 description
=> "Get snapshot configuration",
3954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3957 additionalProperties
=> 0,
3959 node
=> get_standard_option
('pve-node'),
3960 vmid
=> get_standard_option
('pve-vmid'),
3961 snapname
=> get_standard_option
('pve-snapshot-name'),
3964 returns
=> { type
=> "object" },
3968 my $rpcenv = PVE
::RPCEnvironment
::get
();
3970 my $authuser = $rpcenv->get_user();
3972 my $vmid = extract_param
($param, 'vmid');
3974 my $snapname = extract_param
($param, 'snapname');
3976 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3978 my $snap = $conf->{snapshots
}->{$snapname};
3980 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3985 __PACKAGE__-
>register_method({
3987 path
=> '{vmid}/snapshot/{snapname}/rollback',
3991 description
=> "Rollback VM state to specified snapshot.",
3993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3996 additionalProperties
=> 0,
3998 node
=> get_standard_option
('pve-node'),
3999 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4000 snapname
=> get_standard_option
('pve-snapshot-name'),
4005 description
=> "the task ID.",
4010 my $rpcenv = PVE
::RPCEnvironment
::get
();
4012 my $authuser = $rpcenv->get_user();
4014 my $node = extract_param
($param, 'node');
4016 my $vmid = extract_param
($param, 'vmid');
4018 my $snapname = extract_param
($param, 'snapname');
4021 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4022 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4026 # hold migration lock, this makes sure that nobody create replication snapshots
4027 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4030 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4033 __PACKAGE__-
>register_method({
4034 name
=> 'delsnapshot',
4035 path
=> '{vmid}/snapshot/{snapname}',
4039 description
=> "Delete a VM snapshot.",
4041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4044 additionalProperties
=> 0,
4046 node
=> get_standard_option
('pve-node'),
4047 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4048 snapname
=> get_standard_option
('pve-snapshot-name'),
4052 description
=> "For removal from config file, even if removing disk snapshots fails.",
4058 description
=> "the task ID.",
4063 my $rpcenv = PVE
::RPCEnvironment
::get
();
4065 my $authuser = $rpcenv->get_user();
4067 my $node = extract_param
($param, 'node');
4069 my $vmid = extract_param
($param, 'vmid');
4071 my $snapname = extract_param
($param, 'snapname');
4074 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4075 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4078 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4081 __PACKAGE__-
>register_method({
4083 path
=> '{vmid}/template',
4087 description
=> "Create a Template.",
4089 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4090 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4093 additionalProperties
=> 0,
4095 node
=> get_standard_option
('pve-node'),
4096 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4100 description
=> "If you want to convert only 1 disk to base image.",
4101 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4106 returns
=> { type
=> 'null'},
4110 my $rpcenv = PVE
::RPCEnvironment
::get
();
4112 my $authuser = $rpcenv->get_user();
4114 my $node = extract_param
($param, 'node');
4116 my $vmid = extract_param
($param, 'vmid');
4118 my $disk = extract_param
($param, 'disk');
4120 my $updatefn = sub {
4122 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4124 PVE
::QemuConfig-
>check_lock($conf);
4126 die "unable to create template, because VM contains snapshots\n"
4127 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4129 die "you can't convert a template to a template\n"
4130 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4132 die "you can't convert a VM to template if VM is running\n"
4133 if PVE
::QemuServer
::check_running
($vmid);
4136 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4139 $conf->{template
} = 1;
4140 PVE
::QemuConfig-
>write_config($vmid, $conf);
4142 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4145 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4149 __PACKAGE__-
>register_method({
4150 name
=> 'cloudinit_generated_config_dump',
4151 path
=> '{vmid}/cloudinit/dump',
4154 description
=> "Get automatically generated cloudinit config.",
4156 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4159 additionalProperties
=> 0,
4161 node
=> get_standard_option
('pve-node'),
4162 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4164 description
=> 'Config type.',
4166 enum
=> ['user', 'network', 'meta'],
4176 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4178 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});