1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 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.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_modify_config_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (@$key_list) {
338 # some checks (e.g., disk, serial port, usb) need to be done somewhere
339 # else, as there the permission can be value dependend
340 next if PVE
::QemuServer
::is_valid_drivename
($opt);
341 next if $opt eq 'cdrom';
342 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
345 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
346 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
347 } elsif ($memoryoptions->{$opt}) {
348 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
349 } elsif ($hwtypeoptions->{$opt}) {
350 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
351 } elsif ($generaloptions->{$opt}) {
352 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
353 # special case for startup since it changes host behaviour
354 if ($opt eq 'startup') {
355 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
357 } elsif ($vmpoweroptions->{$opt}) {
358 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
359 } elsif ($diskoptions->{$opt}) {
360 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
361 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
362 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
363 } elsif ($cloudinitoptions->{$opt}) {
364 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
365 } elsif ($opt eq 'vmstate') {
366 # the user needs Disk and PowerMgmt privileges to change the vmstate
367 # also needs privileges on the storage, that will be checked later
368 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
370 # catches hostpci\d+, args, lock, etc.
371 # new options will be checked here
372 die "only root can set '$opt' config\n";
379 __PACKAGE__-
>register_method({
383 description
=> "Virtual machine index (per node).",
385 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
389 protected
=> 1, # qemu pid files are only readable by root
391 additionalProperties
=> 0,
393 node
=> get_standard_option
('pve-node'),
397 description
=> "Determine the full status of active VMs.",
405 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
407 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
412 my $rpcenv = PVE
::RPCEnvironment
::get
();
413 my $authuser = $rpcenv->get_user();
415 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
418 foreach my $vmid (keys %$vmstatus) {
419 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
421 my $data = $vmstatus->{$vmid};
428 my $parse_restore_archive = sub {
429 my ($storecfg, $archive) = @_;
431 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
433 if (defined($archive_storeid)) {
434 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
435 if ($scfg->{type
} eq 'pbs') {
442 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
450 __PACKAGE__-
>register_method({
454 description
=> "Create or restore a virtual machine.",
456 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
457 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
458 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
459 user
=> 'all', # check inside
464 additionalProperties
=> 0,
465 properties
=> PVE
::QemuServer
::json_config_properties
(
467 node
=> get_standard_option
('pve-node'),
468 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
470 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.",
474 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
476 storage
=> get_standard_option
('pve-storage-id', {
477 description
=> "Default storage.",
479 completion
=> \
&PVE
::QemuServer
::complete_storage
,
484 description
=> "Allow to overwrite existing VM.",
485 requires
=> 'archive',
490 description
=> "Assign a unique random ethernet address.",
491 requires
=> 'archive',
495 type
=> 'string', format
=> 'pve-poolid',
496 description
=> "Add the VM to the specified pool.",
499 description
=> "Override I/O bandwidth limit (in KiB/s).",
503 default => 'restore limit from datacenter or storage config',
509 description
=> "Start VM after it was created successfully.",
519 my $rpcenv = PVE
::RPCEnvironment
::get
();
520 my $authuser = $rpcenv->get_user();
522 my $node = extract_param
($param, 'node');
523 my $vmid = extract_param
($param, 'vmid');
525 my $archive = extract_param
($param, 'archive');
526 my $is_restore = !!$archive;
528 my $bwlimit = extract_param
($param, 'bwlimit');
529 my $force = extract_param
($param, 'force');
530 my $pool = extract_param
($param, 'pool');
531 my $start_after_create = extract_param
($param, 'start');
532 my $storage = extract_param
($param, 'storage');
533 my $unique = extract_param
($param, 'unique');
535 if (defined(my $ssh_keys = $param->{sshkeys
})) {
536 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
537 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
540 PVE
::Cluster
::check_cfs_quorum
();
542 my $filename = PVE
::QemuConfig-
>config_file($vmid);
543 my $storecfg = PVE
::Storage
::config
();
545 if (defined($pool)) {
546 $rpcenv->check_pool_exist($pool);
549 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
550 if defined($storage);
552 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
554 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
556 } elsif ($archive && $force && (-f
$filename) &&
557 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
558 # OK: user has VM.Backup permissions, and want to restore an existing VM
564 &$resolve_cdrom_alias($param);
566 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
568 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
570 &$check_cpu_model_access($rpcenv, $authuser, $param);
572 foreach my $opt (keys %$param) {
573 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
574 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
575 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
577 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
578 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
582 PVE
::QemuServer
::add_random_macs
($param);
584 my $keystr = join(' ', keys %$param);
585 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
587 if ($archive eq '-') {
588 die "pipe requires cli environment\n"
589 if $rpcenv->{type
} ne 'cli';
590 $archive = { type
=> 'pipe' };
592 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
594 $archive = $parse_restore_archive->($storecfg, $archive);
598 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
600 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
601 die "$emsg $@" if $@;
603 my $restorefn = sub {
604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
606 PVE
::QemuConfig-
>check_protection($conf, $emsg);
608 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
611 my $restore_options = {
617 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
618 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
619 } elsif ($archive->{type
} eq 'pbs') {
620 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
622 die "unknown backup archive type\n";
624 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
625 # Convert restored VM to template if backup was VM template
626 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
627 warn "Convert to template.\n";
628 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
632 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
635 # ensure no old replication state are exists
636 PVE
::ReplicationState
::delete_guest_states
($vmid);
638 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
640 if ($start_after_create) {
641 print "Execute autostart\n";
642 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
648 # ensure no old replication state are exists
649 PVE
::ReplicationState
::delete_guest_states
($vmid);
653 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
657 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
659 if (!$conf->{bootdisk
}) {
660 my $firstdisk = PVE
::QemuServer
::Drive
::resolve_first_disk
($conf);
661 $conf->{bootdisk
} = $firstdisk if $firstdisk;
664 # auto generate uuid if user did not specify smbios1 option
665 if (!$conf->{smbios1
}) {
666 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
669 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
670 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
673 PVE
::QemuConfig-
>write_config($vmid, $conf);
679 foreach my $volid (@$vollist) {
680 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
686 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
689 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
691 if ($start_after_create) {
692 print "Execute autostart\n";
693 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
698 my ($code, $worker_name);
700 $worker_name = 'qmrestore';
702 eval { $restorefn->() };
704 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
710 $worker_name = 'qmcreate';
712 eval { $createfn->() };
715 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
716 unlink($conffile) or die "failed to remove config file: $!\n";
724 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
727 __PACKAGE__-
>register_method({
732 description
=> "Directory index",
737 additionalProperties
=> 0,
739 node
=> get_standard_option
('pve-node'),
740 vmid
=> get_standard_option
('pve-vmid'),
748 subdir
=> { type
=> 'string' },
751 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
757 { subdir
=> 'config' },
758 { subdir
=> 'pending' },
759 { subdir
=> 'status' },
760 { subdir
=> 'unlink' },
761 { subdir
=> 'vncproxy' },
762 { subdir
=> 'termproxy' },
763 { subdir
=> 'migrate' },
764 { subdir
=> 'resize' },
765 { subdir
=> 'move' },
767 { subdir
=> 'rrddata' },
768 { subdir
=> 'monitor' },
769 { subdir
=> 'agent' },
770 { subdir
=> 'snapshot' },
771 { subdir
=> 'spiceproxy' },
772 { subdir
=> 'sendkey' },
773 { subdir
=> 'firewall' },
779 __PACKAGE__-
>register_method ({
780 subclass
=> "PVE::API2::Firewall::VM",
781 path
=> '{vmid}/firewall',
784 __PACKAGE__-
>register_method ({
785 subclass
=> "PVE::API2::Qemu::Agent",
786 path
=> '{vmid}/agent',
789 __PACKAGE__-
>register_method({
791 path
=> '{vmid}/rrd',
793 protected
=> 1, # fixme: can we avoid that?
795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
797 description
=> "Read VM RRD statistics (returns PNG)",
799 additionalProperties
=> 0,
801 node
=> get_standard_option
('pve-node'),
802 vmid
=> get_standard_option
('pve-vmid'),
804 description
=> "Specify the time frame you are interested in.",
806 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
809 description
=> "The list of datasources you want to display.",
810 type
=> 'string', format
=> 'pve-configid-list',
813 description
=> "The RRD consolidation function",
815 enum
=> [ 'AVERAGE', 'MAX' ],
823 filename
=> { type
=> 'string' },
829 return PVE
::RRD
::create_rrd_graph
(
830 "pve2-vm/$param->{vmid}", $param->{timeframe
},
831 $param->{ds
}, $param->{cf
});
835 __PACKAGE__-
>register_method({
837 path
=> '{vmid}/rrddata',
839 protected
=> 1, # fixme: can we avoid that?
841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
843 description
=> "Read VM RRD statistics",
845 additionalProperties
=> 0,
847 node
=> get_standard_option
('pve-node'),
848 vmid
=> get_standard_option
('pve-vmid'),
850 description
=> "Specify the time frame you are interested in.",
852 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
855 description
=> "The RRD consolidation function",
857 enum
=> [ 'AVERAGE', 'MAX' ],
872 return PVE
::RRD
::create_rrd_data
(
873 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
877 __PACKAGE__-
>register_method({
879 path
=> '{vmid}/config',
882 description
=> "Get the virtual machine configuration with pending configuration " .
883 "changes applied. Set the 'current' parameter to get the current configuration instead.",
885 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
888 additionalProperties
=> 0,
890 node
=> get_standard_option
('pve-node'),
891 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
893 description
=> "Get current values (instead of pending values).",
898 snapshot
=> get_standard_option
('pve-snapshot-name', {
899 description
=> "Fetch config values from given snapshot.",
902 my ($cmd, $pname, $cur, $args) = @_;
903 PVE
::QemuConfig-
>snapshot_list($args->[0]);
909 description
=> "The VM configuration.",
911 properties
=> PVE
::QemuServer
::json_config_properties
({
914 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
921 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
922 current
=> "cannot use 'snapshot' parameter with 'current'"})
923 if ($param->{snapshot
} && $param->{current
});
926 if ($param->{snapshot
}) {
927 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
929 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
931 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
936 __PACKAGE__-
>register_method({
937 name
=> 'vm_pending',
938 path
=> '{vmid}/pending',
941 description
=> "Get the virtual machine configuration with both current and pending values.",
943 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
946 additionalProperties
=> 0,
948 node
=> get_standard_option
('pve-node'),
949 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
958 description
=> "Configuration option name.",
962 description
=> "Current value.",
967 description
=> "Pending value.",
972 description
=> "Indicates a pending delete request if present and not 0. " .
973 "The value 2 indicates a force-delete request.",
985 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
987 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
989 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
990 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
992 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
995 # POST/PUT {vmid}/config implementation
997 # The original API used PUT (idempotent) an we assumed that all operations
998 # are fast. But it turned out that almost any configuration change can
999 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1000 # time to complete and have side effects (not idempotent).
1002 # The new implementation uses POST and forks a worker process. We added
1003 # a new option 'background_delay'. If specified we wait up to
1004 # 'background_delay' second for the worker task to complete. It returns null
1005 # if the task is finished within that time, else we return the UPID.
1007 my $update_vm_api = sub {
1008 my ($param, $sync) = @_;
1010 my $rpcenv = PVE
::RPCEnvironment
::get
();
1012 my $authuser = $rpcenv->get_user();
1014 my $node = extract_param
($param, 'node');
1016 my $vmid = extract_param
($param, 'vmid');
1018 my $digest = extract_param
($param, 'digest');
1020 my $background_delay = extract_param
($param, 'background_delay');
1022 if (defined(my $cipassword = $param->{cipassword
})) {
1023 # Same logic as in cloud-init (but with the regex fixed...)
1024 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1025 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1028 my @paramarr = (); # used for log message
1029 foreach my $key (sort keys %$param) {
1030 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1031 push @paramarr, "-$key", $value;
1034 my $skiplock = extract_param
($param, 'skiplock');
1035 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1036 if $skiplock && $authuser ne 'root@pam';
1038 my $delete_str = extract_param
($param, 'delete');
1040 my $revert_str = extract_param
($param, 'revert');
1042 my $force = extract_param
($param, 'force');
1044 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1045 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1046 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1049 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1051 my $storecfg = PVE
::Storage
::config
();
1053 my $defaults = PVE
::QemuServer
::load_defaults
();
1055 &$resolve_cdrom_alias($param);
1057 # now try to verify all parameters
1060 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1061 if (!PVE
::QemuServer
::option_exists
($opt)) {
1062 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1065 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1066 "-revert $opt' at the same time" })
1067 if defined($param->{$opt});
1069 $revert->{$opt} = 1;
1073 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1074 $opt = 'ide2' if $opt eq 'cdrom';
1076 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1077 "-delete $opt' at the same time" })
1078 if defined($param->{$opt});
1080 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1081 "-revert $opt' at the same time" })
1084 if (!PVE
::QemuServer
::option_exists
($opt)) {
1085 raise_param_exc
({ delete => "unknown option '$opt'" });
1091 my $repl_conf = PVE
::ReplicationConfig-
>new();
1092 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1093 my $check_replication = sub {
1095 return if !$is_replicated;
1096 my $volid = $drive->{file
};
1097 return if !$volid || !($drive->{replicate
}//1);
1098 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1100 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1101 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1102 if !defined($storeid);
1104 return if defined($volname) && $volname eq 'cloudinit';
1107 if ($volid =~ $NEW_DISK_RE) {
1109 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1111 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1113 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1115 return if $scfg->{shared
};
1116 die "cannot add non-replicatable volume to a replicated VM\n";
1119 foreach my $opt (keys %$param) {
1120 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1121 # cleanup drive path
1122 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1123 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1124 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1125 $check_replication->($drive);
1126 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1127 } elsif ($opt =~ m/^net(\d+)$/) {
1129 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1130 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1131 } elsif ($opt eq 'vmgenid') {
1132 if ($param->{$opt} eq '1') {
1133 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1135 } elsif ($opt eq 'hookscript') {
1136 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1137 raise_param_exc
({ $opt => $@ }) if $@;
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1143 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1145 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1147 my $updatefn = sub {
1149 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1151 die "checksum missmatch (file change by other user?)\n"
1152 if $digest && $digest ne $conf->{digest
};
1154 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1156 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1157 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1158 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1159 delete $conf->{lock}; # for check lock check, not written out
1160 push @delete, 'lock'; # this is the real deal to write it out
1162 push @delete, 'runningmachine' if $conf->{runningmachine
};
1163 push @delete, 'runningcpu' if $conf->{runningcpu
};
1166 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1168 foreach my $opt (keys %$revert) {
1169 if (defined($conf->{$opt})) {
1170 $param->{$opt} = $conf->{$opt};
1171 } elsif (defined($conf->{pending
}->{$opt})) {
1176 if ($param->{memory
} || defined($param->{balloon
})) {
1177 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1178 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1180 die "balloon value too large (must be smaller than assigned memory)\n"
1181 if $balloon && $balloon > $maxmem;
1184 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1188 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1190 # write updates to pending section
1192 my $modified = {}; # record what $option we modify
1194 foreach my $opt (@delete) {
1195 $modified->{$opt} = 1;
1196 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1198 # value of what we want to delete, independent if pending or not
1199 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1200 if (!defined($val)) {
1201 warn "cannot delete '$opt' - not set in current configuration!\n";
1202 $modified->{$opt} = 0;
1205 my $is_pending_val = defined($conf->{pending
}->{$opt});
1206 delete $conf->{pending
}->{$opt};
1208 if ($opt =~ m/^unused/) {
1209 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1210 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1211 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1212 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1213 delete $conf->{$opt};
1214 PVE
::QemuConfig-
>write_config($vmid, $conf);
1216 } elsif ($opt eq 'vmstate') {
1217 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1218 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1219 delete $conf->{$opt};
1220 PVE
::QemuConfig-
>write_config($vmid, $conf);
1222 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1223 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1224 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1225 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1227 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1228 PVE
::QemuConfig-
>write_config($vmid, $conf);
1229 } elsif ($opt =~ m/^serial\d+$/) {
1230 if ($val eq 'socket') {
1231 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1232 } elsif ($authuser ne 'root@pam') {
1233 die "only root can delete '$opt' config for real devices\n";
1235 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1236 PVE
::QemuConfig-
>write_config($vmid, $conf);
1237 } elsif ($opt =~ m/^usb\d+$/) {
1238 if ($val =~ m/spice/) {
1239 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1240 } elsif ($authuser ne 'root@pam') {
1241 die "only root can delete '$opt' config for real devices\n";
1243 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1244 PVE
::QemuConfig-
>write_config($vmid, $conf);
1246 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1247 PVE
::QemuConfig-
>write_config($vmid, $conf);
1251 foreach my $opt (keys %$param) { # add/change
1252 $modified->{$opt} = 1;
1253 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1254 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1256 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1258 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1259 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1260 # FIXME: cloudinit: CDROM or Disk?
1261 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1262 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1264 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1266 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1267 if defined($conf->{pending
}->{$opt});
1269 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1270 } elsif ($opt =~ m/^serial\d+/) {
1271 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1272 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1273 } elsif ($authuser ne 'root@pam') {
1274 die "only root can modify '$opt' config for real devices\n";
1276 $conf->{pending
}->{$opt} = $param->{$opt};
1277 } elsif ($opt =~ m/^usb\d+/) {
1278 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1279 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1280 } elsif ($authuser ne 'root@pam') {
1281 die "only root can modify '$opt' config for real devices\n";
1283 $conf->{pending
}->{$opt} = $param->{$opt};
1285 $conf->{pending
}->{$opt} = $param->{$opt};
1287 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1288 PVE
::QemuConfig-
>write_config($vmid, $conf);
1291 # remove pending changes when nothing changed
1292 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1293 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1294 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1296 return if !scalar(keys %{$conf->{pending
}});
1298 my $running = PVE
::QemuServer
::check_running
($vmid);
1300 # apply pending changes
1302 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1306 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1308 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1310 raise_param_exc
($errors) if scalar(keys %$errors);
1319 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1321 if ($background_delay) {
1323 # Note: It would be better to do that in the Event based HTTPServer
1324 # to avoid blocking call to sleep.
1326 my $end_time = time() + $background_delay;
1328 my $task = PVE
::Tools
::upid_decode
($upid);
1331 while (time() < $end_time) {
1332 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1334 sleep(1); # this gets interrupted when child process ends
1338 my $status = PVE
::Tools
::upid_read_status
($upid);
1339 return undef if $status eq 'OK';
1348 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1351 my $vm_config_perm_list = [
1356 'VM.Config.Network',
1358 'VM.Config.Options',
1359 'VM.Config.Cloudinit',
1362 __PACKAGE__-
>register_method({
1363 name
=> 'update_vm_async',
1364 path
=> '{vmid}/config',
1368 description
=> "Set virtual machine options (asynchrounous API).",
1370 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1373 additionalProperties
=> 0,
1374 properties
=> PVE
::QemuServer
::json_config_properties
(
1376 node
=> get_standard_option
('pve-node'),
1377 vmid
=> get_standard_option
('pve-vmid'),
1378 skiplock
=> get_standard_option
('skiplock'),
1380 type
=> 'string', format
=> 'pve-configid-list',
1381 description
=> "A list of settings you want to delete.",
1385 type
=> 'string', format
=> 'pve-configid-list',
1386 description
=> "Revert a pending change.",
1391 description
=> $opt_force_description,
1393 requires
=> 'delete',
1397 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1401 background_delay
=> {
1403 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1414 code
=> $update_vm_api,
1417 __PACKAGE__-
>register_method({
1418 name
=> 'update_vm',
1419 path
=> '{vmid}/config',
1423 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1425 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1428 additionalProperties
=> 0,
1429 properties
=> PVE
::QemuServer
::json_config_properties
(
1431 node
=> get_standard_option
('pve-node'),
1432 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1433 skiplock
=> get_standard_option
('skiplock'),
1435 type
=> 'string', format
=> 'pve-configid-list',
1436 description
=> "A list of settings you want to delete.",
1440 type
=> 'string', format
=> 'pve-configid-list',
1441 description
=> "Revert a pending change.",
1446 description
=> $opt_force_description,
1448 requires
=> 'delete',
1452 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1458 returns
=> { type
=> 'null' },
1461 &$update_vm_api($param, 1);
1466 __PACKAGE__-
>register_method({
1467 name
=> 'destroy_vm',
1472 description
=> "Destroy the vm (also delete all used/owned volumes).",
1474 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1477 additionalProperties
=> 0,
1479 node
=> get_standard_option
('pve-node'),
1480 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1481 skiplock
=> get_standard_option
('skiplock'),
1484 description
=> "Remove vmid from backup cron jobs.",
1495 my $rpcenv = PVE
::RPCEnvironment
::get
();
1496 my $authuser = $rpcenv->get_user();
1497 my $vmid = $param->{vmid
};
1499 my $skiplock = $param->{skiplock
};
1500 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1501 if $skiplock && $authuser ne 'root@pam';
1503 my $early_checks = sub {
1505 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1506 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1508 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1510 if (!$param->{purge
}) {
1511 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1513 # don't allow destroy if with replication jobs but no purge param
1514 my $repl_conf = PVE
::ReplicationConfig-
>new();
1515 $repl_conf->check_for_existing_jobs($vmid);
1518 die "VM $vmid is running - destroy failed\n"
1519 if PVE
::QemuServer
::check_running
($vmid);
1529 my $storecfg = PVE
::Storage
::config
();
1531 syslog
('info', "destroy VM $vmid: $upid\n");
1532 PVE
::QemuConfig-
>lock_config($vmid, sub {
1533 # repeat, config might have changed
1534 my $ha_managed = $early_checks->();
1536 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1538 PVE
::AccessControl
::remove_vm_access
($vmid);
1539 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1540 if ($param->{purge
}) {
1541 print "purging VM $vmid from related configurations..\n";
1542 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1543 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1546 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1547 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1551 # only now remove the zombie config, else we can have reuse race
1552 PVE
::QemuConfig-
>destroy_config($vmid);
1556 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1559 __PACKAGE__-
>register_method({
1561 path
=> '{vmid}/unlink',
1565 description
=> "Unlink/delete disk images.",
1567 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1570 additionalProperties
=> 0,
1572 node
=> get_standard_option
('pve-node'),
1573 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1575 type
=> 'string', format
=> 'pve-configid-list',
1576 description
=> "A list of disk IDs you want to delete.",
1580 description
=> $opt_force_description,
1585 returns
=> { type
=> 'null'},
1589 $param->{delete} = extract_param
($param, 'idlist');
1591 __PACKAGE__-
>update_vm($param);
1596 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1597 my $gen_rand_chars = sub {
1600 die "invalid length $length" if $length < 1;
1602 my $min = ord('!'); # first printable ascii
1604 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1605 die "failed to generate random bytes!\n"
1608 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1615 __PACKAGE__-
>register_method({
1617 path
=> '{vmid}/vncproxy',
1621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1623 description
=> "Creates a TCP VNC proxy connections.",
1625 additionalProperties
=> 0,
1627 node
=> get_standard_option
('pve-node'),
1628 vmid
=> get_standard_option
('pve-vmid'),
1632 description
=> "starts websockify instead of vncproxy",
1634 'generate-password' => {
1638 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1643 additionalProperties
=> 0,
1645 user
=> { type
=> 'string' },
1646 ticket
=> { type
=> 'string' },
1649 description
=> "Returned if requested with 'generate-password' param."
1650 ." Consists of printable ASCII characters ('!' .. '~').",
1653 cert
=> { type
=> 'string' },
1654 port
=> { type
=> 'integer' },
1655 upid
=> { type
=> 'string' },
1661 my $rpcenv = PVE
::RPCEnvironment
::get
();
1663 my $authuser = $rpcenv->get_user();
1665 my $vmid = $param->{vmid
};
1666 my $node = $param->{node
};
1667 my $websocket = $param->{websocket
};
1669 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1673 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1674 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1677 my $authpath = "/vms/$vmid";
1679 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1680 my $password = $ticket;
1681 if ($param->{'generate-password'}) {
1682 $password = $gen_rand_chars->(8);
1685 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1691 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1692 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1693 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1694 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1695 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1697 $family = PVE
::Tools
::get_host_address_family
($node);
1700 my $port = PVE
::Tools
::next_vnc_port
($family);
1707 syslog
('info', "starting vnc proxy $upid\n");
1711 if (defined($serial)) {
1713 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1715 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1716 '-timeout', $timeout, '-authpath', $authpath,
1717 '-perm', 'Sys.Console'];
1719 if ($param->{websocket
}) {
1720 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1721 push @$cmd, '-notls', '-listen', 'localhost';
1724 push @$cmd, '-c', @$remcmd, @$termcmd;
1726 PVE
::Tools
::run_command
($cmd);
1730 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1732 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1734 my $sock = IO
::Socket
::IP-
>new(
1739 GetAddrInfoFlags
=> 0,
1740 ) or die "failed to create socket: $!\n";
1741 # Inside the worker we shouldn't have any previous alarms
1742 # running anyway...:
1744 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1746 accept(my $cli, $sock) or die "connection failed: $!\n";
1749 if (PVE
::Tools
::run_command
($cmd,
1750 output
=> '>&'.fileno($cli),
1751 input
=> '<&'.fileno($cli),
1754 die "Failed to run vncproxy.\n";
1761 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1763 PVE
::Tools
::wait_for_vnc_port
($port);
1772 $res->{password
} = $password if $param->{'generate-password'};
1777 __PACKAGE__-
>register_method({
1778 name
=> 'termproxy',
1779 path
=> '{vmid}/termproxy',
1783 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1785 description
=> "Creates a TCP proxy connections.",
1787 additionalProperties
=> 0,
1789 node
=> get_standard_option
('pve-node'),
1790 vmid
=> get_standard_option
('pve-vmid'),
1794 enum
=> [qw(serial0 serial1 serial2 serial3)],
1795 description
=> "opens a serial terminal (defaults to display)",
1800 additionalProperties
=> 0,
1802 user
=> { type
=> 'string' },
1803 ticket
=> { type
=> 'string' },
1804 port
=> { type
=> 'integer' },
1805 upid
=> { type
=> 'string' },
1811 my $rpcenv = PVE
::RPCEnvironment
::get
();
1813 my $authuser = $rpcenv->get_user();
1815 my $vmid = $param->{vmid
};
1816 my $node = $param->{node
};
1817 my $serial = $param->{serial
};
1819 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1821 if (!defined($serial)) {
1823 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1824 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1828 my $authpath = "/vms/$vmid";
1830 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1835 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1836 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1837 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1838 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1839 push @$remcmd, '--';
1841 $family = PVE
::Tools
::get_host_address_family
($node);
1844 my $port = PVE
::Tools
::next_vnc_port
($family);
1846 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1847 push @$termcmd, '-iface', $serial if $serial;
1852 syslog
('info', "starting qemu termproxy $upid\n");
1854 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1855 '--perm', 'VM.Console', '--'];
1856 push @$cmd, @$remcmd, @$termcmd;
1858 PVE
::Tools
::run_command
($cmd);
1861 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1863 PVE
::Tools
::wait_for_vnc_port
($port);
1873 __PACKAGE__-
>register_method({
1874 name
=> 'vncwebsocket',
1875 path
=> '{vmid}/vncwebsocket',
1878 description
=> "You also need to pass a valid ticket (vncticket).",
1879 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1881 description
=> "Opens a weksocket for VNC traffic.",
1883 additionalProperties
=> 0,
1885 node
=> get_standard_option
('pve-node'),
1886 vmid
=> get_standard_option
('pve-vmid'),
1888 description
=> "Ticket from previous call to vncproxy.",
1893 description
=> "Port number returned by previous vncproxy call.",
1903 port
=> { type
=> 'string' },
1909 my $rpcenv = PVE
::RPCEnvironment
::get
();
1911 my $authuser = $rpcenv->get_user();
1913 my $vmid = $param->{vmid
};
1914 my $node = $param->{node
};
1916 my $authpath = "/vms/$vmid";
1918 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1920 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1922 # Note: VNC ports are acessible from outside, so we do not gain any
1923 # security if we verify that $param->{port} belongs to VM $vmid. This
1924 # check is done by verifying the VNC ticket (inside VNC protocol).
1926 my $port = $param->{port
};
1928 return { port
=> $port };
1931 __PACKAGE__-
>register_method({
1932 name
=> 'spiceproxy',
1933 path
=> '{vmid}/spiceproxy',
1938 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1940 description
=> "Returns a SPICE configuration to connect to the VM.",
1942 additionalProperties
=> 0,
1944 node
=> get_standard_option
('pve-node'),
1945 vmid
=> get_standard_option
('pve-vmid'),
1946 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1949 returns
=> get_standard_option
('remote-viewer-config'),
1953 my $rpcenv = PVE
::RPCEnvironment
::get
();
1955 my $authuser = $rpcenv->get_user();
1957 my $vmid = $param->{vmid
};
1958 my $node = $param->{node
};
1959 my $proxy = $param->{proxy
};
1961 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1962 my $title = "VM $vmid";
1963 $title .= " - ". $conf->{name
} if $conf->{name
};
1965 my $port = PVE
::QemuServer
::spice_port
($vmid);
1967 my ($ticket, undef, $remote_viewer_config) =
1968 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1970 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1971 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1973 return $remote_viewer_config;
1976 __PACKAGE__-
>register_method({
1978 path
=> '{vmid}/status',
1981 description
=> "Directory index",
1986 additionalProperties
=> 0,
1988 node
=> get_standard_option
('pve-node'),
1989 vmid
=> get_standard_option
('pve-vmid'),
1997 subdir
=> { type
=> 'string' },
2000 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2006 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2009 { subdir
=> 'current' },
2010 { subdir
=> 'start' },
2011 { subdir
=> 'stop' },
2012 { subdir
=> 'reset' },
2013 { subdir
=> 'shutdown' },
2014 { subdir
=> 'suspend' },
2015 { subdir
=> 'reboot' },
2021 __PACKAGE__-
>register_method({
2022 name
=> 'vm_status',
2023 path
=> '{vmid}/status/current',
2026 protected
=> 1, # qemu pid files are only readable by root
2027 description
=> "Get virtual machine status.",
2029 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2032 additionalProperties
=> 0,
2034 node
=> get_standard_option
('pve-node'),
2035 vmid
=> get_standard_option
('pve-vmid'),
2041 %$PVE::QemuServer
::vmstatus_return_properties
,
2043 description
=> "HA manager service status.",
2047 description
=> "Qemu VGA configuration supports spice.",
2052 description
=> "Qemu GuestAgent enabled in config.",
2062 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2064 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2065 my $status = $vmstatus->{$param->{vmid
}};
2067 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2069 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2070 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2075 __PACKAGE__-
>register_method({
2077 path
=> '{vmid}/status/start',
2081 description
=> "Start virtual machine.",
2083 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2086 additionalProperties
=> 0,
2088 node
=> get_standard_option
('pve-node'),
2089 vmid
=> get_standard_option
('pve-vmid',
2090 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2091 skiplock
=> get_standard_option
('skiplock'),
2092 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2093 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2096 enum
=> ['secure', 'insecure'],
2097 description
=> "Migration traffic is encrypted using an SSH " .
2098 "tunnel by default. On secure, completely private networks " .
2099 "this can be disabled to increase performance.",
2102 migration_network
=> {
2103 type
=> 'string', format
=> 'CIDR',
2104 description
=> "CIDR of the (sub) network that is used for migration.",
2107 machine
=> get_standard_option
('pve-qemu-machine'),
2109 description
=> "Override QEMU's -cpu argument with the given string.",
2113 targetstorage
=> get_standard_option
('pve-targetstorage'),
2115 description
=> "Wait maximal timeout seconds.",
2118 default => 'max(30, vm memory in GiB)',
2129 my $rpcenv = PVE
::RPCEnvironment
::get
();
2130 my $authuser = $rpcenv->get_user();
2132 my $node = extract_param
($param, 'node');
2133 my $vmid = extract_param
($param, 'vmid');
2134 my $timeout = extract_param
($param, 'timeout');
2136 my $machine = extract_param
($param, 'machine');
2137 my $force_cpu = extract_param
($param, 'force-cpu');
2139 my $get_root_param = sub {
2140 my $value = extract_param
($param, $_[0]);
2141 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2142 if $value && $authuser ne 'root@pam';
2146 my $stateuri = $get_root_param->('stateuri');
2147 my $skiplock = $get_root_param->('skiplock');
2148 my $migratedfrom = $get_root_param->('migratedfrom');
2149 my $migration_type = $get_root_param->('migration_type');
2150 my $migration_network = $get_root_param->('migration_network');
2151 my $targetstorage = $get_root_param->('targetstorage');
2155 if ($targetstorage) {
2156 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2158 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2159 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2163 # read spice ticket from STDIN
2165 my $nbd_protocol_version = 0;
2166 my $replicated_volumes = {};
2167 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2168 while (defined(my $line = <STDIN
>)) {
2170 if ($line =~ m/^spice_ticket: (.+)$/) {
2172 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2173 $nbd_protocol_version = $1;
2174 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2175 $replicated_volumes->{$1} = 1;
2177 # fallback for old source node
2178 $spice_ticket = $line;
2183 PVE
::Cluster
::check_cfs_quorum
();
2185 my $storecfg = PVE
::Storage
::config
();
2187 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2191 print "Requesting HA start for VM $vmid\n";
2193 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2194 PVE
::Tools
::run_command
($cmd);
2198 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2205 syslog
('info', "start VM $vmid: $upid\n");
2207 my $migrate_opts = {
2208 migratedfrom
=> $migratedfrom,
2209 spice_ticket
=> $spice_ticket,
2210 network
=> $migration_network,
2211 type
=> $migration_type,
2212 storagemap
=> $storagemap,
2213 nbd_proto_version
=> $nbd_protocol_version,
2214 replicated_volumes
=> $replicated_volumes,
2218 statefile
=> $stateuri,
2219 skiplock
=> $skiplock,
2220 forcemachine
=> $machine,
2221 timeout
=> $timeout,
2222 forcecpu
=> $force_cpu,
2225 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2229 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2233 __PACKAGE__-
>register_method({
2235 path
=> '{vmid}/status/stop',
2239 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2240 "is akin to pulling the power plug of a running computer and may damage the VM data",
2242 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2245 additionalProperties
=> 0,
2247 node
=> get_standard_option
('pve-node'),
2248 vmid
=> get_standard_option
('pve-vmid',
2249 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2250 skiplock
=> get_standard_option
('skiplock'),
2251 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2253 description
=> "Wait maximal timeout seconds.",
2259 description
=> "Do not deactivate storage volumes.",
2272 my $rpcenv = PVE
::RPCEnvironment
::get
();
2273 my $authuser = $rpcenv->get_user();
2275 my $node = extract_param
($param, 'node');
2276 my $vmid = extract_param
($param, 'vmid');
2278 my $skiplock = extract_param
($param, 'skiplock');
2279 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2280 if $skiplock && $authuser ne 'root@pam';
2282 my $keepActive = extract_param
($param, 'keepActive');
2283 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2284 if $keepActive && $authuser ne 'root@pam';
2286 my $migratedfrom = extract_param
($param, 'migratedfrom');
2287 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2288 if $migratedfrom && $authuser ne 'root@pam';
2291 my $storecfg = PVE
::Storage
::config
();
2293 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2298 print "Requesting HA stop for VM $vmid\n";
2300 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2301 PVE
::Tools
::run_command
($cmd);
2305 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2311 syslog
('info', "stop VM $vmid: $upid\n");
2313 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2314 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2318 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2322 __PACKAGE__-
>register_method({
2324 path
=> '{vmid}/status/reset',
2328 description
=> "Reset virtual machine.",
2330 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2333 additionalProperties
=> 0,
2335 node
=> get_standard_option
('pve-node'),
2336 vmid
=> get_standard_option
('pve-vmid',
2337 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2338 skiplock
=> get_standard_option
('skiplock'),
2347 my $rpcenv = PVE
::RPCEnvironment
::get
();
2349 my $authuser = $rpcenv->get_user();
2351 my $node = extract_param
($param, 'node');
2353 my $vmid = extract_param
($param, 'vmid');
2355 my $skiplock = extract_param
($param, 'skiplock');
2356 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2357 if $skiplock && $authuser ne 'root@pam';
2359 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2364 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2369 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2372 __PACKAGE__-
>register_method({
2373 name
=> 'vm_shutdown',
2374 path
=> '{vmid}/status/shutdown',
2378 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2379 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2381 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2384 additionalProperties
=> 0,
2386 node
=> get_standard_option
('pve-node'),
2387 vmid
=> get_standard_option
('pve-vmid',
2388 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2389 skiplock
=> get_standard_option
('skiplock'),
2391 description
=> "Wait maximal timeout seconds.",
2397 description
=> "Make sure the VM stops.",
2403 description
=> "Do not deactivate storage volumes.",
2416 my $rpcenv = PVE
::RPCEnvironment
::get
();
2417 my $authuser = $rpcenv->get_user();
2419 my $node = extract_param
($param, 'node');
2420 my $vmid = extract_param
($param, 'vmid');
2422 my $skiplock = extract_param
($param, 'skiplock');
2423 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2424 if $skiplock && $authuser ne 'root@pam';
2426 my $keepActive = extract_param
($param, 'keepActive');
2427 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2428 if $keepActive && $authuser ne 'root@pam';
2430 my $storecfg = PVE
::Storage
::config
();
2434 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2435 # otherwise, we will infer a shutdown command, but run into the timeout,
2436 # then when the vm is resumed, it will instantly shutdown
2438 # checking the qmp status here to get feedback to the gui/cli/api
2439 # and the status query should not take too long
2440 my $qmpstatus = eval {
2441 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2442 mon_cmd
($vmid, "query-status");
2446 if (!$err && $qmpstatus->{status
} eq "paused") {
2447 if ($param->{forceStop
}) {
2448 warn "VM is paused - stop instead of shutdown\n";
2451 die "VM is paused - cannot shutdown\n";
2455 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2457 my $timeout = $param->{timeout
} // 60;
2461 print "Requesting HA stop for VM $vmid\n";
2463 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2464 PVE
::Tools
::run_command
($cmd);
2468 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2475 syslog
('info', "shutdown VM $vmid: $upid\n");
2477 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2478 $shutdown, $param->{forceStop
}, $keepActive);
2482 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2486 __PACKAGE__-
>register_method({
2487 name
=> 'vm_reboot',
2488 path
=> '{vmid}/status/reboot',
2492 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2494 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2497 additionalProperties
=> 0,
2499 node
=> get_standard_option
('pve-node'),
2500 vmid
=> get_standard_option
('pve-vmid',
2501 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2503 description
=> "Wait maximal timeout seconds for the shutdown.",
2516 my $rpcenv = PVE
::RPCEnvironment
::get
();
2517 my $authuser = $rpcenv->get_user();
2519 my $node = extract_param
($param, 'node');
2520 my $vmid = extract_param
($param, 'vmid');
2522 my $qmpstatus = eval {
2523 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2524 mon_cmd
($vmid, "query-status");
2528 if (!$err && $qmpstatus->{status
} eq "paused") {
2529 die "VM is paused - cannot shutdown\n";
2532 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2537 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2538 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2542 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2545 __PACKAGE__-
>register_method({
2546 name
=> 'vm_suspend',
2547 path
=> '{vmid}/status/suspend',
2551 description
=> "Suspend virtual machine.",
2553 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2554 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2555 " on the storage for the vmstate.",
2556 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2559 additionalProperties
=> 0,
2561 node
=> get_standard_option
('pve-node'),
2562 vmid
=> get_standard_option
('pve-vmid',
2563 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2564 skiplock
=> get_standard_option
('skiplock'),
2569 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2571 statestorage
=> get_standard_option
('pve-storage-id', {
2572 description
=> "The storage for the VM state",
2573 requires
=> 'todisk',
2575 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2585 my $rpcenv = PVE
::RPCEnvironment
::get
();
2586 my $authuser = $rpcenv->get_user();
2588 my $node = extract_param
($param, 'node');
2589 my $vmid = extract_param
($param, 'vmid');
2591 my $todisk = extract_param
($param, 'todisk') // 0;
2593 my $statestorage = extract_param
($param, 'statestorage');
2595 my $skiplock = extract_param
($param, 'skiplock');
2596 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2597 if $skiplock && $authuser ne 'root@pam';
2599 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2601 die "Cannot suspend HA managed VM to disk\n"
2602 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2604 # early check for storage permission, for better user feedback
2606 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2608 if (!$statestorage) {
2609 # get statestorage from config if none is given
2610 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2611 my $storecfg = PVE
::Storage
::config
();
2612 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2615 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2621 syslog
('info', "suspend VM $vmid: $upid\n");
2623 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2628 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2629 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2632 __PACKAGE__-
>register_method({
2633 name
=> 'vm_resume',
2634 path
=> '{vmid}/status/resume',
2638 description
=> "Resume virtual machine.",
2640 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2643 additionalProperties
=> 0,
2645 node
=> get_standard_option
('pve-node'),
2646 vmid
=> get_standard_option
('pve-vmid',
2647 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2648 skiplock
=> get_standard_option
('skiplock'),
2649 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2659 my $rpcenv = PVE
::RPCEnvironment
::get
();
2661 my $authuser = $rpcenv->get_user();
2663 my $node = extract_param
($param, 'node');
2665 my $vmid = extract_param
($param, 'vmid');
2667 my $skiplock = extract_param
($param, 'skiplock');
2668 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2669 if $skiplock && $authuser ne 'root@pam';
2671 my $nocheck = extract_param
($param, 'nocheck');
2672 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2673 if $nocheck && $authuser ne 'root@pam';
2675 my $to_disk_suspended;
2677 PVE
::QemuConfig-
>lock_config($vmid, sub {
2678 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2679 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2683 die "VM $vmid not running\n"
2684 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2689 syslog
('info', "resume VM $vmid: $upid\n");
2691 if (!$to_disk_suspended) {
2692 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2694 my $storecfg = PVE
::Storage
::config
();
2695 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2701 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2704 __PACKAGE__-
>register_method({
2705 name
=> 'vm_sendkey',
2706 path
=> '{vmid}/sendkey',
2710 description
=> "Send key event to virtual machine.",
2712 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2715 additionalProperties
=> 0,
2717 node
=> get_standard_option
('pve-node'),
2718 vmid
=> get_standard_option
('pve-vmid',
2719 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2720 skiplock
=> get_standard_option
('skiplock'),
2722 description
=> "The key (qemu monitor encoding).",
2727 returns
=> { type
=> 'null'},
2731 my $rpcenv = PVE
::RPCEnvironment
::get
();
2733 my $authuser = $rpcenv->get_user();
2735 my $node = extract_param
($param, 'node');
2737 my $vmid = extract_param
($param, 'vmid');
2739 my $skiplock = extract_param
($param, 'skiplock');
2740 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2741 if $skiplock && $authuser ne 'root@pam';
2743 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2748 __PACKAGE__-
>register_method({
2749 name
=> 'vm_feature',
2750 path
=> '{vmid}/feature',
2754 description
=> "Check if feature for virtual machine is available.",
2756 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2759 additionalProperties
=> 0,
2761 node
=> get_standard_option
('pve-node'),
2762 vmid
=> get_standard_option
('pve-vmid'),
2764 description
=> "Feature to check.",
2766 enum
=> [ 'snapshot', 'clone', 'copy' ],
2768 snapname
=> get_standard_option
('pve-snapshot-name', {
2776 hasFeature
=> { type
=> 'boolean' },
2779 items
=> { type
=> 'string' },
2786 my $node = extract_param
($param, 'node');
2788 my $vmid = extract_param
($param, 'vmid');
2790 my $snapname = extract_param
($param, 'snapname');
2792 my $feature = extract_param
($param, 'feature');
2794 my $running = PVE
::QemuServer
::check_running
($vmid);
2796 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2799 my $snap = $conf->{snapshots
}->{$snapname};
2800 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2803 my $storecfg = PVE
::Storage
::config
();
2805 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2806 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2809 hasFeature
=> $hasFeature,
2810 nodes
=> [ keys %$nodelist ],
2814 __PACKAGE__-
>register_method({
2816 path
=> '{vmid}/clone',
2820 description
=> "Create a copy of virtual machine/template.",
2822 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2823 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2824 "'Datastore.AllocateSpace' on any used storage.",
2827 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2829 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2830 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2835 additionalProperties
=> 0,
2837 node
=> get_standard_option
('pve-node'),
2838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2839 newid
=> get_standard_option
('pve-vmid', {
2840 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2841 description
=> 'VMID for the clone.' }),
2844 type
=> 'string', format
=> 'dns-name',
2845 description
=> "Set a name for the new VM.",
2850 description
=> "Description for the new VM.",
2854 type
=> 'string', format
=> 'pve-poolid',
2855 description
=> "Add the new VM to the specified pool.",
2857 snapname
=> get_standard_option
('pve-snapshot-name', {
2860 storage
=> get_standard_option
('pve-storage-id', {
2861 description
=> "Target storage for full clone.",
2865 description
=> "Target format for file storage. Only valid for full clone.",
2868 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2873 description
=> "Create a full copy of all disks. This is always done when " .
2874 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2876 target
=> get_standard_option
('pve-node', {
2877 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2881 description
=> "Override I/O bandwidth limit (in KiB/s).",
2885 default => 'clone limit from datacenter or storage config',
2895 my $rpcenv = PVE
::RPCEnvironment
::get
();
2896 my $authuser = $rpcenv->get_user();
2898 my $node = extract_param
($param, 'node');
2899 my $vmid = extract_param
($param, 'vmid');
2900 my $newid = extract_param
($param, 'newid');
2901 my $pool = extract_param
($param, 'pool');
2902 $rpcenv->check_pool_exist($pool) if defined($pool);
2904 my $snapname = extract_param
($param, 'snapname');
2905 my $storage = extract_param
($param, 'storage');
2906 my $format = extract_param
($param, 'format');
2907 my $target = extract_param
($param, 'target');
2909 my $localnode = PVE
::INotify
::nodename
();
2911 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2915 PVE
::Cluster
::check_node_exists
($target) if $target;
2917 my $storecfg = PVE
::Storage
::config
();
2920 # check if storage is enabled on local node
2921 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2923 # check if storage is available on target node
2924 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2925 # clone only works if target storage is shared
2926 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2927 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2931 PVE
::Cluster
::check_cfs_quorum
();
2933 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2936 # do all tests after lock but before forking worker - if possible
2938 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2939 PVE
::QemuConfig-
>check_lock($conf);
2941 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2942 die "unexpected state change\n" if $verify_running != $running;
2944 die "snapshot '$snapname' does not exist\n"
2945 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2947 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2949 die "parameter 'storage' not allowed for linked clones\n"
2950 if defined($storage) && !$full;
2952 die "parameter 'format' not allowed for linked clones\n"
2953 if defined($format) && !$full;
2955 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2957 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2959 die "can't clone VM to node '$target' (VM uses local storage)\n"
2960 if $target && !$sharedvm;
2962 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2963 die "unable to create VM $newid: config file already exists\n"
2966 my $newconf = { lock => 'clone' };
2971 foreach my $opt (keys %$oldconf) {
2972 my $value = $oldconf->{$opt};
2974 # do not copy snapshot related info
2975 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2976 $opt eq 'vmstate' || $opt eq 'snapstate';
2978 # no need to copy unused images, because VMID(owner) changes anyways
2979 next if $opt =~ m/^unused\d+$/;
2981 # always change MAC! address
2982 if ($opt =~ m/^net(\d+)$/) {
2983 my $net = PVE
::QemuServer
::parse_net
($value);
2984 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2985 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2986 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2987 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2988 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2989 die "unable to parse drive options for '$opt'\n" if !$drive;
2990 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2991 $newconf->{$opt} = $value; # simply copy configuration
2993 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2994 die "Full clone feature is not supported for drive '$opt'\n"
2995 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2996 $fullclone->{$opt} = 1;
2998 # not full means clone instead of copy
2999 die "Linked clone feature is not supported for drive '$opt'\n"
3000 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3002 $drives->{$opt} = $drive;
3003 push @$vollist, $drive->{file
};
3006 # copy everything else
3007 $newconf->{$opt} = $value;
3011 # auto generate a new uuid
3012 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3013 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3014 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3015 # auto generate a new vmgenid only if the option was set for template
3016 if ($newconf->{vmgenid
}) {
3017 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3020 delete $newconf->{template
};
3022 if ($param->{name
}) {
3023 $newconf->{name
} = $param->{name
};
3025 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3028 if ($param->{description
}) {
3029 $newconf->{description
} = $param->{description
};
3032 # create empty/temp config - this fails if VM already exists on other node
3033 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3034 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3039 my $newvollist = [];
3046 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3048 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3050 my $bwlimit = extract_param
($param, 'bwlimit');
3052 my $total_jobs = scalar(keys %{$drives});
3055 foreach my $opt (keys %$drives) {
3056 my $drive = $drives->{$opt};
3057 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3058 my $completion = $skipcomplete ?
'skip' : 'complete';
3060 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3061 my $storage_list = [ $src_sid ];
3062 push @$storage_list, $storage if defined($storage);
3063 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3065 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3066 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3067 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3069 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3071 PVE
::QemuConfig-
>write_config($newid, $newconf);
3075 delete $newconf->{lock};
3077 # do not write pending changes
3078 if (my @changes = keys %{$newconf->{pending
}}) {
3079 my $pending = join(',', @changes);
3080 warn "found pending changes for '$pending', discarding for clone\n";
3081 delete $newconf->{pending
};
3084 PVE
::QemuConfig-
>write_config($newid, $newconf);
3087 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3088 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3089 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3091 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3092 die "Failed to move config to node '$target' - rename failed: $!\n"
3093 if !rename($conffile, $newconffile);
3096 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3099 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3100 sleep 1; # some storage like rbd need to wait before release volume - really?
3102 foreach my $volid (@$newvollist) {
3103 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3107 PVE
::Firewall
::remove_vmfw_conf
($newid);
3109 unlink $conffile; # avoid races -> last thing before die
3111 die "clone failed: $err";
3117 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3119 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3122 # Aquire exclusive lock lock for $newid
3123 my $lock_target_vm = sub {
3124 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3127 # exclusive lock if VM is running - else shared lock is enough;
3129 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3131 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3135 __PACKAGE__-
>register_method({
3136 name
=> 'move_vm_disk',
3137 path
=> '{vmid}/move_disk',
3141 description
=> "Move volume to different storage.",
3143 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3145 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3146 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3150 additionalProperties
=> 0,
3152 node
=> get_standard_option
('pve-node'),
3153 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3156 description
=> "The disk you want to move.",
3157 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3159 storage
=> get_standard_option
('pve-storage-id', {
3160 description
=> "Target storage.",
3161 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3165 description
=> "Target Format.",
3166 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3171 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3177 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3182 description
=> "Override I/O bandwidth limit (in KiB/s).",
3186 default => 'move limit from datacenter or storage config',
3192 description
=> "the task ID.",
3197 my $rpcenv = PVE
::RPCEnvironment
::get
();
3198 my $authuser = $rpcenv->get_user();
3200 my $node = extract_param
($param, 'node');
3201 my $vmid = extract_param
($param, 'vmid');
3202 my $digest = extract_param
($param, 'digest');
3203 my $disk = extract_param
($param, 'disk');
3204 my $storeid = extract_param
($param, 'storage');
3205 my $format = extract_param
($param, 'format');
3207 my $storecfg = PVE
::Storage
::config
();
3209 my $updatefn = sub {
3210 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3211 PVE
::QemuConfig-
>check_lock($conf);
3213 die "VM config checksum missmatch (file change by other user?)\n"
3214 if $digest && $digest ne $conf->{digest
};
3216 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3218 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3220 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3221 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3223 my $old_volid = $drive->{file
};
3225 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3226 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3230 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3231 (!$format || !$oldfmt || $oldfmt eq $format);
3233 # this only checks snapshots because $disk is passed!
3234 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3235 die "you can't move a disk with snapshots and delete the source\n"
3236 if $snapshotted && $param->{delete};
3238 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3240 my $running = PVE
::QemuServer
::check_running
($vmid);
3242 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3245 my $newvollist = [];
3251 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3253 warn "moving disk with snapshots, snapshots will not be moved!\n"
3256 my $bwlimit = extract_param
($param, 'bwlimit');
3257 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3259 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3260 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3262 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3264 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3266 # convert moved disk to base if part of template
3267 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3268 if PVE
::QemuConfig-
>is_template($conf);
3270 PVE
::QemuConfig-
>write_config($vmid, $conf);
3272 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3273 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3274 eval { mon_cmd
($vmid, "guest-fstrim") };
3278 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3279 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3285 foreach my $volid (@$newvollist) {
3286 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3289 die "storage migration failed: $err";
3292 if ($param->{delete}) {
3294 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3295 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3301 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3304 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3307 my $check_vm_disks_local = sub {
3308 my ($storecfg, $vmconf, $vmid) = @_;
3310 my $local_disks = {};
3312 # add some more information to the disks e.g. cdrom
3313 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3314 my ($volid, $attr) = @_;
3316 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3318 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3319 return if $scfg->{shared
};
3321 # The shared attr here is just a special case where the vdisk
3322 # is marked as shared manually
3323 return if $attr->{shared
};
3324 return if $attr->{cdrom
} and $volid eq "none";
3326 if (exists $local_disks->{$volid}) {
3327 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3329 $local_disks->{$volid} = $attr;
3330 # ensure volid is present in case it's needed
3331 $local_disks->{$volid}->{volid
} = $volid;
3335 return $local_disks;
3338 __PACKAGE__-
>register_method({
3339 name
=> 'migrate_vm_precondition',
3340 path
=> '{vmid}/migrate',
3344 description
=> "Get preconditions for migration.",
3346 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3349 additionalProperties
=> 0,
3351 node
=> get_standard_option
('pve-node'),
3352 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3353 target
=> get_standard_option
('pve-node', {
3354 description
=> "Target node.",
3355 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3363 running
=> { type
=> 'boolean' },
3367 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3369 not_allowed_nodes
=> {
3372 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3376 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3378 local_resources
=> {
3380 description
=> "List local resources e.g. pci, usb"
3387 my $rpcenv = PVE
::RPCEnvironment
::get
();
3389 my $authuser = $rpcenv->get_user();
3391 PVE
::Cluster
::check_cfs_quorum
();
3395 my $vmid = extract_param
($param, 'vmid');
3396 my $target = extract_param
($param, 'target');
3397 my $localnode = PVE
::INotify
::nodename
();
3401 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3402 my $storecfg = PVE
::Storage
::config
();
3405 # try to detect errors early
3406 PVE
::QemuConfig-
>check_lock($vmconf);
3408 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3410 # if vm is not running, return target nodes where local storage is available
3411 # for offline migration
3412 if (!$res->{running
}) {
3413 $res->{allowed_nodes
} = [];
3414 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3415 delete $checked_nodes->{$localnode};
3417 foreach my $node (keys %$checked_nodes) {
3418 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3419 push @{$res->{allowed_nodes
}}, $node;
3423 $res->{not_allowed_nodes
} = $checked_nodes;
3427 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3428 $res->{local_disks
} = [ values %$local_disks ];;
3430 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3432 $res->{local_resources
} = $local_resources;
3439 __PACKAGE__-
>register_method({
3440 name
=> 'migrate_vm',
3441 path
=> '{vmid}/migrate',
3445 description
=> "Migrate virtual machine. Creates a new migration task.",
3447 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3450 additionalProperties
=> 0,
3452 node
=> get_standard_option
('pve-node'),
3453 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3454 target
=> get_standard_option
('pve-node', {
3455 description
=> "Target node.",
3456 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3460 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3465 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3470 enum
=> ['secure', 'insecure'],
3471 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3474 migration_network
=> {
3475 type
=> 'string', format
=> 'CIDR',
3476 description
=> "CIDR of the (sub) network that is used for migration.",
3479 "with-local-disks" => {
3481 description
=> "Enable live storage migration for local disk",
3484 targetstorage
=> get_standard_option
('pve-targetstorage', {
3485 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3488 description
=> "Override I/O bandwidth limit (in KiB/s).",
3492 default => 'migrate limit from datacenter or storage config',
3498 description
=> "the task ID.",
3503 my $rpcenv = PVE
::RPCEnvironment
::get
();
3504 my $authuser = $rpcenv->get_user();
3506 my $target = extract_param
($param, 'target');
3508 my $localnode = PVE
::INotify
::nodename
();
3509 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3511 PVE
::Cluster
::check_cfs_quorum
();
3513 PVE
::Cluster
::check_node_exists
($target);
3515 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3517 my $vmid = extract_param
($param, 'vmid');
3519 raise_param_exc
({ force
=> "Only root may use this option." })
3520 if $param->{force
} && $authuser ne 'root@pam';
3522 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3523 if $param->{migration_type
} && $authuser ne 'root@pam';
3525 # allow root only until better network permissions are available
3526 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3527 if $param->{migration_network
} && $authuser ne 'root@pam';
3530 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3532 # try to detect errors early
3534 PVE
::QemuConfig-
>check_lock($conf);
3536 if (PVE
::QemuServer
::check_running
($vmid)) {
3537 die "can't migrate running VM without --online\n" if !$param->{online
};
3539 my $repl_conf = PVE
::ReplicationConfig-
>new();
3540 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3541 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3542 if ($is_replicated && !$is_replicated_to_target) {
3543 if ($param->{force
}) {
3544 warn "WARNING: Node '$target' is not a replication target. Existing replication " .
3545 "jobs will fail after migration!\n";
3547 die "Cannot live-migrate replicated VM to node '$target' - not a replication target." .
3548 " Use 'force' to override.\n";
3552 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3553 $param->{online
} = 0;
3556 my $storecfg = PVE
::Storage
::config
();
3558 if (my $targetstorage = $param->{targetstorage
}) {
3559 my $check_storage = sub {
3560 my ($target_sid) = @_;
3561 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3562 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3563 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3564 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3565 if !$scfg->{content
}->{images
};
3568 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3569 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3572 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3573 if !defined($storagemap->{identity
});
3575 foreach my $source (values %{$storagemap->{entries
}}) {
3576 $check_storage->($source);
3579 $check_storage->($storagemap->{default})
3580 if $storagemap->{default};
3582 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3583 if $storagemap->{identity
};
3585 $param->{storagemap
} = $storagemap;
3587 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3590 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3595 print "Requesting HA migration for VM $vmid to node $target\n";
3597 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3598 PVE
::Tools
::run_command
($cmd);
3602 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3607 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3611 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3614 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3619 __PACKAGE__-
>register_method({
3621 path
=> '{vmid}/monitor',
3625 description
=> "Execute Qemu monitor commands.",
3627 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3628 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3631 additionalProperties
=> 0,
3633 node
=> get_standard_option
('pve-node'),
3634 vmid
=> get_standard_option
('pve-vmid'),
3637 description
=> "The monitor command.",
3641 returns
=> { type
=> 'string'},
3645 my $rpcenv = PVE
::RPCEnvironment
::get
();
3646 my $authuser = $rpcenv->get_user();
3649 my $command = shift;
3650 return $command =~ m/^\s*info(\s+|$)/
3651 || $command =~ m/^\s*help\s*$/;
3654 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3655 if !&$is_ro($param->{command
});
3657 my $vmid = $param->{vmid
};
3659 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3663 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3665 $res = "ERROR: $@" if $@;
3670 __PACKAGE__-
>register_method({
3671 name
=> 'resize_vm',
3672 path
=> '{vmid}/resize',
3676 description
=> "Extend volume size.",
3678 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3681 additionalProperties
=> 0,
3683 node
=> get_standard_option
('pve-node'),
3684 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3685 skiplock
=> get_standard_option
('skiplock'),
3688 description
=> "The disk you want to resize.",
3689 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3693 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3694 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.",
3698 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3704 returns
=> { type
=> 'null'},
3708 my $rpcenv = PVE
::RPCEnvironment
::get
();
3710 my $authuser = $rpcenv->get_user();
3712 my $node = extract_param
($param, 'node');
3714 my $vmid = extract_param
($param, 'vmid');
3716 my $digest = extract_param
($param, 'digest');
3718 my $disk = extract_param
($param, 'disk');
3720 my $sizestr = extract_param
($param, 'size');
3722 my $skiplock = extract_param
($param, 'skiplock');
3723 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3724 if $skiplock && $authuser ne 'root@pam';
3726 my $storecfg = PVE
::Storage
::config
();
3728 my $updatefn = sub {
3730 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3732 die "checksum missmatch (file change by other user?)\n"
3733 if $digest && $digest ne $conf->{digest
};
3734 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3736 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3738 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3740 my (undef, undef, undef, undef, undef, undef, $format) =
3741 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3743 die "can't resize volume: $disk if snapshot exists\n"
3744 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3746 my $volid = $drive->{file
};
3748 die "disk '$disk' has no associated volume\n" if !$volid;
3750 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3752 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3754 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3756 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3757 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3759 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3761 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3762 my ($ext, $newsize, $unit) = ($1, $2, $4);
3765 $newsize = $newsize * 1024;
3766 } elsif ($unit eq 'M') {
3767 $newsize = $newsize * 1024 * 1024;
3768 } elsif ($unit eq 'G') {
3769 $newsize = $newsize * 1024 * 1024 * 1024;
3770 } elsif ($unit eq 'T') {
3771 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3774 $newsize += $size if $ext;
3775 $newsize = int($newsize);
3777 die "shrinking disks is not supported\n" if $newsize < $size;
3779 return if $size == $newsize;
3781 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3783 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3785 $drive->{size
} = $newsize;
3786 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3788 PVE
::QemuConfig-
>write_config($vmid, $conf);
3791 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3795 __PACKAGE__-
>register_method({
3796 name
=> 'snapshot_list',
3797 path
=> '{vmid}/snapshot',
3799 description
=> "List all snapshots.",
3801 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3804 protected
=> 1, # qemu pid files are only readable by root
3806 additionalProperties
=> 0,
3808 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3809 node
=> get_standard_option
('pve-node'),
3818 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3822 description
=> "Snapshot includes RAM.",
3827 description
=> "Snapshot description.",
3831 description
=> "Snapshot creation time",
3833 renderer
=> 'timestamp',
3837 description
=> "Parent snapshot identifier.",
3843 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3848 my $vmid = $param->{vmid
};
3850 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3851 my $snaphash = $conf->{snapshots
} || {};
3855 foreach my $name (keys %$snaphash) {
3856 my $d = $snaphash->{$name};
3859 snaptime
=> $d->{snaptime
} || 0,
3860 vmstate
=> $d->{vmstate
} ?
1 : 0,
3861 description
=> $d->{description
} || '',
3863 $item->{parent
} = $d->{parent
} if $d->{parent
};
3864 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3868 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3871 digest
=> $conf->{digest
},
3872 running
=> $running,
3873 description
=> "You are here!",
3875 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3877 push @$res, $current;
3882 __PACKAGE__-
>register_method({
3884 path
=> '{vmid}/snapshot',
3888 description
=> "Snapshot a VM.",
3890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3893 additionalProperties
=> 0,
3895 node
=> get_standard_option
('pve-node'),
3896 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3897 snapname
=> get_standard_option
('pve-snapshot-name'),
3901 description
=> "Save the vmstate",
3906 description
=> "A textual description or comment.",
3912 description
=> "the task ID.",
3917 my $rpcenv = PVE
::RPCEnvironment
::get
();
3919 my $authuser = $rpcenv->get_user();
3921 my $node = extract_param
($param, 'node');
3923 my $vmid = extract_param
($param, 'vmid');
3925 my $snapname = extract_param
($param, 'snapname');
3927 die "unable to use snapshot name 'current' (reserved name)\n"
3928 if $snapname eq 'current';
3930 die "unable to use snapshot name 'pending' (reserved name)\n"
3931 if lc($snapname) eq 'pending';
3934 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3935 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3936 $param->{description
});
3939 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3942 __PACKAGE__-
>register_method({
3943 name
=> 'snapshot_cmd_idx',
3944 path
=> '{vmid}/snapshot/{snapname}',
3951 additionalProperties
=> 0,
3953 vmid
=> get_standard_option
('pve-vmid'),
3954 node
=> get_standard_option
('pve-node'),
3955 snapname
=> get_standard_option
('pve-snapshot-name'),
3964 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3971 push @$res, { cmd
=> 'rollback' };
3972 push @$res, { cmd
=> 'config' };
3977 __PACKAGE__-
>register_method({
3978 name
=> 'update_snapshot_config',
3979 path
=> '{vmid}/snapshot/{snapname}/config',
3983 description
=> "Update snapshot metadata.",
3985 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3988 additionalProperties
=> 0,
3990 node
=> get_standard_option
('pve-node'),
3991 vmid
=> get_standard_option
('pve-vmid'),
3992 snapname
=> get_standard_option
('pve-snapshot-name'),
3996 description
=> "A textual description or comment.",
4000 returns
=> { type
=> 'null' },
4004 my $rpcenv = PVE
::RPCEnvironment
::get
();
4006 my $authuser = $rpcenv->get_user();
4008 my $vmid = extract_param
($param, 'vmid');
4010 my $snapname = extract_param
($param, 'snapname');
4012 return undef if !defined($param->{description
});
4014 my $updatefn = sub {
4016 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4018 PVE
::QemuConfig-
>check_lock($conf);
4020 my $snap = $conf->{snapshots
}->{$snapname};
4022 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4024 $snap->{description
} = $param->{description
} if defined($param->{description
});
4026 PVE
::QemuConfig-
>write_config($vmid, $conf);
4029 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4034 __PACKAGE__-
>register_method({
4035 name
=> 'get_snapshot_config',
4036 path
=> '{vmid}/snapshot/{snapname}/config',
4039 description
=> "Get snapshot configuration",
4041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4044 additionalProperties
=> 0,
4046 node
=> get_standard_option
('pve-node'),
4047 vmid
=> get_standard_option
('pve-vmid'),
4048 snapname
=> get_standard_option
('pve-snapshot-name'),
4051 returns
=> { type
=> "object" },
4055 my $rpcenv = PVE
::RPCEnvironment
::get
();
4057 my $authuser = $rpcenv->get_user();
4059 my $vmid = extract_param
($param, 'vmid');
4061 my $snapname = extract_param
($param, 'snapname');
4063 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4065 my $snap = $conf->{snapshots
}->{$snapname};
4067 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4072 __PACKAGE__-
>register_method({
4074 path
=> '{vmid}/snapshot/{snapname}/rollback',
4078 description
=> "Rollback VM state to specified snapshot.",
4080 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4083 additionalProperties
=> 0,
4085 node
=> get_standard_option
('pve-node'),
4086 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4087 snapname
=> get_standard_option
('pve-snapshot-name'),
4092 description
=> "the task ID.",
4097 my $rpcenv = PVE
::RPCEnvironment
::get
();
4099 my $authuser = $rpcenv->get_user();
4101 my $node = extract_param
($param, 'node');
4103 my $vmid = extract_param
($param, 'vmid');
4105 my $snapname = extract_param
($param, 'snapname');
4108 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4109 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4113 # hold migration lock, this makes sure that nobody create replication snapshots
4114 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4117 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4120 __PACKAGE__-
>register_method({
4121 name
=> 'delsnapshot',
4122 path
=> '{vmid}/snapshot/{snapname}',
4126 description
=> "Delete a VM snapshot.",
4128 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4131 additionalProperties
=> 0,
4133 node
=> get_standard_option
('pve-node'),
4134 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4135 snapname
=> get_standard_option
('pve-snapshot-name'),
4139 description
=> "For removal from config file, even if removing disk snapshots fails.",
4145 description
=> "the task ID.",
4150 my $rpcenv = PVE
::RPCEnvironment
::get
();
4152 my $authuser = $rpcenv->get_user();
4154 my $node = extract_param
($param, 'node');
4156 my $vmid = extract_param
($param, 'vmid');
4158 my $snapname = extract_param
($param, 'snapname');
4161 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4162 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4165 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4168 __PACKAGE__-
>register_method({
4170 path
=> '{vmid}/template',
4174 description
=> "Create a Template.",
4176 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4177 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4180 additionalProperties
=> 0,
4182 node
=> get_standard_option
('pve-node'),
4183 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4187 description
=> "If you want to convert only 1 disk to base image.",
4188 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4193 returns
=> { type
=> 'null'},
4197 my $rpcenv = PVE
::RPCEnvironment
::get
();
4199 my $authuser = $rpcenv->get_user();
4201 my $node = extract_param
($param, 'node');
4203 my $vmid = extract_param
($param, 'vmid');
4205 my $disk = extract_param
($param, 'disk');
4207 my $updatefn = sub {
4209 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4211 PVE
::QemuConfig-
>check_lock($conf);
4213 die "unable to create template, because VM contains snapshots\n"
4214 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4216 die "you can't convert a template to a template\n"
4217 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4219 die "you can't convert a VM to template if VM is running\n"
4220 if PVE
::QemuServer
::check_running
($vmid);
4223 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4226 $conf->{template
} = 1;
4227 PVE
::QemuConfig-
>write_config($vmid, $conf);
4229 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4232 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4236 __PACKAGE__-
>register_method({
4237 name
=> 'cloudinit_generated_config_dump',
4238 path
=> '{vmid}/cloudinit/dump',
4241 description
=> "Get automatically generated cloudinit config.",
4243 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4246 additionalProperties
=> 0,
4248 node
=> get_standard_option
('pve-node'),
4249 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4251 description
=> 'Config type.',
4253 enum
=> ['user', 'network', 'meta'],
4263 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4265 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});