1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Drive
;
24 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
26 use PVE
::RPCEnvironment
;
27 use PVE
::AccessControl
;
31 use PVE
::API2
::Firewall
::VM
;
32 use PVE
::API2
::Qemu
::Agent
;
33 use PVE
::VZDump
::Plugin
;
34 use PVE
::DataCenterConfig
;
38 if (!$ENV{PVE_GENERATING_DOCS
}) {
39 require PVE
::HA
::Env
::PVE2
;
40 import PVE
::HA
::Env
::PVE2
;
41 require PVE
::HA
::Config
;
42 import PVE
::HA
::Config
;
46 use Data
::Dumper
; # fixme: remove
48 use base
qw(PVE::RESTHandler);
50 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
52 my $resolve_cdrom_alias = sub {
55 if (my $value = $param->{cdrom
}) {
56 $value .= ",media=cdrom" if $value !~ m/media=/;
57 $param->{ide2
} = $value;
58 delete $param->{cdrom
};
62 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
63 my $check_storage_access = sub {
64 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
66 PVE
::QemuConfig-
>foreach_volume($settings, sub {
67 my ($ds, $drive) = @_;
69 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
71 my $volid = $drive->{file
};
72 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
74 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
76 } elsif ($isCDROM && ($volid eq 'cdrom')) {
77 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
83 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
84 if !$scfg->{content
}->{images
};
86 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
90 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
91 if defined($settings->{vmstatestorage
});
94 my $check_storage_access_clone = sub {
95 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
99 PVE
::QemuConfig-
>foreach_volume($conf, sub {
100 my ($ds, $drive) = @_;
102 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
104 my $volid = $drive->{file
};
106 return if !$volid || $volid eq 'none';
109 if ($volid eq 'cdrom') {
110 $rpcenv->check($authuser, "/", ['Sys.Console']);
112 # we simply allow access
113 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
115 $sharedvm = 0 if !$scfg->{shared
};
119 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
120 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
121 $sharedvm = 0 if !$scfg->{shared
};
123 $sid = $storage if $storage;
124 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
128 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
129 if defined($conf->{vmstatestorage
});
134 # Note: $pool is only needed when creating a VM, because pool permissions
135 # are automatically inherited if VM already exists inside a pool.
136 my $create_disks = sub {
137 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
144 my ($ds, $disk) = @_;
146 my $volid = $disk->{file
};
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
149 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
150 delete $disk->{size
};
151 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
152 } elsif (defined($volname) && $volname eq 'cloudinit') {
153 $storeid = $storeid // $default_storage;
154 die "no storage ID specified (and no default storage)\n" if !$storeid;
155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
156 my $name = "vm-$vmid-cloudinit";
160 $fmt = $disk->{format
} // "qcow2";
163 $fmt = $disk->{format
} // "raw";
166 # Initial disk created with 4 MB and aligned to 4MB on regeneration
167 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
168 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
169 $disk->{file
} = $volid;
170 $disk->{media
} = 'cdrom';
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
174 } elsif ($volid =~ $NEW_DISK_RE) {
175 my ($storeid, $size) = ($2 || $default_storage, $3);
176 die "no storage ID specified (and no default storage)\n" if !$storeid;
177 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
178 my $fmt = $disk->{format
} || $defformat;
180 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
183 if ($ds eq 'efidisk0') {
184 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
186 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
188 push @$vollist, $volid;
189 $disk->{file
} = $volid;
190 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
191 delete $disk->{format
}; # no longer needed
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
195 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
197 my $volid_is_new = 1;
200 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
201 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
206 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
208 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
210 die "volume $volid does not exist\n" if !$size;
212 $disk->{size
} = $size;
215 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
219 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
221 # free allocated images on error
223 syslog
('err', "VM $vmid creating disks failed");
224 foreach my $volid (@$vollist) {
225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
231 # modify vm config if everything went well
232 foreach my $ds (keys %$res) {
233 $conf->{$ds} = $res->{$ds};
250 my $memoryoptions = {
256 my $hwtypeoptions = {
269 my $generaloptions = {
276 'migrate_downtime' => 1,
277 'migrate_speed' => 1,
290 my $vmpoweroptions = {
297 'vmstatestorage' => 1,
300 my $cloudinitoptions = {
310 my $check_vm_modify_config_perm = sub {
311 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
313 return 1 if $authuser eq 'root@pam';
315 foreach my $opt (@$key_list) {
316 # some checks (e.g., disk, serial port, usb) need to be done somewhere
317 # else, as there the permission can be value dependend
318 next if PVE
::QemuServer
::is_valid_drivename
($opt);
319 next if $opt eq 'cdrom';
320 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
323 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
324 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
325 } elsif ($memoryoptions->{$opt}) {
326 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
327 } elsif ($hwtypeoptions->{$opt}) {
328 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
329 } elsif ($generaloptions->{$opt}) {
330 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
331 # special case for startup since it changes host behaviour
332 if ($opt eq 'startup') {
333 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
335 } elsif ($vmpoweroptions->{$opt}) {
336 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
337 } elsif ($diskoptions->{$opt}) {
338 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
339 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
340 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 } elsif ($opt eq 'vmstate') {
342 # the user needs Disk and PowerMgmt privileges to change the vmstate
343 # also needs privileges on the storage, that will be checked later
344 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
346 # catches hostpci\d+, args, lock, etc.
347 # new options will be checked here
348 die "only root can set '$opt' config\n";
355 __PACKAGE__-
>register_method({
359 description
=> "Virtual machine index (per node).",
361 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
365 protected
=> 1, # qemu pid files are only readable by root
367 additionalProperties
=> 0,
369 node
=> get_standard_option
('pve-node'),
373 description
=> "Determine the full status of active VMs.",
381 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
383 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
388 my $rpcenv = PVE
::RPCEnvironment
::get
();
389 my $authuser = $rpcenv->get_user();
391 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
394 foreach my $vmid (keys %$vmstatus) {
395 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
397 my $data = $vmstatus->{$vmid};
404 my $parse_restore_archive = sub {
405 my ($storecfg, $archive) = @_;
407 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
409 if (defined($archive_storeid)) {
410 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
411 if ($scfg->{type
} eq 'pbs') {
418 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 __PACKAGE__-
>register_method({
430 description
=> "Create or restore a virtual machine.",
432 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
433 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
434 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
435 user
=> 'all', # check inside
440 additionalProperties
=> 0,
441 properties
=> PVE
::QemuServer
::json_config_properties
(
443 node
=> get_standard_option
('pve-node'),
444 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
446 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
450 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
452 storage
=> get_standard_option
('pve-storage-id', {
453 description
=> "Default storage.",
455 completion
=> \
&PVE
::QemuServer
::complete_storage
,
460 description
=> "Allow to overwrite existing VM.",
461 requires
=> 'archive',
466 description
=> "Assign a unique random ethernet address.",
467 requires
=> 'archive',
471 type
=> 'string', format
=> 'pve-poolid',
472 description
=> "Add the VM to the specified pool.",
475 description
=> "Override I/O bandwidth limit (in KiB/s).",
479 default => 'restore limit from datacenter or storage config',
485 description
=> "Start VM after it was created successfully.",
495 my $rpcenv = PVE
::RPCEnvironment
::get
();
496 my $authuser = $rpcenv->get_user();
498 my $node = extract_param
($param, 'node');
499 my $vmid = extract_param
($param, 'vmid');
501 my $archive = extract_param
($param, 'archive');
502 my $is_restore = !!$archive;
504 my $bwlimit = extract_param
($param, 'bwlimit');
505 my $force = extract_param
($param, 'force');
506 my $pool = extract_param
($param, 'pool');
507 my $start_after_create = extract_param
($param, 'start');
508 my $storage = extract_param
($param, 'storage');
509 my $unique = extract_param
($param, 'unique');
511 if (defined(my $ssh_keys = $param->{sshkeys
})) {
512 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
513 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
516 PVE
::Cluster
::check_cfs_quorum
();
518 my $filename = PVE
::QemuConfig-
>config_file($vmid);
519 my $storecfg = PVE
::Storage
::config
();
521 if (defined($pool)) {
522 $rpcenv->check_pool_exist($pool);
525 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
526 if defined($storage);
528 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
530 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
532 } elsif ($archive && $force && (-f
$filename) &&
533 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
534 # OK: user has VM.Backup permissions, and want to restore an existing VM
540 &$resolve_cdrom_alias($param);
542 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
544 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
546 foreach my $opt (keys %$param) {
547 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
548 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
549 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
551 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
552 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
556 PVE
::QemuServer
::add_random_macs
($param);
558 my $keystr = join(' ', keys %$param);
559 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
561 if ($archive eq '-') {
562 die "pipe requires cli environment\n"
563 if $rpcenv->{type
} ne 'cli';
564 $archive = { type
=> 'pipe' };
566 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
568 $archive = $parse_restore_archive->($storecfg, $archive);
572 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
574 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
575 die "$emsg $@" if $@;
577 my $restorefn = sub {
578 my $conf = PVE
::QemuConfig-
>load_config($vmid);
580 PVE
::QemuConfig-
>check_protection($conf, $emsg);
582 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
585 my $restore_options = {
591 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
592 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
593 } elsif ($archive->{type
} eq 'pbs') {
594 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
596 die "unknown backup archive type\n";
598 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
599 # Convert restored VM to template if backup was VM template
600 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
601 warn "Convert to template.\n";
602 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
606 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
609 # ensure no old replication state are exists
610 PVE
::ReplicationState
::delete_guest_states
($vmid);
612 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
614 if ($start_after_create) {
615 print "Execute autostart\n";
616 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
622 # ensure no old replication state are exists
623 PVE
::ReplicationState
::delete_guest_states
($vmid);
627 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
631 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
633 if (!$conf->{bootdisk
}) {
634 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
635 $conf->{bootdisk
} = $firstdisk if $firstdisk;
638 # auto generate uuid if user did not specify smbios1 option
639 if (!$conf->{smbios1
}) {
640 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
643 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
644 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
647 PVE
::QemuConfig-
>write_config($vmid, $conf);
653 foreach my $volid (@$vollist) {
654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
660 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
663 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
665 if ($start_after_create) {
666 print "Execute autostart\n";
667 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
672 my ($code, $worker_name);
674 $worker_name = 'qmrestore';
676 eval { $restorefn->() };
678 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
684 $worker_name = 'qmcreate';
686 eval { $createfn->() };
689 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
690 unlink($conffile) or die "failed to remove config file: $!\n";
698 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
701 __PACKAGE__-
>register_method({
706 description
=> "Directory index",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
722 subdir
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
731 { subdir
=> 'config' },
732 { subdir
=> 'pending' },
733 { subdir
=> 'status' },
734 { subdir
=> 'unlink' },
735 { subdir
=> 'vncproxy' },
736 { subdir
=> 'termproxy' },
737 { subdir
=> 'migrate' },
738 { subdir
=> 'resize' },
739 { subdir
=> 'move' },
741 { subdir
=> 'rrddata' },
742 { subdir
=> 'monitor' },
743 { subdir
=> 'agent' },
744 { subdir
=> 'snapshot' },
745 { subdir
=> 'spiceproxy' },
746 { subdir
=> 'sendkey' },
747 { subdir
=> 'firewall' },
753 __PACKAGE__-
>register_method ({
754 subclass
=> "PVE::API2::Firewall::VM",
755 path
=> '{vmid}/firewall',
758 __PACKAGE__-
>register_method ({
759 subclass
=> "PVE::API2::Qemu::Agent",
760 path
=> '{vmid}/agent',
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/rrd',
767 protected
=> 1, # fixme: can we avoid that?
769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
771 description
=> "Read VM RRD statistics (returns PNG)",
773 additionalProperties
=> 0,
775 node
=> get_standard_option
('pve-node'),
776 vmid
=> get_standard_option
('pve-vmid'),
778 description
=> "Specify the time frame you are interested in.",
780 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
783 description
=> "The list of datasources you want to display.",
784 type
=> 'string', format
=> 'pve-configid-list',
787 description
=> "The RRD consolidation function",
789 enum
=> [ 'AVERAGE', 'MAX' ],
797 filename
=> { type
=> 'string' },
803 return PVE
::RRD
::create_rrd_graph
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
},
805 $param->{ds
}, $param->{cf
});
809 __PACKAGE__-
>register_method({
811 path
=> '{vmid}/rrddata',
813 protected
=> 1, # fixme: can we avoid that?
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
817 description
=> "Read VM RRD statistics",
819 additionalProperties
=> 0,
821 node
=> get_standard_option
('pve-node'),
822 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "Specify the time frame you are interested in.",
826 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
829 description
=> "The RRD consolidation function",
831 enum
=> [ 'AVERAGE', 'MAX' ],
846 return PVE
::RRD
::create_rrd_data
(
847 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
856 description
=> "Get the virtual machine configuration with pending configuration " .
857 "changes applied. Set the 'current' parameter to get the current configuration instead.",
859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 additionalProperties
=> 0,
864 node
=> get_standard_option
('pve-node'),
865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
867 description
=> "Get current values (instead of pending values).",
872 snapshot
=> get_standard_option
('pve-snapshot-name', {
873 description
=> "Fetch config values from given snapshot.",
876 my ($cmd, $pname, $cur, $args) = @_;
877 PVE
::QemuConfig-
>snapshot_list($args->[0]);
883 description
=> "The VM configuration.",
885 properties
=> PVE
::QemuServer
::json_config_properties
({
888 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
895 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
896 current
=> "cannot use 'snapshot' parameter with 'current'"})
897 if ($param->{snapshot
} && $param->{current
});
900 if ($param->{snapshot
}) {
901 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
903 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
905 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
910 __PACKAGE__-
>register_method({
911 name
=> 'vm_pending',
912 path
=> '{vmid}/pending',
915 description
=> "Get the virtual machine configuration with both current and pending values.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
932 description
=> "Configuration option name.",
936 description
=> "Current value.",
941 description
=> "Pending value.",
946 description
=> "Indicates a pending delete request if present and not 0. " .
947 "The value 2 indicates a force-delete request.",
959 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
961 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
963 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
964 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
966 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
969 # POST/PUT {vmid}/config implementation
971 # The original API used PUT (idempotent) an we assumed that all operations
972 # are fast. But it turned out that almost any configuration change can
973 # involve hot-plug actions, or disk alloc/free. Such actions can take long
974 # time to complete and have side effects (not idempotent).
976 # The new implementation uses POST and forks a worker process. We added
977 # a new option 'background_delay'. If specified we wait up to
978 # 'background_delay' second for the worker task to complete. It returns null
979 # if the task is finished within that time, else we return the UPID.
981 my $update_vm_api = sub {
982 my ($param, $sync) = @_;
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $node = extract_param
($param, 'node');
990 my $vmid = extract_param
($param, 'vmid');
992 my $digest = extract_param
($param, 'digest');
994 my $background_delay = extract_param
($param, 'background_delay');
996 if (defined(my $cipassword = $param->{cipassword
})) {
997 # Same logic as in cloud-init (but with the regex fixed...)
998 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
999 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1002 my @paramarr = (); # used for log message
1003 foreach my $key (sort keys %$param) {
1004 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1005 push @paramarr, "-$key", $value;
1008 my $skiplock = extract_param
($param, 'skiplock');
1009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1010 if $skiplock && $authuser ne 'root@pam';
1012 my $delete_str = extract_param
($param, 'delete');
1014 my $revert_str = extract_param
($param, 'revert');
1016 my $force = extract_param
($param, 'force');
1018 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1019 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1020 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1023 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1025 my $storecfg = PVE
::Storage
::config
();
1027 my $defaults = PVE
::QemuServer
::load_defaults
();
1029 &$resolve_cdrom_alias($param);
1031 # now try to verify all parameters
1034 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1035 if (!PVE
::QemuServer
::option_exists
($opt)) {
1036 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1039 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1040 "-revert $opt' at the same time" })
1041 if defined($param->{$opt});
1043 $revert->{$opt} = 1;
1047 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1048 $opt = 'ide2' if $opt eq 'cdrom';
1050 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1051 "-delete $opt' at the same time" })
1052 if defined($param->{$opt});
1054 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1055 "-revert $opt' at the same time" })
1058 if (!PVE
::QemuServer
::option_exists
($opt)) {
1059 raise_param_exc
({ delete => "unknown option '$opt'" });
1065 my $repl_conf = PVE
::ReplicationConfig-
>new();
1066 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1067 my $check_replication = sub {
1069 return if !$is_replicated;
1070 my $volid = $drive->{file
};
1071 return if !$volid || !($drive->{replicate
}//1);
1072 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1074 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1075 return if $volname eq 'cloudinit';
1078 if ($volid =~ $NEW_DISK_RE) {
1080 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1082 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1084 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1085 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1086 return if $scfg->{shared
};
1087 die "cannot add non-replicatable volume to a replicated VM\n";
1090 foreach my $opt (keys %$param) {
1091 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1092 # cleanup drive path
1093 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1094 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1095 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1096 $check_replication->($drive);
1097 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1098 } elsif ($opt =~ m/^net(\d+)$/) {
1100 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1101 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1102 } elsif ($opt eq 'vmgenid') {
1103 if ($param->{$opt} eq '1') {
1104 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1106 } elsif ($opt eq 'hookscript') {
1107 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1108 raise_param_exc
({ $opt => $@ }) if $@;
1112 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1114 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1116 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1118 my $updatefn = sub {
1120 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1122 die "checksum missmatch (file change by other user?)\n"
1123 if $digest && $digest ne $conf->{digest
};
1125 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1126 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1127 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1128 delete $conf->{lock}; # for check lock check, not written out
1129 push @delete, 'lock'; # this is the real deal to write it out
1131 push @delete, 'runningmachine' if $conf->{runningmachine
};
1132 push @delete, 'runningcpu' if $conf->{runningcpu
};
1135 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1137 foreach my $opt (keys %$revert) {
1138 if (defined($conf->{$opt})) {
1139 $param->{$opt} = $conf->{$opt};
1140 } elsif (defined($conf->{pending
}->{$opt})) {
1145 if ($param->{memory
} || defined($param->{balloon
})) {
1146 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1147 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1149 die "balloon value too large (must be smaller than assigned memory)\n"
1150 if $balloon && $balloon > $maxmem;
1153 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1157 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1159 # write updates to pending section
1161 my $modified = {}; # record what $option we modify
1163 foreach my $opt (@delete) {
1164 $modified->{$opt} = 1;
1165 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1167 # value of what we want to delete, independent if pending or not
1168 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1169 if (!defined($val)) {
1170 warn "cannot delete '$opt' - not set in current configuration!\n";
1171 $modified->{$opt} = 0;
1174 my $is_pending_val = defined($conf->{pending
}->{$opt});
1175 delete $conf->{pending
}->{$opt};
1177 if ($opt =~ m/^unused/) {
1178 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1179 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1180 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1181 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1182 delete $conf->{$opt};
1183 PVE
::QemuConfig-
>write_config($vmid, $conf);
1185 } elsif ($opt eq 'vmstate') {
1186 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1187 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1188 delete $conf->{$opt};
1189 PVE
::QemuConfig-
>write_config($vmid, $conf);
1191 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1192 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1193 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1194 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1196 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1197 PVE
::QemuConfig-
>write_config($vmid, $conf);
1198 } elsif ($opt =~ m/^serial\d+$/) {
1199 if ($val eq 'socket') {
1200 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1201 } elsif ($authuser ne 'root@pam') {
1202 die "only root can delete '$opt' config for real devices\n";
1204 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1205 PVE
::QemuConfig-
>write_config($vmid, $conf);
1206 } elsif ($opt =~ m/^usb\d+$/) {
1207 if ($val =~ m/spice/) {
1208 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1209 } elsif ($authuser ne 'root@pam') {
1210 die "only root can delete '$opt' config for real devices\n";
1212 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1213 PVE
::QemuConfig-
>write_config($vmid, $conf);
1215 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1216 PVE
::QemuConfig-
>write_config($vmid, $conf);
1220 foreach my $opt (keys %$param) { # add/change
1221 $modified->{$opt} = 1;
1222 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1223 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1225 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1227 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1228 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1229 # FIXME: cloudinit: CDROM or Disk?
1230 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1231 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1233 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1235 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1236 if defined($conf->{pending
}->{$opt});
1238 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1239 } elsif ($opt =~ m/^serial\d+/) {
1240 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1241 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1242 } elsif ($authuser ne 'root@pam') {
1243 die "only root can modify '$opt' config for real devices\n";
1245 $conf->{pending
}->{$opt} = $param->{$opt};
1246 } elsif ($opt =~ m/^usb\d+/) {
1247 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1248 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1249 } elsif ($authuser ne 'root@pam') {
1250 die "only root can modify '$opt' config for real devices\n";
1252 $conf->{pending
}->{$opt} = $param->{$opt};
1254 $conf->{pending
}->{$opt} = $param->{$opt};
1256 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1257 PVE
::QemuConfig-
>write_config($vmid, $conf);
1260 # remove pending changes when nothing changed
1261 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1262 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1263 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1265 return if !scalar(keys %{$conf->{pending
}});
1267 my $running = PVE
::QemuServer
::check_running
($vmid);
1269 # apply pending changes
1271 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1275 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1277 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1279 raise_param_exc
($errors) if scalar(keys %$errors);
1288 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1290 if ($background_delay) {
1292 # Note: It would be better to do that in the Event based HTTPServer
1293 # to avoid blocking call to sleep.
1295 my $end_time = time() + $background_delay;
1297 my $task = PVE
::Tools
::upid_decode
($upid);
1300 while (time() < $end_time) {
1301 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1303 sleep(1); # this gets interrupted when child process ends
1307 my $status = PVE
::Tools
::upid_read_status
($upid);
1308 return undef if $status eq 'OK';
1317 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1320 my $vm_config_perm_list = [
1325 'VM.Config.Network',
1327 'VM.Config.Options',
1330 __PACKAGE__-
>register_method({
1331 name
=> 'update_vm_async',
1332 path
=> '{vmid}/config',
1336 description
=> "Set virtual machine options (asynchrounous API).",
1338 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1341 additionalProperties
=> 0,
1342 properties
=> PVE
::QemuServer
::json_config_properties
(
1344 node
=> get_standard_option
('pve-node'),
1345 vmid
=> get_standard_option
('pve-vmid'),
1346 skiplock
=> get_standard_option
('skiplock'),
1348 type
=> 'string', format
=> 'pve-configid-list',
1349 description
=> "A list of settings you want to delete.",
1353 type
=> 'string', format
=> 'pve-configid-list',
1354 description
=> "Revert a pending change.",
1359 description
=> $opt_force_description,
1361 requires
=> 'delete',
1365 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1369 background_delay
=> {
1371 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1382 code
=> $update_vm_api,
1385 __PACKAGE__-
>register_method({
1386 name
=> 'update_vm',
1387 path
=> '{vmid}/config',
1391 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1393 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1396 additionalProperties
=> 0,
1397 properties
=> PVE
::QemuServer
::json_config_properties
(
1399 node
=> get_standard_option
('pve-node'),
1400 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1401 skiplock
=> get_standard_option
('skiplock'),
1403 type
=> 'string', format
=> 'pve-configid-list',
1404 description
=> "A list of settings you want to delete.",
1408 type
=> 'string', format
=> 'pve-configid-list',
1409 description
=> "Revert a pending change.",
1414 description
=> $opt_force_description,
1416 requires
=> 'delete',
1420 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1426 returns
=> { type
=> 'null' },
1429 &$update_vm_api($param, 1);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'destroy_vm',
1440 description
=> "Destroy the vm (also delete all used/owned volumes).",
1442 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1449 skiplock
=> get_standard_option
('skiplock'),
1452 description
=> "Remove vmid from backup cron jobs.",
1463 my $rpcenv = PVE
::RPCEnvironment
::get
();
1464 my $authuser = $rpcenv->get_user();
1465 my $vmid = $param->{vmid
};
1467 my $skiplock = $param->{skiplock
};
1468 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1469 if $skiplock && $authuser ne 'root@pam';
1472 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1473 my $storecfg = PVE
::Storage
::config
();
1474 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1476 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1478 if (!$param->{purge
}) {
1479 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1481 # don't allow destroy if with replication jobs but no purge param
1482 my $repl_conf = PVE
::ReplicationConfig-
>new();
1483 $repl_conf->check_for_existing_jobs($vmid);
1486 # early tests (repeat after locking)
1487 die "VM $vmid is running - destroy failed\n"
1488 if PVE
::QemuServer
::check_running
($vmid);
1493 syslog
('info', "destroy VM $vmid: $upid\n");
1494 PVE
::QemuConfig-
>lock_config($vmid, sub {
1495 die "VM $vmid is running - destroy failed\n"
1496 if (PVE
::QemuServer
::check_running
($vmid));
1498 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1500 PVE
::AccessControl
::remove_vm_access
($vmid);
1501 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1502 if ($param->{purge
}) {
1503 print "purging VM $vmid from related configurations..\n";
1504 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1505 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1508 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1509 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1513 # only now remove the zombie config, else we can have reuse race
1514 PVE
::QemuConfig-
>destroy_config($vmid);
1518 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1521 __PACKAGE__-
>register_method({
1523 path
=> '{vmid}/unlink',
1527 description
=> "Unlink/delete disk images.",
1529 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1532 additionalProperties
=> 0,
1534 node
=> get_standard_option
('pve-node'),
1535 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1537 type
=> 'string', format
=> 'pve-configid-list',
1538 description
=> "A list of disk IDs you want to delete.",
1542 description
=> $opt_force_description,
1547 returns
=> { type
=> 'null'},
1551 $param->{delete} = extract_param
($param, 'idlist');
1553 __PACKAGE__-
>update_vm($param);
1560 __PACKAGE__-
>register_method({
1562 path
=> '{vmid}/vncproxy',
1566 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1568 description
=> "Creates a TCP VNC proxy connections.",
1570 additionalProperties
=> 0,
1572 node
=> get_standard_option
('pve-node'),
1573 vmid
=> get_standard_option
('pve-vmid'),
1577 description
=> "starts websockify instead of vncproxy",
1582 additionalProperties
=> 0,
1584 user
=> { type
=> 'string' },
1585 ticket
=> { type
=> 'string' },
1586 cert
=> { type
=> 'string' },
1587 port
=> { type
=> 'integer' },
1588 upid
=> { type
=> 'string' },
1594 my $rpcenv = PVE
::RPCEnvironment
::get
();
1596 my $authuser = $rpcenv->get_user();
1598 my $vmid = $param->{vmid
};
1599 my $node = $param->{node
};
1600 my $websocket = $param->{websocket
};
1602 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1603 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1605 my $authpath = "/vms/$vmid";
1607 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1609 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1615 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1616 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1617 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1618 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1619 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1621 $family = PVE
::Tools
::get_host_address_family
($node);
1624 my $port = PVE
::Tools
::next_vnc_port
($family);
1631 syslog
('info', "starting vnc proxy $upid\n");
1637 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1639 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1640 '-timeout', $timeout, '-authpath', $authpath,
1641 '-perm', 'Sys.Console'];
1643 if ($param->{websocket
}) {
1644 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1645 push @$cmd, '-notls', '-listen', 'localhost';
1648 push @$cmd, '-c', @$remcmd, @$termcmd;
1650 PVE
::Tools
::run_command
($cmd);
1654 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1656 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1658 my $sock = IO
::Socket
::IP-
>new(
1663 GetAddrInfoFlags
=> 0,
1664 ) or die "failed to create socket: $!\n";
1665 # Inside the worker we shouldn't have any previous alarms
1666 # running anyway...:
1668 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1670 accept(my $cli, $sock) or die "connection failed: $!\n";
1673 if (PVE
::Tools
::run_command
($cmd,
1674 output
=> '>&'.fileno($cli),
1675 input
=> '<&'.fileno($cli),
1678 die "Failed to run vncproxy.\n";
1685 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1687 PVE
::Tools
::wait_for_vnc_port
($port);
1698 __PACKAGE__-
>register_method({
1699 name
=> 'termproxy',
1700 path
=> '{vmid}/termproxy',
1704 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1706 description
=> "Creates a TCP proxy connections.",
1708 additionalProperties
=> 0,
1710 node
=> get_standard_option
('pve-node'),
1711 vmid
=> get_standard_option
('pve-vmid'),
1715 enum
=> [qw(serial0 serial1 serial2 serial3)],
1716 description
=> "opens a serial terminal (defaults to display)",
1721 additionalProperties
=> 0,
1723 user
=> { type
=> 'string' },
1724 ticket
=> { type
=> 'string' },
1725 port
=> { type
=> 'integer' },
1726 upid
=> { type
=> 'string' },
1732 my $rpcenv = PVE
::RPCEnvironment
::get
();
1734 my $authuser = $rpcenv->get_user();
1736 my $vmid = $param->{vmid
};
1737 my $node = $param->{node
};
1738 my $serial = $param->{serial
};
1740 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1742 if (!defined($serial)) {
1743 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1744 $serial = $conf->{vga
};
1748 my $authpath = "/vms/$vmid";
1750 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1755 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1756 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1757 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1758 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1759 push @$remcmd, '--';
1761 $family = PVE
::Tools
::get_host_address_family
($node);
1764 my $port = PVE
::Tools
::next_vnc_port
($family);
1766 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1767 push @$termcmd, '-iface', $serial if $serial;
1772 syslog
('info', "starting qemu termproxy $upid\n");
1774 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1775 '--perm', 'VM.Console', '--'];
1776 push @$cmd, @$remcmd, @$termcmd;
1778 PVE
::Tools
::run_command
($cmd);
1781 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1783 PVE
::Tools
::wait_for_vnc_port
($port);
1793 __PACKAGE__-
>register_method({
1794 name
=> 'vncwebsocket',
1795 path
=> '{vmid}/vncwebsocket',
1798 description
=> "You also need to pass a valid ticket (vncticket).",
1799 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1801 description
=> "Opens a weksocket for VNC traffic.",
1803 additionalProperties
=> 0,
1805 node
=> get_standard_option
('pve-node'),
1806 vmid
=> get_standard_option
('pve-vmid'),
1808 description
=> "Ticket from previous call to vncproxy.",
1813 description
=> "Port number returned by previous vncproxy call.",
1823 port
=> { type
=> 'string' },
1829 my $rpcenv = PVE
::RPCEnvironment
::get
();
1831 my $authuser = $rpcenv->get_user();
1833 my $vmid = $param->{vmid
};
1834 my $node = $param->{node
};
1836 my $authpath = "/vms/$vmid";
1838 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1840 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1842 # Note: VNC ports are acessible from outside, so we do not gain any
1843 # security if we verify that $param->{port} belongs to VM $vmid. This
1844 # check is done by verifying the VNC ticket (inside VNC protocol).
1846 my $port = $param->{port
};
1848 return { port
=> $port };
1851 __PACKAGE__-
>register_method({
1852 name
=> 'spiceproxy',
1853 path
=> '{vmid}/spiceproxy',
1858 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1860 description
=> "Returns a SPICE configuration to connect to the VM.",
1862 additionalProperties
=> 0,
1864 node
=> get_standard_option
('pve-node'),
1865 vmid
=> get_standard_option
('pve-vmid'),
1866 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1869 returns
=> get_standard_option
('remote-viewer-config'),
1873 my $rpcenv = PVE
::RPCEnvironment
::get
();
1875 my $authuser = $rpcenv->get_user();
1877 my $vmid = $param->{vmid
};
1878 my $node = $param->{node
};
1879 my $proxy = $param->{proxy
};
1881 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1882 my $title = "VM $vmid";
1883 $title .= " - ". $conf->{name
} if $conf->{name
};
1885 my $port = PVE
::QemuServer
::spice_port
($vmid);
1887 my ($ticket, undef, $remote_viewer_config) =
1888 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1890 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1891 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1893 return $remote_viewer_config;
1896 __PACKAGE__-
>register_method({
1898 path
=> '{vmid}/status',
1901 description
=> "Directory index",
1906 additionalProperties
=> 0,
1908 node
=> get_standard_option
('pve-node'),
1909 vmid
=> get_standard_option
('pve-vmid'),
1917 subdir
=> { type
=> 'string' },
1920 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1926 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1929 { subdir
=> 'current' },
1930 { subdir
=> 'start' },
1931 { subdir
=> 'stop' },
1932 { subdir
=> 'reset' },
1933 { subdir
=> 'shutdown' },
1934 { subdir
=> 'suspend' },
1935 { subdir
=> 'reboot' },
1941 __PACKAGE__-
>register_method({
1942 name
=> 'vm_status',
1943 path
=> '{vmid}/status/current',
1946 protected
=> 1, # qemu pid files are only readable by root
1947 description
=> "Get virtual machine status.",
1949 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1952 additionalProperties
=> 0,
1954 node
=> get_standard_option
('pve-node'),
1955 vmid
=> get_standard_option
('pve-vmid'),
1961 %$PVE::QemuServer
::vmstatus_return_properties
,
1963 description
=> "HA manager service status.",
1967 description
=> "Qemu VGA configuration supports spice.",
1972 description
=> "Qemu GuestAgent enabled in config.",
1982 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1984 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1985 my $status = $vmstatus->{$param->{vmid
}};
1987 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1989 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1990 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1995 __PACKAGE__-
>register_method({
1997 path
=> '{vmid}/status/start',
2001 description
=> "Start virtual machine.",
2003 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2006 additionalProperties
=> 0,
2008 node
=> get_standard_option
('pve-node'),
2009 vmid
=> get_standard_option
('pve-vmid',
2010 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2011 skiplock
=> get_standard_option
('skiplock'),
2012 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2013 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2016 enum
=> ['secure', 'insecure'],
2017 description
=> "Migration traffic is encrypted using an SSH " .
2018 "tunnel by default. On secure, completely private networks " .
2019 "this can be disabled to increase performance.",
2022 migration_network
=> {
2023 type
=> 'string', format
=> 'CIDR',
2024 description
=> "CIDR of the (sub) network that is used for migration.",
2027 machine
=> get_standard_option
('pve-qemu-machine'),
2029 description
=> "Override QEMU's -cpu argument with the given string.",
2033 targetstorage
=> get_standard_option
('pve-targetstorage'),
2035 description
=> "Wait maximal timeout seconds.",
2038 default => 'max(30, vm memory in GiB)',
2049 my $rpcenv = PVE
::RPCEnvironment
::get
();
2050 my $authuser = $rpcenv->get_user();
2052 my $node = extract_param
($param, 'node');
2053 my $vmid = extract_param
($param, 'vmid');
2054 my $timeout = extract_param
($param, 'timeout');
2056 my $machine = extract_param
($param, 'machine');
2057 my $force_cpu = extract_param
($param, 'force-cpu');
2059 my $get_root_param = sub {
2060 my $value = extract_param
($param, $_[0]);
2061 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2062 if $value && $authuser ne 'root@pam';
2066 my $stateuri = $get_root_param->('stateuri');
2067 my $skiplock = $get_root_param->('skiplock');
2068 my $migratedfrom = $get_root_param->('migratedfrom');
2069 my $migration_type = $get_root_param->('migration_type');
2070 my $migration_network = $get_root_param->('migration_network');
2071 my $targetstorage = $get_root_param->('targetstorage');
2075 if ($targetstorage) {
2076 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2078 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2079 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2083 # read spice ticket from STDIN
2085 my $nbd_protocol_version = 0;
2086 my $replicated_volumes = {};
2087 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2088 while (defined(my $line = <STDIN
>)) {
2090 if ($line =~ m/^spice_ticket: (.+)$/) {
2092 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2093 $nbd_protocol_version = $1;
2094 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2095 $replicated_volumes->{$1} = 1;
2097 # fallback for old source node
2098 $spice_ticket = $line;
2103 PVE
::Cluster
::check_cfs_quorum
();
2105 my $storecfg = PVE
::Storage
::config
();
2107 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2111 print "Requesting HA start for VM $vmid\n";
2113 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2114 PVE
::Tools
::run_command
($cmd);
2118 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2125 syslog
('info', "start VM $vmid: $upid\n");
2127 my $migrate_opts = {
2128 migratedfrom
=> $migratedfrom,
2129 spice_ticket
=> $spice_ticket,
2130 network
=> $migration_network,
2131 type
=> $migration_type,
2132 storagemap
=> $storagemap,
2133 nbd_proto_version
=> $nbd_protocol_version,
2134 replicated_volumes
=> $replicated_volumes,
2138 statefile
=> $stateuri,
2139 skiplock
=> $skiplock,
2140 forcemachine
=> $machine,
2141 timeout
=> $timeout,
2142 forcecpu
=> $force_cpu,
2145 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2149 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2153 __PACKAGE__-
>register_method({
2155 path
=> '{vmid}/status/stop',
2159 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2160 "is akin to pulling the power plug of a running computer and may damage the VM data",
2162 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2165 additionalProperties
=> 0,
2167 node
=> get_standard_option
('pve-node'),
2168 vmid
=> get_standard_option
('pve-vmid',
2169 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2170 skiplock
=> get_standard_option
('skiplock'),
2171 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2173 description
=> "Wait maximal timeout seconds.",
2179 description
=> "Do not deactivate storage volumes.",
2192 my $rpcenv = PVE
::RPCEnvironment
::get
();
2193 my $authuser = $rpcenv->get_user();
2195 my $node = extract_param
($param, 'node');
2196 my $vmid = extract_param
($param, 'vmid');
2198 my $skiplock = extract_param
($param, 'skiplock');
2199 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2200 if $skiplock && $authuser ne 'root@pam';
2202 my $keepActive = extract_param
($param, 'keepActive');
2203 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2204 if $keepActive && $authuser ne 'root@pam';
2206 my $migratedfrom = extract_param
($param, 'migratedfrom');
2207 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2208 if $migratedfrom && $authuser ne 'root@pam';
2211 my $storecfg = PVE
::Storage
::config
();
2213 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2218 print "Requesting HA stop for VM $vmid\n";
2220 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2221 PVE
::Tools
::run_command
($cmd);
2225 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2231 syslog
('info', "stop VM $vmid: $upid\n");
2233 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2234 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2238 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2242 __PACKAGE__-
>register_method({
2244 path
=> '{vmid}/status/reset',
2248 description
=> "Reset virtual machine.",
2250 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2253 additionalProperties
=> 0,
2255 node
=> get_standard_option
('pve-node'),
2256 vmid
=> get_standard_option
('pve-vmid',
2257 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2258 skiplock
=> get_standard_option
('skiplock'),
2267 my $rpcenv = PVE
::RPCEnvironment
::get
();
2269 my $authuser = $rpcenv->get_user();
2271 my $node = extract_param
($param, 'node');
2273 my $vmid = extract_param
($param, 'vmid');
2275 my $skiplock = extract_param
($param, 'skiplock');
2276 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2277 if $skiplock && $authuser ne 'root@pam';
2279 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2284 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2289 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2292 __PACKAGE__-
>register_method({
2293 name
=> 'vm_shutdown',
2294 path
=> '{vmid}/status/shutdown',
2298 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2299 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2301 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2304 additionalProperties
=> 0,
2306 node
=> get_standard_option
('pve-node'),
2307 vmid
=> get_standard_option
('pve-vmid',
2308 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2309 skiplock
=> get_standard_option
('skiplock'),
2311 description
=> "Wait maximal timeout seconds.",
2317 description
=> "Make sure the VM stops.",
2323 description
=> "Do not deactivate storage volumes.",
2336 my $rpcenv = PVE
::RPCEnvironment
::get
();
2337 my $authuser = $rpcenv->get_user();
2339 my $node = extract_param
($param, 'node');
2340 my $vmid = extract_param
($param, 'vmid');
2342 my $skiplock = extract_param
($param, 'skiplock');
2343 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2344 if $skiplock && $authuser ne 'root@pam';
2346 my $keepActive = extract_param
($param, 'keepActive');
2347 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2348 if $keepActive && $authuser ne 'root@pam';
2350 my $storecfg = PVE
::Storage
::config
();
2354 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2355 # otherwise, we will infer a shutdown command, but run into the timeout,
2356 # then when the vm is resumed, it will instantly shutdown
2358 # checking the qmp status here to get feedback to the gui/cli/api
2359 # and the status query should not take too long
2360 my $qmpstatus = eval {
2361 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2362 mon_cmd
($vmid, "query-status");
2366 if (!$err && $qmpstatus->{status
} eq "paused") {
2367 if ($param->{forceStop
}) {
2368 warn "VM is paused - stop instead of shutdown\n";
2371 die "VM is paused - cannot shutdown\n";
2375 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2377 my $timeout = $param->{timeout
} // 60;
2381 print "Requesting HA stop for VM $vmid\n";
2383 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2384 PVE
::Tools
::run_command
($cmd);
2388 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2395 syslog
('info', "shutdown VM $vmid: $upid\n");
2397 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2398 $shutdown, $param->{forceStop
}, $keepActive);
2402 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2406 __PACKAGE__-
>register_method({
2407 name
=> 'vm_reboot',
2408 path
=> '{vmid}/status/reboot',
2412 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2414 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2417 additionalProperties
=> 0,
2419 node
=> get_standard_option
('pve-node'),
2420 vmid
=> get_standard_option
('pve-vmid',
2421 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2423 description
=> "Wait maximal timeout seconds for the shutdown.",
2436 my $rpcenv = PVE
::RPCEnvironment
::get
();
2437 my $authuser = $rpcenv->get_user();
2439 my $node = extract_param
($param, 'node');
2440 my $vmid = extract_param
($param, 'vmid');
2442 my $qmpstatus = eval {
2443 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2444 mon_cmd
($vmid, "query-status");
2448 if (!$err && $qmpstatus->{status
} eq "paused") {
2449 die "VM is paused - cannot shutdown\n";
2452 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2457 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2458 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2462 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2465 __PACKAGE__-
>register_method({
2466 name
=> 'vm_suspend',
2467 path
=> '{vmid}/status/suspend',
2471 description
=> "Suspend virtual machine.",
2473 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2474 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2475 " on the storage for the vmstate.",
2476 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2479 additionalProperties
=> 0,
2481 node
=> get_standard_option
('pve-node'),
2482 vmid
=> get_standard_option
('pve-vmid',
2483 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2484 skiplock
=> get_standard_option
('skiplock'),
2489 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2491 statestorage
=> get_standard_option
('pve-storage-id', {
2492 description
=> "The storage for the VM state",
2493 requires
=> 'todisk',
2495 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2505 my $rpcenv = PVE
::RPCEnvironment
::get
();
2506 my $authuser = $rpcenv->get_user();
2508 my $node = extract_param
($param, 'node');
2509 my $vmid = extract_param
($param, 'vmid');
2511 my $todisk = extract_param
($param, 'todisk') // 0;
2513 my $statestorage = extract_param
($param, 'statestorage');
2515 my $skiplock = extract_param
($param, 'skiplock');
2516 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2517 if $skiplock && $authuser ne 'root@pam';
2519 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2521 die "Cannot suspend HA managed VM to disk\n"
2522 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2524 # early check for storage permission, for better user feedback
2526 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2528 if (!$statestorage) {
2529 # get statestorage from config if none is given
2530 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2531 my $storecfg = PVE
::Storage
::config
();
2532 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2535 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2541 syslog
('info', "suspend VM $vmid: $upid\n");
2543 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2548 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2549 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2552 __PACKAGE__-
>register_method({
2553 name
=> 'vm_resume',
2554 path
=> '{vmid}/status/resume',
2558 description
=> "Resume virtual machine.",
2560 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2563 additionalProperties
=> 0,
2565 node
=> get_standard_option
('pve-node'),
2566 vmid
=> get_standard_option
('pve-vmid',
2567 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2568 skiplock
=> get_standard_option
('skiplock'),
2569 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2579 my $rpcenv = PVE
::RPCEnvironment
::get
();
2581 my $authuser = $rpcenv->get_user();
2583 my $node = extract_param
($param, 'node');
2585 my $vmid = extract_param
($param, 'vmid');
2587 my $skiplock = extract_param
($param, 'skiplock');
2588 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2589 if $skiplock && $authuser ne 'root@pam';
2591 my $nocheck = extract_param
($param, 'nocheck');
2592 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2593 if $nocheck && $authuser ne 'root@pam';
2595 my $to_disk_suspended;
2597 PVE
::QemuConfig-
>lock_config($vmid, sub {
2598 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2599 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2603 die "VM $vmid not running\n"
2604 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2609 syslog
('info', "resume VM $vmid: $upid\n");
2611 if (!$to_disk_suspended) {
2612 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2614 my $storecfg = PVE
::Storage
::config
();
2615 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2621 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2624 __PACKAGE__-
>register_method({
2625 name
=> 'vm_sendkey',
2626 path
=> '{vmid}/sendkey',
2630 description
=> "Send key event to virtual machine.",
2632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2635 additionalProperties
=> 0,
2637 node
=> get_standard_option
('pve-node'),
2638 vmid
=> get_standard_option
('pve-vmid',
2639 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2640 skiplock
=> get_standard_option
('skiplock'),
2642 description
=> "The key (qemu monitor encoding).",
2647 returns
=> { type
=> 'null'},
2651 my $rpcenv = PVE
::RPCEnvironment
::get
();
2653 my $authuser = $rpcenv->get_user();
2655 my $node = extract_param
($param, 'node');
2657 my $vmid = extract_param
($param, 'vmid');
2659 my $skiplock = extract_param
($param, 'skiplock');
2660 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2661 if $skiplock && $authuser ne 'root@pam';
2663 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2668 __PACKAGE__-
>register_method({
2669 name
=> 'vm_feature',
2670 path
=> '{vmid}/feature',
2674 description
=> "Check if feature for virtual machine is available.",
2676 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2679 additionalProperties
=> 0,
2681 node
=> get_standard_option
('pve-node'),
2682 vmid
=> get_standard_option
('pve-vmid'),
2684 description
=> "Feature to check.",
2686 enum
=> [ 'snapshot', 'clone', 'copy' ],
2688 snapname
=> get_standard_option
('pve-snapshot-name', {
2696 hasFeature
=> { type
=> 'boolean' },
2699 items
=> { type
=> 'string' },
2706 my $node = extract_param
($param, 'node');
2708 my $vmid = extract_param
($param, 'vmid');
2710 my $snapname = extract_param
($param, 'snapname');
2712 my $feature = extract_param
($param, 'feature');
2714 my $running = PVE
::QemuServer
::check_running
($vmid);
2716 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2719 my $snap = $conf->{snapshots
}->{$snapname};
2720 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2723 my $storecfg = PVE
::Storage
::config
();
2725 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2726 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2729 hasFeature
=> $hasFeature,
2730 nodes
=> [ keys %$nodelist ],
2734 __PACKAGE__-
>register_method({
2736 path
=> '{vmid}/clone',
2740 description
=> "Create a copy of virtual machine/template.",
2742 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2743 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2744 "'Datastore.AllocateSpace' on any used storage.",
2747 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2749 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2750 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2755 additionalProperties
=> 0,
2757 node
=> get_standard_option
('pve-node'),
2758 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2759 newid
=> get_standard_option
('pve-vmid', {
2760 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2761 description
=> 'VMID for the clone.' }),
2764 type
=> 'string', format
=> 'dns-name',
2765 description
=> "Set a name for the new VM.",
2770 description
=> "Description for the new VM.",
2774 type
=> 'string', format
=> 'pve-poolid',
2775 description
=> "Add the new VM to the specified pool.",
2777 snapname
=> get_standard_option
('pve-snapshot-name', {
2780 storage
=> get_standard_option
('pve-storage-id', {
2781 description
=> "Target storage for full clone.",
2785 description
=> "Target format for file storage. Only valid for full clone.",
2788 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2793 description
=> "Create a full copy of all disks. This is always done when " .
2794 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2796 target
=> get_standard_option
('pve-node', {
2797 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2801 description
=> "Override I/O bandwidth limit (in KiB/s).",
2805 default => 'clone limit from datacenter or storage config',
2815 my $rpcenv = PVE
::RPCEnvironment
::get
();
2816 my $authuser = $rpcenv->get_user();
2818 my $node = extract_param
($param, 'node');
2819 my $vmid = extract_param
($param, 'vmid');
2820 my $newid = extract_param
($param, 'newid');
2821 my $pool = extract_param
($param, 'pool');
2822 $rpcenv->check_pool_exist($pool) if defined($pool);
2824 my $snapname = extract_param
($param, 'snapname');
2825 my $storage = extract_param
($param, 'storage');
2826 my $format = extract_param
($param, 'format');
2827 my $target = extract_param
($param, 'target');
2829 my $localnode = PVE
::INotify
::nodename
();
2831 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2835 PVE
::Cluster
::check_node_exists
($target) if $target;
2837 my $storecfg = PVE
::Storage
::config
();
2840 # check if storage is enabled on local node
2841 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2843 # check if storage is available on target node
2844 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2845 # clone only works if target storage is shared
2846 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2847 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2851 PVE
::Cluster
::check_cfs_quorum
();
2853 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2855 # exclusive lock if VM is running - else shared lock is enough;
2856 my $shared_lock = $running ?
0 : 1;
2859 # do all tests after lock but before forking worker - if possible
2861 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2862 PVE
::QemuConfig-
>check_lock($conf);
2864 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2865 die "unexpected state change\n" if $verify_running != $running;
2867 die "snapshot '$snapname' does not exist\n"
2868 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2870 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2872 die "parameter 'storage' not allowed for linked clones\n"
2873 if defined($storage) && !$full;
2875 die "parameter 'format' not allowed for linked clones\n"
2876 if defined($format) && !$full;
2878 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2880 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2882 die "can't clone VM to node '$target' (VM uses local storage)\n"
2883 if $target && !$sharedvm;
2885 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2886 die "unable to create VM $newid: config file already exists\n"
2889 my $newconf = { lock => 'clone' };
2894 foreach my $opt (keys %$oldconf) {
2895 my $value = $oldconf->{$opt};
2897 # do not copy snapshot related info
2898 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2899 $opt eq 'vmstate' || $opt eq 'snapstate';
2901 # no need to copy unused images, because VMID(owner) changes anyways
2902 next if $opt =~ m/^unused\d+$/;
2904 # always change MAC! address
2905 if ($opt =~ m/^net(\d+)$/) {
2906 my $net = PVE
::QemuServer
::parse_net
($value);
2907 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2908 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2909 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2910 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2911 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2912 die "unable to parse drive options for '$opt'\n" if !$drive;
2913 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2914 $newconf->{$opt} = $value; # simply copy configuration
2916 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2917 die "Full clone feature is not supported for drive '$opt'\n"
2918 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2919 $fullclone->{$opt} = 1;
2921 # not full means clone instead of copy
2922 die "Linked clone feature is not supported for drive '$opt'\n"
2923 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2925 $drives->{$opt} = $drive;
2926 push @$vollist, $drive->{file
};
2929 # copy everything else
2930 $newconf->{$opt} = $value;
2934 # auto generate a new uuid
2935 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2936 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2937 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2938 # auto generate a new vmgenid only if the option was set for template
2939 if ($newconf->{vmgenid
}) {
2940 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2943 delete $newconf->{template
};
2945 if ($param->{name
}) {
2946 $newconf->{name
} = $param->{name
};
2948 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2951 if ($param->{description
}) {
2952 $newconf->{description
} = $param->{description
};
2955 # create empty/temp config - this fails if VM already exists on other node
2956 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2957 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2962 my $newvollist = [];
2969 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2971 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2973 my $bwlimit = extract_param
($param, 'bwlimit');
2975 my $total_jobs = scalar(keys %{$drives});
2978 foreach my $opt (keys %$drives) {
2979 my $drive = $drives->{$opt};
2980 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2981 my $completion = $skipcomplete ?
'skip' : 'complete';
2983 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2984 my $storage_list = [ $src_sid ];
2985 push @$storage_list, $storage if defined($storage);
2986 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2988 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2989 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2990 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
2992 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2994 PVE
::QemuConfig-
>write_config($newid, $newconf);
2998 delete $newconf->{lock};
3000 # do not write pending changes
3001 if (my @changes = keys %{$newconf->{pending
}}) {
3002 my $pending = join(',', @changes);
3003 warn "found pending changes for '$pending', discarding for clone\n";
3004 delete $newconf->{pending
};
3007 PVE
::QemuConfig-
>write_config($newid, $newconf);
3010 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3011 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3012 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3014 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3015 die "Failed to move config to node '$target' - rename failed: $!\n"
3016 if !rename($conffile, $newconffile);
3019 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3022 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3023 sleep 1; # some storage like rbd need to wait before release volume - really?
3025 foreach my $volid (@$newvollist) {
3026 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3030 PVE
::Firewall
::remove_vmfw_conf
($newid);
3032 unlink $conffile; # avoid races -> last thing before die
3034 die "clone failed: $err";
3040 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3042 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3045 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
3046 # Aquire exclusive lock lock for $newid
3047 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3052 __PACKAGE__-
>register_method({
3053 name
=> 'move_vm_disk',
3054 path
=> '{vmid}/move_disk',
3058 description
=> "Move volume to different storage.",
3060 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3062 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3063 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3067 additionalProperties
=> 0,
3069 node
=> get_standard_option
('pve-node'),
3070 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3073 description
=> "The disk you want to move.",
3074 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3076 storage
=> get_standard_option
('pve-storage-id', {
3077 description
=> "Target storage.",
3078 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3082 description
=> "Target Format.",
3083 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3088 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3094 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3099 description
=> "Override I/O bandwidth limit (in KiB/s).",
3103 default => 'move limit from datacenter or storage config',
3109 description
=> "the task ID.",
3114 my $rpcenv = PVE
::RPCEnvironment
::get
();
3115 my $authuser = $rpcenv->get_user();
3117 my $node = extract_param
($param, 'node');
3118 my $vmid = extract_param
($param, 'vmid');
3119 my $digest = extract_param
($param, 'digest');
3120 my $disk = extract_param
($param, 'disk');
3121 my $storeid = extract_param
($param, 'storage');
3122 my $format = extract_param
($param, 'format');
3124 my $storecfg = PVE
::Storage
::config
();
3126 my $updatefn = sub {
3127 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3128 PVE
::QemuConfig-
>check_lock($conf);
3130 die "VM config checksum missmatch (file change by other user?)\n"
3131 if $digest && $digest ne $conf->{digest
};
3133 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3135 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3137 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3138 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3140 my $old_volid = $drive->{file
};
3142 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3143 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3147 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3148 (!$format || !$oldfmt || $oldfmt eq $format);
3150 # this only checks snapshots because $disk is passed!
3151 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3152 die "you can't move a disk with snapshots and delete the source\n"
3153 if $snapshotted && $param->{delete};
3155 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3157 my $running = PVE
::QemuServer
::check_running
($vmid);
3159 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3162 my $newvollist = [];
3168 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3170 warn "moving disk with snapshots, snapshots will not be moved!\n"
3173 my $bwlimit = extract_param
($param, 'bwlimit');
3174 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3176 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3177 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3179 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3181 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3183 # convert moved disk to base if part of template
3184 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3185 if PVE
::QemuConfig-
>is_template($conf);
3187 PVE
::QemuConfig-
>write_config($vmid, $conf);
3189 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3190 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3191 eval { mon_cmd
($vmid, "guest-fstrim") };
3195 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3196 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3202 foreach my $volid (@$newvollist) {
3203 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3206 die "storage migration failed: $err";
3209 if ($param->{delete}) {
3211 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3212 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3218 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3221 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3224 my $check_vm_disks_local = sub {
3225 my ($storecfg, $vmconf, $vmid) = @_;
3227 my $local_disks = {};
3229 # add some more information to the disks e.g. cdrom
3230 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3231 my ($volid, $attr) = @_;
3233 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3235 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3236 return if $scfg->{shared
};
3238 # The shared attr here is just a special case where the vdisk
3239 # is marked as shared manually
3240 return if $attr->{shared
};
3241 return if $attr->{cdrom
} and $volid eq "none";
3243 if (exists $local_disks->{$volid}) {
3244 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3246 $local_disks->{$volid} = $attr;
3247 # ensure volid is present in case it's needed
3248 $local_disks->{$volid}->{volid
} = $volid;
3252 return $local_disks;
3255 __PACKAGE__-
>register_method({
3256 name
=> 'migrate_vm_precondition',
3257 path
=> '{vmid}/migrate',
3261 description
=> "Get preconditions for migration.",
3263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3266 additionalProperties
=> 0,
3268 node
=> get_standard_option
('pve-node'),
3269 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3270 target
=> get_standard_option
('pve-node', {
3271 description
=> "Target node.",
3272 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3280 running
=> { type
=> 'boolean' },
3284 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3286 not_allowed_nodes
=> {
3289 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3293 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3295 local_resources
=> {
3297 description
=> "List local resources e.g. pci, usb"
3304 my $rpcenv = PVE
::RPCEnvironment
::get
();
3306 my $authuser = $rpcenv->get_user();
3308 PVE
::Cluster
::check_cfs_quorum
();
3312 my $vmid = extract_param
($param, 'vmid');
3313 my $target = extract_param
($param, 'target');
3314 my $localnode = PVE
::INotify
::nodename
();
3318 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3319 my $storecfg = PVE
::Storage
::config
();
3322 # try to detect errors early
3323 PVE
::QemuConfig-
>check_lock($vmconf);
3325 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3327 # if vm is not running, return target nodes where local storage is available
3328 # for offline migration
3329 if (!$res->{running
}) {
3330 $res->{allowed_nodes
} = [];
3331 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3332 delete $checked_nodes->{$localnode};
3334 foreach my $node (keys %$checked_nodes) {
3335 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3336 push @{$res->{allowed_nodes
}}, $node;
3340 $res->{not_allowed_nodes
} = $checked_nodes;
3344 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3345 $res->{local_disks
} = [ values %$local_disks ];;
3347 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3349 $res->{local_resources
} = $local_resources;
3356 __PACKAGE__-
>register_method({
3357 name
=> 'migrate_vm',
3358 path
=> '{vmid}/migrate',
3362 description
=> "Migrate virtual machine. Creates a new migration task.",
3364 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3367 additionalProperties
=> 0,
3369 node
=> get_standard_option
('pve-node'),
3370 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3371 target
=> get_standard_option
('pve-node', {
3372 description
=> "Target node.",
3373 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3377 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3382 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3387 enum
=> ['secure', 'insecure'],
3388 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3391 migration_network
=> {
3392 type
=> 'string', format
=> 'CIDR',
3393 description
=> "CIDR of the (sub) network that is used for migration.",
3396 "with-local-disks" => {
3398 description
=> "Enable live storage migration for local disk",
3401 targetstorage
=> get_standard_option
('pve-targetstorage', {
3402 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3405 description
=> "Override I/O bandwidth limit (in KiB/s).",
3409 default => 'migrate limit from datacenter or storage config',
3415 description
=> "the task ID.",
3420 my $rpcenv = PVE
::RPCEnvironment
::get
();
3421 my $authuser = $rpcenv->get_user();
3423 my $target = extract_param
($param, 'target');
3425 my $localnode = PVE
::INotify
::nodename
();
3426 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3428 PVE
::Cluster
::check_cfs_quorum
();
3430 PVE
::Cluster
::check_node_exists
($target);
3432 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3434 my $vmid = extract_param
($param, 'vmid');
3436 raise_param_exc
({ force
=> "Only root may use this option." })
3437 if $param->{force
} && $authuser ne 'root@pam';
3439 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3440 if $param->{migration_type
} && $authuser ne 'root@pam';
3442 # allow root only until better network permissions are available
3443 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3444 if $param->{migration_network
} && $authuser ne 'root@pam';
3447 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3449 # try to detect errors early
3451 PVE
::QemuConfig-
>check_lock($conf);
3453 if (PVE
::QemuServer
::check_running
($vmid)) {
3454 die "can't migrate running VM without --online\n" if !$param->{online
};
3456 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3457 $param->{online
} = 0;
3460 my $storecfg = PVE
::Storage
::config
();
3462 if (my $targetstorage = $param->{targetstorage
}) {
3463 my $check_storage = sub {
3464 my ($target_sid) = @_;
3465 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3466 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3467 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3468 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3469 if !$scfg->{content
}->{images
};
3472 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3473 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3476 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3477 if !defined($storagemap->{identity
});
3479 foreach my $source (values %{$storagemap->{entries
}}) {
3480 $check_storage->($source);
3483 $check_storage->($storagemap->{default})
3484 if $storagemap->{default};
3486 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3487 if $storagemap->{identity
};
3489 $param->{storagemap
} = $storagemap;
3491 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3494 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3499 print "Requesting HA migration for VM $vmid to node $target\n";
3501 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3502 PVE
::Tools
::run_command
($cmd);
3506 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3511 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3515 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3518 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3523 __PACKAGE__-
>register_method({
3525 path
=> '{vmid}/monitor',
3529 description
=> "Execute Qemu monitor commands.",
3531 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3532 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3535 additionalProperties
=> 0,
3537 node
=> get_standard_option
('pve-node'),
3538 vmid
=> get_standard_option
('pve-vmid'),
3541 description
=> "The monitor command.",
3545 returns
=> { type
=> 'string'},
3549 my $rpcenv = PVE
::RPCEnvironment
::get
();
3550 my $authuser = $rpcenv->get_user();
3553 my $command = shift;
3554 return $command =~ m/^\s*info(\s+|$)/
3555 || $command =~ m/^\s*help\s*$/;
3558 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3559 if !&$is_ro($param->{command
});
3561 my $vmid = $param->{vmid
};
3563 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3567 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3569 $res = "ERROR: $@" if $@;
3574 __PACKAGE__-
>register_method({
3575 name
=> 'resize_vm',
3576 path
=> '{vmid}/resize',
3580 description
=> "Extend volume size.",
3582 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3585 additionalProperties
=> 0,
3587 node
=> get_standard_option
('pve-node'),
3588 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3589 skiplock
=> get_standard_option
('skiplock'),
3592 description
=> "The disk you want to resize.",
3593 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3597 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3598 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.",
3602 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3608 returns
=> { type
=> 'null'},
3612 my $rpcenv = PVE
::RPCEnvironment
::get
();
3614 my $authuser = $rpcenv->get_user();
3616 my $node = extract_param
($param, 'node');
3618 my $vmid = extract_param
($param, 'vmid');
3620 my $digest = extract_param
($param, 'digest');
3622 my $disk = extract_param
($param, 'disk');
3624 my $sizestr = extract_param
($param, 'size');
3626 my $skiplock = extract_param
($param, 'skiplock');
3627 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3628 if $skiplock && $authuser ne 'root@pam';
3630 my $storecfg = PVE
::Storage
::config
();
3632 my $updatefn = sub {
3634 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3636 die "checksum missmatch (file change by other user?)\n"
3637 if $digest && $digest ne $conf->{digest
};
3638 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3640 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3642 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3644 my (undef, undef, undef, undef, undef, undef, $format) =
3645 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3647 die "can't resize volume: $disk if snapshot exists\n"
3648 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3650 my $volid = $drive->{file
};
3652 die "disk '$disk' has no associated volume\n" if !$volid;
3654 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3656 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3658 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3660 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3661 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3663 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3665 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3666 my ($ext, $newsize, $unit) = ($1, $2, $4);
3669 $newsize = $newsize * 1024;
3670 } elsif ($unit eq 'M') {
3671 $newsize = $newsize * 1024 * 1024;
3672 } elsif ($unit eq 'G') {
3673 $newsize = $newsize * 1024 * 1024 * 1024;
3674 } elsif ($unit eq 'T') {
3675 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3678 $newsize += $size if $ext;
3679 $newsize = int($newsize);
3681 die "shrinking disks is not supported\n" if $newsize < $size;
3683 return if $size == $newsize;
3685 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3687 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3689 my $effective_size = eval { PVE
::Storage
::volume_size_info
($storecfg, $volid, 3); };
3690 $drive->{size
} = $effective_size // $newsize;
3691 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3693 PVE
::QemuConfig-
>write_config($vmid, $conf);
3696 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3700 __PACKAGE__-
>register_method({
3701 name
=> 'snapshot_list',
3702 path
=> '{vmid}/snapshot',
3704 description
=> "List all snapshots.",
3706 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3709 protected
=> 1, # qemu pid files are only readable by root
3711 additionalProperties
=> 0,
3713 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3714 node
=> get_standard_option
('pve-node'),
3723 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3727 description
=> "Snapshot includes RAM.",
3732 description
=> "Snapshot description.",
3736 description
=> "Snapshot creation time",
3738 renderer
=> 'timestamp',
3742 description
=> "Parent snapshot identifier.",
3748 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3753 my $vmid = $param->{vmid
};
3755 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3756 my $snaphash = $conf->{snapshots
} || {};
3760 foreach my $name (keys %$snaphash) {
3761 my $d = $snaphash->{$name};
3764 snaptime
=> $d->{snaptime
} || 0,
3765 vmstate
=> $d->{vmstate
} ?
1 : 0,
3766 description
=> $d->{description
} || '',
3768 $item->{parent
} = $d->{parent
} if $d->{parent
};
3769 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3773 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3776 digest
=> $conf->{digest
},
3777 running
=> $running,
3778 description
=> "You are here!",
3780 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3782 push @$res, $current;
3787 __PACKAGE__-
>register_method({
3789 path
=> '{vmid}/snapshot',
3793 description
=> "Snapshot a VM.",
3795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3798 additionalProperties
=> 0,
3800 node
=> get_standard_option
('pve-node'),
3801 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3802 snapname
=> get_standard_option
('pve-snapshot-name'),
3806 description
=> "Save the vmstate",
3811 description
=> "A textual description or comment.",
3817 description
=> "the task ID.",
3822 my $rpcenv = PVE
::RPCEnvironment
::get
();
3824 my $authuser = $rpcenv->get_user();
3826 my $node = extract_param
($param, 'node');
3828 my $vmid = extract_param
($param, 'vmid');
3830 my $snapname = extract_param
($param, 'snapname');
3832 die "unable to use snapshot name 'current' (reserved name)\n"
3833 if $snapname eq 'current';
3835 die "unable to use snapshot name 'pending' (reserved name)\n"
3836 if lc($snapname) eq 'pending';
3839 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3840 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3841 $param->{description
});
3844 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3847 __PACKAGE__-
>register_method({
3848 name
=> 'snapshot_cmd_idx',
3849 path
=> '{vmid}/snapshot/{snapname}',
3856 additionalProperties
=> 0,
3858 vmid
=> get_standard_option
('pve-vmid'),
3859 node
=> get_standard_option
('pve-node'),
3860 snapname
=> get_standard_option
('pve-snapshot-name'),
3869 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3876 push @$res, { cmd
=> 'rollback' };
3877 push @$res, { cmd
=> 'config' };
3882 __PACKAGE__-
>register_method({
3883 name
=> 'update_snapshot_config',
3884 path
=> '{vmid}/snapshot/{snapname}/config',
3888 description
=> "Update snapshot metadata.",
3890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3893 additionalProperties
=> 0,
3895 node
=> get_standard_option
('pve-node'),
3896 vmid
=> get_standard_option
('pve-vmid'),
3897 snapname
=> get_standard_option
('pve-snapshot-name'),
3901 description
=> "A textual description or comment.",
3905 returns
=> { type
=> 'null' },
3909 my $rpcenv = PVE
::RPCEnvironment
::get
();
3911 my $authuser = $rpcenv->get_user();
3913 my $vmid = extract_param
($param, 'vmid');
3915 my $snapname = extract_param
($param, 'snapname');
3917 return undef if !defined($param->{description
});
3919 my $updatefn = sub {
3921 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3923 PVE
::QemuConfig-
>check_lock($conf);
3925 my $snap = $conf->{snapshots
}->{$snapname};
3927 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3929 $snap->{description
} = $param->{description
} if defined($param->{description
});
3931 PVE
::QemuConfig-
>write_config($vmid, $conf);
3934 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3939 __PACKAGE__-
>register_method({
3940 name
=> 'get_snapshot_config',
3941 path
=> '{vmid}/snapshot/{snapname}/config',
3944 description
=> "Get snapshot configuration",
3946 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
3949 additionalProperties
=> 0,
3951 node
=> get_standard_option
('pve-node'),
3952 vmid
=> get_standard_option
('pve-vmid'),
3953 snapname
=> get_standard_option
('pve-snapshot-name'),
3956 returns
=> { type
=> "object" },
3960 my $rpcenv = PVE
::RPCEnvironment
::get
();
3962 my $authuser = $rpcenv->get_user();
3964 my $vmid = extract_param
($param, 'vmid');
3966 my $snapname = extract_param
($param, 'snapname');
3968 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3970 my $snap = $conf->{snapshots
}->{$snapname};
3972 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3977 __PACKAGE__-
>register_method({
3979 path
=> '{vmid}/snapshot/{snapname}/rollback',
3983 description
=> "Rollback VM state to specified snapshot.",
3985 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3988 additionalProperties
=> 0,
3990 node
=> get_standard_option
('pve-node'),
3991 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3992 snapname
=> get_standard_option
('pve-snapshot-name'),
3997 description
=> "the task ID.",
4002 my $rpcenv = PVE
::RPCEnvironment
::get
();
4004 my $authuser = $rpcenv->get_user();
4006 my $node = extract_param
($param, 'node');
4008 my $vmid = extract_param
($param, 'vmid');
4010 my $snapname = extract_param
($param, 'snapname');
4013 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4014 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4018 # hold migration lock, this makes sure that nobody create replication snapshots
4019 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4022 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4025 __PACKAGE__-
>register_method({
4026 name
=> 'delsnapshot',
4027 path
=> '{vmid}/snapshot/{snapname}',
4031 description
=> "Delete a VM snapshot.",
4033 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4036 additionalProperties
=> 0,
4038 node
=> get_standard_option
('pve-node'),
4039 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4040 snapname
=> get_standard_option
('pve-snapshot-name'),
4044 description
=> "For removal from config file, even if removing disk snapshots fails.",
4050 description
=> "the task ID.",
4055 my $rpcenv = PVE
::RPCEnvironment
::get
();
4057 my $authuser = $rpcenv->get_user();
4059 my $node = extract_param
($param, 'node');
4061 my $vmid = extract_param
($param, 'vmid');
4063 my $snapname = extract_param
($param, 'snapname');
4066 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4067 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4070 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4073 __PACKAGE__-
>register_method({
4075 path
=> '{vmid}/template',
4079 description
=> "Create a Template.",
4081 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4082 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4085 additionalProperties
=> 0,
4087 node
=> get_standard_option
('pve-node'),
4088 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4092 description
=> "If you want to convert only 1 disk to base image.",
4093 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4098 returns
=> { type
=> 'null'},
4102 my $rpcenv = PVE
::RPCEnvironment
::get
();
4104 my $authuser = $rpcenv->get_user();
4106 my $node = extract_param
($param, 'node');
4108 my $vmid = extract_param
($param, 'vmid');
4110 my $disk = extract_param
($param, 'disk');
4112 my $updatefn = sub {
4114 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4116 PVE
::QemuConfig-
>check_lock($conf);
4118 die "unable to create template, because VM contains snapshots\n"
4119 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4121 die "you can't convert a template to a template\n"
4122 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4124 die "you can't convert a VM to template if VM is running\n"
4125 if PVE
::QemuServer
::check_running
($vmid);
4128 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4131 $conf->{template
} = 1;
4132 PVE
::QemuConfig-
>write_config($vmid, $conf);
4134 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4137 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4141 __PACKAGE__-
>register_method({
4142 name
=> 'cloudinit_generated_config_dump',
4143 path
=> '{vmid}/cloudinit/dump',
4146 description
=> "Get automatically generated cloudinit config.",
4148 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4151 additionalProperties
=> 0,
4153 node
=> get_standard_option
('pve-node'),
4154 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4156 description
=> 'Config type.',
4158 enum
=> ['user', 'network', 'meta'],
4168 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4170 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});