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->{boot
}) {
660 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
661 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
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
1195 if (my $boot = $conf->{boot
}) {
1196 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1197 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1199 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1201 foreach my $opt (@delete) {
1202 $modified->{$opt} = 1;
1203 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1205 # value of what we want to delete, independent if pending or not
1206 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1207 if (!defined($val)) {
1208 warn "cannot delete '$opt' - not set in current configuration!\n";
1209 $modified->{$opt} = 0;
1212 my $is_pending_val = defined($conf->{pending
}->{$opt});
1213 delete $conf->{pending
}->{$opt};
1215 # remove from bootorder if necessary
1216 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1217 @bootorder = grep {$_ ne $opt} @bootorder;
1218 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1219 $modified->{boot
} = 1;
1222 if ($opt =~ m/^unused/) {
1223 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1224 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1225 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1226 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1227 delete $conf->{$opt};
1228 PVE
::QemuConfig-
>write_config($vmid, $conf);
1230 } elsif ($opt eq 'vmstate') {
1231 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1232 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1233 delete $conf->{$opt};
1234 PVE
::QemuConfig-
>write_config($vmid, $conf);
1236 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1237 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1238 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1239 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1241 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1242 PVE
::QemuConfig-
>write_config($vmid, $conf);
1243 } elsif ($opt =~ m/^serial\d+$/) {
1244 if ($val eq 'socket') {
1245 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1246 } elsif ($authuser ne 'root@pam') {
1247 die "only root can delete '$opt' config for real devices\n";
1249 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1250 PVE
::QemuConfig-
>write_config($vmid, $conf);
1251 } elsif ($opt =~ m/^usb\d+$/) {
1252 if ($val =~ m/spice/) {
1253 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1254 } elsif ($authuser ne 'root@pam') {
1255 die "only root can delete '$opt' config for real devices\n";
1257 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1258 PVE
::QemuConfig-
>write_config($vmid, $conf);
1260 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1261 PVE
::QemuConfig-
>write_config($vmid, $conf);
1265 foreach my $opt (keys %$param) { # add/change
1266 $modified->{$opt} = 1;
1267 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1268 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1270 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1272 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1273 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1274 # FIXME: cloudinit: CDROM or Disk?
1275 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1276 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1278 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1280 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1281 if defined($conf->{pending
}->{$opt});
1283 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1284 } elsif ($opt =~ m/^serial\d+/) {
1285 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1286 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1287 } elsif ($authuser ne 'root@pam') {
1288 die "only root can modify '$opt' config for real devices\n";
1290 $conf->{pending
}->{$opt} = $param->{$opt};
1291 } elsif ($opt =~ m/^usb\d+/) {
1292 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1293 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1294 } elsif ($authuser ne 'root@pam') {
1295 die "only root can modify '$opt' config for real devices\n";
1297 $conf->{pending
}->{$opt} = $param->{$opt};
1299 $conf->{pending
}->{$opt} = $param->{$opt};
1301 if ($opt eq 'boot') {
1302 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1303 if ($new_bootcfg->{order
}) {
1304 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1305 for my $dev (@devs) {
1306 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1307 my $deleted = grep {$_ eq $dev} @delete;
1308 die "invalid bootorder: device '$dev' does not exist'\n"
1309 if !$exists || $deleted;
1312 # remove legacy boot order settings if new one set
1313 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1314 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1315 if $conf->{bootdisk
};
1319 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1320 PVE
::QemuConfig-
>write_config($vmid, $conf);
1323 # remove pending changes when nothing changed
1324 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1325 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1326 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1328 return if !scalar(keys %{$conf->{pending
}});
1330 my $running = PVE
::QemuServer
::check_running
($vmid);
1332 # apply pending changes
1334 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1338 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1340 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1342 raise_param_exc
($errors) if scalar(keys %$errors);
1351 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1353 if ($background_delay) {
1355 # Note: It would be better to do that in the Event based HTTPServer
1356 # to avoid blocking call to sleep.
1358 my $end_time = time() + $background_delay;
1360 my $task = PVE
::Tools
::upid_decode
($upid);
1363 while (time() < $end_time) {
1364 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1366 sleep(1); # this gets interrupted when child process ends
1370 my $status = PVE
::Tools
::upid_read_status
($upid);
1371 return if $status eq 'OK';
1380 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1383 my $vm_config_perm_list = [
1388 'VM.Config.Network',
1390 'VM.Config.Options',
1391 'VM.Config.Cloudinit',
1394 __PACKAGE__-
>register_method({
1395 name
=> 'update_vm_async',
1396 path
=> '{vmid}/config',
1400 description
=> "Set virtual machine options (asynchrounous API).",
1402 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1405 additionalProperties
=> 0,
1406 properties
=> PVE
::QemuServer
::json_config_properties
(
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid'),
1410 skiplock
=> get_standard_option
('skiplock'),
1412 type
=> 'string', format
=> 'pve-configid-list',
1413 description
=> "A list of settings you want to delete.",
1417 type
=> 'string', format
=> 'pve-configid-list',
1418 description
=> "Revert a pending change.",
1423 description
=> $opt_force_description,
1425 requires
=> 'delete',
1429 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1433 background_delay
=> {
1435 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1446 code
=> $update_vm_api,
1449 __PACKAGE__-
>register_method({
1450 name
=> 'update_vm',
1451 path
=> '{vmid}/config',
1455 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1457 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1460 additionalProperties
=> 0,
1461 properties
=> PVE
::QemuServer
::json_config_properties
(
1463 node
=> get_standard_option
('pve-node'),
1464 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1465 skiplock
=> get_standard_option
('skiplock'),
1467 type
=> 'string', format
=> 'pve-configid-list',
1468 description
=> "A list of settings you want to delete.",
1472 type
=> 'string', format
=> 'pve-configid-list',
1473 description
=> "Revert a pending change.",
1478 description
=> $opt_force_description,
1480 requires
=> 'delete',
1484 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1490 returns
=> { type
=> 'null' },
1493 &$update_vm_api($param, 1);
1498 __PACKAGE__-
>register_method({
1499 name
=> 'destroy_vm',
1504 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1505 ." and firewall rules",
1507 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1514 skiplock
=> get_standard_option
('skiplock'),
1517 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1520 'destroy-unreferenced-disks' => {
1522 description
=> "If set, destroy additionally all disks not referenced in the config"
1523 ." but with a matching VMID from all enabled storages.",
1525 default => 1, # FIXME: replace to false in PVE 7.0, this is dangerous!
1535 my $rpcenv = PVE
::RPCEnvironment
::get
();
1536 my $authuser = $rpcenv->get_user();
1537 my $vmid = $param->{vmid
};
1539 my $skiplock = $param->{skiplock
};
1540 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1541 if $skiplock && $authuser ne 'root@pam';
1543 my $early_checks = sub {
1545 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1546 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1548 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1550 if (!$param->{purge
}) {
1551 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1553 # don't allow destroy if with replication jobs but no purge param
1554 my $repl_conf = PVE
::ReplicationConfig-
>new();
1555 $repl_conf->check_for_existing_jobs($vmid);
1558 die "VM $vmid is running - destroy failed\n"
1559 if PVE
::QemuServer
::check_running
($vmid);
1569 my $storecfg = PVE
::Storage
::config
();
1571 syslog
('info', "destroy VM $vmid: $upid\n");
1572 PVE
::QemuConfig-
>lock_config($vmid, sub {
1573 # repeat, config might have changed
1574 my $ha_managed = $early_checks->();
1576 # FIXME: drop fallback to true with 7.0, to dangerous for default
1577 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'} // 1;
1579 PVE
::QemuServer
::destroy_vm
(
1582 $skiplock, { lock => 'destroyed' },
1583 $purge_unreferenced,
1586 PVE
::AccessControl
::remove_vm_access
($vmid);
1587 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1588 if ($param->{purge
}) {
1589 print "purging VM $vmid from related configurations..\n";
1590 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1591 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1594 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1595 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1599 # only now remove the zombie config, else we can have reuse race
1600 PVE
::QemuConfig-
>destroy_config($vmid);
1604 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1607 __PACKAGE__-
>register_method({
1609 path
=> '{vmid}/unlink',
1613 description
=> "Unlink/delete disk images.",
1615 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1618 additionalProperties
=> 0,
1620 node
=> get_standard_option
('pve-node'),
1621 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1623 type
=> 'string', format
=> 'pve-configid-list',
1624 description
=> "A list of disk IDs you want to delete.",
1628 description
=> $opt_force_description,
1633 returns
=> { type
=> 'null'},
1637 $param->{delete} = extract_param
($param, 'idlist');
1639 __PACKAGE__-
>update_vm($param);
1644 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1645 my $gen_rand_chars = sub {
1648 die "invalid length $length" if $length < 1;
1650 my $min = ord('!'); # first printable ascii
1652 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1653 die "failed to generate random bytes!\n"
1656 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1663 __PACKAGE__-
>register_method({
1665 path
=> '{vmid}/vncproxy',
1669 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1671 description
=> "Creates a TCP VNC proxy connections.",
1673 additionalProperties
=> 0,
1675 node
=> get_standard_option
('pve-node'),
1676 vmid
=> get_standard_option
('pve-vmid'),
1680 description
=> "starts websockify instead of vncproxy",
1682 'generate-password' => {
1686 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1691 additionalProperties
=> 0,
1693 user
=> { type
=> 'string' },
1694 ticket
=> { type
=> 'string' },
1697 description
=> "Returned if requested with 'generate-password' param."
1698 ." Consists of printable ASCII characters ('!' .. '~').",
1701 cert
=> { type
=> 'string' },
1702 port
=> { type
=> 'integer' },
1703 upid
=> { type
=> 'string' },
1709 my $rpcenv = PVE
::RPCEnvironment
::get
();
1711 my $authuser = $rpcenv->get_user();
1713 my $vmid = $param->{vmid
};
1714 my $node = $param->{node
};
1715 my $websocket = $param->{websocket
};
1717 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1721 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1722 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1725 my $authpath = "/vms/$vmid";
1727 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1728 my $password = $ticket;
1729 if ($param->{'generate-password'}) {
1730 $password = $gen_rand_chars->(8);
1733 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1739 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1740 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1741 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1742 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1743 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1745 $family = PVE
::Tools
::get_host_address_family
($node);
1748 my $port = PVE
::Tools
::next_vnc_port
($family);
1755 syslog
('info', "starting vnc proxy $upid\n");
1759 if (defined($serial)) {
1761 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1763 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1764 '-timeout', $timeout, '-authpath', $authpath,
1765 '-perm', 'Sys.Console'];
1767 if ($param->{websocket
}) {
1768 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1769 push @$cmd, '-notls', '-listen', 'localhost';
1772 push @$cmd, '-c', @$remcmd, @$termcmd;
1774 PVE
::Tools
::run_command
($cmd);
1778 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1780 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1782 my $sock = IO
::Socket
::IP-
>new(
1787 GetAddrInfoFlags
=> 0,
1788 ) or die "failed to create socket: $!\n";
1789 # Inside the worker we shouldn't have any previous alarms
1790 # running anyway...:
1792 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1794 accept(my $cli, $sock) or die "connection failed: $!\n";
1797 if (PVE
::Tools
::run_command
($cmd,
1798 output
=> '>&'.fileno($cli),
1799 input
=> '<&'.fileno($cli),
1802 die "Failed to run vncproxy.\n";
1809 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1811 PVE
::Tools
::wait_for_vnc_port
($port);
1820 $res->{password
} = $password if $param->{'generate-password'};
1825 __PACKAGE__-
>register_method({
1826 name
=> 'termproxy',
1827 path
=> '{vmid}/termproxy',
1831 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1833 description
=> "Creates a TCP proxy connections.",
1835 additionalProperties
=> 0,
1837 node
=> get_standard_option
('pve-node'),
1838 vmid
=> get_standard_option
('pve-vmid'),
1842 enum
=> [qw(serial0 serial1 serial2 serial3)],
1843 description
=> "opens a serial terminal (defaults to display)",
1848 additionalProperties
=> 0,
1850 user
=> { type
=> 'string' },
1851 ticket
=> { type
=> 'string' },
1852 port
=> { type
=> 'integer' },
1853 upid
=> { type
=> 'string' },
1859 my $rpcenv = PVE
::RPCEnvironment
::get
();
1861 my $authuser = $rpcenv->get_user();
1863 my $vmid = $param->{vmid
};
1864 my $node = $param->{node
};
1865 my $serial = $param->{serial
};
1867 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1869 if (!defined($serial)) {
1871 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1872 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1876 my $authpath = "/vms/$vmid";
1878 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1883 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1884 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1885 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1886 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1887 push @$remcmd, '--';
1889 $family = PVE
::Tools
::get_host_address_family
($node);
1892 my $port = PVE
::Tools
::next_vnc_port
($family);
1894 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1895 push @$termcmd, '-iface', $serial if $serial;
1900 syslog
('info', "starting qemu termproxy $upid\n");
1902 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1903 '--perm', 'VM.Console', '--'];
1904 push @$cmd, @$remcmd, @$termcmd;
1906 PVE
::Tools
::run_command
($cmd);
1909 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1911 PVE
::Tools
::wait_for_vnc_port
($port);
1921 __PACKAGE__-
>register_method({
1922 name
=> 'vncwebsocket',
1923 path
=> '{vmid}/vncwebsocket',
1926 description
=> "You also need to pass a valid ticket (vncticket).",
1927 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1929 description
=> "Opens a weksocket for VNC traffic.",
1931 additionalProperties
=> 0,
1933 node
=> get_standard_option
('pve-node'),
1934 vmid
=> get_standard_option
('pve-vmid'),
1936 description
=> "Ticket from previous call to vncproxy.",
1941 description
=> "Port number returned by previous vncproxy call.",
1951 port
=> { type
=> 'string' },
1957 my $rpcenv = PVE
::RPCEnvironment
::get
();
1959 my $authuser = $rpcenv->get_user();
1961 my $vmid = $param->{vmid
};
1962 my $node = $param->{node
};
1964 my $authpath = "/vms/$vmid";
1966 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1968 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1970 # Note: VNC ports are acessible from outside, so we do not gain any
1971 # security if we verify that $param->{port} belongs to VM $vmid. This
1972 # check is done by verifying the VNC ticket (inside VNC protocol).
1974 my $port = $param->{port
};
1976 return { port
=> $port };
1979 __PACKAGE__-
>register_method({
1980 name
=> 'spiceproxy',
1981 path
=> '{vmid}/spiceproxy',
1986 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1988 description
=> "Returns a SPICE configuration to connect to the VM.",
1990 additionalProperties
=> 0,
1992 node
=> get_standard_option
('pve-node'),
1993 vmid
=> get_standard_option
('pve-vmid'),
1994 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1997 returns
=> get_standard_option
('remote-viewer-config'),
2001 my $rpcenv = PVE
::RPCEnvironment
::get
();
2003 my $authuser = $rpcenv->get_user();
2005 my $vmid = $param->{vmid
};
2006 my $node = $param->{node
};
2007 my $proxy = $param->{proxy
};
2009 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2010 my $title = "VM $vmid";
2011 $title .= " - ". $conf->{name
} if $conf->{name
};
2013 my $port = PVE
::QemuServer
::spice_port
($vmid);
2015 my ($ticket, undef, $remote_viewer_config) =
2016 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2018 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2019 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2021 return $remote_viewer_config;
2024 __PACKAGE__-
>register_method({
2026 path
=> '{vmid}/status',
2029 description
=> "Directory index",
2034 additionalProperties
=> 0,
2036 node
=> get_standard_option
('pve-node'),
2037 vmid
=> get_standard_option
('pve-vmid'),
2045 subdir
=> { type
=> 'string' },
2048 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2054 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2057 { subdir
=> 'current' },
2058 { subdir
=> 'start' },
2059 { subdir
=> 'stop' },
2060 { subdir
=> 'reset' },
2061 { subdir
=> 'shutdown' },
2062 { subdir
=> 'suspend' },
2063 { subdir
=> 'reboot' },
2069 __PACKAGE__-
>register_method({
2070 name
=> 'vm_status',
2071 path
=> '{vmid}/status/current',
2074 protected
=> 1, # qemu pid files are only readable by root
2075 description
=> "Get virtual machine status.",
2077 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2080 additionalProperties
=> 0,
2082 node
=> get_standard_option
('pve-node'),
2083 vmid
=> get_standard_option
('pve-vmid'),
2089 %$PVE::QemuServer
::vmstatus_return_properties
,
2091 description
=> "HA manager service status.",
2095 description
=> "Qemu VGA configuration supports spice.",
2100 description
=> "Qemu GuestAgent enabled in config.",
2110 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2112 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2113 my $status = $vmstatus->{$param->{vmid
}};
2115 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2117 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2118 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2123 __PACKAGE__-
>register_method({
2125 path
=> '{vmid}/status/start',
2129 description
=> "Start virtual machine.",
2131 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2134 additionalProperties
=> 0,
2136 node
=> get_standard_option
('pve-node'),
2137 vmid
=> get_standard_option
('pve-vmid',
2138 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2139 skiplock
=> get_standard_option
('skiplock'),
2140 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2141 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2144 enum
=> ['secure', 'insecure'],
2145 description
=> "Migration traffic is encrypted using an SSH " .
2146 "tunnel by default. On secure, completely private networks " .
2147 "this can be disabled to increase performance.",
2150 migration_network
=> {
2151 type
=> 'string', format
=> 'CIDR',
2152 description
=> "CIDR of the (sub) network that is used for migration.",
2155 machine
=> get_standard_option
('pve-qemu-machine'),
2157 description
=> "Override QEMU's -cpu argument with the given string.",
2161 targetstorage
=> get_standard_option
('pve-targetstorage'),
2163 description
=> "Wait maximal timeout seconds.",
2166 default => 'max(30, vm memory in GiB)',
2177 my $rpcenv = PVE
::RPCEnvironment
::get
();
2178 my $authuser = $rpcenv->get_user();
2180 my $node = extract_param
($param, 'node');
2181 my $vmid = extract_param
($param, 'vmid');
2182 my $timeout = extract_param
($param, 'timeout');
2184 my $machine = extract_param
($param, 'machine');
2185 my $force_cpu = extract_param
($param, 'force-cpu');
2187 my $get_root_param = sub {
2188 my $value = extract_param
($param, $_[0]);
2189 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2190 if $value && $authuser ne 'root@pam';
2194 my $stateuri = $get_root_param->('stateuri');
2195 my $skiplock = $get_root_param->('skiplock');
2196 my $migratedfrom = $get_root_param->('migratedfrom');
2197 my $migration_type = $get_root_param->('migration_type');
2198 my $migration_network = $get_root_param->('migration_network');
2199 my $targetstorage = $get_root_param->('targetstorage');
2203 if ($targetstorage) {
2204 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2206 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2207 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2211 # read spice ticket from STDIN
2213 my $nbd_protocol_version = 0;
2214 my $replicated_volumes = {};
2215 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2216 while (defined(my $line = <STDIN
>)) {
2218 if ($line =~ m/^spice_ticket: (.+)$/) {
2220 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2221 $nbd_protocol_version = $1;
2222 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2223 $replicated_volumes->{$1} = 1;
2225 # fallback for old source node
2226 $spice_ticket = $line;
2231 PVE
::Cluster
::check_cfs_quorum
();
2233 my $storecfg = PVE
::Storage
::config
();
2235 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2239 print "Requesting HA start for VM $vmid\n";
2241 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2242 PVE
::Tools
::run_command
($cmd);
2246 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2253 syslog
('info', "start VM $vmid: $upid\n");
2255 my $migrate_opts = {
2256 migratedfrom
=> $migratedfrom,
2257 spice_ticket
=> $spice_ticket,
2258 network
=> $migration_network,
2259 type
=> $migration_type,
2260 storagemap
=> $storagemap,
2261 nbd_proto_version
=> $nbd_protocol_version,
2262 replicated_volumes
=> $replicated_volumes,
2266 statefile
=> $stateuri,
2267 skiplock
=> $skiplock,
2268 forcemachine
=> $machine,
2269 timeout
=> $timeout,
2270 forcecpu
=> $force_cpu,
2273 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2277 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2281 __PACKAGE__-
>register_method({
2283 path
=> '{vmid}/status/stop',
2287 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2288 "is akin to pulling the power plug of a running computer and may damage the VM data",
2290 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2293 additionalProperties
=> 0,
2295 node
=> get_standard_option
('pve-node'),
2296 vmid
=> get_standard_option
('pve-vmid',
2297 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2298 skiplock
=> get_standard_option
('skiplock'),
2299 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2301 description
=> "Wait maximal timeout seconds.",
2307 description
=> "Do not deactivate storage volumes.",
2320 my $rpcenv = PVE
::RPCEnvironment
::get
();
2321 my $authuser = $rpcenv->get_user();
2323 my $node = extract_param
($param, 'node');
2324 my $vmid = extract_param
($param, 'vmid');
2326 my $skiplock = extract_param
($param, 'skiplock');
2327 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2328 if $skiplock && $authuser ne 'root@pam';
2330 my $keepActive = extract_param
($param, 'keepActive');
2331 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2332 if $keepActive && $authuser ne 'root@pam';
2334 my $migratedfrom = extract_param
($param, 'migratedfrom');
2335 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2336 if $migratedfrom && $authuser ne 'root@pam';
2339 my $storecfg = PVE
::Storage
::config
();
2341 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2346 print "Requesting HA stop for VM $vmid\n";
2348 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2349 PVE
::Tools
::run_command
($cmd);
2353 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2359 syslog
('info', "stop VM $vmid: $upid\n");
2361 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2362 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2366 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2370 __PACKAGE__-
>register_method({
2372 path
=> '{vmid}/status/reset',
2376 description
=> "Reset virtual machine.",
2378 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2381 additionalProperties
=> 0,
2383 node
=> get_standard_option
('pve-node'),
2384 vmid
=> get_standard_option
('pve-vmid',
2385 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2386 skiplock
=> get_standard_option
('skiplock'),
2395 my $rpcenv = PVE
::RPCEnvironment
::get
();
2397 my $authuser = $rpcenv->get_user();
2399 my $node = extract_param
($param, 'node');
2401 my $vmid = extract_param
($param, 'vmid');
2403 my $skiplock = extract_param
($param, 'skiplock');
2404 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2405 if $skiplock && $authuser ne 'root@pam';
2407 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2412 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2417 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2420 __PACKAGE__-
>register_method({
2421 name
=> 'vm_shutdown',
2422 path
=> '{vmid}/status/shutdown',
2426 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2427 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2429 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2432 additionalProperties
=> 0,
2434 node
=> get_standard_option
('pve-node'),
2435 vmid
=> get_standard_option
('pve-vmid',
2436 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2437 skiplock
=> get_standard_option
('skiplock'),
2439 description
=> "Wait maximal timeout seconds.",
2445 description
=> "Make sure the VM stops.",
2451 description
=> "Do not deactivate storage volumes.",
2464 my $rpcenv = PVE
::RPCEnvironment
::get
();
2465 my $authuser = $rpcenv->get_user();
2467 my $node = extract_param
($param, 'node');
2468 my $vmid = extract_param
($param, 'vmid');
2470 my $skiplock = extract_param
($param, 'skiplock');
2471 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2472 if $skiplock && $authuser ne 'root@pam';
2474 my $keepActive = extract_param
($param, 'keepActive');
2475 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2476 if $keepActive && $authuser ne 'root@pam';
2478 my $storecfg = PVE
::Storage
::config
();
2482 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2483 # otherwise, we will infer a shutdown command, but run into the timeout,
2484 # then when the vm is resumed, it will instantly shutdown
2486 # checking the qmp status here to get feedback to the gui/cli/api
2487 # and the status query should not take too long
2488 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2489 if ($param->{forceStop
}) {
2490 warn "VM is paused - stop instead of shutdown\n";
2493 die "VM is paused - cannot shutdown\n";
2497 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2499 my $timeout = $param->{timeout
} // 60;
2503 print "Requesting HA stop for VM $vmid\n";
2505 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2506 PVE
::Tools
::run_command
($cmd);
2510 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2517 syslog
('info', "shutdown VM $vmid: $upid\n");
2519 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2520 $shutdown, $param->{forceStop
}, $keepActive);
2524 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2528 __PACKAGE__-
>register_method({
2529 name
=> 'vm_reboot',
2530 path
=> '{vmid}/status/reboot',
2534 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2536 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2539 additionalProperties
=> 0,
2541 node
=> get_standard_option
('pve-node'),
2542 vmid
=> get_standard_option
('pve-vmid',
2543 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2545 description
=> "Wait maximal timeout seconds for the shutdown.",
2558 my $rpcenv = PVE
::RPCEnvironment
::get
();
2559 my $authuser = $rpcenv->get_user();
2561 my $node = extract_param
($param, 'node');
2562 my $vmid = extract_param
($param, 'vmid');
2564 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2566 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2571 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2572 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2576 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2579 __PACKAGE__-
>register_method({
2580 name
=> 'vm_suspend',
2581 path
=> '{vmid}/status/suspend',
2585 description
=> "Suspend virtual machine.",
2587 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2588 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2589 " on the storage for the vmstate.",
2590 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2593 additionalProperties
=> 0,
2595 node
=> get_standard_option
('pve-node'),
2596 vmid
=> get_standard_option
('pve-vmid',
2597 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2598 skiplock
=> get_standard_option
('skiplock'),
2603 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2605 statestorage
=> get_standard_option
('pve-storage-id', {
2606 description
=> "The storage for the VM state",
2607 requires
=> 'todisk',
2609 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2619 my $rpcenv = PVE
::RPCEnvironment
::get
();
2620 my $authuser = $rpcenv->get_user();
2622 my $node = extract_param
($param, 'node');
2623 my $vmid = extract_param
($param, 'vmid');
2625 my $todisk = extract_param
($param, 'todisk') // 0;
2627 my $statestorage = extract_param
($param, 'statestorage');
2629 my $skiplock = extract_param
($param, 'skiplock');
2630 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2631 if $skiplock && $authuser ne 'root@pam';
2633 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2635 die "Cannot suspend HA managed VM to disk\n"
2636 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2638 # early check for storage permission, for better user feedback
2640 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2642 if (!$statestorage) {
2643 # get statestorage from config if none is given
2644 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2645 my $storecfg = PVE
::Storage
::config
();
2646 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2649 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2655 syslog
('info', "suspend VM $vmid: $upid\n");
2657 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2662 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2663 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2666 __PACKAGE__-
>register_method({
2667 name
=> 'vm_resume',
2668 path
=> '{vmid}/status/resume',
2672 description
=> "Resume virtual machine.",
2674 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2677 additionalProperties
=> 0,
2679 node
=> get_standard_option
('pve-node'),
2680 vmid
=> get_standard_option
('pve-vmid',
2681 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2682 skiplock
=> get_standard_option
('skiplock'),
2683 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2693 my $rpcenv = PVE
::RPCEnvironment
::get
();
2695 my $authuser = $rpcenv->get_user();
2697 my $node = extract_param
($param, 'node');
2699 my $vmid = extract_param
($param, 'vmid');
2701 my $skiplock = extract_param
($param, 'skiplock');
2702 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2703 if $skiplock && $authuser ne 'root@pam';
2705 my $nocheck = extract_param
($param, 'nocheck');
2706 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2707 if $nocheck && $authuser ne 'root@pam';
2709 my $to_disk_suspended;
2711 PVE
::QemuConfig-
>lock_config($vmid, sub {
2712 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2713 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2717 die "VM $vmid not running\n"
2718 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2723 syslog
('info', "resume VM $vmid: $upid\n");
2725 if (!$to_disk_suspended) {
2726 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2728 my $storecfg = PVE
::Storage
::config
();
2729 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2735 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2738 __PACKAGE__-
>register_method({
2739 name
=> 'vm_sendkey',
2740 path
=> '{vmid}/sendkey',
2744 description
=> "Send key event to virtual machine.",
2746 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2749 additionalProperties
=> 0,
2751 node
=> get_standard_option
('pve-node'),
2752 vmid
=> get_standard_option
('pve-vmid',
2753 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2754 skiplock
=> get_standard_option
('skiplock'),
2756 description
=> "The key (qemu monitor encoding).",
2761 returns
=> { type
=> 'null'},
2765 my $rpcenv = PVE
::RPCEnvironment
::get
();
2767 my $authuser = $rpcenv->get_user();
2769 my $node = extract_param
($param, 'node');
2771 my $vmid = extract_param
($param, 'vmid');
2773 my $skiplock = extract_param
($param, 'skiplock');
2774 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2775 if $skiplock && $authuser ne 'root@pam';
2777 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2782 __PACKAGE__-
>register_method({
2783 name
=> 'vm_feature',
2784 path
=> '{vmid}/feature',
2788 description
=> "Check if feature for virtual machine is available.",
2790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2793 additionalProperties
=> 0,
2795 node
=> get_standard_option
('pve-node'),
2796 vmid
=> get_standard_option
('pve-vmid'),
2798 description
=> "Feature to check.",
2800 enum
=> [ 'snapshot', 'clone', 'copy' ],
2802 snapname
=> get_standard_option
('pve-snapshot-name', {
2810 hasFeature
=> { type
=> 'boolean' },
2813 items
=> { type
=> 'string' },
2820 my $node = extract_param
($param, 'node');
2822 my $vmid = extract_param
($param, 'vmid');
2824 my $snapname = extract_param
($param, 'snapname');
2826 my $feature = extract_param
($param, 'feature');
2828 my $running = PVE
::QemuServer
::check_running
($vmid);
2830 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2833 my $snap = $conf->{snapshots
}->{$snapname};
2834 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2837 my $storecfg = PVE
::Storage
::config
();
2839 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2840 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2843 hasFeature
=> $hasFeature,
2844 nodes
=> [ keys %$nodelist ],
2848 __PACKAGE__-
>register_method({
2850 path
=> '{vmid}/clone',
2854 description
=> "Create a copy of virtual machine/template.",
2856 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2857 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2858 "'Datastore.AllocateSpace' on any used storage.",
2861 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2863 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2864 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2869 additionalProperties
=> 0,
2871 node
=> get_standard_option
('pve-node'),
2872 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2873 newid
=> get_standard_option
('pve-vmid', {
2874 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2875 description
=> 'VMID for the clone.' }),
2878 type
=> 'string', format
=> 'dns-name',
2879 description
=> "Set a name for the new VM.",
2884 description
=> "Description for the new VM.",
2888 type
=> 'string', format
=> 'pve-poolid',
2889 description
=> "Add the new VM to the specified pool.",
2891 snapname
=> get_standard_option
('pve-snapshot-name', {
2894 storage
=> get_standard_option
('pve-storage-id', {
2895 description
=> "Target storage for full clone.",
2899 description
=> "Target format for file storage. Only valid for full clone.",
2902 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2907 description
=> "Create a full copy of all disks. This is always done when " .
2908 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2910 target
=> get_standard_option
('pve-node', {
2911 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2915 description
=> "Override I/O bandwidth limit (in KiB/s).",
2919 default => 'clone limit from datacenter or storage config',
2929 my $rpcenv = PVE
::RPCEnvironment
::get
();
2930 my $authuser = $rpcenv->get_user();
2932 my $node = extract_param
($param, 'node');
2933 my $vmid = extract_param
($param, 'vmid');
2934 my $newid = extract_param
($param, 'newid');
2935 my $pool = extract_param
($param, 'pool');
2936 $rpcenv->check_pool_exist($pool) if defined($pool);
2938 my $snapname = extract_param
($param, 'snapname');
2939 my $storage = extract_param
($param, 'storage');
2940 my $format = extract_param
($param, 'format');
2941 my $target = extract_param
($param, 'target');
2943 my $localnode = PVE
::INotify
::nodename
();
2945 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2949 PVE
::Cluster
::check_node_exists
($target) if $target;
2951 my $storecfg = PVE
::Storage
::config
();
2954 # check if storage is enabled on local node
2955 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2957 # check if storage is available on target node
2958 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2959 # clone only works if target storage is shared
2960 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2961 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2965 PVE
::Cluster
::check_cfs_quorum
();
2967 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2970 # do all tests after lock but before forking worker - if possible
2972 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2973 PVE
::QemuConfig-
>check_lock($conf);
2975 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2976 die "unexpected state change\n" if $verify_running != $running;
2978 die "snapshot '$snapname' does not exist\n"
2979 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2981 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2983 die "parameter 'storage' not allowed for linked clones\n"
2984 if defined($storage) && !$full;
2986 die "parameter 'format' not allowed for linked clones\n"
2987 if defined($format) && !$full;
2989 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2991 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2993 die "can't clone VM to node '$target' (VM uses local storage)\n"
2994 if $target && !$sharedvm;
2996 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2997 die "unable to create VM $newid: config file already exists\n"
3000 my $newconf = { lock => 'clone' };
3005 foreach my $opt (keys %$oldconf) {
3006 my $value = $oldconf->{$opt};
3008 # do not copy snapshot related info
3009 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3010 $opt eq 'vmstate' || $opt eq 'snapstate';
3012 # no need to copy unused images, because VMID(owner) changes anyways
3013 next if $opt =~ m/^unused\d+$/;
3015 # always change MAC! address
3016 if ($opt =~ m/^net(\d+)$/) {
3017 my $net = PVE
::QemuServer
::parse_net
($value);
3018 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3019 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3020 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3021 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3022 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3023 die "unable to parse drive options for '$opt'\n" if !$drive;
3024 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3025 $newconf->{$opt} = $value; # simply copy configuration
3027 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3028 die "Full clone feature is not supported for drive '$opt'\n"
3029 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3030 $fullclone->{$opt} = 1;
3032 # not full means clone instead of copy
3033 die "Linked clone feature is not supported for drive '$opt'\n"
3034 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3036 $drives->{$opt} = $drive;
3037 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3038 push @$vollist, $drive->{file
};
3041 # copy everything else
3042 $newconf->{$opt} = $value;
3046 # auto generate a new uuid
3047 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3048 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3049 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3050 # auto generate a new vmgenid only if the option was set for template
3051 if ($newconf->{vmgenid
}) {
3052 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3055 delete $newconf->{template
};
3057 if ($param->{name
}) {
3058 $newconf->{name
} = $param->{name
};
3060 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3063 if ($param->{description
}) {
3064 $newconf->{description
} = $param->{description
};
3067 # create empty/temp config - this fails if VM already exists on other node
3068 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3069 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3074 my $newvollist = [];
3081 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3083 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3085 my $bwlimit = extract_param
($param, 'bwlimit');
3087 my $total_jobs = scalar(keys %{$drives});
3090 foreach my $opt (keys %$drives) {
3091 my $drive = $drives->{$opt};
3092 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3093 my $completion = $skipcomplete ?
'skip' : 'complete';
3095 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3096 my $storage_list = [ $src_sid ];
3097 push @$storage_list, $storage if defined($storage);
3098 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3100 my $newdrive = PVE
::QemuServer
::clone_disk
(
3119 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3121 PVE
::QemuConfig-
>write_config($newid, $newconf);
3125 delete $newconf->{lock};
3127 # do not write pending changes
3128 if (my @changes = keys %{$newconf->{pending
}}) {
3129 my $pending = join(',', @changes);
3130 warn "found pending changes for '$pending', discarding for clone\n";
3131 delete $newconf->{pending
};
3134 PVE
::QemuConfig-
>write_config($newid, $newconf);
3137 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3138 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3139 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3141 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3142 die "Failed to move config to node '$target' - rename failed: $!\n"
3143 if !rename($conffile, $newconffile);
3146 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3149 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3150 sleep 1; # some storage like rbd need to wait before release volume - really?
3152 foreach my $volid (@$newvollist) {
3153 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3157 PVE
::Firewall
::remove_vmfw_conf
($newid);
3159 unlink $conffile; # avoid races -> last thing before die
3161 die "clone failed: $err";
3167 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3169 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3172 # Aquire exclusive lock lock for $newid
3173 my $lock_target_vm = sub {
3174 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3177 # exclusive lock if VM is running - else shared lock is enough;
3179 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3181 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3185 __PACKAGE__-
>register_method({
3186 name
=> 'move_vm_disk',
3187 path
=> '{vmid}/move_disk',
3191 description
=> "Move volume to different storage.",
3193 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3195 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3196 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3200 additionalProperties
=> 0,
3202 node
=> get_standard_option
('pve-node'),
3203 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3206 description
=> "The disk you want to move.",
3207 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3209 storage
=> get_standard_option
('pve-storage-id', {
3210 description
=> "Target storage.",
3211 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3215 description
=> "Target Format.",
3216 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3221 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3227 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3232 description
=> "Override I/O bandwidth limit (in KiB/s).",
3236 default => 'move limit from datacenter or storage config',
3242 description
=> "the task ID.",
3247 my $rpcenv = PVE
::RPCEnvironment
::get
();
3248 my $authuser = $rpcenv->get_user();
3250 my $node = extract_param
($param, 'node');
3251 my $vmid = extract_param
($param, 'vmid');
3252 my $digest = extract_param
($param, 'digest');
3253 my $disk = extract_param
($param, 'disk');
3254 my $storeid = extract_param
($param, 'storage');
3255 my $format = extract_param
($param, 'format');
3257 my $storecfg = PVE
::Storage
::config
();
3259 my $updatefn = sub {
3260 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3261 PVE
::QemuConfig-
>check_lock($conf);
3263 die "VM config checksum missmatch (file change by other user?)\n"
3264 if $digest && $digest ne $conf->{digest
};
3266 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3268 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3270 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3271 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3273 my $old_volid = $drive->{file
};
3275 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3276 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3280 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3281 (!$format || !$oldfmt || $oldfmt eq $format);
3283 # this only checks snapshots because $disk is passed!
3284 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3285 die "you can't move a disk with snapshots and delete the source\n"
3286 if $snapshotted && $param->{delete};
3288 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3290 my $running = PVE
::QemuServer
::check_running
($vmid);
3292 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3295 my $newvollist = [];
3301 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3303 warn "moving disk with snapshots, snapshots will not be moved!\n"
3306 my $bwlimit = extract_param
($param, 'bwlimit');
3307 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3309 my $newdrive = PVE
::QemuServer
::clone_disk
(
3327 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3329 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3331 # convert moved disk to base if part of template
3332 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3333 if PVE
::QemuConfig-
>is_template($conf);
3335 PVE
::QemuConfig-
>write_config($vmid, $conf);
3337 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3338 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3339 eval { mon_cmd
($vmid, "guest-fstrim") };
3343 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3344 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3350 foreach my $volid (@$newvollist) {
3351 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3354 die "storage migration failed: $err";
3357 if ($param->{delete}) {
3359 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3360 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3366 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3369 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3372 my $check_vm_disks_local = sub {
3373 my ($storecfg, $vmconf, $vmid) = @_;
3375 my $local_disks = {};
3377 # add some more information to the disks e.g. cdrom
3378 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3379 my ($volid, $attr) = @_;
3381 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3383 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3384 return if $scfg->{shared
};
3386 # The shared attr here is just a special case where the vdisk
3387 # is marked as shared manually
3388 return if $attr->{shared
};
3389 return if $attr->{cdrom
} and $volid eq "none";
3391 if (exists $local_disks->{$volid}) {
3392 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3394 $local_disks->{$volid} = $attr;
3395 # ensure volid is present in case it's needed
3396 $local_disks->{$volid}->{volid
} = $volid;
3400 return $local_disks;
3403 __PACKAGE__-
>register_method({
3404 name
=> 'migrate_vm_precondition',
3405 path
=> '{vmid}/migrate',
3409 description
=> "Get preconditions for migration.",
3411 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3414 additionalProperties
=> 0,
3416 node
=> get_standard_option
('pve-node'),
3417 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3418 target
=> get_standard_option
('pve-node', {
3419 description
=> "Target node.",
3420 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3428 running
=> { type
=> 'boolean' },
3432 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3434 not_allowed_nodes
=> {
3437 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3441 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3443 local_resources
=> {
3445 description
=> "List local resources e.g. pci, usb"
3452 my $rpcenv = PVE
::RPCEnvironment
::get
();
3454 my $authuser = $rpcenv->get_user();
3456 PVE
::Cluster
::check_cfs_quorum
();
3460 my $vmid = extract_param
($param, 'vmid');
3461 my $target = extract_param
($param, 'target');
3462 my $localnode = PVE
::INotify
::nodename
();
3466 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3467 my $storecfg = PVE
::Storage
::config
();
3470 # try to detect errors early
3471 PVE
::QemuConfig-
>check_lock($vmconf);
3473 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3475 # if vm is not running, return target nodes where local storage is available
3476 # for offline migration
3477 if (!$res->{running
}) {
3478 $res->{allowed_nodes
} = [];
3479 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3480 delete $checked_nodes->{$localnode};
3482 foreach my $node (keys %$checked_nodes) {
3483 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3484 push @{$res->{allowed_nodes
}}, $node;
3488 $res->{not_allowed_nodes
} = $checked_nodes;
3492 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3493 $res->{local_disks
} = [ values %$local_disks ];;
3495 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3497 $res->{local_resources
} = $local_resources;
3504 __PACKAGE__-
>register_method({
3505 name
=> 'migrate_vm',
3506 path
=> '{vmid}/migrate',
3510 description
=> "Migrate virtual machine. Creates a new migration task.",
3512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3515 additionalProperties
=> 0,
3517 node
=> get_standard_option
('pve-node'),
3518 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3519 target
=> get_standard_option
('pve-node', {
3520 description
=> "Target node.",
3521 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3525 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3530 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3535 enum
=> ['secure', 'insecure'],
3536 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3539 migration_network
=> {
3540 type
=> 'string', format
=> 'CIDR',
3541 description
=> "CIDR of the (sub) network that is used for migration.",
3544 "with-local-disks" => {
3546 description
=> "Enable live storage migration for local disk",
3549 targetstorage
=> get_standard_option
('pve-targetstorage', {
3550 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3553 description
=> "Override I/O bandwidth limit (in KiB/s).",
3557 default => 'migrate limit from datacenter or storage config',
3563 description
=> "the task ID.",
3568 my $rpcenv = PVE
::RPCEnvironment
::get
();
3569 my $authuser = $rpcenv->get_user();
3571 my $target = extract_param
($param, 'target');
3573 my $localnode = PVE
::INotify
::nodename
();
3574 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3576 PVE
::Cluster
::check_cfs_quorum
();
3578 PVE
::Cluster
::check_node_exists
($target);
3580 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3582 my $vmid = extract_param
($param, 'vmid');
3584 raise_param_exc
({ force
=> "Only root may use this option." })
3585 if $param->{force
} && $authuser ne 'root@pam';
3587 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3588 if $param->{migration_type
} && $authuser ne 'root@pam';
3590 # allow root only until better network permissions are available
3591 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3592 if $param->{migration_network
} && $authuser ne 'root@pam';
3595 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3597 # try to detect errors early
3599 PVE
::QemuConfig-
>check_lock($conf);
3601 if (PVE
::QemuServer
::check_running
($vmid)) {
3602 die "can't migrate running VM without --online\n" if !$param->{online
};
3604 my $repl_conf = PVE
::ReplicationConfig-
>new();
3605 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3606 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3607 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3608 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3609 "target. Use 'force' to override.\n";
3612 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3613 $param->{online
} = 0;
3616 my $storecfg = PVE
::Storage
::config
();
3618 if (my $targetstorage = $param->{targetstorage
}) {
3619 my $check_storage = sub {
3620 my ($target_sid) = @_;
3621 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3622 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3623 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3624 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3625 if !$scfg->{content
}->{images
};
3628 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3629 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3632 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3633 if !defined($storagemap->{identity
});
3635 foreach my $source (values %{$storagemap->{entries
}}) {
3636 $check_storage->($source);
3639 $check_storage->($storagemap->{default})
3640 if $storagemap->{default};
3642 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3643 if $storagemap->{identity
};
3645 $param->{storagemap
} = $storagemap;
3647 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3650 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3655 print "Requesting HA migration for VM $vmid to node $target\n";
3657 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3658 PVE
::Tools
::run_command
($cmd);
3662 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3667 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3671 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3674 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3679 __PACKAGE__-
>register_method({
3681 path
=> '{vmid}/monitor',
3685 description
=> "Execute Qemu monitor commands.",
3687 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3688 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3691 additionalProperties
=> 0,
3693 node
=> get_standard_option
('pve-node'),
3694 vmid
=> get_standard_option
('pve-vmid'),
3697 description
=> "The monitor command.",
3701 returns
=> { type
=> 'string'},
3705 my $rpcenv = PVE
::RPCEnvironment
::get
();
3706 my $authuser = $rpcenv->get_user();
3709 my $command = shift;
3710 return $command =~ m/^\s*info(\s+|$)/
3711 || $command =~ m/^\s*help\s*$/;
3714 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3715 if !&$is_ro($param->{command
});
3717 my $vmid = $param->{vmid
};
3719 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3723 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3725 $res = "ERROR: $@" if $@;
3730 __PACKAGE__-
>register_method({
3731 name
=> 'resize_vm',
3732 path
=> '{vmid}/resize',
3736 description
=> "Extend volume size.",
3738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3741 additionalProperties
=> 0,
3743 node
=> get_standard_option
('pve-node'),
3744 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3745 skiplock
=> get_standard_option
('skiplock'),
3748 description
=> "The disk you want to resize.",
3749 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3753 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3754 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.",
3758 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3764 returns
=> { type
=> 'null'},
3768 my $rpcenv = PVE
::RPCEnvironment
::get
();
3770 my $authuser = $rpcenv->get_user();
3772 my $node = extract_param
($param, 'node');
3774 my $vmid = extract_param
($param, 'vmid');
3776 my $digest = extract_param
($param, 'digest');
3778 my $disk = extract_param
($param, 'disk');
3780 my $sizestr = extract_param
($param, 'size');
3782 my $skiplock = extract_param
($param, 'skiplock');
3783 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3784 if $skiplock && $authuser ne 'root@pam';
3786 my $storecfg = PVE
::Storage
::config
();
3788 my $updatefn = sub {
3790 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3792 die "checksum missmatch (file change by other user?)\n"
3793 if $digest && $digest ne $conf->{digest
};
3794 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3796 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3798 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3800 my (undef, undef, undef, undef, undef, undef, $format) =
3801 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3803 die "can't resize volume: $disk if snapshot exists\n"
3804 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3806 my $volid = $drive->{file
};
3808 die "disk '$disk' has no associated volume\n" if !$volid;
3810 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3812 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3814 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3816 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3817 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3819 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3821 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3822 my ($ext, $newsize, $unit) = ($1, $2, $4);
3825 $newsize = $newsize * 1024;
3826 } elsif ($unit eq 'M') {
3827 $newsize = $newsize * 1024 * 1024;
3828 } elsif ($unit eq 'G') {
3829 $newsize = $newsize * 1024 * 1024 * 1024;
3830 } elsif ($unit eq 'T') {
3831 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3834 $newsize += $size if $ext;
3835 $newsize = int($newsize);
3837 die "shrinking disks is not supported\n" if $newsize < $size;
3839 return if $size == $newsize;
3841 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3843 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3845 $drive->{size
} = $newsize;
3846 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3848 PVE
::QemuConfig-
>write_config($vmid, $conf);
3851 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3855 __PACKAGE__-
>register_method({
3856 name
=> 'snapshot_list',
3857 path
=> '{vmid}/snapshot',
3859 description
=> "List all snapshots.",
3861 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3864 protected
=> 1, # qemu pid files are only readable by root
3866 additionalProperties
=> 0,
3868 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3869 node
=> get_standard_option
('pve-node'),
3878 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3882 description
=> "Snapshot includes RAM.",
3887 description
=> "Snapshot description.",
3891 description
=> "Snapshot creation time",
3893 renderer
=> 'timestamp',
3897 description
=> "Parent snapshot identifier.",
3903 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3908 my $vmid = $param->{vmid
};
3910 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3911 my $snaphash = $conf->{snapshots
} || {};
3915 foreach my $name (keys %$snaphash) {
3916 my $d = $snaphash->{$name};
3919 snaptime
=> $d->{snaptime
} || 0,
3920 vmstate
=> $d->{vmstate
} ?
1 : 0,
3921 description
=> $d->{description
} || '',
3923 $item->{parent
} = $d->{parent
} if $d->{parent
};
3924 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3928 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3931 digest
=> $conf->{digest
},
3932 running
=> $running,
3933 description
=> "You are here!",
3935 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3937 push @$res, $current;
3942 __PACKAGE__-
>register_method({
3944 path
=> '{vmid}/snapshot',
3948 description
=> "Snapshot a VM.",
3950 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3953 additionalProperties
=> 0,
3955 node
=> get_standard_option
('pve-node'),
3956 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3957 snapname
=> get_standard_option
('pve-snapshot-name'),
3961 description
=> "Save the vmstate",
3966 description
=> "A textual description or comment.",
3972 description
=> "the task ID.",
3977 my $rpcenv = PVE
::RPCEnvironment
::get
();
3979 my $authuser = $rpcenv->get_user();
3981 my $node = extract_param
($param, 'node');
3983 my $vmid = extract_param
($param, 'vmid');
3985 my $snapname = extract_param
($param, 'snapname');
3987 die "unable to use snapshot name 'current' (reserved name)\n"
3988 if $snapname eq 'current';
3990 die "unable to use snapshot name 'pending' (reserved name)\n"
3991 if lc($snapname) eq 'pending';
3994 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3995 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3996 $param->{description
});
3999 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4002 __PACKAGE__-
>register_method({
4003 name
=> 'snapshot_cmd_idx',
4004 path
=> '{vmid}/snapshot/{snapname}',
4011 additionalProperties
=> 0,
4013 vmid
=> get_standard_option
('pve-vmid'),
4014 node
=> get_standard_option
('pve-node'),
4015 snapname
=> get_standard_option
('pve-snapshot-name'),
4024 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4031 push @$res, { cmd
=> 'rollback' };
4032 push @$res, { cmd
=> 'config' };
4037 __PACKAGE__-
>register_method({
4038 name
=> 'update_snapshot_config',
4039 path
=> '{vmid}/snapshot/{snapname}/config',
4043 description
=> "Update snapshot metadata.",
4045 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4048 additionalProperties
=> 0,
4050 node
=> get_standard_option
('pve-node'),
4051 vmid
=> get_standard_option
('pve-vmid'),
4052 snapname
=> get_standard_option
('pve-snapshot-name'),
4056 description
=> "A textual description or comment.",
4060 returns
=> { type
=> 'null' },
4064 my $rpcenv = PVE
::RPCEnvironment
::get
();
4066 my $authuser = $rpcenv->get_user();
4068 my $vmid = extract_param
($param, 'vmid');
4070 my $snapname = extract_param
($param, 'snapname');
4072 return if !defined($param->{description
});
4074 my $updatefn = sub {
4076 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4078 PVE
::QemuConfig-
>check_lock($conf);
4080 my $snap = $conf->{snapshots
}->{$snapname};
4082 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4084 $snap->{description
} = $param->{description
} if defined($param->{description
});
4086 PVE
::QemuConfig-
>write_config($vmid, $conf);
4089 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4094 __PACKAGE__-
>register_method({
4095 name
=> 'get_snapshot_config',
4096 path
=> '{vmid}/snapshot/{snapname}/config',
4099 description
=> "Get snapshot configuration",
4101 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4104 additionalProperties
=> 0,
4106 node
=> get_standard_option
('pve-node'),
4107 vmid
=> get_standard_option
('pve-vmid'),
4108 snapname
=> get_standard_option
('pve-snapshot-name'),
4111 returns
=> { type
=> "object" },
4115 my $rpcenv = PVE
::RPCEnvironment
::get
();
4117 my $authuser = $rpcenv->get_user();
4119 my $vmid = extract_param
($param, 'vmid');
4121 my $snapname = extract_param
($param, 'snapname');
4123 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4125 my $snap = $conf->{snapshots
}->{$snapname};
4127 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4132 __PACKAGE__-
>register_method({
4134 path
=> '{vmid}/snapshot/{snapname}/rollback',
4138 description
=> "Rollback VM state to specified snapshot.",
4140 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4143 additionalProperties
=> 0,
4145 node
=> get_standard_option
('pve-node'),
4146 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4147 snapname
=> get_standard_option
('pve-snapshot-name'),
4152 description
=> "the task ID.",
4157 my $rpcenv = PVE
::RPCEnvironment
::get
();
4159 my $authuser = $rpcenv->get_user();
4161 my $node = extract_param
($param, 'node');
4163 my $vmid = extract_param
($param, 'vmid');
4165 my $snapname = extract_param
($param, 'snapname');
4168 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4169 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4173 # hold migration lock, this makes sure that nobody create replication snapshots
4174 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4177 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4180 __PACKAGE__-
>register_method({
4181 name
=> 'delsnapshot',
4182 path
=> '{vmid}/snapshot/{snapname}',
4186 description
=> "Delete a VM snapshot.",
4188 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4191 additionalProperties
=> 0,
4193 node
=> get_standard_option
('pve-node'),
4194 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4195 snapname
=> get_standard_option
('pve-snapshot-name'),
4199 description
=> "For removal from config file, even if removing disk snapshots fails.",
4205 description
=> "the task ID.",
4210 my $rpcenv = PVE
::RPCEnvironment
::get
();
4212 my $authuser = $rpcenv->get_user();
4214 my $node = extract_param
($param, 'node');
4216 my $vmid = extract_param
($param, 'vmid');
4218 my $snapname = extract_param
($param, 'snapname');
4221 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4222 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4225 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4228 __PACKAGE__-
>register_method({
4230 path
=> '{vmid}/template',
4234 description
=> "Create a Template.",
4236 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4237 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4240 additionalProperties
=> 0,
4242 node
=> get_standard_option
('pve-node'),
4243 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4247 description
=> "If you want to convert only 1 disk to base image.",
4248 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4253 returns
=> { type
=> 'null'},
4257 my $rpcenv = PVE
::RPCEnvironment
::get
();
4259 my $authuser = $rpcenv->get_user();
4261 my $node = extract_param
($param, 'node');
4263 my $vmid = extract_param
($param, 'vmid');
4265 my $disk = extract_param
($param, 'disk');
4267 my $updatefn = sub {
4269 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4271 PVE
::QemuConfig-
>check_lock($conf);
4273 die "unable to create template, because VM contains snapshots\n"
4274 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4276 die "you can't convert a template to a template\n"
4277 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4279 die "you can't convert a VM to template if VM is running\n"
4280 if PVE
::QemuServer
::check_running
($vmid);
4283 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4286 $conf->{template
} = 1;
4287 PVE
::QemuConfig-
>write_config($vmid, $conf);
4289 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4292 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4296 __PACKAGE__-
>register_method({
4297 name
=> 'cloudinit_generated_config_dump',
4298 path
=> '{vmid}/cloudinit/dump',
4301 description
=> "Get automatically generated cloudinit config.",
4303 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4306 additionalProperties
=> 0,
4308 node
=> get_standard_option
('pve-node'),
4309 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4311 description
=> 'Config type.',
4313 enum
=> ['user', 'network', 'meta'],
4323 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4325 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});